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