以下內容轉載自:轉載地址
在網絡通信應用中,我們往往需要自定義應用層通信協議,例如基於UDP的Real-Time Transport Protocol以及基於TCP的RTP over HTTP。鑑於RTP協議的廣泛性,wireshark(ethereal)內置了對RTP協議的支持,調試解析非常方便。RTP over HTTP作爲一種擴展的RTP協議,尚未得到wireshark的支持。在《RTP Payload Format for Transport of MPEG-4 Elementary Streams over http》中,使用wireshark只能抓獲到原始的TCP數據包,需要我們自己解析蘊含其中的RTP報文,剝離RTP over HTTP 16字節的報文頭,然後萃取出MP4V_ES碼流數據。
怎麼樣使wireshark支持自定義通信協議的解析呢?基於GPL v2的wireshark強大的插件系統支持用戶編寫自定義協議解析器插件。wireshark使用C語言編寫而成,它支持動態鏈接庫形式的插件擴展。除此之外,wireshark內置了Lua腳本引擎,可以使用Lua腳本語言編寫dissector插件。打開C:/Program Files/Wireshark/init.lua,確保disable_lua = false,以開啓wireshark的Lua Console。
Lua是一種功能強大的、快速的、輕量的、嵌入式的腳本語言,使用Lua編寫wireshark dissector插件是非常方便的。本文例解使用Lua編寫ROH(RTP over HTTP)協議解析插件。
以下爲GET http://219.117.194.183:60151/rtpOverHttp?Url=nphMpeg4/nil-640x480,獲取MP4V_ES的一個片段,在沒有ROH解析支持時,Packet 294顯示爲TCP報文。
關於RTP over HTTP報文的解析,參考《RTP Payload Format for Transport of MPEG-4 Elementary Streams over http》。關於Lua語法,參考Wireshark User’s Guide的《Lua Support in Wireshark》一節。
以下爲自定義RTP over HTTP協議解析器插件代碼roh.lua:
do
--[[
Proto.new(name, desc)
name: displayed in the column of “Protocol” in the packet list
desc: displayed as the dissection tree root in the packet details
--]]
local PROTO_ROH = Proto("ROH", "Rtp Over Http")
--[[
ProtoField:
to be used when adding items to the dissection tree
--]]
--[[
(1)Rtp Over Http Header
--]]
-- rtp over http header flag(1 byte)
local f_roh_headerflag = ProtoField.uint8("ROH.HeaderFlag", "Header Flag", base.HEX)
-- rtp over http interleaved channel(1 byte)
local f_roh_interleave = ProtoField.uint8("ROH.InterleavedChannel", "Interleaved Channel", base.DEC)
-- rtp over http packet length(2 bytes)
local f_roh_packlen = ProtoField.uint16("ROH.PacketLen", "Packet Length", base.DEC)
--[[
(2)RTP Over Http
--]]
-- rtp header(1 byte = V:2+P:1+X:1+CC:4)
local f_rtp_header = ProtoField.uint8("ROH.Header", "Rtp Header", base.HEX)
-- rtp payloadtype(1 byte = M:1+PT:7)
local f_rtp_payloadtype = ProtoField.uint8("ROH.PayloadType", "Rtp Payload Type", base.HEX)
-- rtp sequence number(2 bytes)
local f_rtp_sequence = ProtoField.uint16("ROH.Sequence", "Rtp Sequence Number", base.DEC)
-- rtp timestamp(4 bytes)
local f_rtp_timestamp = ProtoField.uint32("ROH.Timestamp", "Rtp Timestamp", base.DEC)
-- rtp synchronization source identifier(4 bytes)
local f_rtp_ssrc = ProtoField.uint32("ROH.SSRC", "Rtp SSRC", base.DEC)
-- define the fields table of this dissector(as a protoField array)
PROTO_ROH.fields = {f_roh_headerflag, f_roh_interleave, f_roh_packlen, f_rtp_header, f_rtp_payloadtype, f_rtp_sequence, f_rtp_timestamp, f_rtp_ssrc}
--[[
Data Section
--]]
local data_dis = Dissector.get("data")
--[[
ROH Dissector Function
--]]
local function roh_dissector(buf, pkt, root)
-- check buffer length
local buf_len = buf:len()
if buf_len < 16
then
return false
end
-- check header flag
if buf(0,2):uint() ~= 0x2400
then
return false
end
--[[
packet list columns
--]]
pkt.cols.protocol = "ROH"
pkt.cols.info = "Rtp Over Http"
--[[
dissection tree in packet details
--]]
-- tree root
local t = root:add(PROTO_ROH, buf(0,16))
-- child items
-- ROH Header
t:add(f_roh_headerflag, buf(0,1))
t:add(f_roh_interleave, buf(1,1))
t:add(f_roh_packlen, buf(2,2))
-- ROH
-- (1)header
t:add(f_rtp_header, buf(4,1))
-- (2)payloadtype
t:add(f_rtp_payloadtype, buf(5,1))
-- (3)sequence number
t:add(f_rtp_sequence, buf(6,2))
-- (4)timestamp
t:add(f_rtp_timestamp, buf(8,4))
-- (5)ssrc
t:add(f_rtp_ssrc, buf(12,4))
if buf_len > 16
then
local data_len = buf:len()-16;
local d = root:add(buf(16, data_len), "Data")
d:append_text("("..data_len.." bytes)")
d:add(buf(16, data_len), "Data: ")
d:add(buf(16,0), "[Length: "..data_len.."]")
local start_code = buf(16,4):uint()
if start_code == 0x000001b0
then
d:add(buf(16,0), "[Stream Info: VOS]")
elseif start_code == 0x000001b6
then
local frame_flag = buf(20,1):uint()
if frame_flag<2^6
then
d:add(buf(16,0), "[Stream Info: I-Frame]")
elseif frame_flag<2^7
then
d:add(buf(16,0), "[Stream Info: P-Frame]")
else
d:add(buf(16,0), "[Stream Info: B-Frame]")
end
end
end
return true
end
--[[
Dissect Process
--]]
function PROTO_ROH.dissector(buf, pkt, root)
if roh_dissector(buf, pkt, root)
then
-- valid ROH diagram
else
data_dis:call(buf, pkt, root)
end
end
--[[
Specify Protocol Port
--]]
local tcp_encap_table = DissectorTable.get("tcp.port")
tcp_encap_table:add(60151, PROTO_ROH)
end
通過菜單“ToolsàLuaàEvaluate”打開Lua調試窗口,將以上代碼輸入Evaluate窗口,然後點擊“Evaluate”按鈕,若無錯誤提示,且末尾提示“--[[ Evaluated --]]”,則說明代碼無語法錯誤。這時,通過菜單“HelpàSupported Protocols”可以發現,wireshark已經添加了ROH協議調試支持,在Display Filter中輸入“roh”,則可以看到具體解析結果。
將以上代碼保存爲C:/Program Files/Wireshark/roh.lua。在C:/Program Files/Wireshark/init.lua末尾添加代碼行:dofile("roh.lua")。這樣在以後啓動wireshark時,自動加載roh.lua。
增加roh.Lua插件擴展後,Packet 294將解析爲RTP over HTTP報文。最後的Data部分爲剝離16字節報文頭後的碼流。
由於尚未掌握Lua的位域(bitfield)操作,因此上面對於RTP的解析不完整,第一個字節(V:2+P:1+X:1+CC:4)和第二字節(M:1+PT:7)的相關位域沒有解析出來。
既然wireshark內置了對RTP協議的支持,我們在解析完RTP over HTTP的頭4個字節後,可以將餘下的報文交由RTP協議解析器解析。以下爲調用內置RTP協議解析器代碼roh_rtp.lua:
do
--[[
Proto.new(name, desc)
name: displayed in the column of “Protocol” in the packet list
desc: displayed as the dissection tree root in the packet details
--]]
local PROTO_ROH = Proto("ROH", "Rtp Over Http")
local PROTO_PANASONIC_PTZ = Proto("PANASONIC_PTZ", "Panasonic PTZ Protocol")
--[[
ProtoField:
to be used when adding items to the dissection tree
--]]
--[[
1.ROH ProtoField
--]]
--rtp over http header flag(1 byte)
local f_roh_headerflag = ProtoField.uint8("ROH.HeaderFlag", "Header Flag", base.HEX)
--rtp over http interleaved channel(1 byte)
local f_roh_interleave = ProtoField.uint8("ROH.InterleavedChannel", "Interleaved Channel", base.DEC)
--rtp over http packet length(2 bytes)
local f_roh_packlen = ProtoField.uint16("ROH.PacketLen", "Packet Length", base.DEC)
--define the fields table of this dissector(as a protoField array)
PROTO_ROH.fields = {f_roh_headerflag, f_roh_interleave, f_roh_packlen}
--[[
2.PANASONIC_PTZ ProtoField
--]]
-- panasonic ptz header flag(32 ascii)
local f_panasonic_ptz_flag = ProtoField.bytes("PANASONIC_PTZ", "Header Flag")
-- panasonic ptz command(6~17 ascii)
local f_panasonic_ptz_cmd = ProtoField.bytes("PANASONIC_PTZ", "Command")
--define the fields table of this dissector(as a protoField array)
PROTO_PANASONIC_PTZ.fields = {f_panasonic_ptz_flag, f_panasonic_ptz_cmd}
--[[
Data Section
--]]
local data_dis = Dissector.get("data")
--[[
ROH Dissector Function
--]]
local function roh_dissector(buf, pkt, root)
-- check buffer length
local buf_len = buf:len()
if buf_len < 16
then
return false
end
-- check header flag
if buf(0,2):uint() ~= 0x2400
then
return false
end
--[[
packet list columns
--]]
pkt.cols.protocol = "ROH"
pkt.cols.info = "Rtp Over Http"
--[[
dissection tree in packet details
--]]
-- tree root
local t = root:add(PROTO_ROH, buf(0,4))
-- child items
t:add(f_roh_headerflag, buf(0,1))
t:add(f_roh_interleave, buf(1,1))
t:add(f_roh_packlen, buf(2,2))
return true
end
--[[
PANASONIC_PTZ Dissector Function Helper
--]]
local function get_cmd_len(buf)
local found=0
for i=0,17 do
if buf(i,1):uint() == 0x26
then
found = i
break
end
end
return found
end
--[[
PANASONIC_PTZ Dissector Function
--]]
local function panasonic_ptz_dissector(buf, pkt, root)
-- check buffer length
local buf_len = buf:len()
if buf_len < 32
then
return false
end
-- check header flag
if buf(0,32):string() ~= "GET /nphControlCamera?Direction="
then
return false
end
-- check direction
local sub_buf = buf(32, 18):tvb()
local cmd_len = get_cmd_len(sub_buf)
if cmd_len > 0
then
--[[
packet list columns
--]]
pkt.cols.protocol = "PANASONIC_PTZ"
pkt.cols.info = "Panasonic PTZ Protocol"
--[[
dissection tree in packet details
--]]
-- tree root
local t = root:add(PROTO_PANASONIC_PTZ, buf(0,buf_len))
-- child items
local flag = t:add(f_panasonic_ptz_flag, buf(0,32))
flag:add(buf(0,31), "["..buf(0,31):string().."]")
local cmd = t:add(f_panasonic_ptz_cmd, buf(32,cmd_len))
cmd:add(buf(32,cmd_len), "["..buf(32,cmd_len):string().."]")
else
return false
end
return true
end
--[[
Dissect Process
--]]
function PROTO_ROH.dissector(buf, pkt, root)
if roh_dissector(buf, pkt, root)
then
-- skip over rtp over http header
local rtp_buf = buf(4,buf:len()-4):tvb()
-- call internal rtp dissector
local rtp_dissector = Dissector.get("rtp")
rtp_dissector:call(rtp_buf, pkt, root)
elseif panasonic_ptz_dissector(buf, pkt, root)
then
-- valid ptz datagram
else
data_dis:call(buf, pkt, root)
end
end
--[[
Specify Protocol Port
--]]
local tcp_encap_table = DissectorTable.get("tcp.port")
tcp_encap_table:add(60151, PROTO_ROH)
end
此外,wireshark內置了對MP4V_ES碼流解析器,選擇菜單“EditàPreferencesàProtocolsàMP4V_ES”,在“MP4V_ES dynamic payload type:”中填寫“96”。
將以上代碼保存爲C:/Program Files/Wireshark/roh_rtp.lua。在C:/Program Files/Wireshark/init.lua末尾添加代碼行:dofile("roh_rtp.lua"),在dofile("roh.lua")前添加“--”註釋,以免端口衝突(在roh.lua中tcp.port=60151)。保存init.lua後,重啓wireshark。
自定義應用層協議基於傳輸層(UDP/TCP),自定義的協議端口號不能與wireshark內置應用層協議衝突。若將ROH協議指定爲TCP 80,則需要選擇菜單“AnalyzeàEnabled Protocols”勾掉HTTP,這樣TCP 80的報文將按照ROH協議解析。
在Display Filter中輸入“roh”可查看RTP+MP4V_ES解析結果,如下圖所示:
此外,在Display Filter中輸入“panasonic_ptz”可查看PTZ控制報文。
參考:
《Small is Beautiful: the design of Lua》
《使用lua編寫Wireshark的dissector插件》