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