SRT协议的Wireshark解析器编写(Lua)
WireSharks插件編寫(lua)
- 前言
- API
- Proto
- ProtoField
- 滿足按位顯示的例子(同時滿足字符串查找)
- Tvb
- TvbRange
- Pinfo
- TreeItem
- 實現協議里面添加子樹例子
- DissectorTable
- prefs
- 代碼部分
- 大致框架
- 完善
- 完善字段
- 完善解析函數
- 關于標志位的操作,lua中怎么做
- 關于lua中的for循環
- 關于lua中的switch-case語句
- 裝載插件
- Windows
- OSX
- 插件抓包效果圖
- 關于Post-dissector和Listener
前言
關于SRT的解析器已經上傳到SRT官方的倉庫了~大家有需要可以到SRT的官方倉庫去下載使用:
官方GitHub地址
我的GitHub地址
參考:
-
趙子清博客
-
wireshark官方文檔
wireshark的lua插件由于最近研究SRT的使用,發現包并不能抓到,盡管wireshark內支持SRT包,但不知為何抓不到,只能看到下層的UDP包,因此只能自己另外寫了,目前插件寫完已經過去兩周有余,一直想總結,都沒時間。。。這次特地申請了加班過來寫,記錄下來,以備下次編寫時查閱,其中博客部分內容搬運自趙子清的博客,鏈接貼在上方,同時加了很多自己的例子,希望能夠幫助大家
API
Proto
表示一個新的Protocol,在Wireshark中Protocol對象有很多用處,解析器是其中主要的一個。主要接口有:
| proto:__call (name,desc) | 創建Proto對象。name和desc分別是對象的名稱和描述,前者可用于過濾器等 |
| proto.name | get名稱 |
| proto.fields | get/set字段 |
| proto.prefs | get配置項 |
| proto.init | 初始化,無參數 |
| proto.dissector | 解析函數,3個參數tvb,pinfo,tree,分別是報文內容,報文信息和解析樹結構 |
| proto:register_heuristic (listname, func) | 為Proto注冊一個啟發式解析器,被調用時,參數func將被傳入與dissector方法相同的3個參數 |
Proto舉例:
local NAME = "bvc_srt" local bvc_srt = Proto(NAME, "BVC_SRT Protocol")-- 注冊解析器 DissectorTable.get("udp.port"):add(PORT, bvc_srt)ProtoField
表示協議字段,一般用于解析字段后往解析樹上添加節點。根據字段類型不同,其接口可以分為兩大類。
這些接口都會返回一個新的字段對象。方括號內是可選字段,花括號內是可替換的類型字段。
整型:
- 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")-- 添加到樹的時候要注意 -- 如果是網絡序,要使用add_le,而不是add滿足按位顯示的例子(同時滿足字符串查找)
有的時候需要按位去顯示某一些標志位,還有一個需求滿足bvc_srt.FF_state== "[Middle packet]"這樣的查找方式,那就需要給FF_state加一個表去映射
-- FF這個標志位涉及第一個字節最高兩個位 -- 最高兩位對應二進制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)-- 解析函數中直接這么處理就可以了 data_flag_info_tree:add(fields.FF_state, tvb(offset, 1))
不同的類型有不同的參數,具體涉及哪個類型需要查看文檔寫~
Tvb
Tvb(Testy Virtual Buffer)表示報文緩存,也就是實際的報文數據,可以通過下面介紹的TvbRange從報文數據中解出信息。主要接口有:
| tvb:__tostring() | 將報文數據轉化為字符串,可用于調試 |
| tvb:reported_len() | get tvb的(not captured)長度 |
| tvb:len() | get tvb的(captured)長度 |
| tvb:reported_length_remaining() | 獲取當前tvb的剩余長度,如果偏移值大于報文長度,則返回-1 |
| tvb:offset() | 返回原始偏移 |
用法舉例
-- tvb(offset, 4)表示從offset開始之后的4個字節 subtree:add_le(fields.peer_ipaddr, tvb(offset, 4))TvbRange
表示Tvb的可用范圍,常用來從Tvb中解出信息。主要接口有
| tvb:range([offset], [length]) | 從tvb創建TvbRange,可選參數分別是偏移和長度,默認值分別是0和總長度 |
| tvbrange:{type}() | 將tvbrange所表示范圍內的數據轉換成type類型的值,type包括但不限于:uint,uint64,int,int64,float,ipv4,ether,nstime,string,ustring,bytes,bitfield等,其中某些類型的方法可以帶一些參數 |
emmm,這個部分基本沒有用到過。。我編的時候沒有用到。
Pinfo
報文信息(packet information)。主要接口有:
| pinfo.len pinfo.caplen | get報文長度 |
| pinfo.abs_ts | get報文捕獲時間 |
| pinfo.number | get報文編號 |
| pinfo.src pinfo.dst | get/set報文的源地址、目的地址 |
| pinfo.columns pinfo.cols | get報文列表列(界面) |
使用舉例
-- 修改協議名稱(效果見下圖) pinfo.cols.protocol = bvc_srt.name -- 為報文的信息尾部添加字符串(效果見下圖) pinfo.cols.info:append(" [ACK]") -- 還有一種便是直接覆蓋 pinfo.cols.info = "[ACK]"TreeItem
表示報文解析樹中的一個樹節點。主要接口有:
| treeitem:add([protofield], [tvbrange], [value], [label]) | 向當前樹節點添加一個子節點 |
| treeitem:set_text(text) | 設置當前樹節點的文本 |
| treeitem:prepend_text(text) | 在當前樹節點文本的前面加上text |
| treeitem:append_text(text) | 在當前樹節點文本的后面加上text |
還有注意一下網絡字節序的問題,如果是網絡字節序需要用add_le添加節點~
添加節點舉例
實現協議里面添加子樹例子
-- 子樹其實也是一個節點,因此也需要在fields里面添加字段 fields.pack_type_tree = ProtoField.uint32(NAME .. ".pack_type_tree", "Packet Type", base.HEX) -- 創建子樹 pack_type_tree = subtree:add(fields.pack_type_tree, tvb(offset, 4)) pack_type_tree:add(fields.msg_type, tvb(offset, 2))DissectorTable
表示一個具體協議的解析表,比如,協議TCP的解析表”tcp.port”包括http,smtp,ftp等。可以依次點擊wireshark菜單欄的視圖->內部->解析器表->Integer Tables,來查看當前的所有解析表。tcp.port解析表在“Integer tables”選項卡中,顧名思義,它是通過類型為整型的tcp端口號來識別下游協議的
這次我解析的是SRT的包,因此用的是UDP包,要通過UDP的端口去識別下游協議
| DissectorTable.get(name) | get名為name的解析表的引用 |
| dissectortable:add(pattern, dissector) | 將Proto或Dissector對象添加到解析表,即注冊。pattern可以是整型值,整型值范圍或字符串,這取決于當前解析表的類型 |
| dissectortable:remove(pattern, dissector) | 將滿足pattern的一個或一組Proto、Dissector對象從解析表中刪除 |
將srt注冊到udp的協議下
DissectorTable.get("udp.port"):add(PORT, bvc_srt)prefs
有的時候需要去指定解析某個端口,則使用prefs,prefs就是首選項,通過修改首選項可以指定解析某一個端口或者,指定解析某些包等,總之就是一個類似于偏好設置的東西,由于大部分博客,都未涉及到這一部分,只有wireshark官方手冊上孤零零的一個Proto.prefs_changed,導致我壓根不知道怎么寫,這部分是參考github上其他人寫的lua插件寫的。。。。。
下面介紹一下這個首選項應該怎么用~
| prefs:__newindex(name, pref) | 創建一個新的首選項,pref參數指的是使用Pref.uint等創建的內容 |
| Pref.uint(label, default, descr) | 創建一個uint型的Pref,可以作為__newindex的參數 |
| prefs:__index(name) | 獲取對應參數的默認值 |
使用示例
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")-- 第二種方式 -- 使用[]的方式,和上面其實是一樣的,只是寫法不同而已 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地址
大致框架
這部分包含了一個完整的解析器需要的部分:解析器對象,解析器函數,注冊解析器到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)-- 解析函數內部邏輯 end-- register this dissector DissectorTable.get("udp.port"):add(PORT, bvc_srt)完善
完善字段
報文中的每一段數據都有自己的名稱,這些名稱我們都需要添加到表中,因此需要創建對象存到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)完善解析函數
給出一部分解析Data Packet的代碼,這段代碼可以解析一部分的data的報文,其中包括了怎么在節點后添加信息,怎么創建子樹,并在子樹添加節點
-- dissect packet function bvc_srt.dissector (tvb, pinfo, tree)-- 解析函數內部邏輯 -- 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字節為包序號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標志位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關于標志位的操作,lua中怎么做
上述例子中有關于FF_state的處理,bit.band就是將給定的數與掩碼進行與操作,得到的結果給了變量,然后做相關不同的處理,由于位操作比較多,因此經常用到
local FF_state = bit.rshift(bit.band(tvb(offset, 1):uint(), 0xC0), 6)- bit.rshift(a, b)表示對a向右移b位
- bit.band()則是對數進行按位與,參數可以有多個
注意:位操作需要數據類型是整型,但是tvb中取出來的并非整型,需要用uint()轉換一下
關于lua中的for循環
由于lua的for循環并不支持在循環體內改變循環變量,其實這么做確實不安全,有的時候會死循環,但是在有些場景下,改變循環變量更方便處理邏輯,比如:
現在有一段數據,里面包含的是丟失包的序號:
- 有的時候,這個長度是4字節,里面只包含了這個丟失包的序號
- 有的時候這個長度是8字節,前4個字節表示丟包的開始序號,后4字節表示丟包結束序號
- 數據中既有第一種表示方式,也有第二種表示方式,有可能有多個這樣的表示方式的數據在一起
那么我現在要處理這個數據,怎么辦
lua中可以通過閉包的方式改變循環變量,即循環變量通過一個函數去改變,通過這個函數去做實際的處理,給出示例代碼
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閉包
關于lua中的switch-case語句
lua中沒有switch-case的方式,只能通過函數數組去搞,其實c語言中也是這么去實現的,用函數指針數組。
比如有多種type的包,每種type數據都不一樣,那要么用if else,但是我想用switch啊,那么lua中就如下面例子中這么處理~
local switch = {[1] = function()-- parse dataend,[2] = function()-- parse dataend,[3] = function()-- parse dataend,[4] = function()-- parse dataend } -- 處理對應的msg_type,通過switch實現 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")然后直接啟動wireshark即可,或者點擊Analyze->Reload Lua Plugins。
插件抓包效果圖
抓取SRT協議Data Packet的效果圖
關于Post-dissector和Listener
由于自己并未使用到,因為傳輸的數據是視頻數據,因此包的數據內容部分沒有包含什么有效信息,未用到這兩個,因此貼出趙子清的博客地址,以供之后方便查閱,若以后有寫wireshark插件寫到這部分,再來做補充
關于Post-dissector和Listener博客
總結
以上是生活随笔為你收集整理的SRT协议的Wireshark解析器编写(Lua)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP获取跳转后的真实地址
- 下一篇: matlab 直流-直流变换器毕业论文,