zoukankan      html  css  js  c++  java
  • 使用Lua编写Wireshark插件解析KCP UDP包,解析视频RTP包

     前段时间写了一个局域网音视频通话的程序,使用开源 KCP 来实现可靠UDP传输。

    通过研究发现KCP在发包时,会在数据包前面加上它自己的头。如果数据包较小,KCP可能会把多个数据包合成一个包发送,提高效率。

    如下图所示。

    kcp udp 包结构
    28 bytes 4 bytes 4 bytes len1 28 bytes 4 bytes 4 bytes len2
    ├────────────┼────────┬────────┼────────┼────────────┼────────┬────────┼────────┤ │kcp header │ size1 │msg type│msg data│kcp header │ size2 │msg type│msg data│ ... └────────────┴────────┴────────┴────────┴────────────┴────────┴────────┴────────┘

    size1 = 8 + len1
    size2 = 8 + len2

     kcp头后面是程序里自定义的数据包结构,由8字节数据包头和实际发送的数据包组成,8字节数据包头里前4字节是头和数据包的总长度,后4字节是消息类型。

    查看kcp代码,由下面两个函数确定kcp头的结构

     

    发送视频包时,把 FFmpeg 编码后的视频帧拆成RTP包,先构造8字节头,再加上拆好后的RTP包用KCP发送,程序里视频包的msg type值为4738。

    我过滤了一个只有视频包的抓包,直接打开如下:

    用Lua实现 KCP 和 RTP 解析器插件后,再次打开效果如下,可以看到 KCP 头和 RTP 头各个字段的信息:

    GitHub kcp_rtp_dissector下载抓包文件和代码。

    打开wireshark安装目录下文件 d:Program FilesWiresharkPortableAppWiresharkinit.lua

    在最后一行加上 dofile(DATA_DIR.."kcp_dissector.lua")--add this line ,如下图所示

    if not running_superuser or run_user_scripts_when_superuser then
        dofile(DATA_DIR.."console.lua")
    end
    --dofile(DATA_DIR.."dtd_gen.lua")
    dofile(DATA_DIR.."kcp_dissector.lua")--add this line

    把kcp_dissector.lua复制到init.lua所在目录,直接打开下载的抓包文件kcp_video_61961-26098.pcapng,就能看到上图解析后的KCP 和 RTP包内容。

    代码kcp_dissector.lua如下,

      1 -- author: yinkaisheng@foxmail.com
      2 -- for decoding kcp udp msg
      3 require "bit32"
      4 
      5 do
      6     kcp_parse_table = { }
      7     msg_header_size = 8
      8 
      9     function append_str(str, strformat, key, value)
     10         if string.len(str) == 0 or string.sub(str, -1, -1) == '{' then
     11             return str .. string.format(strformat, key, value)
     12         else
     13             return str .. ',' .. string.format(strformat, key, value)
     14         end
     15     end
     16 
     17     function parse_le_uint8(protocol_type_name, start, name, buf, pkt, root, col_str)
     18         local value = buf(start, 1):le_uint()
     19         col_str = append_str(col_str, '%s=%u', name, value)
     20         root:add_le(_G[protocol_type_name].fields[name], buf(start, 1))
     21         return start + 1, col_str
     22     end
     23 
     24     function parse_le_uint16(protocol_type_name, start, name, buf, pkt, root, col_str)
     25         local value = buf(start, 2):le_uint()
     26         col_str = append_str(col_str, '%s=%u', name, value)
     27         root:add_le(_G[protocol_type_name].fields[name], buf(start, 2))
     28         return start + 2, col_str
     29     end
     30 
     31     function parse_le_uint32(protocol_type_name, start, name, buf, pkt, root, col_str)
     32         local value = buf(start, 4):le_uint()
     33         col_str = append_str(col_str, '%s=%u', name, value)
     34         root:add_le(_G[protocol_type_name].fields[name], buf(start, 4))
     35         return start + 4, col_str
     36     end
     37 
     38     function parse_uint8(protocol_type_name, start, name, buf, pkt, root, col_str)
     39         local value = buf(start, 1):uint()
     40         col_str = append_str(col_str, '%s=%u', name, value)
     41         root:add(_G[protocol_type_name].fields[name], buf(start, 1))
     42         return start + 1, col_str
     43     end
     44 
     45     function parse_int16(protocol_type_name, start, name, buf, pkt, root, col_str)
     46         local value = buf(start, 2):int()
     47         col_str = append_str(col_str, '%s=%d', name, value)
     48         root:add(_G[protocol_type_name].fields[name], buf(start, 2))
     49         return start + 2, col_str
     50     end
     51 
     52     function parse_int32(protocol_type_name, start, name, buf, pkt, root, col_str)
     53         local value = buf(start, 4):int()
     54         col_str = append_str(col_str, '%s=%d', name, value)
     55         root:add(_G[protocol_type_name].fields[name], buf(start, 4))
     56         return start + 4, col_str
     57     end
     58 
     59     function parse_uint16(protocol_type_name, start, name, buf, pkt, root, col_str)
     60         local value = buf(start, 2):uint()
     61         col_str = append_str(col_str, '%s=%u', name, value)
     62         root:add(_G[protocol_type_name].fields[name], buf(start, 2))
     63         return start + 2, col_str
     64     end
     65 
     66     function parse_uint32(protocol_type_name, start, name, buf, pkt, root, col_str)
     67         local value = buf(start, 4):uint()
     68         col_str = append_str(col_str, '%s=%u', name, value)
     69         root:add(_G[protocol_type_name].fields[name], buf(start, 4))
     70         return start + 4, col_str
     71     end
     72 
     73     -- rtp video
     74     KCP_VIDEO_RTP_MSG_TYPE = 4738
     75     kcp_video_protocol_name = 'KCPVideo'
     76     kcp_video_protocol_desc = 'KCP Video Msg'
     77     ProtoKCPVideo = Proto(kcp_video_protocol_name, kcp_video_protocol_desc)
     78     field_kcp_length = ProtoField.uint32('KCP.Length', 'MsgLen', base.DEC)
     79     field_kcp_msgtype = ProtoField.uint32('KCP.MsgType', 'MsgType', base.DEC)
     80     field_rtp_payload = ProtoField.uint32('RTP.Payload', 'Payload', base.DEC)
     81     field_rtp_marker = ProtoField.uint32('RTP.Marker', 'Marker', base.DEC)
     82     field_rtp_seqno = ProtoField.uint32('RTP.SeqNO', 'SeqNo', base.DEC)
     83     field_rtp_timestamp = ProtoField.uint32('RTP.TimeStamp', 'TimeStamp', base.DEC)
     84     field_rtp_ssrc = ProtoField.uint32('HYP.SSRC', 'SSRC', base.DEC)
     85     field_rtp_data = ProtoField.bytes('RTP.Data', 'RtpData')
     86 
     87     ProtoKCPVideo.fields = {field_kcp_length, field_kcp_msgtype, field_rtp_seqno, field_rtp_timestamp, field_rtp_ssrc, field_rtp_data}
     88 
     89     function parse_udp_video(start, msg_type, kcp_data_len, buf, pkt, root)
     90         -- kcp_data_len = buf(20,4):le_uint()
     91         local payload_index = start+msg_header_size + 1
     92         local seqno_index = start+msg_header_size + 2
     93         local timestamp_index = start+msg_header_size + 4
     94         local ssrc_index = start+msg_header_size + 8
     95         local indicator_index = start+msg_header_size + 12--rtp head 12
     96         local second_byte_value = buf(payload_index, 1):uint()
     97         local rtp_payload = bit32.band(second_byte_value, 0x7F)-- or second_byte_value >> 1 -- require lua 5.3
     98         local rtp_marker = bit32.rshift(second_byte_value, 7)-- or second_byte_value & 1 -- require lua 5.3
     99         local rtp_seqno = buf(seqno_index, 2):uint()
    100         local rtp_timestamp = buf(timestamp_index, 4):uint()
    101         local rtp_ssrc = buf(ssrc_index, 4):uint()
    102         local indicator = buf(indicator_index, 1):uint()
    103         local indicator_type = bit32.band(indicator, 0x1F)
    104         local fu_start = 0
    105         local fu_end = 0
    106         if indicator_type == 28 then
    107             local fuheader_index = indicator_index + 1
    108             local fuheader = buf(fuheader_index, 1):uint()
    109             fu_start = bit32.rshift(fuheader, 7)
    110             fu_end = bit32.band(bit32.rshift(fuheader, 6), 1)
    111         end
    112         protocol_name = tostring(pkt.cols.protocol)
    113         if protocol_name ~= kcp_video_protocol_name then
    114             pkt.cols.protocol = kcp_video_protocol_name
    115         end
    116         local rtp_str = string.format(',SeqNo=%u,TimeStamp=%u,SSRC=%u,Payload=%u', rtp_seqno, rtp_timestamp, rtp_ssrc, rtp_payload)
    117         if fu_start == 1 then
    118             rtp_str = rtp_str .. ',Start=1'
    119         end
    120         if fu_end == 1 then
    121             rtp_str = rtp_str .. ',End=1'
    122         end
    123         if rtp_marker == 1 then
    124             rtp_str = rtp_str .. ',Marker=1'
    125         end
    126         col_str = tostring(pkt.cols.info) .. rtp_str
    127         pkt.cols.info = col_str
    128         local t = root:add(ProtoKCPVideo, buf(start, kcp_data_len))
    129         t:add(field_kcp_length, buf(start, 4))
    130         t:add(field_kcp_msgtype, buf(start + 4, 4))
    131         t:add(field_rtp_seqno, buf(seqno_index, 2))
    132         t:add(field_rtp_timestamp, buf(timestamp_index, 4))
    133         t:add(field_rtp_ssrc, buf(ssrc_index, 4))
    134         t:add(field_rtp_data, buf(start + msg_header_size, kcp_data_len - msg_header_size))
    135         return start + kcp_data_len - msg_header_size, col_str
    136     end
    137 
    138     kcp_parse_table[KCP_VIDEO_RTP_MSG_TYPE] = parse_udp_video
    139 
    140     -- kcp
    141     kcp_conv_table = {}
    142     kcp_head_size = 28
    143     kcp_header_protocol_name = 'KCPHeader'
    144     kcp_header_protocol_desc = 'KCP Header'
    145     ProtoKCPHeader = Proto(kcp_header_protocol_name, kcp_header_protocol_desc)
    146     KCPHeaders = {
    147             {'conv',    ProtoField.uint32, parse_le_uint32, base.DEC}, -- default DEC, can be omitted
    148             {'cmd',     ProtoField.uint32, parse_le_uint8,  base.DEC},
    149             {'frg',     ProtoField.uint32, parse_le_uint8,  base.DEC},
    150             {'wnd',     ProtoField.uint32, parse_le_uint16, base.DEC},
    151             {'ts',      ProtoField.uint32, parse_le_uint32, base.DEC},
    152             {'sn',      ProtoField.uint32, parse_le_uint32, base.DEC},
    153             {'una',     ProtoField.uint32, parse_le_uint32, base.DEC},
    154             {'len',     ProtoField.uint32, parse_le_uint32, base.DEC},
    155             {'snd_una', ProtoField.uint32, parse_le_uint32, base.DEC},
    156         }
    157     for key, value in pairs(KCPHeaders) do
    158         local field = value[2](kcp_header_protocol_name .. '.' .. value[1], value[1])
    159         ProtoKCPHeader.fields[value[1]] = field
    160     end
    161 
    162     function parse_kcp(start, msg_type, kcp_len, buf, pkt, root)
    163         local buf_len = buf:len()
    164         protocol_name = tostring(pkt.cols.protocol)
    165         if protocol_name == 'UDP' then
    166             pkt.cols.protocol = kcp_header_protocol_name
    167         end
    168         local kcp_conv = buf(start, 4):le_uint()
    169         kcp_conv_table[kcp_conv] = 1
    170         local tree = root:add(ProtoKCPHeader, buf(start, kcp_head_size))
    171         col_str = '{'
    172         for key, value in pairs(KCPHeaders) do
    173             start, col_str = value[3]('ProtoKCPHeader', start, value[1], buf, pkt, tree, col_str)
    174         end
    175         col_str = col_str .. '}'
    176         old_str = tostring(pkt.cols.info)
    177         if string.find(old_str, '{conv') == nil then
    178             fs, fe = string.find(old_str, '')
    179             if fe == nil then
    180                 pkt.cols.info = col_str
    181             else
    182                 fs, fe = string.find(old_str, ' ', fe + 1)
    183                 if fs == nil then
    184                     pkt.cols.info = col_str
    185                 else
    186                     pkt.cols.info = string.sub(old_str, 1, fs) .. col_str
    187                 end
    188             end
    189         else
    190             col_str = old_str .. col_str
    191             pkt.cols.info = col_str
    192         end
    193         if start + msg_header_size <= buf_len then
    194             local kcp_data_len = buf(start, 4):uint()
    195             msg_type = buf(start + 4, 4):uint()
    196             if kcp_len == kcp_data_len and start + kcp_data_len <= buf_len then
    197                 local parse_func = kcp_parse_table[msg_type]
    198                 if parse_func then
    199                     start_new, col_str = parse_func(start, msg_type, kcp_data_len, buf, pkt, root)
    200                 else
    201                     pkt.cols.info = tostring(pkt.cols.info) .. string.format(', no parse function for msg type %u', msg_type)
    202                 end
    203                 start = start + kcp_data_len
    204                 if start + kcp_head_size <= buf_len then
    205                     kcp_conv = buf(start, 4):le_uint()
    206                     kcp_len = buf(start + 20, 4):le_uint()
    207                     if kcp_conv_table[kcp_conv] == 1 then
    208                         parse_kcp(start, 0, kcp_len, buf, pkt, root)
    209                     else
    210                     end
    211                 end
    212             else
    213                 if start + kcp_head_size <= buf_len then
    214                     kcp_conv = buf(start, 4):le_uint()
    215                     kcp_len = buf(start + 20, 4):le_uint()
    216                     if kcp_conv_table[kcp_conv] == 1 then
    217                         parse_kcp(start, 0, kcp_len, buf, pkt, root)
    218                     else
    219                     end
    220                 end
    221             end
    222         end
    223         return start, col_str
    224     end
    225 
    226     -- protocal
    227     kcp_protocol_name = 'KCP'
    228     kcp_protocol_desc = 'KCP Protocol'
    229     ProtoKCP = Proto(kcp_protocol_name, kcp_protocol_desc)
    230 
    231     -- dissector
    232     function ProtoKCP.dissector(buf, pkt, root)
    233         local buf_len = buf:len()
    234         if buf_len < msg_header_size then
    235             return
    236         end
    237         protocol_name = tostring(pkt.cols.protocol)
    238         -- pkt.cols.info = tostring(pkt.cols.info) .. '  |' .. protocol_name .. '|'
    239         -- if 1 then
    240             -- return
    241         -- end
    242         local data_len = buf(0, 4):uint()
    243         if buf_len == data_len then
    244             local msg_type = buf(4, 4):uint()
    245             local parse_func = kcp_parse_table[msg_type]
    246             if parse_func then
    247                 parse_func(0, msg_type, buf_len, buf, pkt, root)
    248             else
    249                 pkt.cols.info = tostring(pkt.cols.info) .. string.format(', no parse function for msg id %u', msg_type)
    250             end
    251         elseif kcp_head_size + 8 <= buf_len then
    252             data_len = buf(20, 4):le_uint()
    253             local kcp_data_len = buf(kcp_head_size, 4):uint()
    254             if data_len == kcp_data_len then
    255                 parse_kcp(0, 0, data_len, buf, pkt, root)
    256             else
    257                 local kcp_conv = buf(0, 4):le_uint()
    258                 if kcp_conv_table[kcp_conv] == 1 then
    259                     parse_kcp(0, 0, data_len, buf, pkt, root)
    260                 else
    261                 end
    262             end
    263         elseif kcp_head_size <= buf_len then
    264             local kcp_conv = buf(0, 4):le_uint()
    265             if kcp_conv_table[kcp_conv] == 1 then
    266                 parse_kcp(0, 0, data_len, buf, pkt, root)
    267             else
    268             end
    269         else
    270         end
    271     end
    272 
    273     local udp_table = DissectorTable.get('udp.port')
    274     udp_table:add('61961', ProtoKCP)
    275 end

    未完待续...

  • 相关阅读:
    [pixhawk笔记]8-半物理仿真环境
    Python超参数自动搜索模块GridSearchCV上手
    Oriented Response Networks 阅读笔记(一)
    聚类算法评价指标学习笔记
    基于sklearn的常用分类任务指标Python实现
    使用h5py库读写超过内存的大数据
    基于MXNet使用自己的图像数据集训练网络--准备数据与撰写预处理脚本
    在Ubuntu操作系统中添加环境变量
    Jetson TK1 开发板初用体会
    一条脚本搞定OpenCV
  • 原文地址:https://www.cnblogs.com/Yinkaisheng/p/11119229.html
Copyright © 2011-2022 走看看