Last active
September 19, 2024 03:24
-
-
Save Ginurx/06ed00b65b62b144647b354de8716686 to your computer and use it in GitHub Desktop.
KCP protocol dissector for Wireshark
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- This is an improved version of https://github.com/cfadmin-cn/kcp_dissector | |
-- which provides: | |
-- more detailed "Main" information, | |
-- multiple KCP segments in one UDP packet support | |
do | |
local IKCP_CMD_PUSH = 81 | |
local IKCP_CMD_ACK = 82 | |
local IKCP_CMD_WASK = 83 | |
local IKCP_CMD_WINS = 84 | |
local NAME = "KCP" | |
local PORT = 8082 | |
local KCP = Proto(NAME, "KCP Protocol") | |
-- KCP Protocol Fields. | |
local convField = ProtoField.uint32(NAME .. ".conv", "Conversation ID", base.DEC, nil, nil, "KCP conversation ID") | |
local cmdField = ProtoField.uint8(NAME .. ".cmd", "Command", base.DEC, nil, nil, "The command of the KCP packet.\n81: CMD_PUSH\n82: CMD_ACK\n83: CMD_WASK\n84: CMD_WINS") | |
local frgField = ProtoField.uint8(NAME .. ".frg", "Fragment ID", base.DEC, nil, nil, "The fragment ID of the KCP packet.\nThe IDs are ordered from largest to smallest (0).\n0 means the last fragment or isn't fragmented.") | |
local wndField = ProtoField.uint16(NAME .. ".wnd", "Window Size", base.DEC, nil, nil, "The receive window size (in KCP packet count)") | |
local tsField = ProtoField.uint32(NAME .. ".ts", "TS", base.DEC, nil, nil, "The timestamp of the current KCP packet.\nFor an ACK packet, it's the timestamp of the KCP packet to be acknowledged.") | |
local snField = ProtoField.uint32(NAME .. ".sn", "SN", base.DEC, nil, nil, "The sequence number of the current KCP packet.\nFor an ACK packet, it's the same as the SN of the KCP packet to be acknowledged.") | |
local unaField = ProtoField.uint32(NAME .. ".una", "UNA", base.DEC, nil, nil, "The first expected SN of the next KCP packet") | |
local lenField = ProtoField.uint32(NAME .. ".len", "Data Length", base.DEC, nil, nil, "The data length of the KCP packet" ) | |
local dataField = ProtoField.bytes(NAME .. ".data", "Data Payload", base.SPACE, "The data payload of the KCP packet") | |
--[[ | |
0 4 5 6 8 (BYTE) | |
+---------------+---+---+-------+ | |
| conv |cmd|frg| wnd | | |
+---------------+---+---+-------+ 8 | |
| ts | sn | | |
+---------------+---------------+ 16 | |
| una | len | | |
+---------------+---------------+ 24 | |
| | | |
| DATA (optional) | | |
| | | |
+-------------------------------+ | |
--]] | |
KCP.fields = { | |
convField, cmdField, frgField, wndField, | |
tsField, snField, | |
unaField, lenField, | |
dataField | |
} | |
local function getKcpCmdName(cmd) | |
if cmd == IKCP_CMD_PUSH then | |
return "PUSH" | |
elseif cmd == IKCP_CMD_ACK then | |
return "ACK" | |
elseif cmd == IKCP_CMD_WASK then | |
return "WASK" | |
elseif cmd == IKCP_CMD_WINS then | |
return "WINS" | |
end | |
return "UNK(" .. cmd .. ")" | |
end | |
-- The KCP dissect() callback. | |
-- tvb: The Tvb to dissect. https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tvb.html#lua_class_Tvb | |
-- pinfo: The packet’s Pinfo. https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Pinfo.html#lua_class_Pinfo | |
-- tree: The TreeItem on which to add the protocol items. https://www.wireshark.org/docs/wsdg_html_chunked/lua_module_Tree.html#lua_class_TreeItem | |
function KCP.dissector(tvb, pinfo, tree) | |
if tvb:len() < 24 then return end | |
-- registers the protocol name | |
pinfo.cols.protocol = KCP.name | |
local mainInfoColText = pinfo.src_port .. " → " .. pinfo.dst_port | |
local segStartOffset = 0 | |
local readOffset = 0 | |
local isFirstSeg = true | |
local segIndex = 0 | |
local segCount = 0 | |
local segSummaryArray = {} | |
-- multiple KCP segments can come up in a single packet | |
while true do | |
segIndex = segIndex + 1 | |
segCount = segCount + 1 | |
local convDataView = tvb(readOffset, 4) | |
local conv = convDataView:le_uint() | |
readOffset = readOffset + 4 | |
local cmdDataView = tvb(readOffset, 1) | |
local cmd = cmdDataView:le_uint() | |
readOffset = readOffset + 1 | |
local fragDataView = tvb(readOffset, 1) | |
local frag = fragDataView:le_uint() | |
readOffset = readOffset + 1 | |
local wndDataView = tvb(readOffset, 2) | |
local wnd = wndDataView:le_uint() | |
readOffset = readOffset + 2 | |
local tsDataView = tvb(readOffset, 4) | |
local ts = tsDataView:le_uint() | |
readOffset = readOffset + 4 | |
local snDataView = tvb(readOffset, 4) | |
local sn = snDataView:le_uint() | |
readOffset = readOffset + 4 | |
local unaDataView = tvb(readOffset, 4) | |
local una = unaDataView:le_uint() | |
readOffset = readOffset + 4 | |
local lenDataView = tvb(readOffset, 4) | |
local len = lenDataView:le_uint() | |
readOffset = readOffset + 4 | |
local dataPayloadDataView = tvb(readOffset, len) | |
readOffset = readOffset + len | |
-- create a subtree for the current segment | |
local kcpTree = tree:add(KCP, tvb(segStartOffset, readOffset - segStartOffset)) | |
kcpTree:add_le(convField, convDataView) | |
kcpTree:add_le(cmdField, cmdDataView) | |
kcpTree:add_le(frgField, fragDataView) | |
kcpTree:add_le(wndField, wndDataView) | |
kcpTree:add_le(tsField, tsDataView) | |
kcpTree:add_le(snField, snDataView) | |
kcpTree:add_le(unaField, unaDataView) | |
kcpTree:add_le(lenField, lenDataView) | |
kcpTree:add(dataField, dataPayloadDataView) | |
-- summary line in the protocol tree | |
kcpTree:append_text(", Conv: " .. conv) | |
kcpTree:append_text(", Cmd: " .. getKcpCmdName(cmd)) | |
kcpTree:append_text(", Sn: " .. sn) | |
kcpTree:append_text(", Ts: " .. ts) | |
kcpTree:append_text(", Una: " .. una) | |
kcpTree:append_text(", Frg: " .. frag) | |
kcpTree:append_text(", Wnd: " .. wnd) | |
kcpTree:append_text(", Len: " .. len) | |
-- the conversation id only needs to be printed once as all segments in a single packet have the same conv | |
if isFirstSeg then | |
mainInfoColText = mainInfoColText .. ", Conv=" .. conv | |
isFirstSeg = false | |
end | |
local segSummary = getKcpCmdName(cmd) | |
if cmd == IKCP_CMD_PUSH then | |
segSummary = segSummary .. ", Ts=" .. ts .. ", Sn=" .. sn .. ", Una=" .. una .. ", Frg=" .. frag .. ", Wnd=" .. wnd .. ", Len=" .. len | |
elseif cmd == IKCP_CMD_ACK then | |
segSummary = segSummary .. ", Ts=" .. ts .. ", Sn=" .. sn .. ", Una=" .. una .. ", Wnd=" .. wnd | |
elseif cmd == IKCP_CMD_WASK then | |
segSummary = segSummary .. ", Ts=" .. ts | |
elseif cmd == IKCP_CMD_WINS then | |
segSummary = segSummary .. ", Ts=" .. ts .. ", Wnd=" .. wnd | |
end | |
segSummaryArray[segIndex] = segSummary | |
if readOffset >= tvb:len() then | |
break | |
end | |
segStartOffset = readOffset | |
end | |
if (segCount == 1) then | |
mainInfoColText = mainInfoColText .. ", " .. segSummaryArray[1] | |
else | |
for i = 1, segCount do | |
mainInfoColText = mainInfoColText .. ", [" .. i .. "/" .. segCount .. "] " .. segSummaryArray[i] | |
end | |
end | |
pinfo.cols.info = mainInfoColText | |
end | |
DissectorTable.get("udp.port"):add(PORT, KCP) | |
end |
Author
Ginurx
commented
Sep 4, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment