前段时间写了一个局域网音视频通话的程序,使用开源 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如下,

 -- author: yinkaisheng@foxmail.com
-- for decoding kcp udp msg
require "bit32" do
kcp_parse_table = { }
msg_header_size = function append_str(str, strformat, key, value)
if string.len(str) == or string.sub(str, -, -) == '{' then
return str .. string.format(strformat, key, value)
else
return str .. ',' .. string.format(strformat, key, value)
end
end function parse_le_uint8(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):le_uint()
col_str = append_str(col_str, '%s=%u', name, value)
root:add_le(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end function parse_le_uint16(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):le_uint()
col_str = append_str(col_str, '%s=%u', name, value)
root:add_le(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end function parse_le_uint32(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):le_uint()
col_str = append_str(col_str, '%s=%u', name, value)
root:add_le(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end function parse_uint8(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):uint()
col_str = append_str(col_str, '%s=%u', name, value)
root:add(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end function parse_int16(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):int()
col_str = append_str(col_str, '%s=%d', name, value)
root:add(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end function parse_int32(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):int()
col_str = append_str(col_str, '%s=%d', name, value)
root:add(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end function parse_uint16(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):uint()
col_str = append_str(col_str, '%s=%u', name, value)
root:add(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end function parse_uint32(protocol_type_name, start, name, buf, pkt, root, col_str)
local value = buf(start, ):uint()
col_str = append_str(col_str, '%s=%u', name, value)
root:add(_G[protocol_type_name].fields[name], buf(start, ))
return start + , col_str
end -- rtp video
KCP_VIDEO_RTP_MSG_TYPE =
kcp_video_protocol_name = 'KCPVideo'
kcp_video_protocol_desc = 'KCP Video Msg'
ProtoKCPVideo = Proto(kcp_video_protocol_name, kcp_video_protocol_desc)
field_kcp_length = ProtoField.uint32('KCP.Length', 'MsgLen', base.DEC)
field_kcp_msgtype = ProtoField.uint32('KCP.MsgType', 'MsgType', base.DEC)
field_rtp_payload = ProtoField.uint32('RTP.Payload', 'Payload', base.DEC)
field_rtp_marker = ProtoField.uint32('RTP.Marker', 'Marker', base.DEC)
field_rtp_seqno = ProtoField.uint32('RTP.SeqNO', 'SeqNo', base.DEC)
field_rtp_timestamp = ProtoField.uint32('RTP.TimeStamp', 'TimeStamp', base.DEC)
field_rtp_ssrc = ProtoField.uint32('HYP.SSRC', 'SSRC', base.DEC)
field_rtp_data = ProtoField.bytes('RTP.Data', 'RtpData') ProtoKCPVideo.fields = {field_kcp_length, field_kcp_msgtype, field_rtp_seqno, field_rtp_timestamp, field_rtp_ssrc, field_rtp_data} function parse_udp_video(start, msg_type, kcp_data_len, buf, pkt, root)
-- kcp_data_len = buf(20,4):le_uint()
local payload_index = start+msg_header_size +
local seqno_index = start+msg_header_size +
local timestamp_index = start+msg_header_size +
local ssrc_index = start+msg_header_size +
local indicator_index = start+msg_header_size + --rtp head 12
local second_byte_value = buf(payload_index, ):uint()
local rtp_payload = bit32.band(second_byte_value, 0x7F)-- or second_byte_value >> 1 -- require lua 5.3
local rtp_marker = bit32.rshift(second_byte_value, )-- or second_byte_value & 1 -- require lua 5.3
local rtp_seqno = buf(seqno_index, ):uint()
local rtp_timestamp = buf(timestamp_index, ):uint()
local rtp_ssrc = buf(ssrc_index, ):uint()
local indicator = buf(indicator_index, ):uint()
local indicator_type = bit32.band(indicator, 0x1F)
local fu_start =
local fu_end =
if indicator_type == then
local fuheader_index = indicator_index +
local fuheader = buf(fuheader_index, ):uint()
fu_start = bit32.rshift(fuheader, )
fu_end = bit32.band(bit32.rshift(fuheader, ), )
end
protocol_name = tostring(pkt.cols.protocol)
if protocol_name ~= kcp_video_protocol_name then
pkt.cols.protocol = kcp_video_protocol_name
end
local rtp_str = string.format(',SeqNo=%u,TimeStamp=%u,SSRC=%u,Payload=%u', rtp_seqno, rtp_timestamp, rtp_ssrc, rtp_payload)
if fu_start == then
rtp_str = rtp_str .. ',Start=1'
end
if fu_end == then
rtp_str = rtp_str .. ',End=1'
end
if rtp_marker == then
rtp_str = rtp_str .. ',Marker=1'
end
col_str = tostring(pkt.cols.info) .. rtp_str
pkt.cols.info = col_str
local t = root:add(ProtoKCPVideo, buf(start, kcp_data_len))
t:add(field_kcp_length, buf(start, ))
t:add(field_kcp_msgtype, buf(start + , ))
t:add(field_rtp_seqno, buf(seqno_index, ))
t:add(field_rtp_timestamp, buf(timestamp_index, ))
t:add(field_rtp_ssrc, buf(ssrc_index, ))
t:add(field_rtp_data, buf(start + msg_header_size, kcp_data_len - msg_header_size))
return start + kcp_data_len - msg_header_size, col_str
end kcp_parse_table[KCP_VIDEO_RTP_MSG_TYPE] = parse_udp_video -- kcp
kcp_conv_table = {}
kcp_head_size =
kcp_header_protocol_name = 'KCPHeader'
kcp_header_protocol_desc = 'KCP Header'
ProtoKCPHeader = Proto(kcp_header_protocol_name, kcp_header_protocol_desc)
KCPHeaders = {
{'conv', ProtoField.uint32, parse_le_uint32, base.DEC}, -- default DEC, can be omitted
{'cmd', ProtoField.uint32, parse_le_uint8, base.DEC},
{'frg', ProtoField.uint32, parse_le_uint8, base.DEC},
{'wnd', ProtoField.uint32, parse_le_uint16, base.DEC},
{'ts', ProtoField.uint32, parse_le_uint32, base.DEC},
{'sn', ProtoField.uint32, parse_le_uint32, base.DEC},
{'una', ProtoField.uint32, parse_le_uint32, base.DEC},
{'len', ProtoField.uint32, parse_le_uint32, base.DEC},
{'snd_una', ProtoField.uint32, parse_le_uint32, base.DEC},
}
for key, value in pairs(KCPHeaders) do
local field = value[](kcp_header_protocol_name .. '.' .. value[], value[])
ProtoKCPHeader.fields[value[]] = field
end function parse_kcp(start, msg_type, kcp_len, buf, pkt, root)
local buf_len = buf:len()
protocol_name = tostring(pkt.cols.protocol)
if protocol_name == 'UDP' then
pkt.cols.protocol = kcp_header_protocol_name
end
local kcp_conv = buf(start, ):le_uint()
kcp_conv_table[kcp_conv] =
local tree = root:add(ProtoKCPHeader, buf(start, kcp_head_size))
col_str = '{'
for key, value in pairs(KCPHeaders) do
start, col_str = value[]('ProtoKCPHeader', start, value[], buf, pkt, tree, col_str)
end
col_str = col_str .. '}'
old_str = tostring(pkt.cols.info)
if string.find(old_str, '{conv') == nil then
fs, fe = string.find(old_str, ' → ')
if fe == nil then
pkt.cols.info = col_str
else
fs, fe = string.find(old_str, ' ', fe + )
if fs == nil then
pkt.cols.info = col_str
else
pkt.cols.info = string.sub(old_str, , fs) .. col_str
end
end
else
col_str = old_str .. col_str
pkt.cols.info = col_str
end
if start + msg_header_size <= buf_len then
local kcp_data_len = buf(start, ):uint()
msg_type = buf(start + , ):uint()
if kcp_len == kcp_data_len and start + kcp_data_len <= buf_len then
local parse_func = kcp_parse_table[msg_type]
if parse_func then
start_new, col_str = parse_func(start, msg_type, kcp_data_len, buf, pkt, root)
else
pkt.cols.info = tostring(pkt.cols.info) .. string.format(', no parse function for msg type %u', msg_type)
end
start = start + kcp_data_len
if start + kcp_head_size <= buf_len then
kcp_conv = buf(start, ):le_uint()
kcp_len = buf(start + , ):le_uint()
if kcp_conv_table[kcp_conv] == then
parse_kcp(start, , kcp_len, buf, pkt, root)
else
end
end
else
if start + kcp_head_size <= buf_len then
kcp_conv = buf(start, ):le_uint()
kcp_len = buf(start + , ):le_uint()
if kcp_conv_table[kcp_conv] == then
parse_kcp(start, , kcp_len, buf, pkt, root)
else
end
end
end
end
return start, col_str
end -- protocal
kcp_protocol_name = 'KCP'
kcp_protocol_desc = 'KCP Protocol'
ProtoKCP = Proto(kcp_protocol_name, kcp_protocol_desc) -- dissector
function ProtoKCP.dissector(buf, pkt, root)
local buf_len = buf:len()
if buf_len < msg_header_size then
return
end
protocol_name = tostring(pkt.cols.protocol)
-- pkt.cols.info = tostring(pkt.cols.info) .. ' |' .. protocol_name .. '|'
-- if 1 then
-- return
-- end
local data_len = buf(, ):uint()
if buf_len == data_len then
local msg_type = buf(, ):uint()
local parse_func = kcp_parse_table[msg_type]
if parse_func then
parse_func(, msg_type, buf_len, buf, pkt, root)
else
pkt.cols.info = tostring(pkt.cols.info) .. string.format(', no parse function for msg id %u', msg_type)
end
elseif kcp_head_size + <= buf_len then
data_len = buf(, ):le_uint()
local kcp_data_len = buf(kcp_head_size, ):uint()
if data_len == kcp_data_len then
parse_kcp(, , data_len, buf, pkt, root)
else
local kcp_conv = buf(, ):le_uint()
if kcp_conv_table[kcp_conv] == then
parse_kcp(, , data_len, buf, pkt, root)
else
end
end
elseif kcp_head_size <= buf_len then
local kcp_conv = buf(, ):le_uint()
if kcp_conv_table[kcp_conv] == then
parse_kcp(, , data_len, buf, pkt, root)
else
end
else
end
end local udp_table = DissectorTable.get('udp.port')
udp_table:add('', ProtoKCP)
end

未完待续...

使用Lua编写Wireshark插件解析KCP UDP包,解析视频RTP包的更多相关文章

  1. Lua编写wireshark插件初探——解析Websocket上的MQTT协议

    一.背景 最近在做物联网流量分析时发现, App在使用MQTT协议时往往通过SSL+WebSocket+MQTT这种方式与服务器通信,在使用SSL中间人截获数据后,Wireshark不能自动解析出MQ ...

  2. 用lua编写wireshark插件分析自己定义的协议

    参见: https://yoursunny.com/study/IS409/ScoreBoard.htm https://wiki.wireshark.org/LuaAPI/TreeItem http ...

  3. 使用 lua 编写 wireshark 协议解析插件

    一.平台 操作系统:windows 7 wireshark:1.10.3 lua:5.1 二.准备 lua 语言基本语法,特别是关于表操作和循环 wireshark 文档,包括用户使用文档和开发者文档 ...

  4. RTP协议解析及H264/H265 音视频RTP打包分析

    一 概述 实时传输协议(Real-time Transport Protocol或简写RTP)是一个网络传输协议,它是由IETF的多媒体传输工作小组1996年在RFC 1889中公布的. RTP协议详 ...

  5. Wireshark插件编写

    Wireshark插件编写 在抓包的过程中学习了使用wireshark,同时发现wireshark可以进行加载插件,便在网上学习了一下相应的插件开发技术. 需求编写一个私有协议名为SYC,使用UDP端 ...

  6. Wireshark Lua: 一个从RTP抓包里导出H.264 Payload,变成264裸码流文件(xxx.264)的Wireshark插件

    Wireshark Lua: 一个从RTP抓包里导出H.264 Payload,变成264裸码流文件(xxx.264)的Wireshark插件 在win7-64, wireshark Version ...

  7. 【babel+小程序】记“编写babel插件”与“通过语法解析替换小程序路由表”的经历

    话不多说先上图,简要说明一下干了些什么事.图可能太模糊,可以点svg看看 背景 最近公司开展了小程序的业务,派我去负责这一块的业务,其中需要处理的一个问题是接入我们web开发的传统架构--模块化开发. ...

  8. Qt 显示透明flash和编写QtWebkit插件

    Qt 有两种方法可以显示flash. 1. 通过QAxWidget 调用com形式显示flash, 需要本机安装IE flash插件 2. 直接通过qwebview显示flash, 需要下载webki ...

  9. 使用Qt编写模块化插件式应用程序

    动态链接库技术使软件工程师们兽血沸腾,它使得应用系统(程序)可以以二进制模块的形式灵活地组建起来.比起源码级别的模块化,二进制级别的模块划分使得各模块更加独立,各模块可以分别编译和链接,模块的升级不会 ...

随机推荐

  1. 两个ESP8266一个作为服务器一个作为客户端实现互相通讯

    两个ESP8266一个作为服务器一个作为客户端实现互相通讯

  2. keras Dense 层

    文档地址:https://keras.io/layers/core/#dense keras.layers.Dense(units, activation=None, use_bias=True, k ...

  3. 【转载】微信小程序-开发入门(一)

    微信小程序已经火了一段时间了,之前一直也在关注,就这半年的发展来看,相对原生APP大部分公司还是不愿意将主营业务放到微信平台上,以免受制于腾讯,不过就小程序的应用场景(用完即走和二维码分发等)还是很值 ...

  4. elasticsearch _search结果解析

    kibana中输入:GET /_search 会返回一下结果: { "took": 9, # took:整个搜索请求花费多少毫秒 "timed_out": fa ...

  5. Ubuntu 14.04 apache安装配置

    http://jingyan.baidu.com/article/6d704a130c8a0d28da51ca5f.html Ubuntu 14.04 apache安装配置 1.安装 ~# apt-g ...

  6. 【Leetcode_easy】693. Binary Number with Alternating Bits

    problem 693. Binary Number with Alternating Bits solution1: class Solution { public: bool hasAlterna ...

  7. react 生命周期函数的一些心得体会

    一.理论 组件本质上是状态机,输入确定,输出一定确定 生命周期的三个阶段,三者时间是不固定的,只是在逻辑上的分类: 二.初始化阶段: getDefaultProps:获取实例的默认属性(即使没有生成实 ...

  8. ELK之elasticsearch插件导致filebeat没有上传日志至elasticsearch解决办法

    使用filebeat收集nginx发现日志为上传,elasticsearch没有日志,kibana没有展示 查看filebeat日志 日志目录为/var/log/filebeat  下面有多个日志文件 ...

  9. eclipse设置html js css自动提示

    eclipse也可以像Visual Studio 2008那样完全智能提示HTML/JS/CSS代码,使用eclipse自带的插件,无需另外安装插件,具体步骤如下 1.打开eclipse→Window ...

  10. linux环境上报异常java.lang.NoSuchMethodError

    23-Apr-2019 18:11:35.545 INFO [http-nio-10052-exec-10] org.apache.catalina.core.ApplicationContext.l ...