From e5644d24c51634c0e263a758bd57b797d5949c9c Mon Sep 17 00:00:00 2001 From: bt Date: Tue, 10 Mar 2026 14:11:01 +0100 Subject: Use Kaitai to describe protocol spec, autogenerate Wireshark plugin and diagram --- README.md | 8 ++ build_spec.sh | 22 +++++ run.sh | 2 +- solec.ksy | 59 +++++++++++++ solec.lua | 179 ++++++++++++++++++++++++++++++------- solec.svg | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 515 insertions(+), 32 deletions(-) create mode 100755 build_spec.sh create mode 100644 solec.ksy create mode 100644 solec.svg diff --git a/README.md b/README.md index 39f7571..24cb6c3 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ cd solecd go run cmd/daemon/main.go ``` +## Wireshark plugin + +To build wireshark plugin you'll need Kaitai Struct Compiler. +KSC currently doesn't support Wireshark plugin generation but +[there is a discussion]("https://github.com/kaitai-io/kaitai_struct/issues/50#issuecomment-1485044090"). +After building foked KSC use `./build_spec.sh` to build the plugin and then put +it into Wireshark plugins directory. + ## TODO - Protocol diff --git a/build_spec.sh b/build_spec.sh new file mode 100755 index 0000000..ae78b81 --- /dev/null +++ b/build_spec.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +if [[ "$OSTYPE" == "darwin"* ]]; then + SED=gsed +else + SED=sed +fi + +kaitai-struct-compiler -t graphviz solec.ksy +dot -Tsvg solec.dot > solec.svg +rm solec.dot + +~/bin/kaitai-struct-compiler-ws/bin/kaitai-struct-compiler -t wireshark --read-pos solec.ksy + +${SED} -i -e '5s/.*/package.path = "kaitai_struct_lua_runtime\/?.lua" .. package.path/' solec.lua +${SED} -i -e 's/, self.payload_type//' solec.lua + +cat <> solec.lua +local tcp_port = DissectorTable.get("tcp.port") +tcp_port:add(9999, proto) +EOT + diff --git a/run.sh b/run.sh index 4f4d96f..a33fad9 100755 --- a/run.sh +++ b/run.sh @@ -2,5 +2,5 @@ tmux \ new-session "go run cmd/daemon/main.go; read" \; \ - split-window "go run cmd/client/main.go; read" \; \ + split-window "sleep 0.5; go run cmd/client/main.go; read" \; \ select-layout even-horizontal; \ No newline at end of file diff --git a/solec.ksy b/solec.ksy new file mode 100644 index 0000000..01c7481 --- /dev/null +++ b/solec.ksy @@ -0,0 +1,59 @@ +meta: + id: solec + file-extension: hex + endian: be + +enums: + type: + 0x01: handshake + 0x02: ping + 0x03: pong + 0x04: message + 0xFF: test + +seq: + - id: payload_type + type: u1 + enum: type + - id: payload_length + type: u2 + - id: payload + type: + switch-on: payload_type + cases: + 'type::test': test + +types: + string: + seq: + - id: len_payload + type: u2 + - id: payload + type: str + size: len_payload + encoding: UTF-8 + binary: + seq: + - id: len_payload + type: u2 + - id: payload + size: len_payload + test: + seq: + - id: num1 + type: u1 + - id: time1 + type: u8 + - id: str1 + type: string + - id: num2 + type: u2 + - id: bin1 + type: binary + - id: num3 + type: u4 + - id: str2 + type: string + - id: num4 + type: u8 + diff --git a/solec.lua b/solec.lua index a367358..f48831b 100644 --- a/solec.lua +++ b/solec.lua @@ -1,43 +1,160 @@ --- Wireshark plugin --- Use following filter: _ws.col.protocol == "SOLEC" +-- This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild +-- +-- This file is compatible with Lua 5.3 -solec = Proto("SOLEC", "SOLEC Protocol") +package.path = "kaitai_struct_lua_runtime/?.lua" .. package.path +local class = require("class") +require("tvbstream") +require("kaitaistruct") +local proto = Proto('solec', 'Solec') +proto.fields = {} +local enum = require("enum") +local Solec_payload_type = ProtoField.new('payload_type', 'Solec.payload_type', ftypes.BYTES) +table.insert(proto.fields, Solec_payload_type) +local Solec_payload_length = ProtoField.new('payload_length', 'Solec.payload_length', ftypes.UINT32) +table.insert(proto.fields, Solec_payload_length) +local Solec_String_len_payload = ProtoField.new('len_payload', 'Solec.String.len_payload', ftypes.UINT32) +table.insert(proto.fields, Solec_String_len_payload) +local str_decode = require("string_decode") +local Solec_String_payload = ProtoField.new('payload', 'Solec.String.payload', ftypes.STRINGZ) +table.insert(proto.fields, Solec_String_payload) +local Solec_Binary_len_payload = ProtoField.new('len_payload', 'Solec.Binary.len_payload', ftypes.UINT32) +table.insert(proto.fields, Solec_Binary_len_payload) +local Solec_Binary_payload = ProtoField.new('payload', 'Solec.Binary.payload', ftypes.BYTES) +table.insert(proto.fields, Solec_Binary_payload) +local Solec_Test_num1 = ProtoField.new('num1', 'Solec.Test.num1', ftypes.UINT8) +table.insert(proto.fields, Solec_Test_num1) +local Solec_Test_time1 = ProtoField.new('time1', 'Solec.Test.time1', ftypes.UINT32) +table.insert(proto.fields, Solec_Test_time1) +local Solec_Test_num2 = ProtoField.new('num2', 'Solec.Test.num2', ftypes.UINT32) +table.insert(proto.fields, Solec_Test_num2) +local Solec_Test_num3 = ProtoField.new('num3', 'Solec.Test.num3', ftypes.UINT32) +table.insert(proto.fields, Solec_Test_num3) +local Solec_Test_num4 = ProtoField.new('num4', 'Solec.Test.num4', ftypes.UINT32) +table.insert(proto.fields, Solec_Test_num4) -s_datatype = ProtoField.uint8("solec.datatype", "Datatype", base.HEX, { - [0x01] = "handshake", - [0x02] = "ping", - [0x03] = "pong", - [0x04] = "message", - [0xFF] = "test", -}) +function proto.dissector(tvb, pinfo, root) + pinfo.cols.protocol = 'Solec' + local tree = root:add(proto, tvb(), 'Solec') + local io = KaitaiStream(TVBStream(tvb)) + local obj = Solec(io, tree) +end + +Solec = class.class(KaitaiStruct) + +Solec.Type = enum.Enum { + handshake = 1, + ping = 2, + pong = 3, + message = 4, + test = 255, +} + +function Solec:_init(io, tree, parent, root) + KaitaiStruct._init(self, io) + self._parent = parent + self._root = root or self + self._tree = tree + self:_read() +end + +function Solec:_read() + local _offset = self._io:pos() + self.payload_type = Solec.Type(self._io:read_u1()) + self._tree:add(Solec_payload_type, self._io._io.tvb(_offset, self._io:pos() - _offset)) + local _offset = self._io:pos() + self.payload_length = self._io:read_u2be() + self._tree:add(Solec_payload_length, self._io._io.tvb(_offset, self._io:pos() - _offset), self.payload_length) + local _offset = self._io:pos() + local _on = self.payload_type + if _on == Solec.Type.test then + local _tree = self._tree:add(self._io._io.tvb(_offset, 0), 'payload') + self.payload = Solec.Test(self._io, _tree, self, self._root) + _tree:set_len(self._io:pos() - _offset) + end +end + + +Solec.String = class.class(KaitaiStruct) -s_handshake_version = ProtoField.uint8("solec.handshake.version", "Protocol version", base.HEX) -s_pong_timestamp = ProtoField.uint64("solec.pong.timestamp", "Timestamp", base.DEC) +function Solec.String:_init(io, tree, parent, root) + KaitaiStruct._init(self, io) + self._parent = parent + self._root = root or self + self._tree = tree + self:_read() +end -solec.fields = { s_datatype, s_handshake_version, s_pong_timestamp } +function Solec.String:_read() + local _offset = self._io:pos() + self.len_payload = self._io:read_u2be() + self._tree:add(Solec_String_len_payload, self._io._io.tvb(_offset, self._io:pos() - _offset), self.len_payload) + local _offset = self._io:pos() + self.payload = str_decode.decode(self._io:read_bytes(self.len_payload), "UTF-8") + self._tree:add(Solec_String_payload, self._io._io.tvb(_offset, self._io:pos() - _offset), self.payload) +end -function solec.dissector(buffer, pinfo, tree) - length = buffer:len() - if length == 0 then - return - end - pinfo.cols.protocol = solec.name +Solec.Binary = class.class(KaitaiStruct) - local subtree = tree:add(solec, buffer(), "SOLEC Protocol Data") - local dtype = buffer(0,1):uint() +function Solec.Binary:_init(io, tree, parent, root) + KaitaiStruct._init(self, io) + self._parent = parent + self._root = root or self + self._tree = tree + self:_read() +end + +function Solec.Binary:_read() + local _offset = self._io:pos() + self.len_payload = self._io:read_u2be() + self._tree:add(Solec_Binary_len_payload, self._io._io.tvb(_offset, self._io:pos() - _offset), self.len_payload) + local _offset = self._io:pos() + self.payload = self._io:read_bytes(self.len_payload) + self._tree:add(Solec_Binary_payload, self._io._io.tvb(_offset, self._io:pos() - _offset), self.payload) +end - subtree:add_le(s_datatype, dtype) - if dtype == 01 then - local subtree = tree:add(solec, buffer(), "Handshake") - subtree:add_le(s_handshake_version, buffer(1, 1):uint()) - elseif dtype == 0x03 then - local subtree = tree:add(solec, buffer(), "Pong") - local timestamp = buffer(1, 8):uint64() - subtree:add(s_pong_timestamp, timestamp):append_text(" (" .. os.date('%Y/%m/%d %X', tonumber(timestamp)) .. ")") - end +Solec.Test = class.class(KaitaiStruct) + +function Solec.Test:_init(io, tree, parent, root) + KaitaiStruct._init(self, io) + self._parent = parent + self._root = root or self + self._tree = tree + self:_read() +end + +function Solec.Test:_read() + local _offset = self._io:pos() + self.num1 = self._io:read_u1() + self._tree:add(Solec_Test_num1, self._io._io.tvb(_offset, self._io:pos() - _offset), self.num1) + local _offset = self._io:pos() + self.time1 = self._io:read_u8be() + self._tree:add(Solec_Test_time1, self._io._io.tvb(_offset, self._io:pos() - _offset), self.time1) + local _offset = self._io:pos() + local _tree = self._tree:add(self._io._io.tvb(_offset, 0), 'str1') + self.str1 = Solec.String(self._io, _tree, self, self._root) + _tree:set_len(self._io:pos() - _offset) + local _offset = self._io:pos() + self.num2 = self._io:read_u2be() + self._tree:add(Solec_Test_num2, self._io._io.tvb(_offset, self._io:pos() - _offset), self.num2) + local _offset = self._io:pos() + local _tree = self._tree:add(self._io._io.tvb(_offset, 0), 'bin1') + self.bin1 = Solec.Binary(self._io, _tree, self, self._root) + _tree:set_len(self._io:pos() - _offset) + local _offset = self._io:pos() + self.num3 = self._io:read_u4be() + self._tree:add(Solec_Test_num3, self._io._io.tvb(_offset, self._io:pos() - _offset), self.num3) + local _offset = self._io:pos() + local _tree = self._tree:add(self._io._io.tvb(_offset, 0), 'str2') + self.str2 = Solec.String(self._io, _tree, self, self._root) + _tree:set_len(self._io:pos() - _offset) + local _offset = self._io:pos() + self.num4 = self._io:read_u8be() + self._tree:add(Solec_Test_num4, self._io._io.tvb(_offset, self._io:pos() - _offset), self.num4) end + local tcp_port = DissectorTable.get("tcp.port") -tcp_port:add(9999, solec) +tcp_port:add(9999, proto) diff --git a/solec.svg b/solec.svg new file mode 100644 index 0000000..30b2ba8 --- /dev/null +++ b/solec.svg @@ -0,0 +1,277 @@ + + + + + + + + +cluster__solec + +Solec + + +cluster__binary + +Solec::Binary + + +cluster__string + +Solec::String + + +cluster__test + +Solec::Test + + + +solec__seq + + +pos + + +size + + +type + + +id + +0 + +1 + +u1→Type + +payload_type + +1 + +2 + +u2be + +payload_length + +3 + +... + +switch (payload_type) + +payload + + + +solec__seq:payload_type_type->solec__seq:payload_type + + + + + +solec__seq_payload_switch + + +case + + +type + +:type_test + +Test + + + +solec__seq:payload_type->solec__seq_payload_switch + + + + + +test__seq + + +pos + + +size + + +type + + +id + +0 + +1 + +u1 + +num1 + +1 + +8 + +u8be + +time1 + +9 + +... + +String + +str1 + +... + +2 + +u2be + +num2 + +... + +... + +Binary + +bin1 + +... + +4 + +u4be + +num3 + +... + +... + +String + +str2 + +... + +8 + +u8be + +num4 + + + +solec__seq_payload_switch:case0->test__seq + + + + + +binary__seq + + +pos + + +size + + +type + + +id + +0 + +2 + +u2be + +len_payload + +2 + +len_payload + + +payload + + + +binary__seq:len_payload_type->binary__seq:payload_size + + + + + +string__seq + + +pos + + +size + + +type + + +id + +0 + +2 + +u2be + +len_payload + +2 + +len_payload + +str(UTF-8) + +payload + + + +string__seq:len_payload_type->string__seq:payload_size + + + + + +test__seq:bin1_type->binary__seq + + + + + +test__seq:str1_type->string__seq + + + + + +test__seq:str2_type->string__seq + + + + + -- cgit v1.2.3