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