SRT协议的Wireshark解析器编写(Lua)
WireSharks插件編寫(lua)
- 前言
- API
- Proto
- ProtoField
- 滿足按位顯示的例子(同時(shí)滿足字符串查找)
- Tvb
- TvbRange
- Pinfo
- TreeItem
- 實(shí)現(xiàn)協(xié)議里面添加子樹例子
- DissectorTable
- prefs
- 代碼部分
- 大致框架
- 完善
- 完善字段
- 完善解析函數(shù)
- 關(guān)于標(biāo)志位的操作,lua中怎么做
- 關(guān)于lua中的for循環(huán)
- 關(guān)于lua中的switch-case語句
- 裝載插件
- Windows
- OSX
- 插件抓包效果圖
- 關(guān)于Post-dissector和Listener
前言
關(guān)于SRT的解析器已經(jīng)上傳到SRT官方的倉庫了~大家有需要可以到SRT的官方倉庫去下載使用:
官方GitHub地址
我的GitHub地址
參考:
-
趙子清博客
-
wireshark官方文檔
wireshark的lua插件由于最近研究SRT的使用,發(fā)現(xiàn)包并不能抓到,盡管wireshark內(nèi)支持SRT包,但不知為何抓不到,只能看到下層的UDP包,因此只能自己另外寫了,目前插件寫完已經(jīng)過去兩周有余,一直想總結(jié),都沒時(shí)間。。。這次特地申請了加班過來寫,記錄下來,以備下次編寫時(shí)查閱,其中博客部分內(nèi)容搬運(yùn)自趙子清的博客,鏈接貼在上方,同時(shí)加了很多自己的例子,希望能夠幫助大家
API
Proto
表示一個(gè)新的Protocol,在Wireshark中Protocol對象有很多用處,解析器是其中主要的一個(gè)。主要接口有:
| proto:__call (name,desc) | 創(chuàng)建Proto對象。name和desc分別是對象的名稱和描述,前者可用于過濾器等 |
| proto.name | get名稱 |
| proto.fields | get/set字段 |
| proto.prefs | get配置項(xiàng) |
| proto.init | 初始化,無參數(shù) |
| proto.dissector | 解析函數(shù),3個(gè)參數(shù)tvb,pinfo,tree,分別是報(bào)文內(nèi)容,報(bào)文信息和解析樹結(jié)構(gòu) |
| proto:register_heuristic (listname, func) | 為Proto注冊一個(gè)啟發(fā)式解析器,被調(diào)用時(shí),參數(shù)func將被傳入與dissector方法相同的3個(gè)參數(shù) |
Proto舉例:
local NAME = "bvc_srt" local bvc_srt = Proto(NAME, "BVC_SRT Protocol")-- 注冊解析器 DissectorTable.get("udp.port"):add(PORT, bvc_srt)ProtoField
表示協(xié)議字段,一般用于解析字段后往解析樹上添加節(jié)點(diǎn)。根據(jù)字段類型不同,其接口可以分為兩大類。
這些接口都會(huì)返回一個(gè)新的字段對象。方括號(hào)內(nèi)是可選字段,花括號(hào)內(nèi)是可替換的類型字段。
整型:
- ProtoField.{type} (abbr, [name], [base], [valuestring], [mask], [desc])
type包括:uint8, uint16, uint24, uint32, uint64, framenum
舉例:
fields.time_stamp = ProtoField.uint32("bvc_srt.time_stamp", "Time Stamp", base.DEC)其他類型
- ProtoField.{type} (abbr, [name], [base], [valuestring], [mask], [desc])
type包括:float, double, string, stringz, bytes, bool, ipv4, ipv6, ether,oid, guid
以IP地址的方式顯示舉例:
fields.peer_ipaddr = ProtoField.ipv4("bvc_srt.peer_ipaddr", "Peer IP address")-- 添加到樹的時(shí)候要注意 -- 如果是網(wǎng)絡(luò)序,要使用add_le,而不是add滿足按位顯示的例子(同時(shí)滿足字符串查找)
有的時(shí)候需要按位去顯示某一些標(biāo)志位,還有一個(gè)需求滿足bvc_srt.FF_state== "[Middle packet]"這樣的查找方式,那就需要給FF_state加一個(gè)表去映射
-- FF這個(gè)標(biāo)志位涉及第一個(gè)字節(jié)最高兩個(gè)位 -- 最高兩位對應(yīng)二進(jìn)制1100 0000 -> 0xc0 local FF_state_select = {[0] = "[Middle packet]",[1] = "[Last packet]",[2] = "[First packet]",[3] = "[Single packet]" } fields.FF_state = ProtoField.uint8("bvc_srt.FF_state", "FF state", base.HEX, FF_state_select, 0xC0)-- 解析函數(shù)中直接這么處理就可以了 data_flag_info_tree:add(fields.FF_state, tvb(offset, 1))
不同的類型有不同的參數(shù),具體涉及哪個(gè)類型需要查看文檔寫~
Tvb
Tvb(Testy Virtual Buffer)表示報(bào)文緩存,也就是實(shí)際的報(bào)文數(shù)據(jù),可以通過下面介紹的TvbRange從報(bào)文數(shù)據(jù)中解出信息。主要接口有:
| tvb:__tostring() | 將報(bào)文數(shù)據(jù)轉(zhuǎn)化為字符串,可用于調(diào)試 |
| tvb:reported_len() | get tvb的(not captured)長度 |
| tvb:len() | get tvb的(captured)長度 |
| tvb:reported_length_remaining() | 獲取當(dāng)前tvb的剩余長度,如果偏移值大于報(bào)文長度,則返回-1 |
| tvb:offset() | 返回原始偏移 |
用法舉例
-- tvb(offset, 4)表示從offset開始之后的4個(gè)字節(jié) subtree:add_le(fields.peer_ipaddr, tvb(offset, 4))TvbRange
表示Tvb的可用范圍,常用來從Tvb中解出信息。主要接口有
| tvb:range([offset], [length]) | 從tvb創(chuàng)建TvbRange,可選參數(shù)分別是偏移和長度,默認(rèn)值分別是0和總長度 |
| tvbrange:{type}() | 將tvbrange所表示范圍內(nèi)的數(shù)據(jù)轉(zhuǎn)換成type類型的值,type包括但不限于:uint,uint64,int,int64,float,ipv4,ether,nstime,string,ustring,bytes,bitfield等,其中某些類型的方法可以帶一些參數(shù) |
emmm,這個(gè)部分基本沒有用到過。。我編的時(shí)候沒有用到。
Pinfo
報(bào)文信息(packet information)。主要接口有:
| pinfo.len pinfo.caplen | get報(bào)文長度 |
| pinfo.abs_ts | get報(bào)文捕獲時(shí)間 |
| pinfo.number | get報(bào)文編號(hào) |
| pinfo.src pinfo.dst | get/set報(bào)文的源地址、目的地址 |
| pinfo.columns pinfo.cols | get報(bào)文列表列(界面) |
使用舉例
-- 修改協(xié)議名稱(效果見下圖) pinfo.cols.protocol = bvc_srt.name -- 為報(bào)文的信息尾部添加字符串(效果見下圖) pinfo.cols.info:append(" [ACK]") -- 還有一種便是直接覆蓋 pinfo.cols.info = "[ACK]"TreeItem
表示報(bào)文解析樹中的一個(gè)樹節(jié)點(diǎn)。主要接口有:
| treeitem:add([protofield], [tvbrange], [value], [label]) | 向當(dāng)前樹節(jié)點(diǎn)添加一個(gè)子節(jié)點(diǎn) |
| treeitem:set_text(text) | 設(shè)置當(dāng)前樹節(jié)點(diǎn)的文本 |
| treeitem:prepend_text(text) | 在當(dāng)前樹節(jié)點(diǎn)文本的前面加上text |
| treeitem:append_text(text) | 在當(dāng)前樹節(jié)點(diǎn)文本的后面加上text |
還有注意一下網(wǎng)絡(luò)字節(jié)序的問題,如果是網(wǎng)絡(luò)字節(jié)序需要用add_le添加節(jié)點(diǎn)~
添加節(jié)點(diǎn)舉例
實(shí)現(xiàn)協(xié)議里面添加子樹例子
-- 子樹其實(shí)也是一個(gè)節(jié)點(diǎn),因此也需要在fields里面添加字段 fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX) -- 創(chuàng)建子樹 pack_type_tree = subtree:add(fields.pack_type_tree, tvb(offset, 4)) pack_type_tree:add(fields.msg_type, tvb(offset, 2))DissectorTable
表示一個(gè)具體協(xié)議的解析表,比如,協(xié)議TCP的解析表”tcp.port”包括http,smtp,ftp等。可以依次點(diǎn)擊wireshark菜單欄的視圖->內(nèi)部->解析器表->Integer Tables,來查看當(dāng)前的所有解析表。tcp.port解析表在“Integer tables”選項(xiàng)卡中,顧名思義,它是通過類型為整型的tcp端口號(hào)來識(shí)別下游協(xié)議的
這次我解析的是SRT的包,因此用的是UDP包,要通過UDP的端口去識(shí)別下游協(xié)議
| DissectorTable.get(name) | get名為name的解析表的引用 |
| dissectortable:add(pattern, dissector) | 將Proto或Dissector對象添加到解析表,即注冊。pattern可以是整型值,整型值范圍或字符串,這取決于當(dāng)前解析表的類型 |
| dissectortable:remove(pattern, dissector) | 將滿足pattern的一個(gè)或一組Proto、Dissector對象從解析表中刪除 |
將srt注冊到udp的協(xié)議下
DissectorTable.get("udp.port"):add(PORT, bvc_srt)prefs
有的時(shí)候需要去指定解析某個(gè)端口,則使用prefs,prefs就是首選項(xiàng),通過修改首選項(xiàng)可以指定解析某一個(gè)端口或者,指定解析某些包等,總之就是一個(gè)類似于偏好設(shè)置的東西,由于大部分博客,都未涉及到這一部分,只有wireshark官方手冊上孤零零的一個(gè)Proto.prefs_changed,導(dǎo)致我壓根不知道怎么寫,這部分是參考github上其他人寫的lua插件寫的。。。。。
下面介紹一下這個(gè)首選項(xiàng)應(yīng)該怎么用~
| prefs:__newindex(name, pref) | 創(chuàng)建一個(gè)新的首選項(xiàng),pref參數(shù)指的是使用Pref.uint等創(chuàng)建的內(nèi)容 |
| Pref.uint(label, default, descr) | 創(chuàng)建一個(gè)uint型的Pref,可以作為__newindex的參數(shù) |
| prefs:__index(name) | 獲取對應(yīng)參數(shù)的默認(rèn)值 |
使用示例
local port = 1935-- 第一種方式 local pref = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port") srt.prefs:__newindex("srt_udp_port", pref) Pref.uint("SRT UDP Port", srt.prefs:__index("srt_udp_port") , "SRT UDP Port")-- 第二種方式 -- 使用[]的方式,和上面其實(shí)是一樣的,只是寫法不同而已 srt.prefs["srt_udp_port"] = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port")-- 第三種方式 srt.prefs.srt_udp_port = Pref.uint("SRT UDP Port", 1935, "SRT UDP Port")-- 下面的代碼加在任意一種方式后面才算完整 DissectorTable.get("udp.port"):add(port, srt)-- prefs changed will listen at new port function bvc_srt.prefs_changed()if port ~= bvc_srt.prefs.srt_udp_port thenif port ~= 0 thenDissectorTable.get("udp.port"):add(srt.prefs["srt_udp_port"], srt)endport = bvc_srt.prefs.srt_udp_portif port ~= 0 thenDissectorTable.get("udp.port"):add(srt.prefs["srt_udp_port"], srt)endend end代碼部分
需要查看完整代碼請移步SRT官方倉庫或者我自己的GitHub
官方GitHub地址
我的GitHub地址
大致框架
這部分包含了一個(gè)完整的解析器需要的部分:解析器對象,解析器函數(shù),注冊解析器到wireshark的解析表中
-- create a new dissector local NAME = "bvc_srt" local PORT = 1935 local bvc_srt = Proto(NAME, "BVC_SRT Protocol")-- create fields of bvc_srt local fields = bvc_srt.fields local pack_type_select = {[0] = "Data Packet",[1] = "Control Packet" } fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX)-- dissect packet function bvc_srt.dissector (tvb, pinfo, tree)-- 解析函數(shù)內(nèi)部邏輯 end-- register this dissector DissectorTable.get("udp.port"):add(PORT, bvc_srt)完善
完善字段
報(bào)文中的每一段數(shù)據(jù)都有自己的名稱,這些名稱我們都需要添加到表中,因此需要?jiǎng)?chuàng)建對象存到fields中去,以下是一些字段的例子
-- create fields of bvc_srt local fields = bvc_srt.fields local pack_type_select = {[0] = "Data Packet",[1] = "Control Packet" } fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX) fields.pack_type = ProtoField.uint16("bvc_srt.pack_type", "Packet Type", base.HEX, pack_type_select, 0x8000) fields.reserve = ProtoField.uint16("bvc_srt.reserve", "Reserve", base.DEC)完善解析函數(shù)
給出一部分解析Data Packet的代碼,這段代碼可以解析一部分的data的報(bào)文,其中包括了怎么在節(jié)點(diǎn)后添加信息,怎么創(chuàng)建子樹,并在子樹添加節(jié)點(diǎn)
-- dissect packet function bvc_srt.dissector (tvb, pinfo, tree)-- 解析函數(shù)內(nèi)部邏輯 -- 0 -> Data Packetpack_type_tree:add(fields.pack_type, tvb(offset, 2))pack_type_tree:append_text(" (Data Packet)")local seq_num = tvb(offset, 4):uint()pinfo.cols.info:append(" (Data Packet)(Seq Num:" .. seq_num .. ")")-- Data Packet,則前4字節(jié)為包序號(hào)subtree:add(fields.seq_num, tvb(offset, 4))offset = offset + 4data_flag_info_tree = subtree:add(fields.data_flag_info_tree, tvb(offset, 1))-- 處理FF標(biāo)志位local FF_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0xC0), 6)if FF_state == 0 thendata_flag_info_tree:append_text(" [Middle packet]")elseif FF_state == 1 thendata_flag_info_tree:append_text(" [Last packet]")elseif FF_state == 2 thendata_flag_info_tree:append_text(" [First packet]")elsedata_flag_info_tree:append_text(" [Single packet]")enddata_flag_info_tree:add(fields.FF_state, tvb(offset, 1)) end關(guān)于標(biāo)志位的操作,lua中怎么做
上述例子中有關(guān)于FF_state的處理,bit.band就是將給定的數(shù)與掩碼進(jìn)行與操作,得到的結(jié)果給了變量,然后做相關(guān)不同的處理,由于位操作比較多,因此經(jīng)常用到
local FF_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0xC0), 6)- bit.rshift(a, b)表示對a向右移b位
- bit.band()則是對數(shù)進(jìn)行按位與,參數(shù)可以有多個(gè)
注意:位操作需要數(shù)據(jù)類型是整型,但是tvb中取出來的并非整型,需要用uint()轉(zhuǎn)換一下
關(guān)于lua中的for循環(huán)
由于lua的for循環(huán)并不支持在循環(huán)體內(nèi)改變循環(huán)變量,其實(shí)這么做確實(shí)不安全,有的時(shí)候會(huì)死循環(huán),但是在有些場景下,改變循環(huán)變量更方便處理邏輯,比如:
現(xiàn)在有一段數(shù)據(jù),里面包含的是丟失包的序號(hào):
- 有的時(shí)候,這個(gè)長度是4字節(jié),里面只包含了這個(gè)丟失包的序號(hào)
- 有的時(shí)候這個(gè)長度是8字節(jié),前4個(gè)字節(jié)表示丟包的開始序號(hào),后4字節(jié)表示丟包結(jié)束序號(hào)
- 數(shù)據(jù)中既有第一種表示方式,也有第二種表示方式,有可能有多個(gè)這樣的表示方式的數(shù)據(jù)在一起
那么我現(xiàn)在要處理這個(gè)數(shù)據(jù),怎么辦
lua中可以通過閉包的方式改變循環(huán)變量,即循環(huán)變量通過一個(gè)函數(shù)去改變,通過這個(gè)函數(shù)去做實(shí)際的處理,給出示例代碼
local start = offset local ending = tvb:len() local lost_list_tree = subtree:add(fields.lost_list_tree, tvb(offset, ending - offset)) -- 每次start從function中去取,由function去控制變量 for start in function()local first_bit = bit.rshift(tvb(start, 1):uint(), 7)if first_bit == 1 thenlocal lost_pack_range_tree = lost_list_tree:add(fields.lost_pack_range_tree, tvb(start, 8))local lost_start = bit.band(tvb(start, 4):uint(), 0x7FFFFFFF)lost_pack_range_tree:append_text(" (" .. lost_start .. " -> " .. tvb(start + 4, 4):uint() .. ")")lost_pack_range_tree:add(fields.lost_start, tvb(start, 4), lost_start)start = start + 4lost_pack_range_tree:add(fields.up_to, tvb(start, 4))start = start + 4elselost_list_tree:add(fields.lost_pack_seq, tvb(start, 4))start = start + 4endreturn startenddoif start == ending thenbreakend end參考資料:lua閉包
關(guān)于lua中的switch-case語句
lua中沒有switch-case的方式,只能通過函數(shù)數(shù)組去搞,其實(shí)c語言中也是這么去實(shí)現(xiàn)的,用函數(shù)指針數(shù)組。
比如有多種type的包,每種type數(shù)據(jù)都不一樣,那要么用if else,但是我想用switch啊,那么lua中就如下面例子中這么處理~
local switch = {[1] = function()-- parse dataend,[2] = function()-- parse dataend,[3] = function()-- parse dataend,[4] = function()-- parse dataend } -- 處理對應(yīng)的msg_type,通過switch實(shí)現(xiàn) local case = switch[msg_type] if case thencase() else-- default casesubtree:add(fields.msg_type, tvb(offset, 2)):append_text(" [Unknown Message Type]")offset = offset + 4 end裝載插件
Windows
插件放在wireshark安裝目錄的plugins/3.0下即可
我的Windows下的參考目錄是
OSX
將srt-dev.lua腳本放入wireshark目錄下,Mac下參考目錄
/Applications/WireShark.app/Contents/Resources/share/wireshark在文件夾下的init.lua最后加一行加上下面這句話
dofile(DATA_DIR.."srt-dev.lua")然后直接啟動(dòng)wireshark即可,或者點(diǎn)擊Analyze->Reload Lua Plugins。
插件抓包效果圖
抓取SRT協(xié)議Data Packet的效果圖
關(guān)于Post-dissector和Listener
由于自己并未使用到,因?yàn)閭鬏數(shù)臄?shù)據(jù)是視頻數(shù)據(jù),因此包的數(shù)據(jù)內(nèi)容部分沒有包含什么有效信息,未用到這兩個(gè),因此貼出趙子清的博客地址,以供之后方便查閱,若以后有寫wireshark插件寫到這部分,再來做補(bǔ)充
關(guān)于Post-dissector和Listener博客
總結(jié)
以上是生活随笔為你收集整理的SRT协议的Wireshark解析器编写(Lua)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP获取跳转后的真实地址
- 下一篇: 2021年全球CT和PET扫描仪收入大约