使用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 Files\WiresharkPortable\App\Wireshark\init.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

 

未完待续...

 

posted @ 2019-07-02 12:28  YinKaisheng  阅读(2847)  评论(0编辑  收藏  举报