mirror of
https://github.com/matrix-org/pinecone.git
synced 2026-01-17 07:10:20 +00:00
* Add wireshark dissector for pinecone frames * Add copyright notice Co-authored-by: Neil Alexander <neilalexander@users.noreply.github.com>
378 lines
16 KiB
Lua
378 lines
16 KiB
Lua
-- Copyright 2022 The Matrix.org Foundation C.I.C.
|
|
--
|
|
-- Licensed under the Apache License, Version 2.0 (the "License");
|
|
-- you may not use this file except in compliance with the License.
|
|
-- You may obtain a copy of the License at
|
|
--
|
|
-- http://www.apache.org/licenses/LICENSE-2.0
|
|
--
|
|
-- Unless required by applicable law or agreed to in writing, software
|
|
-- distributed under the License is distributed on an "AS IS" BASIS,
|
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
-- See the License for the specific language governing permissions and
|
|
-- limitations under the License.
|
|
|
|
pinecone_protocol = Proto("pine", "Pinecone Protocol")
|
|
|
|
local frame_versions = {[0] = "Version 0"}
|
|
|
|
local frame_types = {
|
|
[0] = "Keepalive",
|
|
[1] = "Tree Announcement",
|
|
[2] = "Tree Routed",
|
|
[3] = "Bootstrap",
|
|
[4] = "Bootstrap ACK",
|
|
[5] = "Setup",
|
|
[6] = "Setup ACK",
|
|
[7] = "Teardown",
|
|
[8] = "SNEK Routed",
|
|
[209] = "SNEK Ping",
|
|
[210] = "SNEK Pong",
|
|
[211] = "Tree Ping",
|
|
[212] = "Tree Pong",
|
|
}
|
|
|
|
header_size = 10
|
|
f_version_idx = 4
|
|
f_type_idx = 5
|
|
f_extra_idx = 6
|
|
f_len_idx = 8
|
|
f_payload_idx = header_size
|
|
|
|
magic_bytes = ProtoField.string("pinecone.magic", "Magic Bytes")
|
|
frame_version = ProtoField.uint8("pinecone.version", "Version", base.DEC,
|
|
frame_versions)
|
|
frame_type = ProtoField.uint8("pinecone.type", "Type", base.DEC, frame_types)
|
|
extra_bytes = ProtoField.bytes("pinecone.extra", "Extra Bytes")
|
|
frame_len = ProtoField.uint16("pinecone.len", "Frame Length")
|
|
|
|
destination_len = ProtoField.uint16("pinecone.dstlen", "Destination Length")
|
|
source_len = ProtoField.uint16("pinecone.srclen", "Source Length")
|
|
payload_len = ProtoField.uint16("pinecone.payloadlen", "Payload Length")
|
|
|
|
destination = ProtoField.string("pinecone.dst", "Destination Coords")
|
|
destination_key = ProtoField.bytes("pinecone.dstkey", "Destination Key")
|
|
destination_sig = ProtoField.bytes("pinecone.dstsig", "Destination Signature")
|
|
|
|
source = ProtoField.string("pinecone.src", "Source Coords")
|
|
source_key = ProtoField.bytes("pinecone.srckey", "Source Key")
|
|
source_sig = ProtoField.bytes("pinecone.srcsig", "Source Signature")
|
|
|
|
path_sig = ProtoField.bytes("pinecone.pathsig", "Path Signature")
|
|
|
|
hop_count = ProtoField.uint16("pinecone.hops", "Hop Count")
|
|
payload = ProtoField.bytes("pinecone.payload", "Payload", base.SPACE)
|
|
|
|
rootkey = ProtoField.bytes("pinecone.rootkey", "Root public key")
|
|
rootseq = ProtoField.uint32("pinecone.rootseq", "Root sequence number")
|
|
roottgt = ProtoField.bytes("pinecone.roottgt", "Provides coordinates")
|
|
sigport = ProtoField.uint8("pinecone.sigport", "Port")
|
|
sigkey = ProtoField.bytes("pinecone.sigkey", "Public key")
|
|
sigsig = ProtoField.bytes("pinecone.sigsig", "Signature")
|
|
|
|
pathid = ProtoField.bytes("pinecone.pathid", "Path ID")
|
|
failing = ProtoField.uint8("pinecone.failing", "Failing Bootstrap")
|
|
|
|
pinecone_protocol.fields = {
|
|
magic_bytes, frame_version, frame_type, extra_bytes, frame_len, destination_len, source_len,
|
|
payload_len, destination, source, destination_key, source_key, destination_sig, source_sig,
|
|
path_sig, payload, rootkey, rootseq, sigkey, sigport, sigsig, roottgt, pathid, failing
|
|
}
|
|
|
|
function short_pk(key)
|
|
local h = Struct.tohex(key)
|
|
return string.sub(h, 0, 4) .. "…" .. string.sub(h, 61, 64)
|
|
end
|
|
|
|
function full_pk(key) return Struct.tohex(key) end
|
|
|
|
function varu64(bytes)
|
|
local n = 0
|
|
local l = 0
|
|
while l < 10 do
|
|
local b = bytes:get_index(l)
|
|
n = bit32.lshift(n, 7)
|
|
n = bit32.bor(n, bit32.band(b, 0x7f))
|
|
l = l + 1
|
|
if bit32.band(b, 0x80) == 0 then break end
|
|
end
|
|
return n, l
|
|
end
|
|
|
|
function coords(bytes)
|
|
local b = bytes:bytes()
|
|
local c = {}
|
|
local o = 0
|
|
while o < b:len() do
|
|
n, l = varu64(b:subset(o, b:len() - o))
|
|
c[#c + 1] = n
|
|
o = o + l
|
|
end
|
|
return "[" .. table.concat(c, " ") .. "]"
|
|
end
|
|
|
|
local function do_pinecone_length(buffer, pinfo, tree)
|
|
return buffer(8, 2):uint()
|
|
end
|
|
|
|
local function do_pinecone_dissect(buffer, pinfo, tree)
|
|
local subtree = tree:add(pinecone_protocol, buffer(), "Pinecone Protocol")
|
|
subtree:add_le(frame_version, buffer(f_version_idx, 1))
|
|
subtree:add_le(frame_type, buffer(f_type_idx, 1))
|
|
subtree:add_le(extra_bytes, buffer(f_extra_idx, 2))
|
|
subtree:add_le(frame_len, buffer(f_len_idx, 2), buffer(f_len_idx, 2):uint())
|
|
|
|
local ftype = buffer(5, 1):uint()
|
|
if ftype == 0 then
|
|
-- Keepalive
|
|
pinfo.cols.info:set(frame_types[0])
|
|
elseif ftype == 3 then
|
|
-- Bootstrap
|
|
local plen = buffer(f_payload_idx, 2):uint()
|
|
local slen = buffer(f_payload_idx + 2, 2):uint()
|
|
local srccoords = coords(buffer(f_payload_idx + 4, slen))
|
|
local dstkey = buffer(f_payload_idx + 4 + slen, 32)
|
|
local pload = buffer(f_payload_idx + 4 + slen + 32, plen)
|
|
subtree:add(payload_len, buffer(f_payload_idx, 2), plen)
|
|
|
|
local srcsubtree = subtree:add(subtree, buffer(f_payload_idx + 2, slen + 2), "Source")
|
|
srcsubtree:set_text("Source " .. srccoords)
|
|
srcsubtree:add(source_len, buffer(f_payload_idx + 2, 2), slen)
|
|
srcsubtree:add(source, buffer(f_payload_idx + 4, slen), srccoords)
|
|
|
|
subtree:add(destination_key, dstkey)
|
|
|
|
local psubtree = subtree:add(subtree, pload, "Payload")
|
|
psubtree:set_text("Payload")
|
|
psubtree:add(pathid, pload(0, 8))
|
|
psubtree:add(rootkey, pload(8, 32))
|
|
local seq, offset = varu64(pload(40):bytes())
|
|
psubtree:add(rootseq, pload(40, offset), seq)
|
|
psubtree:add(sigsig, pload(40 + offset, 64))
|
|
-- psubtree:add(failing, pload(104 + offset, 1))
|
|
-- TODO : Add sigs if failing
|
|
|
|
-- Info column
|
|
pinfo.cols.info:set(frame_types[3])
|
|
pinfo.cols.info:append(" " .. srccoords .. " → [" ..
|
|
short_pk(dstkey:bytes():raw()) .. "]")
|
|
elseif ftype == 4 then
|
|
-- Bootstrap ACK
|
|
local plen = buffer(f_payload_idx, 2):uint()
|
|
local dlen = buffer(f_payload_idx + 2, 2):uint()
|
|
local slen = buffer(f_payload_idx + 4, 2):uint()
|
|
local dstcoords = coords(buffer(f_payload_idx + 6 + 2, dlen - 2))
|
|
local srccoords = coords(buffer(f_payload_idx + 6 + dlen + 2, slen - 2))
|
|
subtree:add(payload_len, buffer(f_payload_idx, 2), plen)
|
|
subtree:add(destination_len, buffer(f_payload_idx + 2, 2), dlen)
|
|
subtree:add(source_len, buffer(f_payload_idx + 4, 2), slen)
|
|
|
|
local dlen2 = buffer(f_payload_idx + 6, 2):uint()
|
|
local slen2 = buffer(f_payload_idx + 6 + 2 + dlen2, 2):uint()
|
|
subtree:add(destination_len, buffer(f_payload_idx + 6, 2), dlen2)
|
|
subtree:add(destination, buffer(f_payload_idx + 8, dlen2), dstcoords)
|
|
subtree:add(source_len, buffer(f_payload_idx + 8 + dlen2, 2), slen2)
|
|
subtree:add(source, buffer(f_payload_idx + 8 + dlen2 + 2, slen2), srccoords)
|
|
subtree:add(destination_key, buffer(f_payload_idx + 6 + dlen + slen, 32))
|
|
subtree:add(source_key, buffer(f_payload_idx + 6 + dlen + slen + 32, 32))
|
|
local pload = buffer(f_payload_idx + 6 + dlen + slen + 64, plen)
|
|
local psubtree = subtree:add(subtree, pload, "Payload")
|
|
psubtree:set_text("Payload")
|
|
psubtree:add(pathid, pload(0, 8))
|
|
psubtree:add(rootkey, pload(8, 32))
|
|
local seq, offset = varu64(pload(40):bytes())
|
|
psubtree:add(rootseq, pload(40, offset), seq)
|
|
psubtree:add(source_sig, pload(40 + offset, 64))
|
|
psubtree:add(destination_sig, pload(104 + offset, 64))
|
|
|
|
-- Info column
|
|
pinfo.cols.info:set(frame_types[4])
|
|
pinfo.cols.info:append(" " .. srccoords .. " → " .. dstcoords)
|
|
elseif ftype == 5 then
|
|
-- Setup
|
|
local plen = buffer(f_payload_idx, 2):uint()
|
|
subtree:add(payload_len, buffer(f_payload_idx, 2), plen)
|
|
local dlen = buffer(f_payload_idx + 2, 2):uint()
|
|
local dstcoords = coords(buffer(f_payload_idx + 4, dlen))
|
|
subtree:add(destination_len, buffer(f_payload_idx + 2, 2), dlen)
|
|
subtree:add(destination, buffer(f_payload_idx + 4, dlen), dstcoords)
|
|
local srckey = buffer(f_payload_idx + 4 + dlen, 32)
|
|
subtree:add(source_key, buffer(f_payload_idx + 4 + dlen, 32))
|
|
subtree:add(destination_key, buffer(f_payload_idx + 4 + dlen + 32, 32))
|
|
|
|
local pload = buffer(f_payload_idx + 4 + dlen + 64, plen)
|
|
local psubtree = subtree:add(subtree, pload, "Payload")
|
|
psubtree:set_text("Payload")
|
|
psubtree:add(pathid, pload(0, 8))
|
|
psubtree:add(rootkey, pload(8, 32))
|
|
local seq, offset = varu64(pload(40):bytes())
|
|
psubtree:add(rootseq, pload(40, offset), seq)
|
|
psubtree:add(source_sig, pload(40 + offset, 64))
|
|
psubtree:add(destination_sig, pload(104 + offset, 64))
|
|
|
|
-- Info column
|
|
pinfo.cols.info:set(frame_types[5])
|
|
pinfo.cols.info:append(" [" .. short_pk(srckey:bytes():raw()) .. "] → " ..
|
|
dstcoords)
|
|
elseif ftype == 6 then
|
|
-- Setup ACK
|
|
local plen = buffer(f_payload_idx, 2):uint()
|
|
subtree:add(payload_len, buffer(f_payload_idx, 2), plen)
|
|
local dstkey = buffer(f_payload_idx + 2, 32)
|
|
subtree:add(destination_key, buffer(f_payload_idx + 2, 32))
|
|
|
|
local pload = buffer(f_payload_idx + 2 + 32, plen)
|
|
local psubtree = subtree:add(subtree, pload, "Payload")
|
|
psubtree:set_text("Payload")
|
|
psubtree:add(pathid, pload(0, 8))
|
|
psubtree:add(rootkey, pload(8, 32))
|
|
local seq, offset = varu64(pload(40):bytes())
|
|
psubtree:add(rootseq, pload(40, offset), seq)
|
|
psubtree:add(path_sig, pload(40 + offset, 64))
|
|
|
|
-- Info column
|
|
pinfo.cols.info:set(frame_types[6])
|
|
pinfo.cols.info:append(" → [" .. short_pk(dstkey:bytes():raw()) .. "]")
|
|
elseif ftype == 7 then
|
|
-- Teardown
|
|
local plen = buffer(f_payload_idx, 2):uint()
|
|
subtree:add(payload_len, buffer(f_payload_idx, 2), plen)
|
|
local dstkey = buffer(f_payload_idx + 2, 32)
|
|
subtree:add(destination_key, buffer(f_payload_idx + 2, 32))
|
|
|
|
local pload = buffer(f_payload_idx + 2 + 32, plen)
|
|
local psubtree = subtree:add(subtree, pload, "Payload")
|
|
psubtree:set_text("Payload")
|
|
psubtree:add(pathid, pload(0, 8))
|
|
|
|
-- Info column
|
|
pinfo.cols.info:set(frame_types[7])
|
|
pinfo.cols.info:append(" → [" .. short_pk(dstkey:bytes():raw()) .. "]")
|
|
elseif (ftype == 8 or ftype == 209 or ftype == 210) then
|
|
-- SNEK Routed
|
|
-- SNEK Ping
|
|
-- SNEK Pong
|
|
local plen = buffer(f_payload_idx, 2):uint()
|
|
subtree:add(payload_len, buffer(f_payload_idx, 2), plen)
|
|
local dstkey = buffer(f_payload_idx + 2, 32)
|
|
subtree:add(destination_key, buffer(f_payload_idx + 2, 32))
|
|
local srckey = buffer(f_payload_idx + 2 + 32, 32)
|
|
subtree:add(source_key, buffer(f_payload_idx + 2 + 32, 32))
|
|
|
|
local pload = buffer(f_payload_idx + 2 + 64, plen)
|
|
local psubtree = subtree:add(subtree, pload, "Payload")
|
|
psubtree:set_text("Payload")
|
|
|
|
if plen > 0 and ftype == 8 then
|
|
-- SNEK Routed
|
|
quic_dissector = Dissector.get("quic")
|
|
quic_dissector:call(pload:tvb(), pinfo, tree)
|
|
if pinfo.cols.protocol ~= pinecone_protocol.name then
|
|
pinfo.cols.protocol:prepend(pinecone_protocol.name .. "-")
|
|
end
|
|
pinfo.cols.info:set(frame_types[8])
|
|
elseif (ftype == 209 or ftype == 210) then
|
|
if ftype == 209 then
|
|
-- SNEK Ping
|
|
pinfo.cols.info:set(frame_types[209])
|
|
elseif ftype == 210 then
|
|
-- SNEK Pong
|
|
pinfo.cols.info:set(frame_types[210])
|
|
end
|
|
subtree:add(hop_count, buffer(f_extra_idx, 2), buffer(f_extra_idx, 2):uint())
|
|
end
|
|
|
|
-- Info column
|
|
pinfo.cols.info:append(" [" .. short_pk(srckey:string()) .. "] → [" ..
|
|
short_pk(dstkey:string()) .. "]")
|
|
else
|
|
-- Tree Announcement
|
|
-- Tree Routed
|
|
-- Tree Ping
|
|
-- Tree Pong
|
|
local dlen = buffer(f_payload_idx, 2):uint()
|
|
local slen = buffer(f_payload_idx + 2, 2):uint()
|
|
local plen = buffer(f_payload_idx + 4, 2):uint()
|
|
local dstcoords = coords(buffer(f_payload_idx + 6 + 2, dlen - 2))
|
|
local srccoords = coords(buffer(f_payload_idx + 6 + dlen + 2, slen - 2))
|
|
subtree:add(destination_len, buffer(f_payload_idx, 2), dlen)
|
|
subtree:add(source_len, buffer(f_payload_idx + 2, 2), slen)
|
|
subtree:add(payload_len, buffer(f_payload_idx + 4, 2), plen)
|
|
|
|
local dlen2 = buffer(f_payload_idx + 6, 2):uint()
|
|
local slen2 = buffer(f_payload_idx + 6 + 2 + dlen2, 2):uint()
|
|
subtree:add(destination_len, buffer(f_payload_idx + 6, 2), dlen2)
|
|
subtree:add(destination, buffer(f_payload_idx + 8, dlen2), dstcoords)
|
|
subtree:add(source_len, buffer(f_payload_idx + 8 + dlen2, 2), slen2)
|
|
subtree:add(source, buffer(f_payload_idx + 8 + dlen2 + 2, slen2), srccoords)
|
|
local payload = buffer(f_payload_idx + 6 + dlen + slen, plen)
|
|
|
|
if ftype == 1 then
|
|
-- Tree Announcement
|
|
local dhsubtree = subtree:add(subtree, payload, "Root Announcement")
|
|
dhsubtree:add(rootkey, payload(0, 32))
|
|
local seq, offset = varu64(payload(32):bytes())
|
|
dhsubtree:add(rootseq, payload(0, offset), seq)
|
|
pinfo.cols.info:append(" Seq=" .. seq)
|
|
local tgt = dhsubtree:add(roottgt, payload, "None")
|
|
offset = offset + 32
|
|
local ports = {}
|
|
while offset < payload:len() do
|
|
local seq, o = varu64(payload(offset):bytes())
|
|
local sigsubtree = dhsubtree:add(subtree, payload(offset, o),
|
|
"Ancestor Signature")
|
|
sigsubtree:add(sigport, payload(offset, o), seq)
|
|
sigsubtree:add(sigkey, payload(offset + o, 32))
|
|
sigsubtree:add(sigsig, payload(offset + o + 32, 64))
|
|
offset = offset + 32 + 64 + o
|
|
sigsubtree:set_text("Ancestor Signature Coords=[" ..
|
|
table.concat(ports, " ") .. "]")
|
|
ports[#ports + 1] = seq
|
|
tgt:set_text("Provides coordinates: [" ..
|
|
table.concat(ports, " ") .. "]")
|
|
end
|
|
dhsubtree:set_text("Root Announcement (" .. #ports .. " signatures)")
|
|
|
|
-- Info column
|
|
pinfo.cols.info:set(frame_types[1])
|
|
pinfo.cols.info:append(" Root=[" ..
|
|
short_pk(payload(0, 32):bytes():raw()) .. "]")
|
|
pinfo.cols.info:append(" Coords=[" .. table.concat(ports, " ") ..
|
|
"]")
|
|
elseif (ftype == 2 or ftype == 211 or ftype == 212) then
|
|
if plen > 0 and ftype == 2 then
|
|
-- Tree Routed
|
|
quic_dissector = Dissector.get("quic")
|
|
quic_dissector:call(payload:tvb(), pinfo, tree)
|
|
if pinfo.cols.protocol ~= pinecone_protocol.name then
|
|
pinfo.cols.protocol:prepend(pinecone_protocol.name .. "-")
|
|
end
|
|
pinfo.cols.info:set(frame_types[2])
|
|
elseif (ftype == 211 or ftype == 212) then
|
|
if ftype == 211 then
|
|
-- Tree Ping
|
|
pinfo.cols.info:set(frame_types[211])
|
|
elseif ftype == 212 then
|
|
-- Tree Pong
|
|
pinfo.cols.info:set(frame_types[212])
|
|
end
|
|
subtree:add(hop_count, buffer(f_extra_idx, 2), buffer(f_extra_idx, 2):uint())
|
|
end
|
|
|
|
-- Info column
|
|
pinfo.cols.info:append(srccoords .. " → " .. dstcoords)
|
|
end
|
|
end
|
|
end
|
|
|
|
function pinecone_protocol.dissector(buffer, pinfo, tree)
|
|
length = buffer:len()
|
|
if length < header_size then return end
|
|
if buffer(0, 4):string() ~= "pine" then return end
|
|
pinfo.cols.protocol:set(pinecone_protocol.name)
|
|
|
|
dissect_tcp_pdus(buffer, tree, header_size, do_pinecone_length, do_pinecone_dissect)
|
|
return 1
|
|
end
|
|
|
|
pinecone_protocol:register_heuristic("tcp", pinecone_protocol.dissector)
|