/src/wireshark/epan/dissectors/packet-mctp.c
Line | Count | Source |
1 | | /* packet-mctp.c |
2 | | * Routines for Management Component Transport Protocol (MCTP) packet |
3 | | * disassembly |
4 | | * Copyright 2022, Jeremy Kerr <jk@codeconstruct.com.au> |
5 | | * |
6 | | * Wireshark - Network traffic analyzer |
7 | | * By Gerald Combs <gerald@wireshark.org> |
8 | | * Copyright 1998 Gerald Combs |
9 | | * |
10 | | * SPDX-License-Identifier: GPL-2.0-or-later |
11 | | */ |
12 | | |
13 | | /* |
14 | | * MCTP is a datagram-based protocol for intra-platform communication, |
15 | | * typically between a management controller and system devices. |
16 | | * |
17 | | * MCTP is defined by DMTF standard DSP0236: https://www.dmtf.org/dsp/DSP0236 |
18 | | */ |
19 | | |
20 | | #include <config.h> |
21 | | |
22 | | #include <epan/packet.h> |
23 | | #include <epan/reassemble.h> |
24 | | #include <epan/to_str.h> |
25 | | #include <epan/tfs.h> |
26 | | #include <wsutil/array.h> |
27 | | #include "packet-mctp.h" |
28 | | #include "packet-sll.h" |
29 | | |
30 | 0 | #define MCTP_MIN_LENGTH 5 /* 4-byte header, plus message type */ |
31 | | |
32 | | void proto_register_mctp(void); |
33 | | void proto_reg_handoff_mctp(void); |
34 | | |
35 | | static int proto_mctp; |
36 | | |
37 | | static int hf_mctp_ver; |
38 | | static int hf_mctp_dst; |
39 | | static int hf_mctp_src; |
40 | | static int hf_mctp_flags; |
41 | | static int hf_mctp_flags_som; |
42 | | static int hf_mctp_flags_eom; |
43 | | static int hf_mctp_seq; |
44 | | static int hf_mctp_tag; |
45 | | static int hf_mctp_tag_to; |
46 | | static int hf_mctp_tag_value; |
47 | | static int hf_mctp_msg_ic; |
48 | | static int hf_mctp_msg_type; |
49 | | |
50 | | static int ett_mctp; |
51 | | static int ett_mctp_fst; |
52 | | static int ett_mctp_flags; |
53 | | static int ett_mctp_tag; |
54 | | static int ett_mctp_type; |
55 | | |
56 | | static const true_false_string tfs_tag_to = { "Sender", "Receiver" }; |
57 | | |
58 | | static int hf_mctp_fragments; |
59 | | static int hf_mctp_fragment; |
60 | | static int hf_mctp_fragment_overlap; |
61 | | static int hf_mctp_fragment_overlap_conflicts; |
62 | | static int hf_mctp_fragment_multiple_tails; |
63 | | static int hf_mctp_fragment_too_long_fragment; |
64 | | static int hf_mctp_fragment_error; |
65 | | static int hf_mctp_fragment_count; |
66 | | static int hf_mctp_reassembled_in; |
67 | | static int hf_mctp_reassembled_length; |
68 | | static int hf_mctp_reassembled_data; |
69 | | |
70 | | static int ett_mctp_fragment; |
71 | | static int ett_mctp_fragments; |
72 | | |
73 | | static const fragment_items mctp_frag_items = { |
74 | | /* Fragment subtrees */ |
75 | | &ett_mctp_fragment, |
76 | | &ett_mctp_fragments, |
77 | | /* Fragment fields */ |
78 | | &hf_mctp_fragments, |
79 | | &hf_mctp_fragment, |
80 | | &hf_mctp_fragment_overlap, |
81 | | &hf_mctp_fragment_overlap_conflicts, |
82 | | &hf_mctp_fragment_multiple_tails, |
83 | | &hf_mctp_fragment_too_long_fragment, |
84 | | &hf_mctp_fragment_error, |
85 | | &hf_mctp_fragment_count, |
86 | | /* "Reassembled in" field */ |
87 | | &hf_mctp_reassembled_in, |
88 | | /* Reassembled length field */ |
89 | | &hf_mctp_reassembled_length, |
90 | | &hf_mctp_reassembled_data, |
91 | | /* Tag */ |
92 | | "Message fragments" |
93 | | }; |
94 | | |
95 | | static const value_string flag_vals[] = { |
96 | | { 0x00, "none" }, |
97 | | { 0x01, "EOM" }, |
98 | | { 0x02, "SOM" }, |
99 | | { 0x03, "SOM|EOM" }, |
100 | | { 0x00, NULL }, |
101 | | }; |
102 | | |
103 | | static const value_string type_vals[] = { |
104 | | { MCTP_TYPE_CONTROL, "MCTP Control Protocol" }, |
105 | | { MCTP_TYPE_PLDM, "PLDM" }, |
106 | | { MCTP_TYPE_NCSI, "NC-SI" }, |
107 | | { MCTP_TYPE_ETHERNET, "Ethernet" }, |
108 | | { MCTP_TYPE_NVME, "NVMe-MI" }, |
109 | | { 0, NULL }, |
110 | | }; |
111 | | |
112 | | static dissector_table_t mctp_dissector_table; |
113 | | static dissector_table_t mctp_encap_dissector_table; |
114 | | static reassembly_table mctp_reassembly_table; |
115 | | |
116 | | static int |
117 | | dissect_mctp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, |
118 | | void *data _U_) |
119 | 0 | { |
120 | 0 | proto_tree *mctp_tree, *fst_tree; |
121 | 0 | unsigned len, ver, type, seq, fst; |
122 | 0 | bool save_fragmented; |
123 | 0 | proto_item *ti, *tti; |
124 | 0 | tvbuff_t *next_tvb; |
125 | 0 | uint8_t tag; |
126 | |
|
127 | 0 | col_set_str(pinfo->cinfo, COL_PROTOCOL, "MCTP"); |
128 | 0 | col_clear(pinfo->cinfo, COL_INFO); |
129 | | |
130 | | /* Check that the packet is long enough for it to belong to us. */ |
131 | 0 | len = tvb_reported_length(tvb); |
132 | |
|
133 | 0 | if (len < MCTP_MIN_LENGTH) { |
134 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Bogus length %u, minimum %u", |
135 | 0 | len, MCTP_MIN_LENGTH); |
136 | 0 | return tvb_captured_length(tvb); |
137 | 0 | } |
138 | | |
139 | 0 | ver = tvb_get_bits8(tvb, 4, 4); |
140 | 0 | if (ver != 1) { |
141 | 0 | col_add_fstr(pinfo->cinfo, COL_INFO, "Invalid version %u", ver); |
142 | 0 | return tvb_captured_length(tvb); |
143 | 0 | } |
144 | | |
145 | | /* Top-level protocol item & tree */ |
146 | 0 | ti = proto_tree_add_item(tree, proto_mctp, tvb, 0, 4, ENC_NA); |
147 | 0 | mctp_tree = proto_item_add_subtree(ti, ett_mctp); |
148 | |
|
149 | 0 | set_address_tvb(&pinfo->dl_dst, AT_MCTP, 1, tvb, 1); |
150 | 0 | set_address_tvb(&pinfo->dl_src, AT_MCTP, 1, tvb, 2); |
151 | 0 | copy_address_shallow(&pinfo->dst, &pinfo->dl_dst); |
152 | 0 | copy_address_shallow(&pinfo->src, &pinfo->dl_src); |
153 | |
|
154 | 0 | proto_item_append_text(ti, " Dst: %s, Src %s", |
155 | 0 | address_to_str(pinfo->pool, &pinfo->dst), |
156 | 0 | address_to_str(pinfo->pool, &pinfo->src)); |
157 | | |
158 | | /* Standard header fields */ |
159 | 0 | proto_tree_add_item(mctp_tree, hf_mctp_ver, tvb, 0, 1, ENC_NA); |
160 | 0 | proto_tree_add_item(mctp_tree, hf_mctp_dst, tvb, 1, 1, ENC_NA); |
161 | 0 | proto_tree_add_item(mctp_tree, hf_mctp_src, tvb, 2, 1, ENC_NA); |
162 | |
|
163 | 0 | static int * const mctp_flags[] = { |
164 | 0 | &hf_mctp_flags_som, |
165 | 0 | &hf_mctp_flags_eom, |
166 | 0 | NULL |
167 | 0 | }; |
168 | |
|
169 | 0 | static int * const mctp_tag[] = { |
170 | 0 | &hf_mctp_tag_to, |
171 | 0 | &hf_mctp_tag_value, |
172 | 0 | NULL, |
173 | 0 | }; |
174 | |
|
175 | 0 | fst = tvb_get_uint8(tvb, 3); |
176 | 0 | tag = fst & 0x0f; |
177 | 0 | fst_tree = proto_tree_add_subtree_format(mctp_tree, tvb, 3, 1, ett_mctp_fst, |
178 | 0 | &tti, "Flags %s, seq %d, tag %s%d", |
179 | 0 | val_to_str_const(fst >> 6, flag_vals, ""), |
180 | 0 | fst >> 4 & 0x3, |
181 | 0 | fst & 0x08 ? "TO:" : "", |
182 | 0 | fst & 0x7); |
183 | 0 | proto_tree_add_bitmask(fst_tree, tvb, 3, hf_mctp_flags, |
184 | 0 | ett_mctp_flags, mctp_flags, ENC_NA); |
185 | 0 | proto_tree_add_item_ret_uint(fst_tree, hf_mctp_seq, tvb, 3, 1, ENC_NA, &seq); |
186 | 0 | proto_tree_add_bitmask_with_flags(fst_tree, tvb, 3, hf_mctp_tag, |
187 | 0 | ett_mctp_tag, mctp_tag, ENC_NA, BMT_NO_FLAGS); |
188 | | |
189 | | /* use the tags as our port numbers */ |
190 | 0 | pinfo->ptype = PT_MCTP; |
191 | 0 | pinfo->srcport = tag; |
192 | 0 | pinfo->destport = tag ^ 0x08; /* flip tag-owner bit */ |
193 | |
|
194 | 0 | save_fragmented = pinfo->fragmented; |
195 | |
|
196 | 0 | col_set_str(pinfo->cinfo, COL_INFO, "MCTP message"); |
197 | | |
198 | | /* if we're not both the start and end of a message, handle as a |
199 | | * fragment */ |
200 | 0 | if ((fst & 0xc0) != 0xc0) { |
201 | 0 | fragment_head *frag_msg = NULL; |
202 | 0 | tvbuff_t *new_tvb = NULL; |
203 | |
|
204 | 0 | pinfo->fragmented = true; |
205 | 0 | frag_msg = fragment_add_seq_next(&mctp_reassembly_table, |
206 | 0 | tvb, 4, pinfo, |
207 | 0 | fst & 0x7, NULL, |
208 | 0 | tvb_captured_length_remaining(tvb, 4), |
209 | 0 | !(fst & 0x40)); |
210 | |
|
211 | 0 | new_tvb = process_reassembled_data(tvb, 4, pinfo, |
212 | 0 | "reassembled Message", |
213 | 0 | frag_msg, &mctp_frag_items, |
214 | 0 | NULL, mctp_tree); |
215 | |
|
216 | 0 | if (fst & 0x40) |
217 | 0 | col_append_str(pinfo->cinfo, COL_INFO, " reassembled"); |
218 | 0 | else |
219 | 0 | col_append_fstr(pinfo->cinfo, COL_INFO, " frag %u", seq); |
220 | |
|
221 | 0 | next_tvb = new_tvb; |
222 | 0 | } else { |
223 | 0 | next_tvb = tvb_new_subset_remaining(tvb, 4); |
224 | 0 | } |
225 | |
|
226 | 0 | if (next_tvb) { |
227 | 0 | proto_tree *type_tree; |
228 | 0 | int rc; |
229 | |
|
230 | 0 | type = tvb_get_uint8(next_tvb, 0); |
231 | 0 | type_tree = proto_tree_add_subtree_format(mctp_tree, next_tvb, 0, 1, |
232 | 0 | ett_mctp_type, |
233 | 0 | &tti, "Type: %s (0x%x)%s", |
234 | 0 | val_to_str_const(type & 0x7f, |
235 | 0 | type_vals, |
236 | 0 | "unknown"), |
237 | 0 | type & 0x7f, |
238 | 0 | type & 0x80 ? " + IC" : ""); |
239 | |
|
240 | 0 | proto_tree_add_item(type_tree, hf_mctp_msg_type, next_tvb, 0, 1, |
241 | 0 | ENC_NA); |
242 | 0 | proto_tree_add_item(type_tree, hf_mctp_msg_ic, next_tvb, 0, 1, |
243 | 0 | ENC_NA); |
244 | |
|
245 | 0 | rc = dissector_try_uint_with_data(mctp_dissector_table, type & 0x7f, |
246 | 0 | next_tvb, pinfo, tree, true, NULL); |
247 | |
|
248 | 0 | if (!rc && !(type & 0x80)) { |
249 | 0 | tvbuff_t *encap_tvb = tvb_new_subset_remaining(next_tvb, 1); |
250 | 0 | dissector_try_uint_with_data(mctp_encap_dissector_table, type, |
251 | 0 | encap_tvb, pinfo, tree, true, NULL); |
252 | 0 | } |
253 | 0 | } |
254 | |
|
255 | 0 | pinfo->fragmented = save_fragmented; |
256 | |
|
257 | 0 | return tvb_captured_length(tvb); |
258 | 0 | } |
259 | | |
260 | | void |
261 | | proto_register_mctp(void) |
262 | 14 | { |
263 | | /* *INDENT-OFF* */ |
264 | | /* Field definitions */ |
265 | 14 | static hf_register_info hf[] = { |
266 | 14 | { &hf_mctp_ver, |
267 | 14 | { "Version", "mctp.version", |
268 | 14 | FT_UINT8, BASE_DEC, NULL, 0x0f, |
269 | 14 | NULL, HFILL }, |
270 | 14 | }, |
271 | 14 | { &hf_mctp_dst, |
272 | 14 | { "Destination", "mctp.dst", |
273 | 14 | FT_UINT8, BASE_DEC, NULL, 0x00, |
274 | 14 | NULL, HFILL }, |
275 | 14 | }, |
276 | 14 | { &hf_mctp_src, |
277 | 14 | { "Source", "mctp.src", |
278 | 14 | FT_UINT8, BASE_DEC, NULL, 0x00, |
279 | 14 | NULL, HFILL }, |
280 | 14 | }, |
281 | 14 | { &hf_mctp_flags, |
282 | 14 | { "Flags", "mctp.flags", |
283 | 14 | FT_UINT8, BASE_HEX, NULL, 0xc0, |
284 | 14 | NULL, HFILL }, |
285 | 14 | }, |
286 | 14 | { &hf_mctp_flags_som, |
287 | 14 | { "Start of message", "mctp.flags.som", |
288 | 14 | FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x80, |
289 | 14 | NULL, HFILL }, |
290 | 14 | }, |
291 | 14 | { &hf_mctp_flags_eom, |
292 | 14 | { "End of message", "mctp.flags.eom", |
293 | 14 | FT_BOOLEAN, 8, TFS(&tfs_set_notset), 0x40, |
294 | 14 | NULL, HFILL }, |
295 | 14 | }, |
296 | 14 | { &hf_mctp_seq, |
297 | 14 | { "Sequence", "mctp.seq", |
298 | 14 | FT_UINT8, BASE_HEX, NULL, 0x30, |
299 | 14 | NULL, HFILL }, |
300 | 14 | }, |
301 | 14 | { &hf_mctp_tag, |
302 | 14 | { "Tag", "mctp.tag", |
303 | 14 | FT_UINT8, BASE_HEX, NULL, 0x0f, |
304 | 14 | NULL, HFILL }, |
305 | 14 | }, |
306 | 14 | { &hf_mctp_tag_to, |
307 | 14 | { "Tag owner", "mctp.tag.to", |
308 | 14 | FT_BOOLEAN, 8, TFS(&tfs_tag_to), 0x08, |
309 | 14 | NULL, HFILL }, |
310 | 14 | }, |
311 | 14 | { &hf_mctp_tag_value, |
312 | 14 | { "Tag value", "mctp.tag.value", |
313 | 14 | FT_UINT8, BASE_HEX, NULL, 0x07, |
314 | 14 | NULL, HFILL }, |
315 | 14 | }, |
316 | | |
317 | | /* message header */ |
318 | 14 | { &hf_mctp_msg_ic, |
319 | 14 | { "Integrity check", "mctp.msg.ic", |
320 | 14 | FT_BOOLEAN, 8, TFS(&tfs_present_absent), 0x80, |
321 | 14 | NULL, HFILL }, |
322 | 14 | }, |
323 | 14 | { &hf_mctp_msg_type, |
324 | 14 | { "Message type", "mctp.msg.type", |
325 | 14 | FT_UINT8, BASE_HEX, VALS(type_vals), 0x7f, |
326 | 14 | NULL, HFILL }, |
327 | 14 | }, |
328 | | |
329 | | /* generic fragmentation */ |
330 | 14 | {&hf_mctp_fragments, |
331 | 14 | {"Message fragments", "mctp.fragments", |
332 | 14 | FT_NONE, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
333 | 14 | {&hf_mctp_fragment, |
334 | 14 | {"Message fragment", "mctp.fragment", |
335 | 14 | FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
336 | 14 | {&hf_mctp_fragment_overlap, |
337 | 14 | {"Message fragment overlap", "mctp.fragment.overlap", |
338 | 14 | FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
339 | 14 | {&hf_mctp_fragment_overlap_conflicts, |
340 | 14 | {"Message fragment overlapping with conflicting data", |
341 | 14 | "mctp.fragment.overlap.conflicts", |
342 | 14 | FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
343 | 14 | {&hf_mctp_fragment_multiple_tails, |
344 | 14 | {"Message has multiple tail fragments", |
345 | 14 | "mctp.fragment.multiple_tails", |
346 | 14 | FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
347 | 14 | {&hf_mctp_fragment_too_long_fragment, |
348 | 14 | {"Message fragment too long", "mctp.fragment.too_long_fragment", |
349 | 14 | FT_BOOLEAN, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
350 | 14 | {&hf_mctp_fragment_error, |
351 | 14 | {"Message defragmentation error", "mctp.fragment.error", |
352 | 14 | FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
353 | 14 | {&hf_mctp_fragment_count, |
354 | 14 | {"Message fragment count", "mctp.fragment.count", |
355 | 14 | FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }, |
356 | 14 | {&hf_mctp_reassembled_in, |
357 | 14 | {"Reassembled in", "mctp.reassembled.in", |
358 | 14 | FT_FRAMENUM, BASE_NONE, NULL, 0x00, NULL, HFILL } }, |
359 | 14 | {&hf_mctp_reassembled_length, |
360 | 14 | {"Reassembled length", "mctp.reassembled.length", |
361 | 14 | FT_UINT32, BASE_DEC, NULL, 0x00, NULL, HFILL } }, |
362 | 14 | {&hf_mctp_reassembled_data, |
363 | 14 | {"Reassembled data", "mctp.reassembled.data", |
364 | 14 | FT_BYTES, SEP_SPACE, NULL, 0x00, NULL, HFILL } }, |
365 | 14 | }; |
366 | | |
367 | | /* protocol subtree */ |
368 | 14 | static int *ett[] = { |
369 | 14 | &ett_mctp, |
370 | 14 | &ett_mctp_flags, |
371 | 14 | &ett_mctp_fst, |
372 | 14 | &ett_mctp_tag, |
373 | 14 | &ett_mctp_type, |
374 | 14 | &ett_mctp_fragment, |
375 | 14 | &ett_mctp_fragments, |
376 | 14 | }; |
377 | | |
378 | | /* Register the protocol name and description */ |
379 | 14 | proto_mctp = proto_register_protocol("MCTP", "MCTP", "mctp"); |
380 | | |
381 | | /* Required function calls to register the header fields and subtrees */ |
382 | 14 | proto_register_field_array(proto_mctp, hf, array_length(hf)); |
383 | 14 | proto_register_subtree_array(ett, array_length(ett)); |
384 | | |
385 | | /* We have two dissector tables here, both keyed off the type byte, but |
386 | | * with different decode semantics: |
387 | | * |
388 | | * mctp.type: for protocols that are "MCTP-aware" - they perform their |
389 | | * own decoding of the type byte, including the IC bit, and possibly the |
390 | | * message integrity check (which is type-specific!). For example, |
391 | | * NVMe-MI, which includes the type byte in packet specifications |
392 | | * |
393 | | * mctp.encap-type: for protocols that are trivially encapsulated in a |
394 | | * MCTP message, and do not handle the type byte themselves. For |
395 | | * example, NC-SI over MCTP, which just wraps a NC-SI packet within |
396 | | * a MCTP message. |
397 | | * |
398 | | * it doesn't make sense to allow encap-type decoders to also have the IC |
399 | | * bit set, as there is no specification for what format the message |
400 | | * integrity check is in. So, we disallow the IC bit in the type field |
401 | | * for those dissectors. |
402 | | */ |
403 | 14 | mctp_dissector_table = register_dissector_table("mctp.type", "MCTP type", |
404 | 14 | proto_mctp, FT_UINT8, |
405 | 14 | BASE_HEX); |
406 | 14 | mctp_encap_dissector_table = register_dissector_table("mctp.encap-type", |
407 | 14 | "MCTP encapsulated type", |
408 | 14 | proto_mctp, FT_UINT8, |
409 | 14 | BASE_HEX); |
410 | | |
411 | 14 | reassembly_table_register(&mctp_reassembly_table, |
412 | 14 | &addresses_reassembly_table_functions); |
413 | 14 | } |
414 | | |
415 | | void |
416 | | proto_reg_handoff_mctp(void) |
417 | 14 | { |
418 | 14 | dissector_handle_t mctp_handle; |
419 | 14 | mctp_handle = create_dissector_handle(dissect_mctp, proto_mctp); |
420 | 14 | dissector_add_uint("sll.ltype", LINUX_SLL_P_MCTP, mctp_handle); |
421 | 14 | } |
422 | | |
423 | | /* |
424 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
425 | | * |
426 | | * Local variables: |
427 | | * c-basic-offset: 4 |
428 | | * tab-width: 8 |
429 | | * indent-tabs-mode: nil |
430 | | * End: |
431 | | * |
432 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
433 | | * :indentSize=4:tabSize=8:noTabs=true: |
434 | | */ |