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