@leaveye
2026-04-28T10:16:52.000000Z
字数 20282
阅读 27
wireshark lua script ws protocol
--[[-- Drop this file into `plugins` folder in Wireshark folder, which-- should contains `profiles` folder in it.--]]-- set UDP ports for the dissectorslocal ws_proto = { audio_port = 4600, video_port = 4602, subtitle_port = 4604 }-----------------------------------------------------------------------------local base = baselocal ByteArray = ByteArraylocal Dissector = Dissectorlocal DissectorTable = DissectorTablelocal ENC_GB18030 = ENC_GB18030local Proto = Protolocal ProtoField = ProtoField-----------------------------------------------------------------------------local mpeg1layer2_proto = Dissector.get("mpeg-audio")local h264raw_proto = Dissector.get("h264_bytestream") or Dissector.get("h264")local function with_proto(name, description, fn)local proto = Proto(name, description)fn(proto)return protoendlocal function have_prefix(buffer, prefix)local want_len = #prefixreturn buffer:len() >= want_len and buffer:raw(0, want_len) == prefixendlocal function format_bits(data, bitsize, bitmask)local format_string = ""for i = 1, bitsize, 1 dolocal cur_data, cur_mask = data >> (i - 1), bitmask >> (i - 1)local value_bit, mask_bit = cur_data & 1, cur_mask & 1local need_gap = (i & 3) == 1 and i >= 4format_string = (mask_bit == 0 and "." or (value_bit ~= 0 and "1" or "0")).. (need_gap and " " or "").. format_stringendreturn format_stringend-----------------------------------------------------------------------------ws_proto.audio = with_proto("WsAudio", "WS audio proto", function(proto)local fields = {index = ProtoField.uint16("WsAudio.index", "Index", base.DEC),timestamp = ProtoField.double("WsAudio.timestamp", "TS", base.DOUBLE),len = ProtoField.uint16("WsAudio.len", "Len", base.DEC),data = ProtoField.bytes("WsAudio.data", "Data"),}proto.fields = fieldsfunction proto.dissector(buffer, pinfo, tree)local subtree = tree:add(proto, buffer(), "WS Audio Packet")local pkt_index = buffer(2, 2):le_uint()subtree:append_text((", idx %u"):format(pkt_index))subtree:add(fields.len, buffer(0, 2), buffer(0, 2):le_uint())subtree:add(fields.index, buffer(2, 2), pkt_index)subtree:add(fields.timestamp,buffer(4, 4),tonumber(("%u.%04u"):format(buffer(4, 2):le_uint(), buffer(6, 2):le_uint())))if mpeg1layer2_proto thenmpeg1layer2_proto:call(buffer(8, -1):tvb(), pinfo, tree)pinfo.cols.protocol:set("Ws:" .. tostring(pinfo.cols.protocol))pinfo.cols.info:prepend(("idx %u, "):format(pkt_index))elsesubtree:add(fields.data, buffer(8, -1))pinfo.cols.protocol:set("Ws:Mp2")pinfo.cols.info:set(("idx %u, %s"):format(pkt_index, "Audio Layer 2"))endendend)-----------------------------------------------------------------------------ws_proto.video = with_proto("WsVideo", "WS video proto", function(proto)local fields = {data = ProtoField.bytes("WsVideo.data", "Data"),}proto.fields = fieldslocal packet_types = { "normal", "config", "fec", "frame-info" }local packet_control_bytes = {{ "RESET", "\xFF\xFE\xFE\xFE\xFF\xFE\xFE\xFE" },{ "MODE:Changing", "\xFF\xFE\xFE\xFE\xFF\xFE\xFE\xFA" },{ "MODE:Changed", "\xFF\xFE\xFE\xFE\xFF\xFE\xFE\xFB" },}------------------------------------------------------------------ global caches----------------------------------------------------------------local IDX_MASK = 0x1FFF -- 8191local pkt_cache = {} -- pktno -> metalocal frame_cache = {} -- frame_key -> statelocal last_frame = nillocal function clear_cache()pkt_cache = {}frame_cache = {}last_frame = nilendlocal function mod_idx(v)return v & IDX_MASKendlocal function frame_key_of(idx, seq)return mod_idx(idx - seq)endlocal function get_frame(frame_key)local st = frame_cache[frame_key]if st thenreturn stendst = {key = frame_key,want_len = nil,got_len = 0,max_seq = -1,fragments = {}, -- seq -> raw bytespktnos = {}, -- seq -> pktnocomplete = false,assembled = nil,assembled_once = false,checksum = nil,timestamp = nil,src_addr = nil,}frame_cache[frame_key] = streturn stendlocal function add_fragment(st, seq, raw, pktno)if st.fragments[seq] thenreturn falseendst.fragments[seq] = rawst.pktnos[seq] = pktnost.got_len = st.got_len + #rawif seq > st.max_seq thenst.max_seq = seqendreturn trueendlocal function can_complete(st)if not st.want_len thenreturn falseendif st.got_len < st.want_len thenreturn falseendfor i = 0, st.max_seq doif not st.fragments[i] thenreturn falseendendreturn trueendlocal function assemble_frame(st)if st.assembled thenreturn st.assembledendlocal full = ByteArray.new()for i = 0, st.max_seq dolocal s = st.fragments[i]if not s thenreturn nilendfull:append(ByteArray.new(s, true))endst.assembled = fullst.complete = truereturn fullend------------------------------------------------------------------ control packet----------------------------------------------------------------local function dissector_ctrl_bytes(buffer, pinfo, tree)for _, check_item in ipairs(packet_control_bytes) dolocal name, payload = table.unpack(check_item)local found = have_prefix(buffer, payload)if found thenlocal subtree = tree:add(ws_proto.video, buffer(), "WS Video Control Packet: " .. name)local description = "Control:" .. namesubtree:add(buffer(0, #payload), description)pinfo.cols.info:set(description)return trueendendreturn falseend------------------------------------------------------------------ frame header parser----------------------------------------------------------------local function parse_frame_header(frame_header, st, pinfo, tree)local frame_header_len = frame_header:len()if frame_header_len > 13 thenlocal payload_len = frame_header(1, 4):le_uint() - frame_header_lenlocal checksum = frame_header(5, 4):le_uint()local src_addr = frame_header(9, 4):ipv4()local timestamp = frame_header(13, 2):le_uint() + 1E-4 * frame_header(15, 2):le_uint()st.want_len = payload_lenst.checksum = checksumst.timestamp = timestampst.src_addr = tostring(src_addr)pinfo.cols.info:append((" payload %u IDR src %s"):format(payload_len, src_addr))local frame_tree = tree:add(frame_header, "IDR frame header")frame_tree:add(frame_header(0, 1), format_bits(frame_header(0, 1):uint(), 8, 0x01) .. " = IDR frame")frame_tree:add(frame_header(1, 4), ("Len: header %u payload %u"):format(frame_header_len, payload_len))frame_tree:add(frame_header(5, 4), ("Checksum: %08X"):format(checksum))frame_tree:add(frame_header(9, 4), ("Source Address: %s"):format(src_addr))frame_tree:add(frame_header(13, 4), ("Timestamp: %.6f"):format(timestamp))elselocal payload_len = frame_header(1, 4):le_uint() - frame_header_lenlocal checksum = frame_header(5, 4):le_uint()local timestamp = frame_header(9, 2):le_uint() + 1E-4 * frame_header(11, 2):le_uint()st.want_len = payload_lenst.checksum = checksumst.timestamp = timestamppinfo.cols.info:append((" payload %u"):format(payload_len))local frame_tree = tree:add(frame_header, "non-IDR frame header")frame_tree:add(frame_header(0, 1), format_bits(frame_header(0, 1):uint(), 8, 0x01) .. " = P frame")frame_tree:add(frame_header(1, 4), ("Len: header %u payload %u"):format(frame_header_len, payload_len))frame_tree:add(frame_header(5, 4), ("Checksum: %08X"):format(checksum))frame_tree:add(frame_header(9, 4), ("Timestamp: %.6f"):format(timestamp))endend------------------------------------------------------------------ render payload / assembled----------------------------------------------------------------local function render_payload(buffer, payload_tvb, st, seq, pinfo, tree)--[[tree:add(buffer(0, 0),("DEBUG frame=%u want=%s got=%u seq=%u complete=%s"):format(st.key,tostring(st.want_len),st.got_len,seq,tostring(st.complete)))--]]if not h264raw_proto thentree:add(fields.data, buffer(), ("Fragment %u of frame %u"):format(seq, st.key))pinfo.cols.protocol:set("Ws:V")returnendif st.complete thenlocal full = assemble_frame(st)local assembled_tree = tree:add(proto,buffer(0, 0),("Reassembled frame %u: %u packets, %u bytes"):format(st.key, st.max_seq + 1, st.want_len or 0))local tvb = ByteArray.tvb(full, "Reassembled WS Video Frame")h264raw_proto:call(tvb, pinfo, assembled_tree)pinfo.cols.protocol:set("Ws:H264")returnendlocal payload_tree = tree:add(buffer(), ("Frame %u: piece %u, %u bytes"):format(st.key, seq, payload_tvb:len()))h264raw_proto:call(payload_tvb, pinfo, payload_tree)pinfo.cols.protocol:set("Ws:H264")end------------------------------------------------------------------ dissector----------------------------------------------------------------function proto.init()clear_cache()endfunction proto.dissector(buffer, pinfo, tree)if pinfo.number == 1 and not pinfo.visited thenclear_cache()endlocal is_resend = falselocal offset = 0if have_prefix(buffer, "\xFF\xFF\xFF\xFF") thenis_resend = trueoffset = 4elseif dissector_ctrl_bytes(buffer, pinfo, tree) thenreturnendlocal pkt_tree = tree:add(ws_proto.video, buffer(0, offset + 4), "WS Video Packet Header: ")pinfo.cols.info:clear()if is_resend thenpkt_tree:add(buffer(0, offset), "resend mark")pinfo.cols.info:prepend("RESEND ")endlocal header1 = buffer(offset, 2)local header2 = buffer(offset + 2, 2)local header1_data = header1:le_uint()local header2_data = header2:le_uint()local not_full = (header1_data & 0x8000) ~= 0local ext_data = (header1_data & 0x4000) ~= 0offset = offset + 4-------------------------------------------------------------- ext packet------------------------------------------------------------if ext_data thenlocal ext_type = (header1_data & 0x3C00) >> 10local vpkt_type = packet_types[ext_type] or "<unknown>"local summary = ("ext(%s)"):format(vpkt_type)pinfo.cols.info:append(summary)pkt_tree:append_text(summary)if not_full thenpkt_tree:append_text(" not-full")endlocal pkt_index = header1_data & 0x03FFlocal grp_index = header2_datapkt_tree:add(header1, format_bits(header1_data, 16, 0x8000) .. " = reserved")pkt_tree:add(header1, format_bits(header1_data, 16, 0x4000) .. " = extended")pkt_tree:add(header1, format_bits(header1_data, 16, 0x3C00) .. (" = type %s"):format(vpkt_type))pkt_tree:add(header1, format_bits(header1_data, 16, 0x03FF) .. (" = sub idx %u"):format(pkt_index))pkt_tree:add(header2, format_bits(header2_data, 16, 0x3FFF) .. (" = grp idx %u"):format(grp_index))pinfo.cols.info:append((" len %u"):format(buffer:len()))returnend-------------------------------------------------------------- normal packet------------------------------------------------------------local slot_id = header1_data & 0x3FFFlocal seq_id = header2_data & 0x0FFFlocal summary = ("idx %u seq %u"):format(slot_id, seq_id)pinfo.cols.info:append(summary)pkt_tree:append_text(summary)if not_full thenpkt_tree:append_text(" not-full")endpkt_tree:add(header1,format_bits(header1_data, 16, 0x8000) .. (" = %s"):format(not_full and "not full" or "full (1024 byte)"))pkt_tree:add(header1, format_bits(header1_data, 16, 0x4000) .. " = normal")pkt_tree:add(header1, format_bits(header1_data, 16, 0x3FFF) .. (" = idx %u"):format(slot_id))pkt_tree:add(header2, format_bits(header2_data, 16, 0xFFFF) .. (" = seq %u"):format(seq_id))pinfo.cols.info:append((" len %u"):format(buffer:len()))local frame_key = frame_key_of(slot_id, seq_id)local st = get_frame(frame_key)local frame_header_len = 0if seq_id == 0 thenlocal is_idr_frame = buffer(offset, 1):uint() ~= 0frame_header_len = is_idr_frame and 17 or 13local frame_header = buffer(offset, frame_header_len)parse_frame_header(frame_header, st, pinfo, tree)offset = offset + frame_header_lenlast_frame = stendlocal payload_buf = buffer(offset)local payload_tvb = payload_buf:tvb()local payload_raw = payload_tvb:raw()-------------------------------------------------------------- cache packet meta------------------------------------------------------------pkt_cache[pinfo.number] = {idx = slot_id,seq = seq_id,frame_key = frame_key,pktno = pinfo.number,payload = payload_raw,payload_len = #payload_raw,}-------------------------------------------------------------- merge fragment------------------------------------------------------------add_fragment(st, seq_id, payload_raw, pinfo.number)if can_complete(st) thenassemble_frame(st)end-------------------------------------------------------------- render------------------------------------------------------------render_payload(payload_buf, payload_tvb, st, seq_id, pinfo, tree)endend)-----------------------------------------------------------------------------ws_proto.subtitle = with_proto("WsSubt", "WS subtitle proto", function(proto)local fields = {version = ProtoField.uint8("WsSubt.version", "Version", base.DEC),data = ProtoField.bytes("WsSubt.data", "Data"),}proto.fields = fieldslocal function v1_dissector(buffer, pinfo, subtree)pinfo.cols.protocol:set("Ws:Subt1")pinfo.cols.info:set("V1")subtree:append_text(" Version 1")local count = buffer(0, 4):le_uint()subtree:append_text((", total %u items"):format(count))subtree:add(buffer(0, 4), ("Count: %u"):format(count))local n_chars = buffer(4, 4):le_uint()subtree:append_text((" %u chars"):format(n_chars))subtree:add(buffer(4, 4), ("Chars: %u"):format(n_chars))local item_offset, item_size = 8, 8local text_offset = item_offset + count * item_sizelocal pad_offset = text_offset + n_chars * 2local text_buffer = buffer(text_offset, n_chars * 2)local text_past_len = 0for i = 1, count dolocal item_buffer = buffer(item_offset + (i - 1) * item_size, item_size)local item_tree = subtree:add(item_buffer, ("Item #%u"):format(i - 1))local item_string = ""local pos_string = ("(%u/720,%u/576)"):format(item_buffer(0, 2):le_uint(), item_buffer(2, 2):le_uint())item_tree:add(item_buffer(0, 4), ("Pos: %s"):format(pos_string))item_string = item_string .. pos_stringlocal item_text_len = item_buffer(4, 2):le_uint()item_tree:add(item_buffer(4, 2), ("TextLen: %u"):format(item_text_len))if item_text_len > 0 thenlocal item_text_buffer = text_buffer(text_past_len, item_text_len * 2)local item_text = item_text_buffer:string(ENC_GB18030)item_tree:add(item_text_buffer, ('Text: "%s"'):format(item_text))item_string = item_string .. (' "%s"'):format(item_text)text_past_len = text_past_len + item_text_len * 2endendif pad_offset < buffer:len() thensubtree:add(buffer(pad_offset, -1), "padding")endendlocal function v2_dissector(buffer, pinfo, subtree)pinfo.cols.protocol:set("Ws:Subt2")pinfo.cols.info:set("V2")subtree:append_text(" Version 2")subtree:add(buffer(0, 2), "Version: 0x0102")subtree:add(buffer(2, 6), "<reserved>")local count = buffer(8, 2):le_uint()subtree:append_text((", total %u items"):format(count))subtree:add(buffer(8, 2), ("Count: %u"):format(count))local items, item_size = {}, 40local item_offset, font_len, text_len = 10, 0, 0for i = 1, count dolocal item = {}items[i] = itemlocal item_buffer = buffer(item_offset + (i - 1) * item_size, item_size)item.len = item_buffer(0, 4):le_uint()item.font_off, item.font_len = font_len, item_buffer(16, 4):le_uint()item.text_off, item.text_len = text_len, item_buffer(36, 4):le_uint()font_len = font_len + item.font_lentext_len = text_len + item.text_lenendlocal text_offset = item_offset + count * item_sizelocal font_offset = text_offset + text_lenlocal pad_offset = font_offset + font_len-- local item_segment = buffer(item_offset, count * item_size)local text_segment = buffer(text_offset, text_len)local font_segment = buffer(font_offset, font_len)if count > 0 then-- local subtree = subtree:add(item_segment, "Item Segment")for i = 1, count dolocal item, item_string = items[i], ""local item_buffer = buffer(item_offset + (i - 1) * item_size, item_size)local item_tree = subtree:add(item_buffer, ("Item #%u"):format(i - 1))local font_buffer = font_segment:len() <= 0 and font_segmentor font_segment(item.font_off, item.font_len)local text_buffer = text_segment:len() <= 0 and text_segmentor text_segment(item.text_off, item.text_len)item_tree:add(item_buffer(0, 4), ("Len: %u"):format(item.len))item_tree:add(item_buffer(4, 4), ("Dir: %u"):format(item_buffer(4, 4):le_uint()))local pos_string = ("(%u/720,%u/576)"):format(item_buffer(8, 2):le_uint(), item_buffer(10, 2):le_uint())item_tree:add(item_buffer(8, 4), ("Pos: %s"):format(pos_string))item_string = item_string .. pos_stringitem_tree:add(item_buffer(12, 4), ("CodePage: %u"):format(item_buffer(12, 4):le_uint()))item_tree:add(item_buffer(16, 4), ("FontLen: %u"):format(item.font_len))if item.font_len > 0 thenitem_tree:add(font_buffer, ('Font: "%s"'):format(font_buffer:string(ENC_GB18030)))enditem_tree:add(item_buffer(20, 4),("CharSize: %ux%u"):format(item_buffer(20, 2):le_uint(), item_buffer(22, 2):le_uint()))local color_string = ("#%06X"):format(item_buffer(24, 4):le_uint())item_tree:add(item_buffer(24, 4), ("Color: %s"):format(color_string))item_string = item_string .. " " .. color_stringitem_tree:add(item_buffer(28, 4), ("Rotate: %u"):format(item_buffer(28, 4):le_uint()))item_tree:add(item_buffer(32, 4), ("PointSize: %u"):format(item_buffer(32, 4):le_uint()))item_tree:add(item_buffer(36, 4), ("TextLen: %u"):format(item.text_len))if item.text_len > 0 thenlocal text_string = text_buffer:string(ENC_GB18030)item_tree:add(text_buffer, ('Text: "%s"'):format(text_string))item_string = item_string .. (' "%s"'):format(text_string)enditem_tree:append_text((": %s"):format(item_string))pinfo.cols.info:append((", %s"):format(item_string))endendif text_len > 0 then-- local subtree = subtree:add(text_segment, "Text Segment")for i = 1, count dolocal item = items[i]local text_buffer = text_segment(item.text_off, item.text_len)subtree:add(text_buffer, ('Text #%u: "%s"'):format(i - 1, text_buffer:string(ENC_GB18030)))endendif font_len > 0 then-- local subtree = subtree:add(font_segment, "FontName Segment")for i = 1, count dolocal item = items[i]local font_buffer = font_segment(item.font_off, item.font_len)subtree:add(font_buffer, ('FontName #%u: "%s"'):format(i - 1, font_buffer:string(ENC_GB18030)))endendif pad_offset < buffer:len() thensubtree:add(buffer(pad_offset, -1), "padding")endendlocal function v3_dissector(buffer, pinfo, subtree)pinfo.cols.protocol:set("Ws:Subt3")pinfo.cols.info:set("V3")subtree:append_text(" Version 3")subtree:add(buffer(0, 2), "Version: 0x0103")subtree:add(buffer(2, 6), "<reserved>")local count = buffer(8, 2):le_uint()subtree:append_text((", total %u items"):format(count))subtree:add(buffer(8, 2), ("Count: %u"):format(count))local items, item_size = {}, 26local item_offset, font_len, text_len = 10, 0, 0for i = 1, count dolocal item = {}items[i] = itemlocal item_buffer = buffer(item_offset + (i - 1) * item_size, item_size)item.len = item_buffer(0, 2):le_uint()item.font_off, item.font_len = font_len, item_buffer(10, 2):le_uint()item.text_off, item.text_len = text_len, item_buffer(24, 2):le_uint()font_len = font_len + item.font_lentext_len = text_len + item.text_lenendlocal text_offset = item_offset + count * item_sizelocal font_offset = text_offset + text_lenlocal pad_offset = font_offset + font_len-- local item_segment = buffer(item_offset, count * item_size)local text_segment = buffer(text_offset, text_len)local font_segment = buffer(font_offset, font_len)if count > 0 then-- local subtree = subtree:add(item_segment, "Item Segment")for i = 1, count dolocal item, item_string = items[i], ""local item_buffer = buffer(item_offset + (i - 1) * item_size, item_size)local item_tree = subtree:add(item_buffer, ("Item #%u"):format(i - 1))local font_buffer = font_segment:len() <= 0 and font_segmentor font_segment(item.font_off, item.font_len)local text_buffer = text_segment:len() <= 0 and text_segmentor text_segment(item.text_off, item.text_len)item_tree:add(item_buffer(0, 2), ("Len: %u"):format(item.len))item_tree:add(item_buffer(2, 2), ("Dir: %u"):format(item_buffer(2, 2):le_uint()))local pos_string = ("(%.2f%%,%.2f%%)"):format(item_buffer(4, 2):le_uint() / 100,item_buffer(6, 2):le_uint() / 100)item_tree:add(item_buffer(4, 4), ("Pos: %s"):format(pos_string))item_string = item_string .. pos_stringitem_tree:add(item_buffer(8, 2), ("CodePage: %u"):format(item_buffer(8, 2):le_uint()))item_tree:add(item_buffer(10, 2), ("FontLen: %u"):format(item.font_len))if item.font_len > 0 thenitem_tree:add(font_buffer, ('Font: "%s"'):format(font_buffer:string(ENC_GB18030)))enditem_tree:add(item_buffer(12, 4),("CharSize: %ux%u"):format(item_buffer(12, 2):le_uint(), item_buffer(14, 2):le_uint()))local color_string = ("#%06X"):format(item_buffer(16, 4):le_uint())item_tree:add(item_buffer(16, 4), ("Color: %s"):format(color_string))item_string = item_string .. " " .. color_stringitem_tree:add(item_buffer(20, 2), ("Rotate: %u"):format(item_buffer(20, 2):le_uint()))item_tree:add(item_buffer(22, 2), ("PointSize: %u"):format(item_buffer(22, 2):le_uint()))item_tree:add(item_buffer(24, 2), ("TextLen: %u"):format(item.text_len))if item.text_len > 0 thenlocal text_string = text_buffer:string(ENC_GB18030)item_tree:add(text_buffer, ('Text: "%s"'):format(text_string))item_string = item_string .. (' "%s"'):format(text_string)enditem_tree:append_text((": %s"):format(item_string))pinfo.cols.info:append((", %s"):format(item_string))endendif text_len > 0 then-- local subtree = subtree:add(text_segment, "Text Segment")for i = 1, count dolocal item = items[i]local text_buffer = text_segment(item.text_off, item.text_len)subtree:add(text_buffer, ('Text #%u: "%s"'):format(i - 1, text_buffer:string(ENC_GB18030)))endendif font_len > 0 then-- local subtree = subtree:add(font_segment, "FontName Segment")for i = 1, count dolocal item = items[i]local font_buffer = font_segment(item.font_off, item.font_len)subtree:add(font_buffer, ('FontName #%u: "%s"'):format(i - 1, font_buffer:string(ENC_GB18030)))endendif pad_offset < buffer:len() thensubtree:add(buffer(pad_offset, -1), "padding")endendfunction proto.dissector(buffer, pinfo, tree)local real_dissector = ({[0x0102] = v2_dissector,[0x0103] = v3_dissector,})[buffer(0, 2):le_uint()] or v1_dissectorlocal subtree = tree:add(proto, buffer(), "WS Subtitle")real_dissector(buffer, pinfo, subtree)endend)-----------------------------------------------------------------------------local udp_table = DissectorTable.get("udp.port")udp_table:add(ws_proto.audio_port, ws_proto.audio)udp_table:add(ws_proto.video_port, ws_proto.video)udp_table:add(ws_proto.subtitle_port, ws_proto.subtitle)