secnet-wireshark.lua: Add a Wireshark dissector.
[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
265-- Main message-number table.
266local M = { NAK = 0x00000000
267 MSG0 = 0x00020200
268 MSG1 = 0x01010101
269 MSG2 = 0x02020202
270 MSG3 = 0x03030303
271 MSG3BIS = 0x13030313
272 MSG4 = 0x04040404
273 MSG5 = 0x05050505
274 MSG6 = 0x06060606
275 MSG7 = 0x07070707
276 MSG8 = 0x08080808
277 MSG9 = 0x09090909
278 PROD = 0x0a0a0a0a }
279
280-- The `dissect_*' functions follow a common protocol. They parse a thing
281-- from a packet buffer BUF, of size SZ, starting from POS, and store
282-- interesting things in a given TREE; when they're done, they return the
283-- updated index where the next interesting thing might be, and maybe store
284-- interesting things in the state ST. As a result, it's usually a simple
285-- matter to parse a packet by invoking the appropriate primitive dissectors
286-- in the right order.
287
288local function dissect_sequence(dissect, st, buf, tree, pos, sz)
289 -- Dissect pieces of the packed in BUF with each of the dissectors in the
290 -- list DISSECT in turn.
291
292 for _, d in ipairs(dissect) do pos = d(st, buf, tree, pos, sz) end
293 return pos
294end
295
296local function dissect_wtf(st, buf, tree, pos, sz)
297 -- If POS is not at the end of the buffer, note that there's unexpected
298 -- stuff in the packet.
299
300 if pos < sz then tree:add(PF["secnet.wtf"], buf(pos, sz - pos)) end
301 return sz
302end
303
304local dissect_caps
305do
306 -- This will be a list of the capability protocol field names, in the right
307 -- order. We just have to figure out what that will be.
308 local caplist = { }
309
310 do
311 local caps = { }
312
313 -- Firstly, build, in `caps', a list of the capability names and their
314 -- numbers.
315 local i = 1
316 for j, cap in pairs(CAPTAB) do
317 caps[i] = { i = j, cap = cap.name }
318 i = i + 1
319 end
320
321 -- Sort the list. Now they're in the right order.
322 table.sort(caps, function (v0, v1) return v0.i < v1.i end)
323
324 -- Finally, write the entries to `caplist', with the `user' entry at the
325 -- start and the `unassigned' entry at the end.
326 i = 1
327 caplist[i] = "secnet.cap.user"; i = i + 1
328 for _, v in ipairs(caps) do
329 caplist[i] = "secnet.cap." .. v.cap
330 i = i + 1
331 end
332 caplist[i] = "secnet.cap.unassigned"; i = i + 1
333 end
334
335 function dissect_caps(st, buf, tree, pos, sz)
336 -- Dissect a capabilities word.
337
338 if pos < sz then
339 local cap = tree:add(PF["secnet.cap"], buf(pos, 4))
340 for _, pf in ipairs(caplist) do cap:add(PF[pf], buf(pos, 4)) end
341 pos = pos + 4
342 end
343 return pos
344 end
345end
346
347local function dissect_mtu(st, buf, tree, pos, sz)
348 -- Dissect an MTU request.
349
350 if pos < sz then tree:add(PF["secnet.mtu"], buf(pos, 2)); pos = pos + 2 end
351 return pos
352end
353
354local function make_dissect_name_xinfo(label, dissect_xinfo, hook)
355 -- Return a dissector function for reading a name and extra information.
356 -- The function will dissect a subtree rooted at the protocol field LABEL;
357 -- it will dissect the extra information using the list DISSECT_XINFO
358 -- (processed using `dissect_sequence'); and finally, if the packet hasn't
359 -- been visited yet, it will call HOOK(ST, NAME), where NAME is the name
360 -- string extracted from the packet.
361
362 return function (st, buf, tree, pos, sz)
363
364 -- Find the length of the whole thing.
365 local len = buf(pos, 2):uint()
366
367 -- Make the subtree root.
368 local sub = tree:add(PF[label], buf(pos, len + 2))
369
370 -- Find the length of the name. This is rather irritating: I'd like to
371 -- get Wireshark to do this, but it seems that `stringz' doesn't pay
372 -- attention to the buffer limits it's given. So read the whole lot and
373 -- find the null by hand.
374 local name = buf(pos + 2, len):string()
375 local z, _ = string.find(name, "\0", 1, true)
376 if z == nil then
377 z = len
378 else
379 z = z - 1
380 name = string.sub(name, 1, z)
381 end
382
383 -- Fill in the subtree.
384 sub:add(PF["secnet.namex.len"], buf(pos, 2)); pos = pos + 2
385 sub:add(PF["secnet.namex.name"], buf(pos, z))
386 if z < len then
387 dissect_sequence(dissect_xinfo, st, buf, sub, pos + z + 1, pos + len)
388 end
389
390 -- Maybe call the hook.
391 if hook ~= nil and not st.pinfo.visited then hook(st, name) end
392
393 -- We're done.
394 return pos + len
395 end
396end
397
398local function dissect_sndnonce(st, buf, tree, pos, sz)
399 -- Dissect the sender's nonce.
400
401 tree:add(PF["secnet.kx.sndnonce"], buf(pos, 8)); pos = pos + 8
402 return pos
403end
404
405local function dissect_rcvnonce(st, buf, tree, pos, sz)
406 -- Dissect the recipient's nonce.
407
408 tree:add(PF["secnet.kx.rcvnonce"], buf(pos, 8)); pos = pos + 8
409 return pos
410end
411
412local function dissect_transform(st, buf, tree, pos, sz)
413 -- Dissect the selected transform. Note this in the packet state for
414 -- later.
415
416 st.transform = buf(pos, 1):uint()
417 tree:add(PF["secnet.kx.transform"], buf(pos, 1)); pos = pos + 1
418 return pos
419end
420
421local function dissect_lenstr(st, buf, tree, label, pos, sz)
422 -- Dissect a simple string given its length.
423 local len = buf(pos, 2):uint()
424 local sub = tree:add(PF[label], buf(pos, len + 2))
425 sub:add(PF[label .. ".len"], buf(pos, 2)); pos = pos + 2
426 sub:add(PF[label .. ".text"], buf(pos, len)); pos = pos + len
427 return pos
428end
429
430local function dissect_dhval(st, buf, tree, pos, sz)
431 -- Dissect a Diffie--Hellman public value.
432
433 return dissect_lenstr(st, buf, tree, "secnet.kx.dhval", pos, sz)
434end
435
436local function dissect_sig(st, buf, tree, pos, sz)
437 -- Dissect a signature.
438
439 return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz)
440end
441
442local function find_algs_lookup(map, sock, now, ix)
443 -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a
444 -- site; find its peer with index IX; and return the algorithm selection
445 -- current between the pair at time NOW. If the lookup fails, return nil.
446
447 local name = lookup_timeline(map, sock, now)
448 if name == nil then return nil end
449 local site = SITEMAP[name]
450 if site == nil then return nil end
451 local peername = lookup_timeline(site.index, ix, now)
452 if peername == nil then return nil end
453 return lookup_timeline(site.algs, peername, now)
454end
455
456local function find_algs(st)
457 -- Return the algorithm selection which applies to the packet described in
458 -- ST.
459
460 local now = st.pinfo.rel_ts
461 local sock = snd_sockname(st)
462 local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix)
463 if algs ~= nil then return algs
464 else return find_algs_lookup(GUESSMAP, sock, now, st.rcvix)
465 end
466end
467
468-- Transform-specific dissectors...
469local dissect_ct = { }
470function dissect_ct.unknown(st, why, buf, tree, pos, sz)
471 tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos),
472 "Ciphertext with unknown structure: " .. why)
473 return sz
474end
475function dissect_ct.serpent256cbc(st, buf, tree, pos, sz)
476 tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4
477 tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos))
478 return sz
479end
480function dissect_ct.eaxserpent(st, buf, tree, pos, sz)
481 local len = sz - pos - 20
482 tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len
483 tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
484 tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4
485 return pos
486end
487
488local function dissect_ciphertext(st, buf, tree, pos, sz)
489 -- Dissect a ciphertext.
490
491 local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos))
492 local algs = find_algs(st)
493 local xform
494 if algs == nil then xform = nil else xform = algs.transform end
495 if xform == nil then
496 pos = dissect_ct.unknown(st, "unable to find negotiated transform",
497 buf, sub, pos, sz)
498 else
499 local func = dissect_ct[xform]
500 if func == nil then
501 pos = dissect_ct.unknown(st, "unsupported transform " .. xform,
502 buf, sub, pos, sz)
503 else
504 pos = func(st, buf, sub, pos, sz)
505 end
506 end
507 return pos
508end
509
510-----------------------------------------------------------------------------
511--- The protocol information table.
512
513local PKTINFO = {
514 -- This is the main table which describes the protocol. The top level maps
515 -- message labels to structures:
516 --
517 -- * `label' is the category code's symbolic name;
518 --
519 -- * `info' is a prefix for the information column display; and
520 --
521 -- * `dissect' is a sequence of primitive dissectors to run in order to
522 -- parse the rest of the packet.
523
524 [M.NAK] = {
525 label = "NAK",
526 info = "Stimulate fresh key exchange",
527 dissect = { dissect_wtf }
528 },
529 [M.MSG0] = {
530 label = "MSG0",
531 info = "MSG0",
532 dissect = { dissect_ciphertext }
533 },
534 [M.MSG1] = {
535 label = "MSG1",
536 info = "MSG1",
537 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
538 { dissect_caps, dissect_wtf },
539 notice_sndname),
540 make_dissect_name_xinfo("secnet.kx.rcvname",
541 { dissect_wtf },
542 notice_rcvname),
543 dissect_sndnonce,
544 dissect_wtf }
545 },
546 [M.MSG2] = {
547 label = "MSG2",
548 info = "MSG2",
549 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
550 { dissect_caps, dissect_wtf },
551 notice_sndname),
552 make_dissect_name_xinfo("secnet.kx.rcvname",
553 { dissect_wtf },
554 notice_rcvname),
555 dissect_sndnonce, dissect_rcvnonce,
556 dissect_wtf }
557 },
558 [M.MSG3] = {
559 label = "MSG3",
560 info = "MSG3",
561 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
562 { dissect_caps,
563 dissect_mtu,
564 dissect_wtf },
565 notice_sndname),
566 make_dissect_name_xinfo("secnet.kx.rcvname",
567 { dissect_wtf },
568 notice_rcvname),
569 dissect_sndnonce, dissect_rcvnonce,
570 dissect_wtf },
571 hook = notice_alg_selection
572 },
573 [M.MSG3BIS] = {
574 label = "MSG3BIS",
575 info = "MSG3BIS",
576 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
577 { dissect_caps,
578 dissect_mtu,
579 dissect_wtf },
580 notice_sndname),
581 make_dissect_name_xinfo("secnet.kx.rcvname",
582 { dissect_wtf },
583 notice_rcvname),
584 dissect_sndnonce, dissect_rcvnonce,
585 dissect_transform,
586 dissect_dhval, dissect_sig,
587 dissect_wtf },
588 hook = notice_alg_selection
589 },
590 [M.MSG4] = {
591 label = "MSG4",
592 info = "MSG4",
593 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
594 { dissect_caps,
595 dissect_mtu,
596 dissect_wtf },
597 notice_sndname),
598 make_dissect_name_xinfo("secnet.kx.rcvname",
599 { dissect_wtf },
600 notice_rcvname),
601 dissect_sndnonce, dissect_rcvnonce,
602 dissect_dhval, dissect_sig,
603 dissect_wtf }
604 },
605 [M.MSG5] = {
606 label = "MSG5",
607 info = "MSG5",
608 dissect = { dissect_ciphertext }
609 },
610 [M.MSG6] = {
611 label = "MSG6",
612 info = "MSG6",
613 dissect = { dissect_ciphertext }
614 },
615 [M.PROD] = {
616 label = "PROD",
617 info = "PROD",
618 dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
619 { dissect_caps,
620 dissect_wtf },
621 notice_sndname),
622 make_dissect_name_xinfo("secnet.kx.rcvname",
623 { dissect_wtf },
624 notice_rcvname),
625 dissect_wtf }
626 },
627}
628
629do
630 -- Work through the master table and build the `msgtab'' table, mapping
631 -- message codes to their symbolic names for presentation.
632 local msgtab = { }
633 for i, v in pairs(PKTINFO) do msgtab[i] = v.label end
634
635 local capmap = { transform = { }, early = { } }
636 for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end
637
638 local ftab = {
639 -- The protocol fields. This table maps the field names to structures
640 -- used to build the fields, which are then stored in `PF' (declared way
641 -- above):
642 --
643 -- * `name' is the field name to show in the dissector tree view;
644 --
645 -- * `type' is the field type;
646 --
647 -- * `base' is a tweak describing how the field should be formatted;
648 --
649 -- * `mask' is used to single out a piece of a larger bitfield;
650 --
651 -- * `tab' names a mapping table used to convert numerical values to
652 -- symbolic names; and
653 --
654 -- * `hook' is a hook function to run the first time we see a packet,
655 -- to keep track of things.
656
657 ["secnet.hdr"] = {
658 name = "Common message header", type = ftypes.NONE
659 },
660 ["secnet.hdr.rcvix"] = {
661 name = "Recipient's site index for sender",
662 type = ftypes.UINT32, base = base.DEC
663 },
664 ["secnet.hdr.sndix"] = {
665 name = "Sender's site index for recipient",
666 type = ftypes.UINT32, base = base.DEC
667 },
668 ["secnet.hdr.label"] = {
669 name = "Message label", type = ftypes.UINT32,
670 base = base.HEX, tab = msgtab
671 },
672 ["secnet.kx.sndname"] = {
673 name = "Sender's site name and extended information",
674 type = ftypes.NONE
675 },
676 ["secnet.kx.rcvname"] = {
677 name = "Recipient's site name and extended information",
678 type = ftypes.NONE
679 },
680 ["secnet.namex.len"] = {
681 name = "Name/extended info length",
682 type = ftypes.UINT16, base = base.DEC
683 },
684 ["secnet.namex.name"] = {
685 name = "Site name", type = ftypes.STRING,
686 field = true, base = base.ASCII,
687 },
688 ["secnet.cap"] = {
689 name = "Advertised capability bits",
690 type = ftypes.UINT32, base = base.HEX
691 },
692 ["secnet.cap.user"] = {
693 name = "User-assigned capability bits",
694 type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX
695 },
696 ["secnet.mtu"] = {
697 name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC
698 },
699 ["secnet.kx.sndnonce"] = {
700 name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE
701 },
702 ["secnet.kx.rcvnonce"] = {
703 name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE
704 },
705 ["secnet.kx.transform"] = {
706 name = "Selected bulk-crypto transform", type = ftypes.UINT8,
707 base = base.DEC, tab = capmap.transform
708 },
709 ["secnet.kx.dhval"] = {
710 name = "Sender's public Diffie--Hellman value", type = ftypes.NONE
711 },
712 ["secnet.kx.dhval.len"] = {
713 name = "Sender's public Diffie--Hellman length",
714 type = ftypes.UINT16, base = base.DEC
715 },
716 ["secnet.kx.dhval.text"] = {
717 name = "Sender's public Diffie--Hellman text", type = ftypes.STRING,
718 base = base.ASCII
719 },
720 ["secnet.kx.sig"] = {
721 name = "Sender's signature", type = ftypes.NONE
722 },
723 ["secnet.kx.sig.len"] = {
724 name = "Sender's signature length",
725 type = ftypes.UINT16, base = base.DEC
726 },
727 ["secnet.kx.sig.text"] = {
728 name = "Sender's signature text", type = ftypes.STRING,
729 base = base.ASCII
730 },
731 ["secnet.ciphertext"] = {
732 name = "Encrypted data", type = ftypes.NONE
733 },
734 ["secnet.ciphertext.unknown"] = {
735 name = "Ciphertext with unknown structure",
736 type = ftypes.BYTES, base = base.SPACE
737 },
738 ["secnet.ciphertext.iv"] = {
739 name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
740 },
741 ["secnet.ciphertext.sequence"] = {
742 name = "Sequence number", type = ftypes.UINT32, base = base.DEC
743 },
744 ["secnet.ciphertext.payload"] = {
745 name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE
746 },
747 ["secnet.ciphertext.tag"] = {
748 name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
749 },
750 ["secnet.wtf"] = {
751 name = "Unexpected trailing data",
752 type = ftypes.BYTES, base = base.SPACE
753 }
754 }
755
756 -- Add the remaining capability fields. Calculate the unassigned mask
757 -- based on the assigned bits.
758 local unasgn = 0x7fff7f00
759 for i, v in pairs(CAPTAB) do
760 local flag = bit.lshift(1, i)
761 ftab["secnet.cap." .. v.name] = {
762 name = v.desc, type = ftypes.BOOLEAN,
763 mask = flag, base = 32
764 }
765 unasgn = bit.band(unasgn, bit.bnot(flag))
766 end
767 ftab["secnet.cap.unassigned"] = {
768 name = "Unassigned capability bits",
769 type = ftypes.UINT32, mask = unasgn, base = base.HEX
770 }
771
772 -- Convert this table into the protocol fields, and populate `PF'.
773 local ff = { }
774 local i = 1
775
776 -- Figure out whether we can use `none' fields (see below).
777 local use_none_p = rawget(ProtoField, 'none') ~= nil
778 for abbr, args in pairs(ftab) do
779
780 -- An annoying hack. Older versions of Wireshark don't allow setting
781 -- fields with type `none', which is a shame because they're ideal as
782 -- internal tree nodes.
783 ty = args.type
784 b = args.base
785 if ty == ftypes.NONE then
786 if use_none_p then
787 b = base.NONE
788 else
789 ty = ftypes.BYTES
790 b = base.SPACE
791 end
792 end
793
794 -- Go make the field.
795 local f = ProtoField.new(args.name, abbr, ty,
796 args.tab, b, args.mask, args.descr)
797 PF[abbr] = f
798 ff[i] = f; i = i + 1
799 end
800 secnet.fields = PF
801
802 -- Make readable fields corresponding to especially interesting protocol
803 -- fields.
804 for abbr, args in pairs(ftab) do
805 if args.field then F[abbr] = Field.new(abbr) end
806 end
807end
808
809-----------------------------------------------------------------------------
810--- The main dissector.
811
812function secnet.dissector(buf, pinfo, tree)
813
814 -- Fill in the obvious stuff.
815 pinfo.cols.protocol = "Secnet"
816
817 local sz = buf:reported_length_remaining()
818 local sub = tree:add(secnet, buf(0, sz), "Secnet packet")
819 local p = 12
820
821 -- Decode the message header.
822 hdr = sub:add(PF["secnet.hdr"], buf(0, 12))
823 local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4))
824 local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4))
825 local label = buf(8, 4):uint()
826 hdr:add(PF["secnet.hdr.label"], buf(8, 4), label,
827 string.format("Message label (major = 0x%04x, minor = 0x%04x)",
828 msgmajor(label), msgminor(label)))
829 local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix }
830 local info = PKTINFO[label]
831
832 -- Dispatch using the master protocol table.
833 if info == nil then
834 pinfo.cols.info = string.format("Unknown message label 0x%08x", label)
835 else
836 pinfo.cols.info = info.info
837 p = dissect_sequence(info.dissect, st, buf, sub, p, sz)
838 end
839
840 -- Invoke the hook if necessary.
841 if not pinfo.visited and info.hook ~= nil then info.hook(st) end
842
843 -- Return the final position we reached.
844 return p
845end
846
847-- We're done. Register the dissector.
848DissectorTable.get("udp.port"):add(410, secnet)
849
850-------- That's all, folks --------------------------------------------------