--- -*-lua-*- --- --- This file is part of secnet. --- See README for full list of copyright holders. --- --- secnet is free software; you can redistribute it and/or modify it --- under the terms of the GNU General Public License as published by --- the Free Software Foundation; either version d of the License, or --- (at your option) any later version. --- --- secnet is distributed in the hope that it will be useful, but --- WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU --- General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- version 3 along with secnet; if not, see --- https://www.gnu.org/licenses/gpl.html. local secnet = Proto("secnet", "Secnet VPN") ----------------------------------------------------------------------------- --- Session tracking. --- --- This is the hardest part of the dissector. -- Timelines. A timeline associates pieces of information with times T. local function tl_new() -- Return a fresh shiny timeline. return { } end local function tl__find(tl, t) -- Find and return the earliest association in TL not earlier than T. If -- there is no such entry, return nil. local lo = 1 local hi = #tl + 1 -- Plain old binary search. The active interval is half-open, [lo, hi). while true do local w = hi - lo if w == 0 then return nil end local mid = lo + math.floor(w/2) local tv = tl[mid] if tv.t > t then hi = mid elseif tv.t == t or w == 1 then return tv else lo = mid end end end local function tl_find(tl, t) -- Find and return the state of the timeline at time T, i.e., the earliest -- value in TL not earlier than T. If there is no such entry, return nil. local tv = tl__find(tl, t) if tv == nil then return nil else return tv.v end end local function tl_add(tl, t, v) -- Associate the value V with time T in TL. local tv = tl__find(tl, t) if tv ~= nil and tv.t == t then tv.v = v else -- Append the new item. If necessary, sort the vector; we expect that -- we'll see everything in the right order, so this won't be a problem. local n = #tl tl[n + 1] = { t = t, v = v } if n > 0 and tl[n].t > t then table.sort(tl, function (tv0, tv1) return tv0.t < tv1.t end) end end end local function dump_timeline(tl, cvt) -- Dump a timeline TL, using the function CVT to convert each value to a -- string. for _, tv in ipairs(tl) do print("\t" .. tv.t .. ": " .. cvt(tv.v)) end end local function get_timeline_create(map, index) -- If MAP[INDEX] exists, return it; otherwise set MAP[INDEX] to a fresh -- timeline and return that. local tl = map[index] if tl == nil then tl = tl_new(); map[index] = tl end return tl end local function lookup_timeline(map, index, t) -- If it exists, MAP[INDEX] should be a timeline; find its state at time T. -- Return nil if there's nothing there, or T is too early. local tl = map[index] if tl == nil then return nil else return tl_find(tl, t) end end -- The `SITEMAP' maps site names to little structures. -- -- * `algs' is a map from peer site names to a timeline of structures -- described below. -- -- * `index' is a map from site indices to a timeline of names, reflecting -- that, at some time T, this site thought that some index I referred to -- a peer site P. -- -- The `algs' map contains the following slots, populated during . -- -- * `xform' is a timeline of transform names. local SITEMAP = { } -- The `ADDRMAP' maps (IPv4 or IPv6) socket addresses in the form -- `[ADDR]:PORT' to a timeline of site names, populated based on claims made -- by senders about themselves. The `GUESSMAP' is similar, but populated -- based on assertions about recipients. local ADDRMAP = { } local GUESSMAP = { } local function snd_sockname(st) -- Return the sender's socket name as a thing which can be used as a table -- index. local pinfo = st.pinfo return string.format("[%s]:%d", pinfo.net_src, pinfo.src_port) end local function rcv_sockname(st) -- Return the recipient's socket name as a thing which can be used as a -- table index. local pinfo = st.pinfo return string.format("[%s]:%d", pinfo.net_dst, pinfo.dst_port) end local function get_site_create(name) -- If NAME refers to a known site, then return its information structure; -- otherwise create a new one and return that. local site = SITEMAP[name] if site == nil then site = { algs = { }, index = { } } SITEMAP[name] = site end return site end local function notice_site_name(map, st, sock, name) -- Record in MAP that the packet described in the state ST tells us that, -- at that time, the site NAME appeared to be at address SOCK. tl_add(get_timeline_create(map, sock), st.pinfo.rel_ts, name) end local function dump_algs(algs) -- Dump the algorithms selection ALGS from a site structure. return "xform=" .. algs.transform end local function dump_str(str) return str end local function dump_addrmap(what, map) -- Dump MAP, which is an address map like `ADDRMAP' or `GUESSMAP'; WHAT is -- a string describing which map it is. print(what .. "...") for addr, tl in pairs(map) do print(" " .. addr) dump_timeline(tl, dump_str) end end local function dump_tracking_state() -- Dump the entire tracking state to standard output. dump_addrmap("Address map", ADDRMAP) dump_addrmap("Guess map", GUESSMAP) print("Site map...") for name, site in pairs(SITEMAP) do print(" " .. name) print(" algs...") for peer, tl in pairs(site.algs) do print(" " .. peer) dump_timeline(tl, dump_algs) end print(" index...") for ix, tl in pairs(site.index) do print(" " .. ix) dump_timeline(tl, dump_str) end end end local function notice_sndname(st, name) -- Record that sender of the packet described by state ST is called NAME. st.sndname = name notice_site_name(ADDRMAP, st, snd_sockname(st), name) end local function notice_rcvname(st, name) -- Record that the sender of the packet described by ST thought that its -- recipient was called NAME. st.rcvname = name notice_site_name(GUESSMAP, st, rcv_sockname(st), name) if st.sndname ~= nil then local site = get_site_create(st.sndname) tl_add(get_timeline_create(site.index, st.sndix), st.pinfo.rel_ts, name) end end -- Tables describing the kinds of algorithms which can be selected. local CAPTAB = { [8] = { name = "serpent256cbc", kind = "transform", desc = "Deprecated Serpent256-CBC transform" }, [9] = { name = "eaxserpent", kind = "transform", desc = "Serpent256-EAX transform" }, [31] = { name = "mobile-priority", kind = "early", desc = "Mobile site takes priority in case of MSG1 crossing" } } local function get_algname(kind, cap, dflt) -- Fetch an algorithm of the given KIND, given its capability number CAP; -- if CAP is nil, then return DFLT instead. local name if cap == nil then name = dflt else local info = CAPTAB[cap] if info ~= nil and info.kind == kind then name = info.name else name = string.format("Unknown %s #%d", kind, cap) end end return name end local function notice_alg_selection(st) -- Record the algorithm selections declared in the packet described by ST. local transform = get_algname("transform", st.transform, "serpent256cbc") local site = get_site_create(st.sndname) local peer = get_site_create(st.rcvname) local now = st.pinfo.rel_ts local algs = { transform = transform } tl_add(get_timeline_create(site.algs, st.rcvname), now, algs) tl_add(get_timeline_create(peer.algs, st.sndname), now, algs) end ----------------------------------------------------------------------------- --- Protocol dissection primitives. local PF = { } -- The table of protocol fields, filled in later. local F = { } -- A table of field values, also filled in later. local function msgcode(major, minor) -- Construct a Secnet message number according to the complicated rules. local majlo = bit.band(major, 0x000f) local majhi = bit.band(major, 0xfff0) local minlo = bit.band(minor, 0x000f) local minhi = bit.band(minor, 0xfff0) return bit.bxor(bit.lshift(majlo, 0), bit.lshift(majlo, 8), bit.lshift(majlo, 16), bit.lshift(majlo, 24), bit.lshift(majhi, 4), bit.lshift(minlo, 4), bit.lshift(minlo, 28), bit.lshift(minhi, 16)) end local function msgmajor(label) -- Return the major message number from a LABEL. local lo = bit.band(label, 0x000f) local hi = bit.band(bit.rshift(label, 4), 0xfff0) return bit.bxor(lo, bit.lshift(lo, 4), bit.lshift(lo, 12), hi) end local function msgminor(label) -- Return the minor message number from a LABEL. return bit.bxor(bit.lshift(bit.band(label, 0x00ff), 8), bit.band(bit.rshift(label, 4), 0x000f), bit.band(bit.rshift(label, 16), 0xfff0)) end -- Main message-number table. local M = { NAK = msgcode( 0, 0), MSG0 = msgcode(0x2020, 0), -- ! MSG1 = msgcode( 1, 0), MSG2 = msgcode( 2, 0), MSG3 = msgcode( 3, 0), MSG3BIS = msgcode( 3, 1), MSG4 = msgcode( 4, 0), MSG5 = msgcode( 5, 0), MSG6 = msgcode( 6, 0), MSG7 = msgcode( 7, 0), MSG8 = msgcode( 8, 0), MSG9 = msgcode( 9, 0), PROD = msgcode( 10, 0)} -- The `dissect_*' functions follow a common protocol. They parse a thing -- from a packet buffer BUF, of size SZ, starting from POS, and store -- interesting things in a given TREE; when they're done, they return the -- updated index where the next interesting thing might be, and maybe store -- interesting things in the state ST. As a result, it's usually a simple -- matter to parse a packet by invoking the appropriate primitive dissectors -- in the right order. local function dissect_sequence(dissect, st, buf, tree, pos, sz) -- Dissect pieces of the packed in BUF with each of the dissectors in the -- list DISSECT in turn. for _, d in ipairs(dissect) do pos = d(st, buf, tree, pos, sz) end return pos end local function dissect_wtf(st, buf, tree, pos, sz) -- If POS is not at the end of the buffer, note that there's unexpected -- stuff in the packet. if pos < sz then tree:add(PF["secnet.wtf"], buf(pos, sz - pos)) end return sz end local dissect_caps do -- This will be a list of the capability protocol field names, in the right -- order. We just have to figure out what that will be. local caplist = { } do local caps = { } -- Firstly, build, in `caps', a list of the capability names and their -- numbers. local i = 1 for j, cap in pairs(CAPTAB) do caps[i] = { i = j, cap = cap.name } i = i + 1 end -- Sort the list. Now they're in the right order. table.sort(caps, function (v0, v1) return v0.i < v1.i end) -- Finally, write the entries to `caplist', with the `user' entry at the -- start and the `unassigned' entry at the end. i = 1 caplist[i] = "secnet.cap.user"; i = i + 1 for _, v in ipairs(caps) do caplist[i] = "secnet.cap." .. v.cap i = i + 1 end caplist[i] = "secnet.cap.unassigned"; i = i + 1 end function dissect_caps(st, buf, tree, pos, sz) -- Dissect a capabilities word. if pos < sz then local cap = tree:add(PF["secnet.cap"], buf(pos, 4)) for _, pf in ipairs(caplist) do cap:add(PF[pf], buf(pos, 4)) end pos = pos + 4 end return pos end end local function dissect_mtu(st, buf, tree, pos, sz) -- Dissect an MTU request. if pos < sz then tree:add(PF["secnet.mtu"], buf(pos, 2)); pos = pos + 2 end return pos end local function make_dissect_name_xinfo(label, dissect_xinfo, hook) -- Return a dissector function for reading a name and extra information. -- The function will dissect a subtree rooted at the protocol field LABEL; -- it will dissect the extra information using the list DISSECT_XINFO -- (processed using `dissect_sequence'); and finally, if the packet hasn't -- been visited yet, it will call HOOK(ST, NAME), where NAME is the name -- string extracted from the packet. return function (st, buf, tree, pos, sz) -- Find the length of the whole thing. local len = buf(pos, 2):uint() -- Make the subtree root. local sub = tree:add(PF[label], buf(pos, len + 2)) -- Find the length of the name. This is rather irritating: I'd like to -- get Wireshark to do this, but it seems that `stringz' doesn't pay -- attention to the buffer limits it's given. So read the whole lot and -- find the null by hand. local name = buf(pos + 2, len):string() local z, _ = string.find(name, "\0", 1, true) if z == nil then z = len else z = z - 1 name = string.sub(name, 1, z) end -- Fill in the subtree. sub:add(PF["secnet.namex.len"], buf(pos, 2)); pos = pos + 2 sub:add(PF["secnet.namex.name"], buf(pos, z)) if z < len then dissect_sequence(dissect_xinfo, st, buf, sub, pos + z + 1, pos + len) end -- Maybe call the hook. if hook ~= nil and not st.pinfo.visited then hook(st, name) end -- We're done. return pos + len end end local function dissect_sndnonce(st, buf, tree, pos, sz) -- Dissect the sender's nonce. tree:add(PF["secnet.kx.sndnonce"], buf(pos, 8)); pos = pos + 8 return pos end local function dissect_rcvnonce(st, buf, tree, pos, sz) -- Dissect the recipient's nonce. tree:add(PF["secnet.kx.rcvnonce"], buf(pos, 8)); pos = pos + 8 return pos end local function dissect_transform(st, buf, tree, pos, sz) -- Dissect the selected transform. Note this in the packet state for -- later. st.transform = buf(pos, 1):uint() tree:add(PF["secnet.kx.transform"], buf(pos, 1)); pos = pos + 1 return pos end local function dissect_lenstr(st, buf, tree, label, pos, sz) -- Dissect a simple string given its length. local len = buf(pos, 2):uint() local sub = tree:add(PF[label], buf(pos, len + 2)) sub:add(PF[label .. ".len"], buf(pos, 2)); pos = pos + 2 sub:add(PF[label .. ".text"], buf(pos, len)); pos = pos + len return pos end local function dissect_dhval(st, buf, tree, pos, sz) -- Dissect a Diffie--Hellman public value. return dissect_lenstr(st, buf, tree, "secnet.kx.dhval", pos, sz) end local function dissect_sig(st, buf, tree, pos, sz) -- Dissect a signature. return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz) end local function find_algs_lookup(map, sock, now, ix) -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a -- site; find its peer with index IX; and return the algorithm selection -- current between the pair at time NOW. If the lookup fails, return nil. local name = lookup_timeline(map, sock, now) if name == nil then return nil end local site = SITEMAP[name] if site == nil then return nil end local peername = lookup_timeline(site.index, ix, now) if peername == nil then return nil end return lookup_timeline(site.algs, peername, now) end local function find_algs(st) -- Return the algorithm selection which applies to the packet described in -- ST. local now = st.pinfo.rel_ts local sock = snd_sockname(st) local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix) if algs ~= nil then return algs else return find_algs_lookup(GUESSMAP, sock, now, st.rcvix) end end -- Transform-specific dissectors... local dissect_ct = { } function dissect_ct.unknown(st, why, buf, tree, pos, sz) tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos), "Ciphertext with unknown structure: " .. why) return sz end function dissect_ct.serpent256cbc(st, buf, tree, pos, sz) tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4 tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos)) return sz end function dissect_ct.eaxserpent(st, buf, tree, pos, sz) local len = sz - pos - 20 tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16 tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4 return pos end local function dissect_ciphertext(st, buf, tree, pos, sz) -- Dissect a ciphertext. local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos)) local algs = find_algs(st) local xform if algs == nil then xform = nil else xform = algs.transform end if xform == nil then pos = dissect_ct.unknown(st, "unable to find negotiated transform", buf, sub, pos, sz) else local func = dissect_ct[xform] if func == nil then pos = dissect_ct.unknown(st, "unsupported transform " .. xform, buf, sub, pos, sz) else pos = func(st, buf, sub, pos, sz) end end return pos end ----------------------------------------------------------------------------- --- The protocol information table. local PKTINFO = { -- This is the main table which describes the protocol. The top level maps -- message labels to structures: -- -- * `label' is the category code's symbolic name; -- -- * `info' is a prefix for the information column display; and -- -- * `dissect' is a sequence of primitive dissectors to run in order to -- parse the rest of the packet. [M.NAK] = { label = "NAK", info = "Stimulate fresh key exchange", dissect = { dissect_wtf } }, [M.MSG0] = { label = "MSG0", info = "MSG0", dissect = { dissect_ciphertext } }, [M.MSG1] = { label = "MSG1", info = "MSG1", dissect = { make_dissect_name_xinfo("secnet.kx.sndname", { dissect_caps, dissect_wtf }, notice_sndname), make_dissect_name_xinfo("secnet.kx.rcvname", { dissect_wtf }, notice_rcvname), dissect_sndnonce, dissect_wtf } }, [M.MSG2] = { label = "MSG2", info = "MSG2", dissect = { make_dissect_name_xinfo("secnet.kx.sndname", { dissect_caps, dissect_wtf }, notice_sndname), make_dissect_name_xinfo("secnet.kx.rcvname", { dissect_wtf }, notice_rcvname), dissect_sndnonce, dissect_rcvnonce, dissect_wtf } }, [M.MSG3] = { label = "MSG3", info = "MSG3", dissect = { make_dissect_name_xinfo("secnet.kx.sndname", { dissect_caps, dissect_mtu, dissect_wtf }, notice_sndname), make_dissect_name_xinfo("secnet.kx.rcvname", { dissect_wtf }, notice_rcvname), dissect_sndnonce, dissect_rcvnonce, dissect_wtf }, hook = notice_alg_selection }, [M.MSG3BIS] = { label = "MSG3BIS", info = "MSG3BIS", dissect = { make_dissect_name_xinfo("secnet.kx.sndname", { dissect_caps, dissect_mtu, dissect_wtf }, notice_sndname), make_dissect_name_xinfo("secnet.kx.rcvname", { dissect_wtf }, notice_rcvname), dissect_sndnonce, dissect_rcvnonce, dissect_transform, dissect_dhval, dissect_sig, dissect_wtf }, hook = notice_alg_selection }, [M.MSG4] = { label = "MSG4", info = "MSG4", dissect = { make_dissect_name_xinfo("secnet.kx.sndname", { dissect_caps, dissect_mtu, dissect_wtf }, notice_sndname), make_dissect_name_xinfo("secnet.kx.rcvname", { dissect_wtf }, notice_rcvname), dissect_sndnonce, dissect_rcvnonce, dissect_dhval, dissect_sig, dissect_wtf } }, [M.MSG5] = { label = "MSG5", info = "MSG5", dissect = { dissect_ciphertext } }, [M.MSG6] = { label = "MSG6", info = "MSG6", dissect = { dissect_ciphertext } }, [M.PROD] = { label = "PROD", info = "PROD", dissect = { make_dissect_name_xinfo("secnet.kx.sndname", { dissect_caps, dissect_wtf }, notice_sndname), make_dissect_name_xinfo("secnet.kx.rcvname", { dissect_wtf }, notice_rcvname), dissect_wtf } }, } do -- Work through the master table and build the `msgtab'' table, mapping -- message codes to their symbolic names for presentation. local msgtab = { } for i, v in pairs(PKTINFO) do msgtab[i] = v.label end local capmap = { transform = { }, early = { } } for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end local ftab = { -- The protocol fields. This table maps the field names to structures -- used to build the fields, which are then stored in `PF' (declared way -- above): -- -- * `name' is the field name to show in the dissector tree view; -- -- * `type' is the field type; -- -- * `base' is a tweak describing how the field should be formatted; -- -- * `mask' is used to single out a piece of a larger bitfield; -- -- * `tab' names a mapping table used to convert numerical values to -- symbolic names; and -- -- * `hook' is a hook function to run the first time we see a packet, -- to keep track of things. ["secnet.hdr"] = { name = "Common message header", type = ftypes.NONE }, ["secnet.hdr.rcvix"] = { name = "Recipient's site index for sender", type = ftypes.UINT32, base = base.DEC }, ["secnet.hdr.sndix"] = { name = "Sender's site index for recipient", type = ftypes.UINT32, base = base.DEC }, ["secnet.hdr.label"] = { name = "Message label", type = ftypes.UINT32, base = base.HEX, tab = msgtab }, ["secnet.kx.sndname"] = { name = "Sender's site name and extended information", type = ftypes.NONE }, ["secnet.kx.rcvname"] = { name = "Recipient's site name and extended information", type = ftypes.NONE }, ["secnet.namex.len"] = { name = "Name/extended info length", type = ftypes.UINT16, base = base.DEC }, ["secnet.namex.name"] = { name = "Site name", type = ftypes.STRING, field = true, base = base.ASCII, }, ["secnet.cap"] = { name = "Advertised capability bits", type = ftypes.UINT32, base = base.HEX }, ["secnet.cap.user"] = { name = "User-assigned capability bits", type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX }, ["secnet.mtu"] = { name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC }, ["secnet.kx.sndnonce"] = { name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE }, ["secnet.kx.rcvnonce"] = { name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE }, ["secnet.kx.transform"] = { name = "Selected bulk-crypto transform", type = ftypes.UINT8, base = base.DEC, tab = capmap.transform }, ["secnet.kx.dhval"] = { name = "Sender's public Diffie--Hellman value", type = ftypes.NONE }, ["secnet.kx.dhval.len"] = { name = "Sender's public Diffie--Hellman length", type = ftypes.UINT16, base = base.DEC }, ["secnet.kx.dhval.text"] = { name = "Sender's public Diffie--Hellman text", type = ftypes.STRING, base = base.ASCII }, ["secnet.kx.sig"] = { name = "Sender's signature", type = ftypes.NONE }, ["secnet.kx.sig.len"] = { name = "Sender's signature length", type = ftypes.UINT16, base = base.DEC }, ["secnet.kx.sig.text"] = { name = "Sender's signature text", type = ftypes.STRING, base = base.ASCII }, ["secnet.ciphertext"] = { name = "Encrypted data", type = ftypes.NONE }, ["secnet.ciphertext.unknown"] = { name = "Ciphertext with unknown structure", type = ftypes.BYTES, base = base.SPACE }, ["secnet.ciphertext.iv"] = { name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE }, ["secnet.ciphertext.sequence"] = { name = "Sequence number", type = ftypes.UINT32, base = base.DEC }, ["secnet.ciphertext.payload"] = { name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE }, ["secnet.ciphertext.tag"] = { name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE }, ["secnet.wtf"] = { name = "Unexpected trailing data", type = ftypes.BYTES, base = base.SPACE } } -- Add the remaining capability fields. Calculate the unassigned mask -- based on the assigned bits. local unasgn = 0x7fff7f00 for i, v in pairs(CAPTAB) do local flag = bit.lshift(1, i) ftab["secnet.cap." .. v.name] = { name = v.desc, type = ftypes.BOOLEAN, mask = flag, base = 32 } unasgn = bit.band(unasgn, bit.bnot(flag)) end ftab["secnet.cap.unassigned"] = { name = "Unassigned capability bits", type = ftypes.UINT32, mask = unasgn, base = base.HEX } -- Convert this table into the protocol fields, and populate `PF'. local ff = { } local i = 1 -- Figure out whether we can use `none' fields (see below). local use_none_p = rawget(ProtoField, 'none') ~= nil for abbr, args in pairs(ftab) do -- An annoying hack. Older versions of Wireshark don't allow setting -- fields with type `none', which is a shame because they're ideal as -- internal tree nodes. ty = args.type b = args.base if ty == ftypes.NONE then if use_none_p then b = base.NONE else ty = ftypes.BYTES b = base.SPACE end end -- Go make the field. local f = ProtoField.new(args.name, abbr, ty, args.tab, b, args.mask, args.descr) PF[abbr] = f ff[i] = f; i = i + 1 end secnet.fields = PF -- Make readable fields corresponding to especially interesting protocol -- fields. for abbr, args in pairs(ftab) do if args.field then F[abbr] = Field.new(abbr) end end end ----------------------------------------------------------------------------- --- The main dissector. function secnet.dissector(buf, pinfo, tree) -- Fill in the obvious stuff. pinfo.cols.protocol = "Secnet" local sz = buf:reported_length_remaining() local sub = tree:add(secnet, buf(0, sz), "Secnet packet") local p = 12 -- Decode the message header. hdr = sub:add(PF["secnet.hdr"], buf(0, 12)) local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4)) local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4)) local label = buf(8, 4):uint() hdr:add(PF["secnet.hdr.label"], buf(8, 4), label, string.format("Message label (major = 0x%04x, minor = 0x%04x)", msgmajor(label), msgminor(label))) local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix } local info = PKTINFO[label] -- Dispatch using the master protocol table. if info == nil then pinfo.cols.info = string.format("Unknown message label 0x%08x", label) else pinfo.cols.info = info.info p = dissect_sequence(info.dissect, st, buf, sub, p, sz) end -- Invoke the hook if necessary. if not pinfo.visited and info.hook ~= nil then info.hook(st) end -- Return the final position we reached. return p end -- We're done. Register the dissector. DissectorTable.get("udp.port"):add(410, secnet) -------- That's all, folks --------------------------------------------------