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