-
Notifications
You must be signed in to change notification settings - Fork 2
/
dissector.lua
390 lines (330 loc) · 12 KB
/
dissector.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
-- EBM Wireshark Dissector
-- Copyright (C) 2023, coreMem Limited <info@coremem.com>
-- SPDX-License-Identifier: AGPL-3.0-only
-- The Ethernet Boot & Management (EBM) protocol smells like an
-- RPC for I2C. You ask for a register and it returns its value.
--
-- Interestingly that the registers present and their encodings
-- mostly implement VDSL2-LINE-MIB (RFC 5650).
--
-- Everything though is guess work, so errors are guarenteed!
--
-- Assumptions are codified with expert.group.ASSUMPTION
arg = { [0] = __DIR__ .. (__DIR__:len() > 0 and __DIR_SEPARATOR__ or "") .. "dissector.lua" }
local dir = arg[0]:match("^(.-/?)[^/]+.lua$")
local vs_dir = {
[1] = "Response",
[2] = "Request"
}
local vs_mode = {
[1] = "Read",
[2] = "Write"
}
local vs_register, vs_register_inv = assert(loadfile(dir .. "register.lua"))(arg)
local REQUEST_ID = {
HELLO_CLIENT = 0x6c360000,
HELLO_SERVER = 0x6c364556
}
local vs_status = {
[0] = "Success",
[255] = "No error"
}
local proto = Proto.new("EBM", "Ethernet Boot & Management Protocol")
proto.fields.hdr = ProtoField.none("ebm.hdr", "Header")
proto.fields.hdr_plen = ProtoField.uint16("ebm.hdr.payload_len", "Payload Length")
proto.fields.hdr_flags = ProtoField.uint8("ebm.hdr.flags", "Flags", base.HEX)
proto.fields.hdr_dir = ProtoField.bool("ebm.hdr.dir", "Direction", 8, vs_dir, 0x80)
proto.fields.hdr_mode = ProtoField.uint8("ebm.hdr.mode", "Mode", base.DEC, vs_mode, 0x03)
proto.fields.hdr_id = ProtoField.uint32("ebm.hdr.id", "Request Id")
proto.fields.hdr_status = ProtoField.uint8("ebm.hdr.status", "Status", nil, vs_status)
proto.fields.payload = ProtoField.none("ebm.payload", "Payload")
proto.fields.padding = ProtoField.none("ebm.padding", "Padding")
proto.fields.cmd = ProtoField.none("ebm.cmd", "Command")
proto.fields.cmd_type = ProtoField.uint8("ebm.cmd.type", "Type")
--
proto.fields.cmd_read_reg = ProtoField.uint24("ebm.cmd.reg", "Register Address", base.HEX, vs_register)
proto.fields.cmd_read_len = ProtoField.uint16("ebm.cmd.reglen", "Register Length")
proto.fields.cmd_read_val = ProtoField.uint24("ebm.cmd.regval", "Register Value", base.HEX)
--
proto.fields.data = ProtoField.bytes("ebm.data", "Data")
--
proto.fields.frame_request = ProtoField.framenum("ebm.request", "Request In", nil, frametype.REQUEST)
proto.fields.frame_response = ProtoField.framenum("ebm.response", "Response In", nil, frametype.RESPONSE)
proto.experts.id = ProtoExpert.new("ebm.id.magic", "Request Id Magic", expert.group.SEQUENCE, expert.severity.NOTE)
proto.experts.assert = ProtoExpert.new("ebm.assert", "Protocol", expert.group.ASSUMPTION, expert.severity.WARN)
proto.experts.orphan = ProtoExpert.new("ebm.orphan", "Orphan", expert.group.PROTOCOL, expert.severity.NOTE)
local f_plen = Field.new("ebm.hdr.payload_len")
local f_dir = Field.new("ebm.hdr.dir")
local f_mode = Field.new("ebm.hdr.mode")
local f_id = Field.new("ebm.hdr.id")
local f_status = Field.new("ebm.hdr.status")
-- conversation tracking for populating
-- frametype and reconciling reads with data
local convlist, convlist_pre
function proto.init ()
convlist = {}
convlist_pre = {}
end
function proto.dissector (tvb, pinfo, tree)
local len = tvb:len()
pinfo.cols.info = proto.description
pinfo.cols.protocol = proto.name
local ebm_tree = tree:add(proto, tvb())
-- 0 1 2 3
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Payload Length | Flags | Request Id :
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- : Request Id | Status |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Payload...
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Padding...
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
-- Payload Length (plen)
--
-- Request - Length of payload field
--
-- Response - Length of payload incremented by six (6)
-- (header length excluding 'plen' field)
--
-- Request Id
--
-- Value used reconcile responses back to their originating requests
--
-- DSLmonitor behaviour:
--
-- * simple incrementing counter starting at 1 (not zero) is used
--
-- Two magic numbers observed when using DSLmonitor:
--
-- 0x6c360000 - Client Hello Handshake (request)
--
-- 0x6c364556 - Server Hello Handshake (response)
--
-- Flags
--
-- 0 1 2 3 4 5 6 7
-- +-+-+-+-+-+-+-+-+
-- |D ? ? ? ? ? M M|
-- +-+-+-+-+-+-+-+-+
--
-- D (Direction)
--
-- 0 - Request
--
-- Note: if Request Id is a magic number D = 0 always
--
-- 1 - Response
--
-- M (Mode)
--
-- 1 - Read
--
-- 2 - Write
--
-- Status
--
-- 0 - Success
--
-- 255 - Not applicable
local hdr_tvb = tvb(0, 8)
local hdr_tree = ebm_tree:add(proto.fields.hdr, hdr_tvb())
local payload_len_tree = hdr_tree:add(proto.fields.hdr_plen, hdr_tvb(0, 2))
local hdr_flags_tvb = hdr_tvb(2, 1)
local hdr_flags = hdr_tree:add(proto.fields.hdr_flags, hdr_flags_tvb())
if bit.band(hdr_flags_tvb():uint(), 0x7c) ~= 0 then
hdr_flags:add_proto_expert_info(proto.experts.assert, "Flag bits 1-5 not all unset")
end
hdr_flags:add(proto.fields.hdr_dir, hdr_flags_tvb())
hdr_flags:add(proto.fields.hdr_mode, hdr_flags_tvb())
local hdr_id_tree = hdr_tree:add(proto.fields.hdr_id, hdr_tvb(3, 4))
local id = f_id()()
if id == REQUEST_ID.HELLO_CLIENT or id == REQUEST_ID.HELLO_SERVER then
hdr_id_tree:add_proto_expert_info(proto.experts.id):append_text(" (" .. ((id == REQUEST_ID.HELLO_CLIENT) and "Client" or "Server") .. " Hello)")
end
local response = (id == REQUEST_ID.HELLO_CLIENT or id == REQUEST_ID.HELLO_SERVER) and (id == REQUEST_ID.HELLO_SERVER) or f_dir()()
pinfo.cols.info:append(response and ": Response" or ": Request")
if pinfo.visited then
-- guard incase we catch mid-conversation and see only the response
if convlist[pinfo.number].partner then
hdr_tree:add(proto.fields["frame_" .. (response and "request" or "response")], convlist[pinfo.number].partner):set_generated()
end
else
-- convlist_pre is used to temporarily hold the request frame number
-- for the response to discover. It is keyed by 'dev' (server MAC
-- concatenated with client MAC) and then by the reference id.
-- When the response is processed, it looks here for the request's
-- frame number and then sets up convlist. When handshakes are
-- detected, convlist_pre[dev] is flushed.
local server = response and pinfo.src or pinfo.dst
local client = response and pinfo.dst or pinfo.src
local dev = tostring(server) .. " " .. tostring(client)
convlist[pinfo.number] = {}
if not convlist_pre[dev] then
convlist_pre[dev] = {}
end
if not response then
if id == REQUEST_ID.HELLO_CLIENT then
convlist_pre[dev] = {}
id = -1
end
convlist_pre[dev][id] = pinfo.number
else
if id == REQUEST_ID.HELLO_SERVER then
id = -1
end
-- guard incase we never see the request
if convlist_pre[dev] and convlist_pre[dev][id] then
convlist[pinfo.number].partner = convlist_pre[dev][id]
convlist[convlist_pre[dev][id]].partner = pinfo.number
table.remove(convlist_pre[dev], id)
end
-- again, guard incase we never see the request
if id == -1 then
convlist_pre[dev] = {}
end
end
end
hdr_tree:add(proto.fields.hdr_status, hdr_tvb(7, 1))
local hdr_status = f_status()()
if not ((response and hdr_status == 0x00) or (not response and hdr_status == 0xff)) then
hdr_tree:add_proto_expert_info(proto.experts.assert, "Status has unexpected value")
end
local payload_len = f_plen()()
if response then payload_len = payload_len - 6 end
local payload_tvb, payload_tree
if payload_len > 0 then
payload_tvb = tvb(8, payload_len)
payload_tree = ebm_tree:add(proto.fields.payload, payload_tvb())
if response then payload_len_tree:append_text(" (inc hdr ex plen [= 6 bytes])") end
end
if tvb:len() > 8 + payload_len then
local padding_tvb = tvb(8 + payload_len)
local padding_tree = ebm_tree:add(proto.fields.padding, padding_tvb())
for i=1, padding_tvb:len() - 1 do
if padding_tvb(i, 1):uint() ~= 0 then
padding_tree:add_proto_expert_info(proto.experts.assert, "Padding has non-zero bytes")
break
end
end
end
if payload_len == 0 then
return len
end
local pi, pi_tvb
local offset = 0
local records
if response then
-- 0 1 2 3
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Data...
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
--
-- Data
--
-- Length comes from request
--
-- Note: only observed three (3) octets
--
local cmds
if convlist[pinfo.number].partner then
records = 0
cmds = convlist[convlist[pinfo.number].partner].cmds
else
payload_tree:add_proto_expert_info(proto.experts.orphan)
cmds = {}
end
for i, cmd in pairs(cmds) do
records = records + 1
if offset >= payload_len then
payload_tree:add_proto_expert_info(proto.experts.assert, "Truncated Response")
break
end
pi_tvb = payload_tvb(offset, math.min(cmd[3], payload_len - offset))
pi = payload_tree:add(proto.fields.data, pi_tvb())
offset = offset + pi_tvb:len()
if pinfo.visited then
local gpi = pi:add(proto.fields.cmd):set_generated()
gpi:add(proto.fields.cmd_type, cmd[1])
gpi:add(proto.fields.cmd_read_reg, cmd[2])
end
if pi_tvb:len() < cmd[3] then
pi:add_proto_expert_info(proto.experts.assert, "Truncated Data")
break
end
end
else
-- 0 1 2 3
-- 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Type | Register |
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Register Length | Value :
-- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-- | Value |
-- +-+-+-+-+-+-+-+-+
--
-- Type
--
-- 1 - ???
--
-- 2 - ???
--
-- Value
--
-- The Value field is three octets, and is present only when M in
-- in the main header is set to two (2) indicating write.
if not pinfo.visited then
convlist[pinfo.number].cmds = {}
end
records = 0
while offset < payload_len do
records = records + 1
if offset >= payload_len then
payload_tree:add_proto_expert_info(proto.experts.assert, "Truncated Request")
break
end
local cmd_type = payload_tvb(offset, 1):uint()
if cmd_type == 1 then
local mode = f_mode()()
pi_tvb = payload_tvb(offset, mode == 1 and 6 or 9)
pi = payload_tree:add(proto.fields.cmd, pi_tvb())
offset = offset + pi_tvb:len()
pi:add(proto.fields.cmd_type, pi_tvb(0, 1))
pi:add(proto.fields.cmd_read_reg, pi_tvb(1, 3))
pi:add(proto.fields.cmd_read_len, pi_tvb(4, 2))
if pi_tvb(4, 2):uint() ~= 3 then
pi:add_proto_expert_info(proto.experts.assert, "Register Length expected to be 3")
end
if mode == 2 then
pi:add(proto.fields.cmd_read_val, pi_tvb(6, 3))
end
if not pinfo.visited then
table.insert(convlist[pinfo.number].cmds, { cmd_type, pi_tvb(1, 3):uint(), pi_tvb(4, 2):uint() })
end
else
pi_tvb = payload_tvb(offset)
pi = payload_tree:add(proto.fields.cmd, pi_tvb())
offset = offset + pi_tvb:len()
pi:add(proto.fields.cmd_type, pi_tvb(0, 1))
pi:add_proto_expert_info(proto.experts.assert, "Command Type expected to be 1")
end
end
end
if records then
payload_tree:append_text(" [" .. tostring(records) .. " record(s)]")
end
return len
end
set_plugin_info({
version = "0.1",
author = "Alexander Clouter",
email = "alex@digriz.org.uk",
copyright = "Copyright (c) 2023, coreMem Limited.",
license = "AGPLv3 license",
repository = "https://github.com/jimdigriz/mt5311"
})
DissectorTable.get("ethertype"):add(0x6120, proto)