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

    原文连接 历史 React是一个用于构建Web应用程序UI组件的JavaScript库. React由Facebook维护,许多领先的科技品牌在其开发环境中使用React. React被Faceboo ...

  2. CommMonitor8.0 串口过滤驱动 SDK DLL版本 C#/Delphi调用DEMO

    CommMonitor8.0 SDK DLL 版本,此版本是直接调用DLL. Delphi调用定义: constCommMOnitor8x = ‘CommMOnitor8x.dll’; typeTOn ...

  3. 人工神经网络反向传播算法(BP算法)证明推导

    为了搞明白这个没少在网上搜,但是结果不尽人意,最后找到了一篇很好很详细的证明过程,摘抄整理为 latex 如下. (原文:https://blog.csdn.net/weixin_41718085/a ...

  4. 【ARM-Linux开发】使用QT和Gstreanmer 遇到的一些问题

    1.如果出现错误,可能是在安装UCT PCRF时,相关组件不全,略举两个碰到的错误. 1)curl/curl.h:No such file or directory --可能原因是libcurl及相关 ...

  5. 给引入页面的js和css资源加上版本号,防止浏览器缓存资源

    最近因为在做前端开发的相关工作,每次发布新版本以后,不到5分钟,测试童鞋一个接一个的抱怨说BUG根本就没有修改,这个时候你说的最多的话就是“清缓存!!清页面缓存!!你没有清缓存!!你清理了页面缓存就对 ...

  6. 上传文件报错500或者文件大于2M上传不上去解决方法

    修改php.ini 配置文件: 先找到配置文件------find / -name php.ini 打开php.ini修改内容:post_max_size ------ post请求上传参数的大小限制 ...

  7. luoguP1463:反素数ant(打表心得☆)

    题目描述 对于任何正整数x,其约数的个数记作g(x).例如g()=.g()=. 如果某个正整数x满足:g(x)>g(i) <i<x,则称x为反质数.例如,整数1,,,6等都是反质数. ...

  8. 转:SLAM算法解析:抓住视觉SLAM难点,了解技术发展大趋势

    SLAM(Simultaneous Localization and Mapping)是业界公认视觉领域空间定位技术的前沿方向,中文译名为“同步定位与地图构建”,它主要用于解决机器人在未知环境运动时的 ...

  9. windows 下安装ElasticSearch方法

    1.https://www.oracle.com/technetwork/java/javase/downloads/jdk12-downloads-5295953.html 在此页面下载安装JDK1 ...

  10. 数据库数据生成Excel表格(多用在导出数据)

    最近在项目开发中遇到这样一个需求,用户聊天模块产品要求记录用户聊天信息,但只保存当天的,每天都要刷新清空数据,但聊天记录要以Excel的形式打印出来,于是就引出了将数据库的数据导出成Excel表格的需 ...