前段时间写了一个局域网音视频通话的程序,使用开源 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. PAT 甲级 1055 The World's Richest (25 分)(简单题,要用printf和scanf,否则超时,string 的输入输出要注意)

    1055 The World's Richest (25 分)   Forbes magazine publishes every year its list of billionaires base ...

  2. (转)Flink简介

    1. Flink的引入 这几年大数据的飞速发展,出现了很多热门的开源社区,其中著名的有 Hadoop.Storm,以及后来的 Spark,他们都有着各自专注的应用场景.Spark 掀开了内存计算的先河 ...

  3. 【ARTS】01_33_左耳听风-201900624~201900630

    ARTS: Algrothm: leetcode算法题目 Review: 阅读并且点评一篇英文技术文章 Tip/Techni: 学习一个技术技巧 Share: 分享一篇有观点和思考的技术文章 Algo ...

  4. SQL Server数据同步交换

    一.为了解决数据同步汇聚,数据分发,数据转换,数据维护等需求,TreeSoft将复杂的网状的同步链路变成了星型数据链路.     TreeSoft作为中间传输载体负责连接各种数据源,为各种异构数据库之 ...

  5. 2019-10-24 李宗盛 spss作业

    3.1数据排序.  在统计分析时最初的变量.  可能不符合统计分析的要求,需要用户对目标数据进行整理,来符合分析方法个案排序.数据——个案排序.排序依据,排序顺序,变量排序 数据——变量排序 变量视图 ...

  6. PPM / PGM / PBM 图像文件格式

    PPM / PGM / PBM 图像文件格式 声明:引用请注明出处http://blog.csdn.net/lg1259156776/ 说明:在进行图像压缩后传输,然后解压缩显示的过程中,通常会用到P ...

  7. C#实现动态发布IIS站点帮助类

    准备工作: 1.引用 System.DirectoryServices 系统程序集 2.引用 Microsoft.Web.Administration 程序集,类库位置在 C:\Windows\Sys ...

  8. 解决电脑开机连不上网问题(Windows检测:远程计算机或设备将不接受连接)

    打开Google Chrome浏览器  -----> 设置 ------> 高级设置 -----> 打开代理设置 -----> 连接 -----> 局域网设置 ----& ...

  9. CentOS系统下Tomcat的优化

    一.JVM内存优化(线程优化) vim ./bin/catalina.sh 在catalina.sh文件中添加以下配置: JAVA_OPTS="-server -Xms128m -Xmx12 ...

  10. eNSP——STP配置和选路规则

    原理: STP是用来避免数据链路层出现逻辑环路的协议,使用BPDU传递网络信息计算出一根无环的树状网络结构,并阻塞特定端口. 在网络出现故障的时候,STP能快速发现链路故障,并尽快找出另外一条路径进行 ...