chiark / gitweb /
2a6761d12c7876279efb1fc69e0915e633462fd1
[secnet] / secnet-wireshark.lua
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
20 local 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
29 local function tl_new()
30   -- Return a fresh shiny timeline.
31
32   return { }
33 end
34
35 local 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
53 end
54
55 local 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
61 end
62
63 local 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
78 end
79
80 local 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
85 end
86
87 local 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
94 end
95
96 local 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
104 end
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.
118 local 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.
124 local ADDRMAP = { }
125 local GUESSMAP = { }
126
127 local 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)
133 end
134
135 local 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)
141 end
142
143 local 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
153 end
154
155 local 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)
160 end
161
162 local function dump_algs(algs)
163   -- Dump the algorithms selection ALGS from a site structure.
164
165   return "xform=" .. algs.transform .. "; dh=" .. algs.dhgroup
166 end
167
168 local function dump_str(str) return str end
169
170 local 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
179 end
180
181 local 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
200 end
201
202 local 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)
207 end
208
209 local 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
219 end
220
221 -- Tables describing the kinds of algorithms which can be selected.
222 local 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   [10] = { name = "tradzp", kind = "dhgroup",
228            desc = "Traditional Z_p Diffie--Hellman key agreement" },
229   [31] = { name = "mobile-priority", kind = "early",
230            desc = "Mobile site takes priority in case of MSG1 crossing" }
231 }
232
233 local function get_algname(kind, cap, dflt)
234   -- Fetch an algorithm of the given KIND, given its capability number CAP;
235   -- if CAP is nil, then return DFLT instead.
236
237   local name
238   if cap == nil then
239     name = dflt
240   else
241     local info = CAPTAB[cap]
242     if info ~= nil and info.kind == kind then name = info.name
243     else name = string.format("Unknown %s #%d", kind, cap)
244     end
245   end
246   return name
247 end
248
249 local function notice_alg_selection(st)
250   -- Record the algorithm selections declared in the packet described by ST.
251
252   local transform = get_algname("transform", st.transform, "serpent256cbc")
253   local dhgroup = get_algname("dhgroup", st.dhgroup, "tradzp")
254   local site = get_site_create(st.sndname)
255   local peer = get_site_create(st.rcvname)
256   local now = st.pinfo.rel_ts
257   local algs = { transform = transform, dhgroup = dhgroup }
258   tl_add(get_timeline_create(site.algs, st.rcvname), now, algs)
259   tl_add(get_timeline_create(peer.algs, st.sndname), now, algs)
260 end
261
262 -----------------------------------------------------------------------------
263 --- Protocol dissection primitives.
264
265 local PF = { } -- The table of protocol fields, filled in later.
266 local F = { } -- A table of field values, also filled in later.
267
268 local function msgcode(major, minor)
269   -- Construct a Secnet message number according to the complicated rules.
270
271   local majlo = bit.band(major, 0x000f)
272   local majhi = bit.band(major, 0xfff0)
273   local minlo = bit.band(minor, 0x000f)
274   local minhi = bit.band(minor, 0xfff0)
275   return bit.bxor(bit.lshift(majlo,  0),
276                   bit.lshift(majlo,  8),
277                   bit.lshift(majlo, 16),
278                   bit.lshift(majlo, 24),
279                   bit.lshift(majhi,  4),
280                   bit.lshift(minlo,  4),
281                   bit.lshift(minlo, 28),
282                   bit.lshift(minhi, 16))
283 end
284
285 local function msgmajor(label)
286   -- Return the major message number from a LABEL.
287
288   local lo = bit.band(label, 0x000f)
289   local hi = bit.band(bit.rshift(label, 4), 0xfff0)
290   return bit.bxor(lo, bit.lshift(lo, 4), bit.lshift(lo, 12), hi)
291 end
292
293 local function msgminor(label)
294   -- Return the minor message number from a LABEL.
295
296   return bit.bxor(bit.lshift(bit.band(label, 0x00ff), 8),
297                   bit.band(bit.rshift(label, 4), 0x000f),
298                   bit.band(bit.rshift(label, 16), 0xfff0))
299 end
300
301 -- Main message-number table.
302 local M = { NAK         = msgcode(     0, 0),
303             MSG0        = msgcode(0x2020, 0), -- !
304             MSG1        = msgcode(     1, 0),
305             MSG2        = msgcode(     2, 0),
306             MSG3        = msgcode(     3, 0),
307             MSG3BIS     = msgcode(     3, 1),
308             MSG3TER     = msgcode(     3, 2),
309             MSG4        = msgcode(     4, 0),
310             MSG5        = msgcode(     5, 0),
311             MSG6        = msgcode(     6, 0),
312             MSG7        = msgcode(     7, 0),
313             MSG8        = msgcode(     8, 0),
314             MSG9        = msgcode(     9, 0),
315             PROD        = msgcode(    10, 0)}
316
317 -- The `dissect_*' functions follow a common protocol.  They parse a thing
318 -- from a packet buffer BUF, of size SZ, starting from POS, and store
319 -- interesting things in a given TREE; when they're done, they return the
320 -- updated index where the next interesting thing might be, and maybe store
321 -- interesting things in the state ST.  As a result, it's usually a simple
322 -- matter to parse a packet by invoking the appropriate primitive dissectors
323 -- in the right order.
324
325 local function dissect_sequence(dissect, st, buf, tree, pos, sz)
326   -- Dissect pieces of the packed in BUF with each of the dissectors in the
327   -- list DISSECT in turn.
328
329   for _, d in ipairs(dissect) do pos = d(st, buf, tree, pos, sz) end
330   return pos
331 end
332
333 local function dissect_wtf(st, buf, tree, pos, sz)
334   -- If POS is not at the end of the buffer, note that there's unexpected
335   -- stuff in the packet.
336
337   if pos < sz then tree:add(PF["secnet.wtf"], buf(pos, sz - pos)) end
338   return sz
339 end
340
341 local dissect_caps
342 do
343   -- This will be a list of the capability protocol field names, in the right
344   -- order.  We just have to figure out what that will be.
345   local caplist = { }
346
347   do
348     local caps = { }
349
350     -- Firstly, build, in `caps', a list of the capability names and their
351     -- numbers.
352     local i = 1
353     caps[i] = { i = 15, cap = "explicit" }; i = 1 + 1
354     for j, cap in pairs(CAPTAB) do
355       caps[i] = { i = j, cap = cap.name }
356       i = i + 1
357     end
358
359     -- Sort the list.  Now they're in the right order.
360     table.sort(caps, function (v0, v1) return v0.i < v1.i end)
361
362     -- Finally, write the entries to `caplist', with the `user' entry at the
363     -- start and the `unassigned' entry at the end.
364     i = 1
365     caplist[i] = "secnet.cap.user"; i = i + 1
366     for _, v in ipairs(caps) do
367       caplist[i] = "secnet.cap." .. v.cap
368       i = i + 1
369     end
370     caplist[i] = "secnet.cap.unassigned"; i = i + 1
371   end
372
373   function dissect_caps(st, buf, tree, pos, sz)
374     -- Dissect a capabilities word.
375
376     if pos < sz then
377       local cap = tree:add(PF["secnet.cap"], buf(pos, 4))
378       for _, pf in ipairs(caplist) do cap:add(PF[pf], buf(pos, 4)) end
379       pos = pos + 4
380     end
381     return pos
382   end
383 end
384
385 local function dissect_mtu(st, buf, tree, pos, sz)
386   -- Dissect an MTU request.
387
388   if pos < sz then tree:add(PF["secnet.mtu"], buf(pos, 2)); pos = pos + 2 end
389   return pos
390 end
391
392 local function make_dissect_name_xinfo(label, dissect_xinfo, hook)
393   -- Return a dissector function for reading a name and extra information.
394   -- The function will dissect a subtree rooted at the protocol field LABEL;
395   -- it will dissect the extra information using the list DISSECT_XINFO
396   -- (processed using `dissect_sequence'); and finally, if the packet hasn't
397   -- been visited yet, it will call HOOK(ST, NAME), where NAME is the name
398   -- string extracted from the packet.
399
400   return function (st, buf, tree, pos, sz)
401
402     -- Find the length of the whole thing.
403     local len = buf(pos, 2):uint()
404
405     -- Make the subtree root.
406     local sub = tree:add(PF[label], buf(pos, len + 2))
407
408     -- Find the length of the name.  This is rather irritating: I'd like to
409     -- get Wireshark to do this, but it seems that `stringz' doesn't pay
410     -- attention to the buffer limits it's given.  So read the whole lot and
411     -- find the null by hand.
412     local name = buf(pos + 2, len):string()
413     local z, _ = string.find(name, "\0", 1, true)
414     if z == nil then
415       z = len
416     else
417       z = z - 1
418       name = string.sub(name, 1, z)
419     end
420
421     -- Fill in the subtree.
422     sub:add(PF["secnet.namex.len"], buf(pos, 2)); pos = pos + 2
423     sub:add(PF["secnet.namex.name"], buf(pos, z))
424     if z < len then
425       dissect_sequence(dissect_xinfo, st, buf, sub, pos + z + 1, pos + len)
426     end
427
428     -- Maybe call the hook.
429     if hook ~= nil and not st.pinfo.visited then hook(st, name) end
430
431     -- We're done.
432     return pos + len
433   end
434 end
435
436 local function dissect_sndnonce(st, buf, tree, pos, sz)
437   -- Dissect the sender's nonce.
438
439   tree:add(PF["secnet.kx.sndnonce"], buf(pos, 8)); pos = pos + 8
440   return pos
441 end
442
443 local function dissect_rcvnonce(st, buf, tree, pos, sz)
444   -- Dissect the recipient's nonce.
445
446   tree:add(PF["secnet.kx.rcvnonce"], buf(pos, 8)); pos = pos + 8
447   return pos
448 end
449
450 local function dissect_transform(st, buf, tree, pos, sz)
451   -- Dissect the selected transform.  Note this in the packet state for
452   -- later.
453
454   st.transform = buf(pos, 1):uint()
455   tree:add(PF["secnet.kx.transform"], buf(pos, 1)); pos = pos + 1
456   return pos
457 end
458
459 local function dissect_dhgroup(st, buf, tree, pos, sz)
460   -- Dissect the selected DH group.  Note this in the packet state for later.
461
462   st.dhgroup = buf(pos, 1):uint()
463   tree:add(PF["secnet.kx.dhgroup"], buf(pos, 1)); pos = pos + 1
464   return pos
465 end
466
467 local function dissect_lenstr(st, buf, tree, label, pos, sz)
468   -- Dissect a simple string given its length.
469   local len = buf(pos, 2):uint()
470   local sub = tree:add(PF[label], buf(pos, len + 2))
471   sub:add(PF[label .. ".len"], buf(pos, 2)); pos = pos + 2
472   sub:add(PF[label .. ".text"], buf(pos, len)); pos = pos + len
473   return pos
474 end
475
476 local function dissect_dhval(st, buf, tree, pos, sz)
477   -- Dissect a Diffie--Hellman public value.
478
479   local len = buf(pos, 2):uint()
480   local sub = tree:add(PF["secnet.kx.dhval"], buf(pos, len + 2))
481   sub:add(PF["secnet.kx.dhval.len"], buf(pos, 2)); pos = pos + 2
482   sub:add(PF["secnet.kx.dhval.bytes"], buf(pos, len)); pos = pos + len
483   return pos
484 end
485
486 local function dissect_sig(st, buf, tree, pos, sz)
487   -- Dissect a signature.
488
489   return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz)
490 end
491
492 local function find_algs_lookup(map, sock, now, ix)
493   -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a
494   -- site; find its peer with index IX; and return the algorithm selection
495   -- current between the pair at time NOW.  If the lookup fails, return nil.
496
497   local name = lookup_timeline(map, sock, now)
498   if name == nil then return nil end
499   local site = SITEMAP[name]
500   if site == nil then return nil end
501   local peername = lookup_timeline(site.index, ix, now)
502   if peername == nil then return nil end
503   return lookup_timeline(site.algs, peername, now)
504 end
505
506 local function find_algs(st)
507   -- Return the algorithm selection which applies to the packet described in
508   -- ST.
509
510   local now = st.pinfo.rel_ts
511   local sock = snd_sockname(st)
512   local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix)
513   if algs ~= nil then return algs
514   else return  find_algs_lookup(GUESSMAP, sock, now, st.rcvix)
515   end
516 end
517
518 -- Transform-specific dissectors...
519 local dissect_ct = { }
520 function dissect_ct.unknown(st, why, buf, tree, pos, sz)
521   tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos),
522            "Ciphertext with unknown structure: " .. why)
523   return sz
524 end
525 function dissect_ct.serpent256cbc(st, buf, tree, pos, sz)
526   tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4
527   tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos))
528   return sz
529 end
530 function dissect_ct.eaxserpent(st, buf, tree, pos, sz)
531   local len = sz - pos - 20
532   tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len
533   tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
534   tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4
535   return pos
536 end
537
538 local function dissect_ciphertext(st, buf, tree, pos, sz)
539   -- Dissect a ciphertext.
540
541   local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos))
542   local algs = find_algs(st)
543   local xform
544   if algs == nil then xform = nil else xform = algs.transform end
545   if xform == nil then
546     pos = dissect_ct.unknown(st, "unable to find negotiated transform",
547                              buf, sub, pos, sz)
548   else
549     local func = dissect_ct[xform]
550     if func == nil then
551       pos = dissect_ct.unknown(st, "unsupported transform " .. xform,
552                                buf, sub, pos, sz)
553     else
554       pos = func(st, buf, sub, pos, sz)
555     end
556   end
557   return pos
558 end
559
560 -----------------------------------------------------------------------------
561 --- The protocol information table.
562
563 local PKTINFO = {
564   -- This is the main table which describes the protocol.  The top level maps
565   -- message labels to structures:
566   --
567   --   * `label' is the category code's symbolic name;
568   --
569   --   * `info' is a prefix for the information column display; and
570   --
571   --   * `dissect' is a sequence of primitive dissectors to run in order to
572   --     parse the rest of the packet.
573
574   [M.NAK] = {
575     label = "NAK",
576     info = "Stimulate fresh key exchange",
577     dissect = { dissect_wtf }
578   },
579   [M.MSG0] = {
580     label = "MSG0",
581     info = "MSG0",
582     dissect = { dissect_ciphertext }
583   },
584   [M.MSG1] = {
585     label = "MSG1",
586     info = "MSG1",
587     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
588                                         { dissect_caps, dissect_wtf },
589                                         notice_sndname),
590                 make_dissect_name_xinfo("secnet.kx.rcvname",
591                                         { dissect_wtf },
592                                         notice_rcvname),
593                 dissect_sndnonce,
594                 dissect_wtf }
595   },
596   [M.MSG2] = {
597     label = "MSG2",
598     info = "MSG2",
599     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
600                                         { dissect_caps, dissect_wtf },
601                                         notice_sndname),
602                 make_dissect_name_xinfo("secnet.kx.rcvname",
603                                         { dissect_wtf },
604                                         notice_rcvname),
605                 dissect_sndnonce, dissect_rcvnonce,
606                 dissect_wtf }
607   },
608   [M.MSG3] = {
609     label = "MSG3",
610     info = "MSG3",
611     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
612                                         { dissect_caps,
613                                           dissect_mtu,
614                                           dissect_wtf },
615                                         notice_sndname),
616                 make_dissect_name_xinfo("secnet.kx.rcvname",
617                                         { dissect_wtf },
618                                         notice_rcvname),
619                 dissect_sndnonce, dissect_rcvnonce,
620                 dissect_wtf },
621     hook = notice_alg_selection
622   },
623   [M.MSG3BIS] = {
624     label = "MSG3BIS",
625     info = "MSG3BIS",
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_transform,
636                 dissect_dhval, dissect_sig,
637                 dissect_wtf },
638     hook = notice_alg_selection
639   },
640   [M.MSG3TER] = {
641     label = "MSG3TER",
642     info = "MSG3TER",
643     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
644                                         { dissect_caps,
645                                           dissect_mtu,
646                                           dissect_wtf },
647                                         notice_sndname),
648                 make_dissect_name_xinfo("secnet.kx.rcvname",
649                                         { dissect_wtf },
650                                         notice_rcvname),
651                 dissect_sndnonce, dissect_rcvnonce,
652                 dissect_transform, dissect_dhgroup,
653                 dissect_dhval, dissect_sig,
654                 dissect_wtf },
655     hook = notice_alg_selection
656   },
657   [M.MSG4] = {
658     label = "MSG4",
659     info = "MSG4",
660     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
661                                         { dissect_caps,
662                                           dissect_mtu,
663                                           dissect_wtf },
664                                         notice_sndname),
665                 make_dissect_name_xinfo("secnet.kx.rcvname",
666                                         { dissect_wtf },
667                                         notice_rcvname),
668                 dissect_sndnonce, dissect_rcvnonce,
669                 dissect_dhval, dissect_sig,
670                 dissect_wtf }
671   },
672   [M.MSG5] = {
673     label = "MSG5",
674     info = "MSG5",
675     dissect = { dissect_ciphertext }
676   },
677   [M.MSG6] = {
678     label = "MSG6",
679     info = "MSG6",
680     dissect = { dissect_ciphertext }
681   },
682   [M.PROD] = {
683     label = "PROD",
684     info = "PROD",
685     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
686                                         { dissect_caps,
687                                           dissect_wtf },
688                                         notice_sndname),
689                 make_dissect_name_xinfo("secnet.kx.rcvname",
690                                         { dissect_wtf },
691                                         notice_rcvname),
692                 dissect_wtf }
693   },
694 }
695
696 do
697   -- Work through the master table and build the `msgtab'' table, mapping
698   -- message codes to their symbolic names for presentation.
699   local msgtab = { }
700   for i, v in pairs(PKTINFO) do msgtab[i] = v.label end
701
702   local capmap = { transform = { }, dhgroup = { }, early = { } }
703   for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end
704
705   local ftab = {
706     -- The protocol fields.  This table maps the field names to structures
707     -- used to build the fields, which are then stored in `PF' (declared way
708     -- above):
709     --
710     --   * `name' is the field name to show in the dissector tree view;
711     --
712     --   * `type' is the field type;
713     --
714     --   * `base' is a tweak describing how the field should be formatted;
715     --
716     --   * `mask' is used to single out a piece of a larger bitfield;
717     --
718     --   * `tab' names a mapping table used to convert numerical values to
719     --     symbolic names; and
720     --
721     --   * `hook' is a hook function to run the first time we see a packet,
722     --     to keep track of things.
723
724     ["secnet.hdr"] = {
725       name = "Common message header", type = ftypes.NONE
726     },
727     ["secnet.hdr.rcvix"] = {
728       name = "Recipient's site index for sender",
729       type = ftypes.UINT32, base = base.DEC
730     },
731     ["secnet.hdr.sndix"] = {
732       name = "Sender's site index for recipient",
733       type = ftypes.UINT32, base = base.DEC
734     },
735     ["secnet.hdr.label"] = {
736       name = "Message label", type = ftypes.UINT32,
737       base = base.HEX, tab = msgtab
738     },
739     ["secnet.kx.sndname"] = {
740       name = "Sender's site name and extended information",
741       type = ftypes.NONE
742     },
743     ["secnet.kx.rcvname"] = {
744       name = "Recipient's site name and extended information",
745       type = ftypes.NONE
746     },
747     ["secnet.namex.len"] = {
748       name = "Name/extended info length",
749       type = ftypes.UINT16, base = base.DEC
750     },
751     ["secnet.namex.name"] = {
752       name = "Site name", type = ftypes.STRING,
753       field = true, base = base.ASCII,
754     },
755     ["secnet.cap"] = {
756       name = "Advertised capability bits",
757       type = ftypes.UINT32, base = base.HEX
758     },
759     ["secnet.cap.user"] = {
760       name = "User-assigned capability bits",
761       type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX
762     },
763     ["secnet.cap.explicit"] = {
764       name = "Transforms listed explicitly; all capability bits used",
765       type = ftypes.BOOLEAN, mask = 0x00008000, base = 32
766     },
767     ["secnet.mtu"] = {
768       name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC
769     },
770     ["secnet.kx.sndnonce"] = {
771       name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE
772     },
773     ["secnet.kx.rcvnonce"] = {
774       name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE
775     },
776     ["secnet.kx.transform"] = {
777       name = "Selected bulk-crypto transform", type = ftypes.UINT8,
778       base = base.DEC, tab = capmap.transform
779     },
780     ["secnet.kx.dhgroup"] = {
781       name = "Selected Diffie--Hellman group kind", type = ftypes.UINT8,
782       base = base.DEC, tab = capmap.dhgroup
783     },
784     ["secnet.kx.dhval"] = {
785       name = "Sender's public Diffie--Hellman value", type = ftypes.NONE
786     },
787     ["secnet.kx.dhval.len"] = {
788       name = "Sender's public Diffie--Hellman length",
789       type = ftypes.UINT16, base = base.DEC
790     },
791     ["secnet.kx.dhval.bytes"] = {
792       name = "Sender's public Diffie--Hellman value bytes",
793       type = ftypes.BYTES, base = base.SPACE
794     },
795     ["secnet.kx.sig"] = {
796       name = "Sender's signature", type = ftypes.NONE
797     },
798     ["secnet.kx.sig.len"] = {
799       name = "Sender's signature length",
800       type = ftypes.UINT16, base = base.DEC
801     },
802     ["secnet.kx.sig.text"] = {
803       name = "Sender's signature text", type = ftypes.STRING,
804       base = base.ASCII
805     },
806     ["secnet.ciphertext"] = {
807       name = "Encrypted data", type = ftypes.NONE
808     },
809     ["secnet.ciphertext.unknown"] = {
810       name = "Ciphertext with unknown structure",
811       type = ftypes.BYTES, base = base.SPACE
812     },
813     ["secnet.ciphertext.iv"] = {
814       name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
815     },
816     ["secnet.ciphertext.sequence"] = {
817       name = "Sequence number", type = ftypes.UINT32, base = base.DEC
818     },
819     ["secnet.ciphertext.payload"] = {
820       name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE
821     },
822     ["secnet.ciphertext.tag"] = {
823       name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
824     },
825     ["secnet.wtf"] = {
826       name = "Unexpected trailing data",
827       type = ftypes.BYTES, base = base.SPACE
828     }
829   }
830
831   -- Add the remaining capability fields.  Calculate the unassigned mask
832   -- based on the assigned bits.
833   local unasgn = 0x7fff7f00
834   for i, v in pairs(CAPTAB) do
835     local flag = bit.lshift(1, i)
836     ftab["secnet.cap." .. v.name] = {
837       name = v.desc, type = ftypes.BOOLEAN,
838       mask = flag, base = 32
839     }
840     unasgn = bit.band(unasgn, bit.bnot(flag))
841   end
842   ftab["secnet.cap.unassigned"] = {
843     name = "Unassigned capability bits",
844     type = ftypes.UINT32, mask = unasgn, base = base.HEX
845   }
846
847   -- Convert this table into the protocol fields, and populate `PF'.
848   local ff = { }
849   local i = 1
850
851   -- Figure out whether we can use `none' fields (see below).
852   local use_none_p = rawget(ProtoField, 'none') ~= nil
853   for abbr, args in pairs(ftab) do
854
855     -- An annoying hack.  Older versions of Wireshark don't allow setting
856     -- fields with type `none', which is a shame because they're ideal as
857     -- internal tree nodes.
858     ty = args.type
859     b = args.base
860     if ty == ftypes.NONE then
861       if use_none_p then
862         b = base.NONE
863       else
864         ty = ftypes.BYTES
865         b = base.SPACE
866       end
867     end
868
869     -- Go make the field.
870     local f = ProtoField.new(args.name, abbr, ty,
871                              args.tab, b, args.mask, args.descr)
872     PF[abbr] = f
873     ff[i] = f; i = i + 1
874   end
875   secnet.fields = PF
876
877   -- Make readable fields corresponding to especially interesting protocol
878   -- fields.
879   for abbr, args in pairs(ftab) do
880     if args.field then F[abbr] = Field.new(abbr) end
881   end
882 end
883
884 -----------------------------------------------------------------------------
885 --- The main dissector.
886
887 function secnet.dissector(buf, pinfo, tree)
888
889   -- Fill in the obvious stuff.
890   pinfo.cols.protocol = "Secnet"
891
892   local sz = buf:reported_length_remaining()
893   local sub = tree:add(secnet, buf(0, sz), "Secnet packet")
894   local p = 12
895
896   -- Decode the message header.
897   hdr = sub:add(PF["secnet.hdr"], buf(0, 12))
898   local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4))
899   local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4))
900   local label = buf(8, 4):uint()
901   hdr:add(PF["secnet.hdr.label"], buf(8, 4), label,
902           string.format("Message label (major = 0x%04x, minor = 0x%04x)",
903                         msgmajor(label), msgminor(label)))
904   local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix  }
905   local info = PKTINFO[label]
906
907   -- Dispatch using the master protocol table.
908   if info == nil then
909     pinfo.cols.info = string.format("Unknown message label 0x%08x", label)
910   else
911     pinfo.cols.info = info.info
912     p = dissect_sequence(info.dissect, st, buf, sub, p, sz)
913   end
914
915   -- Invoke the hook if necessary.
916   if not pinfo.visited and info.hook ~= nil then info.hook(st) end
917
918   -- Return the final position we reached.
919   return p
920 end
921
922 -- We're done.  Register the dissector.
923 DissectorTable.get("udp.port"):add(410, secnet)
924
925 -------- That's all, folks --------------------------------------------------