ec-field-test.c: Make the field-element type use internal format.
[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" },
7111e1f7
MW
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" },
010e0a70
MW
233 [31] = { name = "mobile-priority", kind = "early",
234 desc = "Mobile site takes priority in case of MSG1 crossing" }
235}
236
237local 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
251end
252
253local 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")
9c6af4ec 257 local dhgroup = get_algname("dhgroup", st.dhgroup, "tradzp")
010e0a70
MW
258 local site = get_site_create(st.sndname)
259 local peer = get_site_create(st.rcvname)
260 local now = st.pinfo.rel_ts
9c6af4ec 261 local algs = { transform = transform, dhgroup = dhgroup }
010e0a70
MW
262 tl_add(get_timeline_create(site.algs, st.rcvname), now, algs)
263 tl_add(get_timeline_create(peer.algs, st.sndname), now, algs)
264end
265
266-----------------------------------------------------------------------------
267--- Protocol dissection primitives.
268
269local PF = { } -- The table of protocol fields, filled in later.
270local F = { } -- A table of field values, also filled in later.
271
7b2ef224
MW
272local 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))
287end
288
289local 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)
295end
296
297local 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))
303end
304
010e0a70 305-- Main message-number table.
7b2ef224
MW
306local 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),
9c6af4ec 312 MSG3TER = msgcode( 3, 2),
7b2ef224
MW
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)}
010e0a70
MW
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
329local 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
335end
336
337local 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
343end
344
345local dissect_caps
346do
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
9c6af4ec 357 caps[i] = { i = 15, cap = "explicit" }; i = 1 + 1
010e0a70
MW
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
387end
388
389local 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
394end
395
396local 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
438end
439
440local 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
445end
446
447local 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
452end
453
454local 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
461end
462
9c6af4ec
MW
463local 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
469end
470
010e0a70
MW
471local 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
478end
479
480local function dissect_dhval(st, buf, tree, pos, sz)
481 -- Dissect a Diffie--Hellman public value.
482
a736afc8
MW
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
010e0a70
MW
488end
489
490local 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)
494end
495
496local 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)
508end
509
510local 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
520end
521
522-- Transform-specific dissectors...
523local dissect_ct = { }
524function 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
528end
529function 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
533end
534function 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
540end
541
542local 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
562end
563
564-----------------------------------------------------------------------------
565--- The protocol information table.
566
567local 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 },
9c6af4ec
MW
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 },
010e0a70
MW
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
700do
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
9c6af4ec 706 local capmap = { transform = { }, dhgroup = { }, early = { } }
010e0a70
MW
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 },
9c6af4ec
MW
767 ["secnet.cap.explicit"] = {
768 name = "Transforms listed explicitly; all capability bits used",
769 type = ftypes.BOOLEAN, mask = 0x00008000, base = 32
770 },
010e0a70
MW
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 },
9c6af4ec
MW
784 ["secnet.kx.dhgroup"] = {
785 name = "Selected Diffie--Hellman group kind", type = ftypes.UINT8,
786 base = base.DEC, tab = capmap.dhgroup
787 },
010e0a70
MW
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 },
a736afc8
MW
795 ["secnet.kx.dhval.bytes"] = {
796 name = "Sender's public Diffie--Hellman value bytes",
797 type = ftypes.BYTES, base = base.SPACE
010e0a70
MW
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
886end
887
888-----------------------------------------------------------------------------
889--- The main dissector.
890
891function 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
924end
925
926-- We're done. Register the dissector.
927DissectorTable.get("udp.port"):add(410, secnet)
928
929-------- That's all, folks --------------------------------------------------