Skip to content

Instantly share code, notes, and snippets.

@Ginurx
Last active September 19, 2024 03:24
Show Gist options
  • Save Ginurx/06ed00b65b62b144647b354de8716686 to your computer and use it in GitHub Desktop.
Save Ginurx/06ed00b65b62b144647b354de8716686 to your computer and use it in GitHub Desktop.
KCP protocol dissector for Wireshark
-- 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
@Ginurx
Copy link
Author

Ginurx commented Sep 4, 2024

image

@Ginurx
Copy link
Author

Ginurx commented Sep 4, 2024

The data payload field is displayed as a HEX string instead of an UTF-8 string now.
image

@Ginurx
Copy link
Author

Ginurx commented Sep 5, 2024

multiple segments in one packet support :

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment