前段时间写了一个局域网音视频通话的程序,使用开源 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. NTC热敏电阻基础以及应用和选择(转)

    源:NTC热敏电阻基础以及应用和选择 NTC被称为负温度系数热敏电阻,是由Mn-Co-Ni的氧化物充分混合后烧结而成的陶瓷材料制备而来,它在实现小型化的同时,还具有电阻值-温度特性波动小.对各种温度变 ...

  2. Build Telemetry for Distributed Services之OpenCensus:Tracing2(待续)

    part 1:Tracing1 Sampling Sampling Samplers Global sampler Per span sampler Rules References

  3. Mybase解决保存文件后再打开不能修改编辑

    1.问题复现 2.解决方式 3.可以修改编辑

  4. Spring MVC Action参数类型 List集合类型(简单案例)

    题目:定义一个员工实体(Employee),实现批量添加员工功能,在表单中可以一次添加多个员工,数据可以不持久化 1,新建一个项目 2, 然后选择Maven框架选择 maven-archetype-w ...

  5. LeetCode_172. Factorial Trailing Zeroes

    172. Factorial Trailing Zeroes Easy Given an integer n, return the number of trailing zeroes in n!. ...

  6. Oracle SQL 脚本跟踪

    NC Oracle SQL 脚本跟踪 脚本: select * from v$sqlarea a and a.LAST_ACTIVE_TIME >= to_date( '2013-02-21 1 ...

  7. mysql新建数据库(database)设置为utf8

    CREATE DATABASE IF NOT EXISTS yourdbname DEFAULT CHARSET utf8 COLLATE utf8_general_ci;

  8. python面向对象之封装,继承,多态

    封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容.在python的类中,封装即通过__init__函数将数据赋给对应的变量进行保存,便于其他地方使用 所以,在使用面向对象的封装特 ...

  9. Direct2D 学习笔记(3)图层 Layer

    利用图层Layer绘制资源网址:https://docs.microsoft.com/zh-cn/windows/win32/direct2d/direct2d-layers-overview   1 ...

  10. Spark学习笔记0——简单了解和技术架构

    目录 Spark学习笔记0--简单了解和技术架构 什么是Spark 技术架构和软件栈 Spark Core Spark SQL Spark Streaming MLlib GraphX 集群管理器 受 ...