chiark / gitweb /
5166918e05e58d5192ace01522120de43e973b9f
[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   return dissect_lenstr(st, buf, tree, "secnet.kx.dhval", pos, sz)
480 end
481
482 local function dissect_sig(st, buf, tree, pos, sz)
483   -- Dissect a signature.
484
485   return dissect_lenstr(st, buf, tree, "secnet.kx.sig", pos, sz)
486 end
487
488 local function find_algs_lookup(map, sock, now, ix)
489   -- Utility for `find_algs': look SOCK up in the address map ADDR, to find a
490   -- site; find its peer with index IX; and return the algorithm selection
491   -- current between the pair at time NOW.  If the lookup fails, return nil.
492
493   local name = lookup_timeline(map, sock, now)
494   if name == nil then return nil end
495   local site = SITEMAP[name]
496   if site == nil then return nil end
497   local peername = lookup_timeline(site.index, ix, now)
498   if peername == nil then return nil end
499   return lookup_timeline(site.algs, peername, now)
500 end
501
502 local function find_algs(st)
503   -- Return the algorithm selection which applies to the packet described in
504   -- ST.
505
506   local now = st.pinfo.rel_ts
507   local sock = snd_sockname(st)
508   local algs = find_algs_lookup(ADDRMAP, sock, now, st.sndix)
509   if algs ~= nil then return algs
510   else return  find_algs_lookup(GUESSMAP, sock, now, st.rcvix)
511   end
512 end
513
514 -- Transform-specific dissectors...
515 local dissect_ct = { }
516 function dissect_ct.unknown(st, why, buf, tree, pos, sz)
517   tree:add(PF["secnet.ciphertext.unknown"], buf(pos, sz - pos),
518            "Ciphertext with unknown structure: " .. why)
519   return sz
520 end
521 function dissect_ct.serpent256cbc(st, buf, tree, pos, sz)
522   tree:add(PF["secnet.ciphertext.iv"], buf(pos, 4)); pos = pos + 4
523   tree:add(PF["secnet.ciphertext.payload"], buf(pos, sz - pos))
524   return sz
525 end
526 function dissect_ct.eaxserpent(st, buf, tree, pos, sz)
527   local len = sz - pos - 20
528   tree:add(PF["secnet.ciphertext.payload"], buf(pos, len)); pos = pos + len
529   tree:add(PF["secnet.ciphertext.tag"], buf(pos, 16)); pos = pos + 16
530   tree:add(PF["secnet.ciphertext.sequence"], buf(pos, 4)); pos = pos + 4
531   return pos
532 end
533
534 local function dissect_ciphertext(st, buf, tree, pos, sz)
535   -- Dissect a ciphertext.
536
537   local sub = tree:add(PF["secnet.ciphertext"], buf(pos, sz - pos))
538   local algs = find_algs(st)
539   local xform
540   if algs == nil then xform = nil else xform = algs.transform end
541   if xform == nil then
542     pos = dissect_ct.unknown(st, "unable to find negotiated transform",
543                              buf, sub, pos, sz)
544   else
545     local func = dissect_ct[xform]
546     if func == nil then
547       pos = dissect_ct.unknown(st, "unsupported transform " .. xform,
548                                buf, sub, pos, sz)
549     else
550       pos = func(st, buf, sub, pos, sz)
551     end
552   end
553   return pos
554 end
555
556 -----------------------------------------------------------------------------
557 --- The protocol information table.
558
559 local PKTINFO = {
560   -- This is the main table which describes the protocol.  The top level maps
561   -- message labels to structures:
562   --
563   --   * `label' is the category code's symbolic name;
564   --
565   --   * `info' is a prefix for the information column display; and
566   --
567   --   * `dissect' is a sequence of primitive dissectors to run in order to
568   --     parse the rest of the packet.
569
570   [M.NAK] = {
571     label = "NAK",
572     info = "Stimulate fresh key exchange",
573     dissect = { dissect_wtf }
574   },
575   [M.MSG0] = {
576     label = "MSG0",
577     info = "MSG0",
578     dissect = { dissect_ciphertext }
579   },
580   [M.MSG1] = {
581     label = "MSG1",
582     info = "MSG1",
583     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
584                                         { dissect_caps, dissect_wtf },
585                                         notice_sndname),
586                 make_dissect_name_xinfo("secnet.kx.rcvname",
587                                         { dissect_wtf },
588                                         notice_rcvname),
589                 dissect_sndnonce,
590                 dissect_wtf }
591   },
592   [M.MSG2] = {
593     label = "MSG2",
594     info = "MSG2",
595     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
596                                         { dissect_caps, 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_wtf }
603   },
604   [M.MSG3] = {
605     label = "MSG3",
606     info = "MSG3",
607     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
608                                         { dissect_caps,
609                                           dissect_mtu,
610                                           dissect_wtf },
611                                         notice_sndname),
612                 make_dissect_name_xinfo("secnet.kx.rcvname",
613                                         { dissect_wtf },
614                                         notice_rcvname),
615                 dissect_sndnonce, dissect_rcvnonce,
616                 dissect_wtf },
617     hook = notice_alg_selection
618   },
619   [M.MSG3BIS] = {
620     label = "MSG3BIS",
621     info = "MSG3BIS",
622     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
623                                         { dissect_caps,
624                                           dissect_mtu,
625                                           dissect_wtf },
626                                         notice_sndname),
627                 make_dissect_name_xinfo("secnet.kx.rcvname",
628                                         { dissect_wtf },
629                                         notice_rcvname),
630                 dissect_sndnonce, dissect_rcvnonce,
631                 dissect_transform,
632                 dissect_dhval, dissect_sig,
633                 dissect_wtf },
634     hook = notice_alg_selection
635   },
636   [M.MSG3TER] = {
637     label = "MSG3TER",
638     info = "MSG3TER",
639     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
640                                         { dissect_caps,
641                                           dissect_mtu,
642                                           dissect_wtf },
643                                         notice_sndname),
644                 make_dissect_name_xinfo("secnet.kx.rcvname",
645                                         { dissect_wtf },
646                                         notice_rcvname),
647                 dissect_sndnonce, dissect_rcvnonce,
648                 dissect_transform, dissect_dhgroup,
649                 dissect_dhval, dissect_sig,
650                 dissect_wtf },
651     hook = notice_alg_selection
652   },
653   [M.MSG4] = {
654     label = "MSG4",
655     info = "MSG4",
656     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
657                                         { dissect_caps,
658                                           dissect_mtu,
659                                           dissect_wtf },
660                                         notice_sndname),
661                 make_dissect_name_xinfo("secnet.kx.rcvname",
662                                         { dissect_wtf },
663                                         notice_rcvname),
664                 dissect_sndnonce, dissect_rcvnonce,
665                 dissect_dhval, dissect_sig,
666                 dissect_wtf }
667   },
668   [M.MSG5] = {
669     label = "MSG5",
670     info = "MSG5",
671     dissect = { dissect_ciphertext }
672   },
673   [M.MSG6] = {
674     label = "MSG6",
675     info = "MSG6",
676     dissect = { dissect_ciphertext }
677   },
678   [M.PROD] = {
679     label = "PROD",
680     info = "PROD",
681     dissect = { make_dissect_name_xinfo("secnet.kx.sndname",
682                                         { dissect_caps,
683                                           dissect_wtf },
684                                         notice_sndname),
685                 make_dissect_name_xinfo("secnet.kx.rcvname",
686                                         { dissect_wtf },
687                                         notice_rcvname),
688                 dissect_wtf }
689   },
690 }
691
692 do
693   -- Work through the master table and build the `msgtab'' table, mapping
694   -- message codes to their symbolic names for presentation.
695   local msgtab = { }
696   for i, v in pairs(PKTINFO) do msgtab[i] = v.label end
697
698   local capmap = { transform = { }, dhgroup = { }, early = { } }
699   for i, v in pairs(CAPTAB) do capmap[v.kind][i] = v.desc end
700
701   local ftab = {
702     -- The protocol fields.  This table maps the field names to structures
703     -- used to build the fields, which are then stored in `PF' (declared way
704     -- above):
705     --
706     --   * `name' is the field name to show in the dissector tree view;
707     --
708     --   * `type' is the field type;
709     --
710     --   * `base' is a tweak describing how the field should be formatted;
711     --
712     --   * `mask' is used to single out a piece of a larger bitfield;
713     --
714     --   * `tab' names a mapping table used to convert numerical values to
715     --     symbolic names; and
716     --
717     --   * `hook' is a hook function to run the first time we see a packet,
718     --     to keep track of things.
719
720     ["secnet.hdr"] = {
721       name = "Common message header", type = ftypes.NONE
722     },
723     ["secnet.hdr.rcvix"] = {
724       name = "Recipient's site index for sender",
725       type = ftypes.UINT32, base = base.DEC
726     },
727     ["secnet.hdr.sndix"] = {
728       name = "Sender's site index for recipient",
729       type = ftypes.UINT32, base = base.DEC
730     },
731     ["secnet.hdr.label"] = {
732       name = "Message label", type = ftypes.UINT32,
733       base = base.HEX, tab = msgtab
734     },
735     ["secnet.kx.sndname"] = {
736       name = "Sender's site name and extended information",
737       type = ftypes.NONE
738     },
739     ["secnet.kx.rcvname"] = {
740       name = "Recipient's site name and extended information",
741       type = ftypes.NONE
742     },
743     ["secnet.namex.len"] = {
744       name = "Name/extended info length",
745       type = ftypes.UINT16, base = base.DEC
746     },
747     ["secnet.namex.name"] = {
748       name = "Site name", type = ftypes.STRING,
749       field = true, base = base.ASCII,
750     },
751     ["secnet.cap"] = {
752       name = "Advertised capability bits",
753       type = ftypes.UINT32, base = base.HEX
754     },
755     ["secnet.cap.user"] = {
756       name = "User-assigned capability bits",
757       type = ftypes.UINT32, mask = 0x000000ff, base = base.HEX
758     },
759     ["secnet.cap.explicit"] = {
760       name = "Transforms listed explicitly; all capability bits used",
761       type = ftypes.BOOLEAN, mask = 0x00008000, base = 32
762     },
763     ["secnet.mtu"] = {
764       name = "Sender's requested MTU", type = ftypes.UINT16, base = base.DEC
765     },
766     ["secnet.kx.sndnonce"] = {
767       name = "Sender's nonce", type = ftypes.BYTES, base = base.SPACE
768     },
769     ["secnet.kx.rcvnonce"] = {
770       name = "Recipient's nonce", type = ftypes.BYTES, base = base.SPACE
771     },
772     ["secnet.kx.transform"] = {
773       name = "Selected bulk-crypto transform", type = ftypes.UINT8,
774       base = base.DEC, tab = capmap.transform
775     },
776     ["secnet.kx.dhgroup"] = {
777       name = "Selected Diffie--Hellman group kind", type = ftypes.UINT8,
778       base = base.DEC, tab = capmap.dhgroup
779     },
780     ["secnet.kx.dhval"] = {
781       name = "Sender's public Diffie--Hellman value", type = ftypes.NONE
782     },
783     ["secnet.kx.dhval.len"] = {
784       name = "Sender's public Diffie--Hellman length",
785       type = ftypes.UINT16, base = base.DEC
786     },
787     ["secnet.kx.dhval.text"] = {
788       name = "Sender's public Diffie--Hellman text", type = ftypes.STRING,
789       base = base.ASCII
790     },
791     ["secnet.kx.sig"] = {
792       name = "Sender's signature", type = ftypes.NONE
793     },
794     ["secnet.kx.sig.len"] = {
795       name = "Sender's signature length",
796       type = ftypes.UINT16, base = base.DEC
797     },
798     ["secnet.kx.sig.text"] = {
799       name = "Sender's signature text", type = ftypes.STRING,
800       base = base.ASCII
801     },
802     ["secnet.ciphertext"] = {
803       name = "Encrypted data", type = ftypes.NONE
804     },
805     ["secnet.ciphertext.unknown"] = {
806       name = "Ciphertext with unknown structure",
807       type = ftypes.BYTES, base = base.SPACE
808     },
809     ["secnet.ciphertext.iv"] = {
810       name = "Initialization vector", type = ftypes.BYTES, base = base.SPACE
811     },
812     ["secnet.ciphertext.sequence"] = {
813       name = "Sequence number", type = ftypes.UINT32, base = base.DEC
814     },
815     ["secnet.ciphertext.payload"] = {
816       name = "Encrypted payload", type = ftypes.BYTES, base = base.SPACE
817     },
818     ["secnet.ciphertext.tag"] = {
819       name = "Authentication tag", type = ftypes.BYTES, base = base.SPACE
820     },
821     ["secnet.wtf"] = {
822       name = "Unexpected trailing data",
823       type = ftypes.BYTES, base = base.SPACE
824     }
825   }
826
827   -- Add the remaining capability fields.  Calculate the unassigned mask
828   -- based on the assigned bits.
829   local unasgn = 0x7fff7f00
830   for i, v in pairs(CAPTAB) do
831     local flag = bit.lshift(1, i)
832     ftab["secnet.cap." .. v.name] = {
833       name = v.desc, type = ftypes.BOOLEAN,
834       mask = flag, base = 32
835     }
836     unasgn = bit.band(unasgn, bit.bnot(flag))
837   end
838   ftab["secnet.cap.unassigned"] = {
839     name = "Unassigned capability bits",
840     type = ftypes.UINT32, mask = unasgn, base = base.HEX
841   }
842
843   -- Convert this table into the protocol fields, and populate `PF'.
844   local ff = { }
845   local i = 1
846
847   -- Figure out whether we can use `none' fields (see below).
848   local use_none_p = rawget(ProtoField, 'none') ~= nil
849   for abbr, args in pairs(ftab) do
850
851     -- An annoying hack.  Older versions of Wireshark don't allow setting
852     -- fields with type `none', which is a shame because they're ideal as
853     -- internal tree nodes.
854     ty = args.type
855     b = args.base
856     if ty == ftypes.NONE then
857       if use_none_p then
858         b = base.NONE
859       else
860         ty = ftypes.BYTES
861         b = base.SPACE
862       end
863     end
864
865     -- Go make the field.
866     local f = ProtoField.new(args.name, abbr, ty,
867                              args.tab, b, args.mask, args.descr)
868     PF[abbr] = f
869     ff[i] = f; i = i + 1
870   end
871   secnet.fields = PF
872
873   -- Make readable fields corresponding to especially interesting protocol
874   -- fields.
875   for abbr, args in pairs(ftab) do
876     if args.field then F[abbr] = Field.new(abbr) end
877   end
878 end
879
880 -----------------------------------------------------------------------------
881 --- The main dissector.
882
883 function secnet.dissector(buf, pinfo, tree)
884
885   -- Fill in the obvious stuff.
886   pinfo.cols.protocol = "Secnet"
887
888   local sz = buf:reported_length_remaining()
889   local sub = tree:add(secnet, buf(0, sz), "Secnet packet")
890   local p = 12
891
892   -- Decode the message header.
893   hdr = sub:add(PF["secnet.hdr"], buf(0, 12))
894   local rcvix = buf(0, 4):uint(); hdr:add(PF["secnet.hdr.rcvix"], buf(0, 4))
895   local sndix = buf(4, 4):uint(); hdr:add(PF["secnet.hdr.sndix"], buf(4, 4))
896   local label = buf(8, 4):uint()
897   hdr:add(PF["secnet.hdr.label"], buf(8, 4), label,
898           string.format("Message label (major = 0x%04x, minor = 0x%04x)",
899                         msgmajor(label), msgminor(label)))
900   local st = { pinfo = pinfo, label = label, rcvix = rcvix, sndix = sndix  }
901   local info = PKTINFO[label]
902
903   -- Dispatch using the master protocol table.
904   if info == nil then
905     pinfo.cols.info = string.format("Unknown message label 0x%08x", label)
906   else
907     pinfo.cols.info = info.info
908     p = dissect_sequence(info.dissect, st, buf, sub, p, sz)
909   end
910
911   -- Invoke the hook if necessary.
912   if not pinfo.visited and info.hook ~= nil then info.hook(st) end
913
914   -- Return the final position we reached.
915   return p
916 end
917
918 -- We're done.  Register the dissector.
919 DissectorTable.get("udp.port"):add(410, secnet)
920
921 -------- That's all, folks --------------------------------------------------