mdw-test/: Include some random utilities I've found handy for testing.
[secnet] / secnet-wireshark.lua
CommitLineData
010e0a70
MW
1--- -*-lua-*-
2---
3--- This file is part of secnet.
4--- See README for full list of copyright holders.
5---
6--- secnet is free software; you can redistribute it and/or modify it
7--- under the terms of the GNU General Public License as published by
8--- the Free Software Foundation; either version d of the License, or
9--- (at your option) any later version.
10---
11--- secnet is distributed in the hope that it will be useful, but
12--- WITHOUT ANY WARRANTY; without even the implied warranty of
13--- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14--- General Public License for more details.
15---
16--- You should have received a copy of the GNU General Public License
17--- version 3 along with secnet; if not, see
18--- https://www.gnu.org/licenses/gpl.html.
19
20local secnet = Proto("secnet", "Secnet VPN")
21
22-----------------------------------------------------------------------------
23--- Session tracking.
24---
25--- This is the hardest part of the dissector.
26
27-- Timelines. A timeline associates pieces of information with times T.
28
29local function tl_new()
30 -- Return a fresh shiny timeline.
31
32 return { }
33end
34
35local function tl__find(tl, t)
36 -- Find and return the earliest association in TL not earlier than T. If
37 -- there is no such entry, return nil.
38
39 local lo = 1
40 local hi = #tl + 1
41
42 -- Plain old binary search. The active interval is half-open, [lo, hi).
43 while true do
44 local w = hi - lo
45 if w == 0 then return nil end
46 local mid = lo + math.floor(w/2)
47 local tv = tl[mid]
48 if tv.t > t then hi = mid
49 elseif tv.t == t or w == 1 then return tv
50 else lo = mid
51 end
52 end
53end
54
55local function tl_find(tl, t)
56 -- Find and return the state of the timeline at time T, i.e., the earliest
57 -- value in TL not earlier than T. If there is no such entry, return nil.
58
59 local tv = tl__find(tl, t)
60 if tv == nil then return nil else return tv.v end
61end
62
63local function tl_add(tl, t, v)
64 -- Associate the value V with time T in TL.
65
66 local tv = tl__find(tl, t)
67 if tv ~= nil and tv.t == t then
68 tv.v = v
69 else
70 -- Append the new item. If necessary, sort the vector; we expect that
71 -- we'll see everything in the right order, so this won't be a problem.
72 local n = #tl
73 tl[n + 1] = { t = t, v = v }
74 if n > 0 and tl[n].t > t then
75 table.sort(tl, function (tv0, tv1) return tv0.t < tv1.t end)
76 end
77 end
78end
79
80local function dump_timeline(tl, cvt)
81 -- Dump a timeline TL, using the function CVT to convert each value to a
82 -- string.
83
84 for _, tv in ipairs(tl) do print("\t" .. tv.t .. ": " .. cvt(tv.v)) end
85end
86
87local function get_timeline_create(map, index)
88 -- If MAP[INDEX] exists, return it; otherwise set MAP[INDEX] to a fresh
89 -- timeline and return that.
90
91 local tl = map[index]
92 if tl == nil then tl = tl_new(); map[index] = tl end
93 return tl
94end
95
96local function lookup_timeline(map, index, t)
97 -- If it exists, MAP[INDEX] should be a timeline; find its state at time T.
98 -- Return nil if there's nothing there, or T is too early.
99
100 local tl = map[index]
101 if tl == nil then return nil
102 else return tl_find(tl, t)
103 end
104end
105
106-- The `SITEMAP' maps site names to little structures.
107--
108-- * `algs' is a map from peer site names to a timeline of structures
109-- described below.
110--
111-- * `index' is a map from site indices to a timeline of names, reflecting
112-- that, at some time T, this site thought that some index I referred to
113-- a peer site P.
114--
115-- The `algs' map contains the following slots, populated during .
116--
117-- * `xform' is a timeline of transform names.
118local SITEMAP = { }
119
120-- The `ADDRMAP' maps (IPv4 or IPv6) socket addresses in the form
121-- `[ADDR]:PORT' to a timeline of site names, populated based on claims made
122-- by senders about themselves. The `GUESSMAP' is similar, but populated
123-- based on assertions about recipients.
124local ADDRMAP = { }
125local GUESSMAP = { }
126
127local function snd_sockname(st)
128 -- Return the sender's socket name as a thing which can be used as a table
129 -- index.
130
131 local pinfo = st.pinfo
132 return string.format("[%s]:%d", pinfo.net_src, pinfo.src_port)
133end
134
135local function rcv_sockname(st)
136 -- Return the recipient's socket name as a thing which can be used as a
137 -- table index.
138
139 local pinfo = st.pinfo
140 return string.format("[%s]:%d", pinfo.net_dst, pinfo.dst_port)
141end
142
143local function get_site_create(name)
144 -- If NAME refers to a known site, then return its information structure;
145 -- otherwise create a new one and return that.
146
147 local site = SITEMAP[name]
148 if site == nil then
149 site = { algs = { }, index = { } }
150 SITEMAP[name] = site
151 end
152 return site
153end
154
155local function notice_site_name(map, st, sock, name)
156 -- Record in MAP that the packet described in the state ST tells us that,
157 -- at that time, the site NAME appeared to be at address SOCK.
158
159 tl_add(get_timeline_create(map, sock), st.pinfo.rel_ts, name)
160end
161
162local function dump_algs(algs)
163 -- Dump the algorithms selection ALGS from a site structure.
164
165 return "xform=" .. algs.transform
166end
167
168local function dump_str(str) return str end
169
170local function dump_addrmap(what, map)
171 -- Dump MAP, which is an address map like `ADDRMAP' or `GUESSMAP'; WHAT is
172 -- a string describing which map it is.
173
174 print(what .. "...")
175 for addr, tl in pairs(map) do
176 print(" " .. addr)
177 dump_timeline(tl, dump_str)
178 end
179end
180
181local function dump_tracking_state()
182 -- Dump the entire tracking state to standard output.
183
184 dump_addrmap("Address map", ADDRMAP)
185 dump_addrmap("Guess map", GUESSMAP)
186 print("Site map...")
187 for name, site in pairs(SITEMAP) do
188 print(" " .. name)
189 print(" algs...")
190 for peer, tl in pairs(site.algs) do
191 print(" " .. peer)
192 dump_timeline(tl, dump_algs)
193 end
194 print(" index...")
195 for ix, tl in pairs(site.index) do
196 print(" " .. ix)
197 dump_timeline(tl, dump_str)
198 end
199 end
200end
201
202local function notice_sndname(st, name)
203 -- Record that sender of the packet described by state ST is called NAME.
204
205 st.sndname = name
206 notice_site_name(ADDRMAP, st, snd_sockname(st), name)
207end
208
209local function notice_rcvname(st, name)
210 -- Record that the sender of the packet described by ST thought that its
211 -- recipient was called NAME.
212
213 st.rcvname = name
214 notice_site_name(GUESSMAP, st, rcv_sockname(st), name)
215 if st.sndname ~= nil then
216 local site = get_site_create(st.sndname)
217 tl_add(get_timeline_create(site.index, st.sndix), st.pinfo.rel_ts, name)
218 end
219end
220
221-- Tables describing the kinds of algorithms which can be selected.
222local CAPTAB = {
223 [8] = { name = "serpent256cbc", kind = "transform",
224 desc = "Deprecated Serpent256-CBC transform" },
225 [9] = { name = "eaxserpent", kind = "transform",
226 desc = "Serpent256-EAX transform" },
227 [31] = { name = "mobile-priority", kind = "early",
228 desc = "Mobile site takes priority in case of MSG1 crossing" }
229}
230
231local function get_algname(kind, cap, dflt)
232 -- Fetch an algorithm of the given KIND, given its capability number CAP;
233 -- if CAP is nil, then return DFLT instead.
234
235 local name
236 if cap == nil then
237 name = dflt
238 else
239 local info = CAPTAB[cap]
240 if info ~= nil and info.kind == kind then name = info.name
241 else name = string.format("Unknown %s #%d", kind, cap)
242 end
243 end
244 return name
245end
246
247local function notice_alg_selection(st)
248 -- Record the algorithm selections declared in the packet described by ST.
249
250 local transform = get_algname("transform", st.transform, "serpent256cbc")
251 local site = get_site_create(st.sndname)
252 local peer = get_site_create(st.rcvname)
253 local now = st.pinfo.rel_ts
254 local algs = { transform = transform }
255 tl_add(get_timeline_create(site.algs, st.rcvname), now, algs)
256 tl_add(get_timeline_create(peer.algs, st.sndname), now, algs)
257end
258
259-----------------------------------------------------------------------------
260--- Protocol dissection primitives.
261
262local PF = { } -- The table of protocol fields, filled in later.
263local F = { } -- A table of field values, also filled in later.
264
7b2ef224
MW
265local function msgcode(major, minor)
266 -- Construct a Secnet message number according to the complicated rules.
267
268 local majlo = bit.band(major, 0x000f)
269 local majhi = bit.band(major, 0xfff0)
270 local minlo = bit.band(minor, 0x000f)
271 local minhi = bit.band(minor, 0xfff0)
272 return bit.bxor(bit.lshift(majlo, 0),
273 bit.lshift(majlo, 8),
274 bit.lshift(majlo, 16),
275 bit.lshift(majlo, 24),
276 bit.lshift(majhi, 4),
277 bit.lshift(minlo, 4),
278 bit.lshift(minlo, 28),
279 bit.lshift(minhi, 16))
280end
281
282local function msgmajor(label)
283 -- Return the major message number from a LABEL.
284
285 local lo = bit.band(label, 0x000f)
286 local hi = bit.band(bit.rshift(label, 4), 0xfff0)
287 return bit.bxor(lo, bit.lshift(lo, 4), bit.lshift(lo, 12), hi)
288end
289
290local function msgminor(label)
291 -- Return the minor message number from a LABEL.
292
293 return bit.bxor(bit.lshift(bit.band(label, 0x00ff), 8),
294 bit.band(bit.rshift(label, 4), 0x000f),
295 bit.band(bit.rshift(label, 16), 0xfff0))
296end
297
010e0a70 298-- Main message-number table.
7b2ef224
MW
299local M = { NAK = msgcode( 0, 0),
300 MSG0 = msgcode(0x2020, 0), -- !
301 MSG1 = msgcode( 1, 0),
302 MSG2 = msgcode( 2, 0),
303 MSG3 = msgcode( 3, 0),
304 MSG3BIS = msgcode( 3, 1),
305 MSG4 = msgcode( 4, 0),
306 MSG5 = msgcode( 5, 0),
307 MSG6 = msgcode( 6, 0),
308 MSG7 = msgcode( 7, 0),
309 MSG8 = msgcode( 8, 0),
310 MSG9 = msgcode( 9, 0),
311 PROD = msgcode( 10, 0)}
010e0a70
MW
312
313-- The `dissect_*' functions follow a common protocol. They parse a thing
314-- from a packet buffer BUF, of size SZ, starting from POS, and store
315-- interesting things in a given TREE; when they're done, they return the
316-- updated index where the next interesting thing might be, and maybe store
317-- interesting things in the state ST. As a result, it's usually a simple
318-- matter to parse a packet by invoking the appropriate primitive dissectors
319-- in the right order.
320
321local function dissect_sequence(dissect, st, buf, tree, pos, sz)
322 -- Dissect pieces of the packed in BUF with each of the dissectors in the
323 -- list DISSECT in turn.
324
325 for _, d in ipairs(dissect) do pos = d(st, buf, tree, pos, sz) end
326 return pos
327end
328
329local function dissect_wtf(st, buf, tree, pos, sz)
330 -- If POS is not at the end of the buffer, note that there's unexpected
331 -- stuff in the packet.
332
333 if pos < sz then tree:add(PF["secnet.wtf"], buf(pos, sz - pos)) end
334 return sz
335end
336
337local dissect_caps
338do
339 -- This will be a list of the capability protocol field names, in the right
340 -- order. We just have to figure out what that will be.
341 local caplist = { }
342
343 do
344 local caps = { }
345
346 -- Firstly, build, in `caps', a list of the capability names and their
347 -- numbers.
348 local i = 1
349 for j, cap in pairs(CAPTAB) do
350 caps[i] = { i = j, cap = cap.name }
351 i = i + 1
352 end
353
354 -- Sort the list. Now they're in the right order.
355 table.sort(caps, function (v0, v1) return v0.i < v1.i end)
356
357 -- Finally, write the entries to `caplist', with the `user' entry at the
358 -- start and the `unassigned' entry at the end.
359 i = 1
360 caplist[i] = "secnet.cap.user"; i = i + 1
361 for _, v in ipairs(caps) do
362 caplist[i] = "secnet.cap." .. v.cap
363 i = i + 1
364 end
365 caplist[i] = "secnet.cap.unassigned"; i = i + 1
366 end
367
368 function dissect_caps(st, buf, tree, pos, sz)
369 -- Dissect a capabilities word.
370
371 if pos < sz then
372 local cap = tree:add(PF["secnet.cap"], buf(pos, 4))
373 for _, pf in ipairs(caplist) do cap:add(PF[pf], buf(pos, 4)) end
374 pos = pos + 4
375 end
376 return pos
377 end
378end
379
380local function dissect_mtu(st, buf, tree, pos, sz)
381 -- Dissect an MTU request.
382
383 if pos < sz then tree:add(PF["secnet.mtu"], buf(pos, 2)); pos = pos + 2 end
384 return pos
385end
386
387local function make_dissect_name_xinfo(label, dissect_xinfo, hook)
388 -- Return a dissector function for reading a name and extra information.
389 -- The function will dissect a subtree rooted at the protocol field LABEL;
390 -- it will dissect the extra information using the list DISSECT_XINFO
391 -- (processed using `dissect_sequence'); and finally, if the packet hasn't
392 -- been visited yet, it will call HOOK(ST, NAME), where NAME is the name
393 -- string extracted from the packet.
394
395 return function (st, buf, tree, pos, sz)
396
397 -- Find the length of the whole thing.
398 local len = buf(pos, 2):uint()
399
400 -- Make the subtree root.
401 local sub = tree:add(PF[label], buf(pos, len + 2))
402
403 -- Find the length of the name. This is rather irritating: I'd like to
404 -- get Wireshark to do this, but it seems that `stringz' doesn't pay
405 -- attention to the buffer limits it's given. So read the whole lot and
406 -- find the null by hand.
407 local name = buf(pos + 2, len):string()
408 local z, _ = string.find(name, "\0", 1, true)
409 if z == nil then
410 z = len
411 else
412 z = z - 1
413 name = string.sub(name, 1, z)
414 end
415
416 -- Fill in the subtree.
417 sub:add(PF["secnet.namex.len"], buf(pos, 2)); pos = pos + 2
418 sub:add(PF["secnet.namex.name"], buf(pos, z))
419 if z < len then
420 dissect_sequence(dissect_xinfo, st, buf, sub, pos + z + 1, pos + len)
421 end
422
423 -- Maybe call the hook.
424 if hook ~= nil and not st.pinfo.visited then hook(st, name) end
425
426 -- We're done.
427 return pos + len
428 end
429end
430
431local function dissect_sndnonce(st, buf, tree, pos, sz)
432 -- Dissect the sender's nonce.
433
434 tree:add(PF["secnet.kx.sndnonce"], buf(pos, 8)); pos = pos + 8
435 return pos
436end
437
438local function dissect_rcvnonce(st, buf, tree, pos, sz)
439 -- Dissect the recipient's nonce.
440
441 tree:add(PF["secnet.kx.rcvnonce"], buf(pos, 8)); pos = pos + 8
442 return pos
443end
444
445local function dissect_transform(st, buf, tree, pos, sz)
446 -- Dissect the selected transform. Note this in the packet state for
447 -- later.
448
449 st.transform = buf(pos, 1):uint()
450 tree:add(PF["secnet.kx.transform"], buf(pos, 1)); pos = pos + 1
451 return pos
452end
453
454local function dissect_lenstr(st, buf, tree, label, pos, sz)
455 -- Dissect a simple string given its length.
456 local len = buf(pos, 2):uint()
457 local sub = tree:add(PF[label], buf(pos, len + 2))
458 sub:add(PF[label .. ".len"], buf(pos, 2)); pos = pos + 2
459 sub:add(PF[label .. ".text"], buf(pos, len)); pos = pos + len
460 return pos
461end
462
463local function dissect_dhval(st, buf, tree, pos, sz)
464 -- Dissect a Diffie--Hellman public value.
465
466 return dissect_lenstr(st, buf, tree, "secnet.kx.dhval", pos, sz)
467end
468
469local function dissect_sig(st, buf, tree, pos, sz)
470 -- Dissect a signature.
471
472 return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz)
473end
474
475local function find_algs_lookup(map, sock, now, ix)
476 -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a
477 -- site; find its peer with index IX; and return the algorithm selection
478 -- current between the pair at time NOW. If the lookup fails, return nil.
479
480 local name = lookup_timeline(map, sock, now)
481 if name == nil then return nil end
482 local site = SITEMAP[name]
483 if site == nil then return nil end
484 local peername = lookup_timeline(site.index, ix, now)
485 if peername == nil then return nil end
486 return lookup_timeline(site.algs, peername, now)
487end
488
489local function find_algs(st)
490 -- Return the algorithm selection which applies to the packet described in
491 -- ST.
492
493 local now = st.pinfo.rel_ts
494 local sock = snd_sockname(st)
495 local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix)
496 if algs ~= nil then return algs
497 else return find_algs_lookup(GUESSMAP, sock, now, st.rcvix)
498 end
499end
500
501-- Transform-specific dissectors...
502local dissect_ct = { }
503function dissect_ct.unknown(st, why, buf, tree, pos, sz)
504 tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos),
505 "Ciphertext with unknown structure: " .. why)
506 return sz
507end
508function dissect_ct.serpent256cbc(st, buf, tree, pos, sz)
509 tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4
510 tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos))
511 return sz
512end
513function dissect_ct.eaxserpent(st, buf, tree, pos, sz)
514 local len = sz - pos - 20
515 tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len
516 tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
517 tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4
518 return pos
519end
520
521local function dissect_ciphertext(st, buf, tree, pos, sz)
522 -- Dissect a ciphertext.
523
524 local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos))
525 local algs = find_algs(st)
526 local xform
527 if algs == nil then xform = nil else xform = algs.transform end
528 if xform == nil then
529 pos = dissect_ct.unknown(st, "unable to find negotiated transform",
530 buf, sub, pos, sz)
531 else
532 local func = dissect_ct[xform]
533 if func == nil then
534 pos = dissect_ct.unknown(st, "unsupported transform " .. xform,
535 buf, sub, pos, sz)
536 else
537 pos = func(st, buf, sub, pos, sz)
538 end
539 end
540 return pos
541end
542
543-----------------------------------------------------------------------------
544--- The protocol information table.
545
546local PKTINFO = {
547 -- This is the main table which describes the protocol. The top level maps
548 -- message labels to structures:
549 --
550 -- * `label' is the category code's symbolic name;
551 --
552 -- * `info' is a prefix for the information column display; and
553 --
554 -- * `dissect' is a sequence of primitive dissectors to run in order to
555 -- parse the rest of the packet.
556
557 [M.NAK] = {
558 label = "NAK",
559 info = "Stimulate fresh key exchange",
560 dissect = { dissect_wtf }
561 },
562 [M.MSG0] = {
563 label = "MSG0",
564 info = "MSG0",
565 dissect = { dissect_ciphertext }
566 },
567 [M.MSG1] = {
568 label = "MSG1",
569 info = "MSG1",
570 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
571 { dissect_caps, dissect_wtf },
572 notice_sndname),
573 make_dissect_name_xinfo("secnet.kx.rcvname",
574 { dissect_wtf },
575 notice_rcvname),
576 dissect_sndnonce,
577 dissect_wtf }
578 },
579 [M.MSG2] = {
580 label = "MSG2",
581 info = "MSG2",
582 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
583 { dissect_caps, dissect_wtf },
584 notice_sndname),
585 make_dissect_name_xinfo("secnet.kx.rcvname",
586 { dissect_wtf },
587 notice_rcvname),
588 dissect_sndnonce, dissect_rcvnonce,
589 dissect_wtf }
590 },
591 [M.MSG3] = {
592 label = "MSG3",
593 info = "MSG3",
594 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
595 { dissect_caps,
596 dissect_mtu,
597 dissect_wtf },
598 notice_sndname),
599 make_dissect_name_xinfo("secnet.kx.rcvname",
600 { dissect_wtf },
601 notice_rcvname),
602 dissect_sndnonce, dissect_rcvnonce,
603 dissect_wtf },
604 hook = notice_alg_selection
605 },
606 [M.MSG3BIS] = {
607 label = "MSG3BIS",
608 info = "MSG3BIS",
609 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
610 { dissect_caps,
611 dissect_mtu,
612 dissect_wtf },
613 notice_sndname),
614 make_dissect_name_xinfo("secnet.kx.rcvname",
615 { dissect_wtf },
616 notice_rcvname),
617 dissect_sndnonce, dissect_rcvnonce,
618 dissect_transform,
619 dissect_dhval, dissect_sig,
620 dissect_wtf },
621 hook = notice_alg_selection
622 },
623 [M.MSG4] = {
624 label = "MSG4",
625 info = "MSG4",
626 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
627 { dissect_caps,
628 dissect_mtu,
629 dissect_wtf },
630 notice_sndname),
631 make_dissect_name_xinfo("secnet.kx.rcvname",
632 { dissect_wtf },
633 notice_rcvname),
634 dissect_sndnonce, dissect_rcvnonce,
635 dissect_dhval, dissect_sig,
636 dissect_wtf }
637 },
638 [M.MSG5] = {
639 label = "MSG5",
640 info = "MSG5",
641 dissect = { dissect_ciphertext }
642 },
643 [M.MSG6] = {
644 label = "MSG6",
645 info = "MSG6",
646 dissect = { dissect_ciphertext }
647 },
648 [M.PROD] = {
649 label = "PROD",
650 info = "PROD",
651 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
652 { dissect_caps,
653 dissect_wtf },
654 notice_sndname),
655 make_dissect_name_xinfo("secnet.kx.rcvname",
656 { dissect_wtf },
657 notice_rcvname),
658 dissect_wtf }
659 },
660}
661
662do
663 -- Work through the master table and build the `msgtab'' table, mapping
664 -- message codes to their symbolic names for presentation.
665 local msgtab = { }
666 for i, v in pairs(PKTINFO) do msgtab[i] = v.label end
667
668 local capmap = { transform = { }, early = { } }
669 for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end
670
671 local ftab = {
672 -- The protocol fields. This table maps the field names to structures
673 -- used to build the fields, which are then stored in `PF' (declared way
674 -- above):
675 --
676 -- * `name' is the field name to show in the dissector tree view;
677 --
678 -- * `type' is the field type;
679 --
680 -- * `base' is a tweak describing how the field should be formatted;
681 --
682 -- * `mask' is used to single out a piece of a larger bitfield;
683 --
684 -- * `tab' names a mapping table used to convert numerical values to
685 -- symbolic names; and
686 --
687 -- * `hook' is a hook function to run the first time we see a packet,
688 -- to keep track of things.
689
690 ["secnet.hdr"] = {
691 name = "Common message header", type = ftypes.NONE
692 },
693 ["secnet.hdr.rcvix"] = {
694 name = "Recipient's site index for sender",
695 type = ftypes.UINT32, base = base.DEC
696 },
697 ["secnet.hdr.sndix"] = {
698 name = "Sender's site index for recipient",
699 type = ftypes.UINT32, base = base.DEC
700 },
701 ["secnet.hdr.label"] = {
702 name = "Message label", type = ftypes.UINT32,
703 base = base.HEX, tab = msgtab
704 },
705 ["secnet.kx.sndname"] = {
706 name = "Sender's site name and extended information",
707 type = ftypes.NONE
708 },
709 ["secnet.kx.rcvname"] = {
710 name = "Recipient's site name and extended information",
711 type = ftypes.NONE
712 },
713 ["secnet.namex.len"] = {
714 name = "Name/extended info length",
715 type = ftypes.UINT16, base = base.DEC
716 },
717 ["secnet.namex.name"] = {
718 name = "Site name", type = ftypes.STRING,
719 field = true, base = base.ASCII,
720 },
721 ["secnet.cap"] = {
722 name = "Advertised capability bits",
723 type = ftypes.UINT32, base = base.HEX
724 },
725 ["secnet.cap.user"] = {
726 name = "User-assigned capability bits",
727 type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX
728 },
729 ["secnet.mtu"] = {
730 name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC
731 },
732 ["secnet.kx.sndnonce"] = {
733 name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE
734 },
735 ["secnet.kx.rcvnonce"] = {
736 name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE
737 },
738 ["secnet.kx.transform"] = {
739 name = "Selected bulk-crypto transform", type = ftypes.UINT8,
740 base = base.DEC, tab = capmap.transform
741 },
742 ["secnet.kx.dhval"] = {
743 name = "Sender's public Diffie--Hellman value", type = ftypes.NONE
744 },
745 ["secnet.kx.dhval.len"] = {
746 name = "Sender's public Diffie--Hellman length",
747 type = ftypes.UINT16, base = base.DEC
748 },
749 ["secnet.kx.dhval.text"] = {
750 name = "Sender's public Diffie--Hellman text", type = ftypes.STRING,
751 base = base.ASCII
752 },
753 ["secnet.kx.sig"] = {
754 name = "Sender's signature", type = ftypes.NONE
755 },
756 ["secnet.kx.sig.len"] = {
757 name = "Sender's signature length",
758 type = ftypes.UINT16, base = base.DEC
759 },
760 ["secnet.kx.sig.text"] = {
761 name = "Sender's signature text", type = ftypes.STRING,
762 base = base.ASCII
763 },
764 ["secnet.ciphertext"] = {
765 name = "Encrypted data", type = ftypes.NONE
766 },
767 ["secnet.ciphertext.unknown"] = {
768 name = "Ciphertext with unknown structure",
769 type = ftypes.BYTES, base = base.SPACE
770 },
771 ["secnet.ciphertext.iv"] = {
772 name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
773 },
774 ["secnet.ciphertext.sequence"] = {
775 name = "Sequence number", type = ftypes.UINT32, base = base.DEC
776 },
777 ["secnet.ciphertext.payload"] = {
778 name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE
779 },
780 ["secnet.ciphertext.tag"] = {
781 name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
782 },
783 ["secnet.wtf"] = {
784 name = "Unexpected trailing data",
785 type = ftypes.BYTES, base = base.SPACE
786 }
787 }
788
789 -- Add the remaining capability fields. Calculate the unassigned mask
790 -- based on the assigned bits.
791 local unasgn = 0x7fff7f00
792 for i, v in pairs(CAPTAB) do
793 local flag = bit.lshift(1, i)
794 ftab["secnet.cap." .. v.name] = {
795 name = v.desc, type = ftypes.BOOLEAN,
796 mask = flag, base = 32
797 }
798 unasgn = bit.band(unasgn, bit.bnot(flag))
799 end
800 ftab["secnet.cap.unassigned"] = {
801 name = "Unassigned capability bits",
802 type = ftypes.UINT32, mask = unasgn, base = base.HEX
803 }
804
805 -- Convert this table into the protocol fields, and populate `PF'.
806 local ff = { }
807 local i = 1
808
809 -- Figure out whether we can use `none' fields (see below).
810 local use_none_p = rawget(ProtoField, 'none') ~= nil
811 for abbr, args in pairs(ftab) do
812
813 -- An annoying hack. Older versions of Wireshark don't allow setting
814 -- fields with type `none', which is a shame because they're ideal as
815 -- internal tree nodes.
816 ty = args.type
817 b = args.base
818 if ty == ftypes.NONE then
819 if use_none_p then
820 b = base.NONE
821 else
822 ty = ftypes.BYTES
823 b = base.SPACE
824 end
825 end
826
827 -- Go make the field.
828 local f = ProtoField.new(args.name, abbr, ty,
829 args.tab, b, args.mask, args.descr)
830 PF[abbr] = f
831 ff[i] = f; i = i + 1
832 end
833 secnet.fields = PF
834
835 -- Make readable fields corresponding to especially interesting protocol
836 -- fields.
837 for abbr, args in pairs(ftab) do
838 if args.field then F[abbr] = Field.new(abbr) end
839 end
840end
841
842-----------------------------------------------------------------------------
843--- The main dissector.
844
845function secnet.dissector(buf, pinfo, tree)
846
847 -- Fill in the obvious stuff.
848 pinfo.cols.protocol = "Secnet"
849
850 local sz = buf:reported_length_remaining()
851 local sub = tree:add(secnet, buf(0, sz), "Secnet packet")
852 local p = 12
853
854 -- Decode the message header.
855 hdr = sub:add(PF["secnet.hdr"], buf(0, 12))
856 local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4))
857 local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4))
858 local label = buf(8, 4):uint()
859 hdr:add(PF["secnet.hdr.label"], buf(8, 4), label,
860 string.format("Message label (major = 0x%04x, minor = 0x%04x)",
861 msgmajor(label), msgminor(label)))
862 local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix }
863 local info = PKTINFO[label]
864
865 -- Dispatch using the master protocol table.
866 if info == nil then
867 pinfo.cols.info = string.format("Unknown message label 0x%08x", label)
868 else
869 pinfo.cols.info = info.info
870 p = dissect_sequence(info.dissect, st, buf, sub, p, sz)
871 end
872
873 -- Invoke the hook if necessary.
874 if not pinfo.visited and info.hook ~= nil then info.hook(st) end
875
876 -- Return the final position we reached.
877 return p
878end
879
880-- We're done. Register the dissector.
881DissectorTable.get("udp.port"):add(410, secnet)
882
883-------- That's all, folks --------------------------------------------------