xref: /netbsd-src/external/bsd/tcpdump/dist/print-stp.c (revision 3117ece4fc4a4ca4489ba793710b60b0d26bab6c)
1 /*
2  * Copyright (c) 2000 Lennert Buytenhek
3  *
4  * This software may be distributed either under the terms of the
5  * BSD-style license that accompanies tcpdump or the GNU General
6  * Public License
7  *
8  * Contributed by Lennert Buytenhek <buytenh@gnu.org>
9  */
10 
11 /* \summary: IEEE 802.1d Spanning Tree Protocol (STP) printer */
12 
13 #include <sys/cdefs.h>
14 #ifndef lint
15 __RCSID("$NetBSD: print-stp.c,v 1.10 2024/09/02 16:15:33 christos Exp $");
16 #endif
17 
18 #include <config.h>
19 
20 #include "netdissect-stdinc.h"
21 
22 #include <stdio.h>
23 
24 #include "netdissect.h"
25 #include "extract.h"
26 
27 #define	RSTP_EXTRACT_PORT_ROLE(x) (((x)&0x0C)>>2)
28 /* STP timers are expressed in multiples of 1/256th second */
29 #define STP_TIME_BASE 256
30 #define STP_BPDU_MSTP_MIN_LEN 102
31 
32 struct stp_bpdu_ {
33     nd_uint16_t protocol_id;
34     nd_uint8_t  protocol_version;
35     nd_uint8_t  bpdu_type;
36     nd_uint8_t  flags;
37     nd_byte     root_id[8];
38     nd_uint32_t root_path_cost;
39     nd_byte     bridge_id[8];
40     nd_uint16_t port_id;
41     nd_uint16_t message_age;
42     nd_uint16_t max_age;
43     nd_uint16_t hello_time;
44     nd_uint16_t forward_delay;
45     nd_uint8_t  v1_length;
46 };
47 
48 #define STP_PROTO_REGULAR 0x00
49 #define STP_PROTO_RAPID   0x02
50 #define STP_PROTO_MSTP    0x03
51 #define STP_PROTO_SPB     0x04
52 
53 static const struct tok stp_proto_values[] = {
54     { STP_PROTO_REGULAR, "802.1d" },
55     { STP_PROTO_RAPID, "802.1w" },
56     { STP_PROTO_MSTP, "802.1s" },
57     { STP_PROTO_SPB, "802.1aq" },
58     { 0, NULL}
59 };
60 
61 #define STP_BPDU_TYPE_CONFIG      0x00
62 #define STP_BPDU_TYPE_RSTP        0x02
63 #define STP_BPDU_TYPE_TOPO_CHANGE 0x80
64 
65 static const struct tok stp_bpdu_flag_values[] = {
66     { 0x01, "Topology change" },
67     { 0x02, "Proposal" },
68     { 0x10, "Learn" },
69     { 0x20, "Forward" },
70     { 0x40, "Agreement" },
71     { 0x80, "Topology change ACK" },
72     { 0, NULL}
73 };
74 
75 static const struct tok stp_bpdu_type_values[] = {
76     { STP_BPDU_TYPE_CONFIG, "Config" },
77     { STP_BPDU_TYPE_RSTP, "Rapid STP" },
78     { STP_BPDU_TYPE_TOPO_CHANGE, "Topology Change" },
79     { 0, NULL}
80 };
81 
82 static const struct tok rstp_obj_port_role_values[] = {
83     { 0x00, "Unknown" },
84     { 0x01, "Alternate" },
85     { 0x02, "Root" },
86     { 0x03, "Designated" },
87     { 0, NULL}
88 };
89 
90 static char *
91 stp_print_bridge_id(netdissect_options *ndo, const u_char *p)
92 {
93     static char bridge_id_str[sizeof("pppp.aa:bb:cc:dd:ee:ff")];
94 
95     snprintf(bridge_id_str, sizeof(bridge_id_str),
96              "%.2x%.2x.%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
97              GET_U_1(p), GET_U_1(p + 1), GET_U_1(p + 2),
98              GET_U_1(p + 3), GET_U_1(p + 4), GET_U_1(p + 5),
99              GET_U_1(p + 6), GET_U_1(p + 7));
100 
101     return bridge_id_str;
102 }
103 
104 static void
105 stp_print_config_bpdu(netdissect_options *ndo, const struct stp_bpdu_ *stp_bpdu,
106                       u_int length)
107 {
108     uint8_t bpdu_flags;
109 
110     bpdu_flags = GET_U_1(stp_bpdu->flags);
111     ND_PRINT(", Flags [%s]",
112            bittok2str(stp_bpdu_flag_values, "none", bpdu_flags));
113 
114     ND_PRINT(", bridge-id %s.%04x, length %u",
115            stp_print_bridge_id(ndo, stp_bpdu->bridge_id),
116            GET_BE_U_2(stp_bpdu->port_id), length);
117 
118     /* in non-verbose mode just print the bridge-id */
119     if (!ndo->ndo_vflag) {
120         return;
121     }
122 
123     ND_PRINT("\n\tmessage-age %.2fs, max-age %.2fs"
124            ", hello-time %.2fs, forwarding-delay %.2fs",
125            (float) GET_BE_U_2(stp_bpdu->message_age) / STP_TIME_BASE,
126            (float) GET_BE_U_2(stp_bpdu->max_age) / STP_TIME_BASE,
127            (float) GET_BE_U_2(stp_bpdu->hello_time) / STP_TIME_BASE,
128            (float) GET_BE_U_2(stp_bpdu->forward_delay) / STP_TIME_BASE);
129 
130     ND_PRINT("\n\troot-id %s, root-pathcost %u",
131            stp_print_bridge_id(ndo, stp_bpdu->root_id),
132            GET_BE_U_4(stp_bpdu->root_path_cost));
133 
134     /* Port role is only valid for 802.1w */
135     if (GET_U_1(stp_bpdu->protocol_version) == STP_PROTO_RAPID) {
136         ND_PRINT(", port-role %s",
137                tok2str(rstp_obj_port_role_values, "Unknown",
138                        RSTP_EXTRACT_PORT_ROLE(bpdu_flags)));
139     }
140 }
141 
142 /*
143  * MSTP packet format
144  * Ref. IEEE 802.1Q 2003 Ed. Section 14
145  *
146  * MSTP BPDU
147  *
148  * 2 -  bytes Protocol Id
149  * 1 -  byte  Protocol Ver.
150  * 1 -  byte  BPDU type
151  * 1 -  byte  Flags
152  * 8 -  bytes CIST Root Identifier
153  * 4 -  bytes CIST External Path Cost
154  * 8 -  bytes CIST Regional Root Identifier
155  * 2 -  bytes CIST Port Identifier
156  * 2 -  bytes Message Age
157  * 2 -  bytes Max age
158  * 2 -  bytes Hello Time
159  * 2 -  bytes Forward delay
160  * 1 -  byte  Version 1 length. Must be 0
161  * 2 -  bytes Version 3 length
162  * 1 -  byte  Config Identifier
163  * 32 - bytes Config Name
164  * 2 -  bytes Revision level
165  * 16 - bytes Config Digest [MD5]
166  * 4 -  bytes CIST Internal Root Path Cost
167  * 8 -  bytes CIST Bridge Identifier
168  * 1 -  byte  CIST Remaining Hops
169  * 16 - bytes MSTI information [Max 64 MSTI, each 16 bytes]
170  *
171  *
172  * SPB BPDU
173  * Ref. IEEE 802.1aq. Section 14
174  *
175  * 2 -  bytes Version 4 length
176  * 1 -  byte  Aux Config Identifier
177  * 32 - bytes Aux Config Name
178  * 2 -  bytes Aux Revision level
179  * 16 - bytes Aux Config Digest [MD5]
180  * 1 -  byte  (1 - 2) Agreement Number
181  *            (3 - 4) Discarded Agreement Number
182  *            (5) Agreement Valid Flag
183  *            (6) Restricted Role Flag
184  *            (7 - 8) Unused sent zero
185  * 1 -  byte Unused
186  * 1 -  byte (1 - 4) Agreement Digest Format Identifier
187  *           (5 - 8) Agreement Digest Format Capabilities
188  * 1 -  byte (1 - 4) Agreement Digest Convention Identifier
189  *           (5 - 8) Agreement Digest Convention Capabilities
190  * 2 -  bytes Agreement Digest Edge Count
191  * 8 -  byte Reserved Set
192  * 20 - bytes Computed Topology Digest
193  *
194  *
195  * MSTI Payload
196  *
197  * 1 - byte  MSTI flag
198  * 8 - bytes MSTI Regional Root Identifier
199  * 4 - bytes MSTI Regional Path Cost
200  * 1 - byte  MSTI Bridge Priority
201  * 1 - byte  MSTI Port Priority
202  * 1 - byte  MSTI Remaining Hops
203  *
204  */
205 
206 #define MST_BPDU_MSTI_LENGTH		    16
207 #define MST_BPDU_CONFIG_INFO_LENGTH	    64
208 
209 /* Offsets of fields from the beginning for the packet */
210 #define MST_BPDU_VER3_LEN_OFFSET	    36
211 #define MST_BPDU_CONFIG_NAME_OFFSET	    39
212 #define MST_BPDU_CONFIG_DIGEST_OFFSET	    73
213 #define MST_BPDU_CIST_INT_PATH_COST_OFFSET  89
214 #define MST_BPDU_CIST_BRIDGE_ID_OFFSET	    93
215 #define MST_BPDU_CIST_REMAIN_HOPS_OFFSET    101
216 #define MST_BPDU_MSTI_OFFSET		    102
217 /* Offsets within  an MSTI */
218 #define MST_BPDU_MSTI_ROOT_PRIO_OFFSET	    1
219 #define MST_BPDU_MSTI_ROOT_PATH_COST_OFFSET 9
220 #define MST_BPDU_MSTI_BRIDGE_PRIO_OFFSET    13
221 #define MST_BPDU_MSTI_PORT_PRIO_OFFSET	    14
222 #define MST_BPDU_MSTI_REMAIN_HOPS_OFFSET    15
223 
224 #define SPB_BPDU_MIN_LEN                  87
225 #define SPB_BPDU_CONFIG_NAME_OFFSET       3
226 #define SPB_BPDU_CONFIG_REV_OFFSET        SPB_BPDU_CONFIG_NAME_OFFSET + 32
227 #define SPB_BPDU_CONFIG_DIGEST_OFFSET     SPB_BPDU_CONFIG_REV_OFFSET + 2
228 #define SPB_BPDU_AGREEMENT_OFFSET         SPB_BPDU_CONFIG_DIGEST_OFFSET + 16
229 #define SPB_BPDU_AGREEMENT_UNUSED_OFFSET  SPB_BPDU_AGREEMENT_OFFSET + 1
230 #define SPB_BPDU_AGREEMENT_FORMAT_OFFSET  SPB_BPDU_AGREEMENT_UNUSED_OFFSET + 1
231 #define SPB_BPDU_AGREEMENT_CON_OFFSET     SPB_BPDU_AGREEMENT_FORMAT_OFFSET + 1
232 #define SPB_BPDU_AGREEMENT_EDGE_OFFSET    SPB_BPDU_AGREEMENT_CON_OFFSET + 1
233 #define SPB_BPDU_AGREEMENT_RES1_OFFSET    SPB_BPDU_AGREEMENT_EDGE_OFFSET + 2
234 #define SPB_BPDU_AGREEMENT_RES2_OFFSET    SPB_BPDU_AGREEMENT_RES1_OFFSET + 4
235 #define SPB_BPDU_AGREEMENT_DIGEST_OFFSET  SPB_BPDU_AGREEMENT_RES2_OFFSET + 4
236 
237 static void
238 stp_print_mstp_bpdu(netdissect_options *ndo, const struct stp_bpdu_ *stp_bpdu,
239                     u_int length)
240 {
241     const u_char *ptr;
242     uint8_t	    bpdu_flags;
243     uint16_t	    v3len;
244     uint16_t	    len;
245     uint16_t	    msti;
246     u_int	    offset;
247 
248     ptr = (const u_char *)stp_bpdu;
249     bpdu_flags = GET_U_1(stp_bpdu->flags);
250     ND_PRINT(", CIST Flags [%s], length %u",
251            bittok2str(stp_bpdu_flag_values, "none", bpdu_flags), length);
252 
253     /*
254      * in non-verbose mode just print the flags.
255      */
256     if (!ndo->ndo_vflag) {
257         return;
258     }
259 
260     ND_PRINT("\n\tport-role %s, ",
261            tok2str(rstp_obj_port_role_values, "Unknown",
262                    RSTP_EXTRACT_PORT_ROLE(bpdu_flags)));
263 
264     ND_PRINT("CIST root-id %s, CIST ext-pathcost %u",
265            stp_print_bridge_id(ndo, stp_bpdu->root_id),
266            GET_BE_U_4(stp_bpdu->root_path_cost));
267 
268     ND_PRINT("\n\tCIST regional-root-id %s, ",
269            stp_print_bridge_id(ndo, stp_bpdu->bridge_id));
270 
271     ND_PRINT("CIST port-id %04x,", GET_BE_U_2(stp_bpdu->port_id));
272 
273     ND_PRINT("\n\tmessage-age %.2fs, max-age %.2fs"
274            ", hello-time %.2fs, forwarding-delay %.2fs",
275            (float) GET_BE_U_2(stp_bpdu->message_age) / STP_TIME_BASE,
276            (float) GET_BE_U_2(stp_bpdu->max_age) / STP_TIME_BASE,
277            (float) GET_BE_U_2(stp_bpdu->hello_time) / STP_TIME_BASE,
278            (float) GET_BE_U_2(stp_bpdu->forward_delay) / STP_TIME_BASE);
279 
280     ND_PRINT("\n\tv3len %u, ", GET_BE_U_2(ptr + MST_BPDU_VER3_LEN_OFFSET));
281     ND_PRINT("MCID Name ");
282     nd_printjnp(ndo, ptr + MST_BPDU_CONFIG_NAME_OFFSET, 32);
283     ND_PRINT(", rev %u,"
284             "\n\t\tdigest %08x%08x%08x%08x, ",
285 	          GET_BE_U_2(ptr + MST_BPDU_CONFIG_NAME_OFFSET + 32),
286 	          GET_BE_U_4(ptr + MST_BPDU_CONFIG_DIGEST_OFFSET),
287 	          GET_BE_U_4(ptr + MST_BPDU_CONFIG_DIGEST_OFFSET + 4),
288 	          GET_BE_U_4(ptr + MST_BPDU_CONFIG_DIGEST_OFFSET + 8),
289 	          GET_BE_U_4(ptr + MST_BPDU_CONFIG_DIGEST_OFFSET + 12));
290 
291     ND_PRINT("CIST int-root-pathcost %u,",
292             GET_BE_U_4(ptr + MST_BPDU_CIST_INT_PATH_COST_OFFSET));
293 
294     ND_PRINT("\n\tCIST bridge-id %s, ",
295            stp_print_bridge_id(ndo, ptr + MST_BPDU_CIST_BRIDGE_ID_OFFSET));
296 
297     ND_PRINT("CIST remaining-hops %u",
298              GET_U_1(ptr + MST_BPDU_CIST_REMAIN_HOPS_OFFSET));
299 
300     /* Dump all MSTI's */
301     v3len = GET_BE_U_2(ptr + MST_BPDU_VER3_LEN_OFFSET);
302     if (v3len > MST_BPDU_CONFIG_INFO_LENGTH) {
303         len = v3len - MST_BPDU_CONFIG_INFO_LENGTH;
304         offset = MST_BPDU_MSTI_OFFSET;
305         while (len >= MST_BPDU_MSTI_LENGTH) {
306             msti = GET_BE_U_2(ptr + offset + MST_BPDU_MSTI_ROOT_PRIO_OFFSET);
307             msti = msti & 0x0FFF;
308 
309             ND_PRINT("\n\tMSTI %u, Flags [%s], port-role %s",
310                    msti,
311                    bittok2str(stp_bpdu_flag_values, "none", GET_U_1(ptr + offset)),
312                    tok2str(rstp_obj_port_role_values, "Unknown",
313                            RSTP_EXTRACT_PORT_ROLE(GET_U_1(ptr + offset))));
314             ND_PRINT("\n\t\tMSTI regional-root-id %s, pathcost %u",
315                    stp_print_bridge_id(ndo, ptr + offset +
316                                        MST_BPDU_MSTI_ROOT_PRIO_OFFSET),
317                    GET_BE_U_4(ptr + offset + MST_BPDU_MSTI_ROOT_PATH_COST_OFFSET));
318             ND_PRINT("\n\t\tMSTI bridge-prio %u, port-prio %u, hops %u",
319                    GET_U_1(ptr + offset + MST_BPDU_MSTI_BRIDGE_PRIO_OFFSET) >> 4,
320                    GET_U_1(ptr + offset + MST_BPDU_MSTI_PORT_PRIO_OFFSET) >> 4,
321                    GET_U_1(ptr + offset + MST_BPDU_MSTI_REMAIN_HOPS_OFFSET));
322 
323             len -= MST_BPDU_MSTI_LENGTH;
324             offset += MST_BPDU_MSTI_LENGTH;
325         }
326     }
327 }
328 
329 static void
330 stp_print_spb_bpdu(netdissect_options *ndo, const struct stp_bpdu_ *stp_bpdu,
331                    u_int offset)
332 {
333     const u_char *ptr;
334 
335     /*
336      * in non-verbose mode don't print anything.
337      */
338     if (!ndo->ndo_vflag) {
339         return;
340     }
341 
342     ptr = (const u_char *)stp_bpdu;
343 
344     ND_PRINT("\n\tv4len %u, ", GET_BE_U_2(ptr + offset));
345     ND_PRINT("AUXMCID Name ");
346     nd_printjnp(ndo, ptr + offset + SPB_BPDU_CONFIG_NAME_OFFSET, 32);
347     ND_PRINT(", Rev %u,\n\t\tdigest %08x%08x%08x%08x",
348             GET_BE_U_2(ptr + offset + SPB_BPDU_CONFIG_REV_OFFSET),
349             GET_BE_U_4(ptr + offset + SPB_BPDU_CONFIG_DIGEST_OFFSET),
350             GET_BE_U_4(ptr + offset + SPB_BPDU_CONFIG_DIGEST_OFFSET + 4),
351             GET_BE_U_4(ptr + offset + SPB_BPDU_CONFIG_DIGEST_OFFSET + 8),
352             GET_BE_U_4(ptr + offset + SPB_BPDU_CONFIG_DIGEST_OFFSET + 12));
353 
354     ND_PRINT("\n\tAgreement num %u, Discarded Agreement num %u, Agreement valid-"
355             "flag %u,\n\tRestricted role-flag: %u, Format id %u cap %u, "
356             "Convention id %u cap %u,\n\tEdge count %u, "
357             "Agreement digest %08x%08x%08x%08x%08x",
358             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_OFFSET)>>6,
359             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_OFFSET)>>4 & 0x3,
360             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_OFFSET)>>3 & 0x1,
361             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_OFFSET)>>2 & 0x1,
362             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_FORMAT_OFFSET)>>4,
363             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_FORMAT_OFFSET)&0x00ff,
364             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_CON_OFFSET)>>4,
365             GET_U_1(ptr + offset + SPB_BPDU_AGREEMENT_CON_OFFSET)&0x00ff,
366             GET_BE_U_2(ptr + offset + SPB_BPDU_AGREEMENT_EDGE_OFFSET),
367             GET_BE_U_4(ptr + offset + SPB_BPDU_AGREEMENT_DIGEST_OFFSET),
368             GET_BE_U_4(ptr + offset + SPB_BPDU_AGREEMENT_DIGEST_OFFSET + 4),
369             GET_BE_U_4(ptr + offset + SPB_BPDU_AGREEMENT_DIGEST_OFFSET + 8),
370             GET_BE_U_4(ptr + offset + SPB_BPDU_AGREEMENT_DIGEST_OFFSET + 12),
371             GET_BE_U_4(ptr + offset + SPB_BPDU_AGREEMENT_DIGEST_OFFSET + 16));
372 }
373 
374 /*
375  * Print 802.1d / 802.1w / 802.1q (mstp) / 802.1aq (spb) packets.
376  */
377 void
378 stp_print(netdissect_options *ndo, const u_char *p, u_int length)
379 {
380     const struct stp_bpdu_ *stp_bpdu;
381     u_int                  protocol_version;
382     u_int                  bpdu_type;
383     u_int                  mstp_len;
384     u_int                  spb_len;
385 
386     ndo->ndo_protocol = "stp";
387     stp_bpdu = (const struct stp_bpdu_*)p;
388 
389     /* Minimum STP Frame size. */
390     if (length < 4)
391         goto invalid;
392 
393     if (GET_BE_U_2(stp_bpdu->protocol_id)) {
394         ND_PRINT("unknown STP version, length %u", length);
395         return;
396     }
397 
398     protocol_version = GET_U_1(stp_bpdu->protocol_version);
399     ND_PRINT("STP %s", tok2str(stp_proto_values, "Unknown STP protocol (0x%02x)",
400                          protocol_version));
401 
402     switch (protocol_version) {
403     case STP_PROTO_REGULAR:
404     case STP_PROTO_RAPID:
405     case STP_PROTO_MSTP:
406     case STP_PROTO_SPB:
407         break;
408     default:
409         return;
410     }
411 
412     bpdu_type = GET_U_1(stp_bpdu->bpdu_type);
413     ND_PRINT(", %s", tok2str(stp_bpdu_type_values, "Unknown BPDU Type (0x%02x)",
414                            bpdu_type));
415 
416     switch (bpdu_type) {
417     case STP_BPDU_TYPE_CONFIG:
418         if (length < sizeof(struct stp_bpdu_) - 1) {
419             goto invalid;
420         }
421         stp_print_config_bpdu(ndo, stp_bpdu, length);
422         break;
423 
424     case STP_BPDU_TYPE_RSTP:
425         if (protocol_version == STP_PROTO_RAPID) {
426             if (length < sizeof(struct stp_bpdu_)) {
427                 goto invalid;
428             }
429             stp_print_config_bpdu(ndo, stp_bpdu, length);
430         } else if (protocol_version == STP_PROTO_MSTP ||
431                    protocol_version == STP_PROTO_SPB) {
432             if (length < STP_BPDU_MSTP_MIN_LEN) {
433                 goto invalid;
434             }
435 
436             if (GET_U_1(stp_bpdu->v1_length) != 0) {
437                 /* FIX ME: Emit a message here ? */
438                 goto invalid;
439             }
440 
441             /* Validate v3 length */
442             mstp_len = GET_BE_U_2(p + MST_BPDU_VER3_LEN_OFFSET);
443             mstp_len += 2;  /* length encoding itself is 2 bytes */
444             if (length < (sizeof(struct stp_bpdu_) + mstp_len)) {
445                 goto invalid;
446             }
447             stp_print_mstp_bpdu(ndo, stp_bpdu, length);
448 
449             if (protocol_version == STP_PROTO_SPB) {
450               /* Validate v4 length */
451               spb_len = GET_BE_U_2(p + MST_BPDU_VER3_LEN_OFFSET + mstp_len);
452               spb_len += 2;
453               if (length < (sizeof(struct stp_bpdu_) + mstp_len + spb_len) ||
454                   spb_len < SPB_BPDU_MIN_LEN) {
455                 goto invalid;
456               }
457               stp_print_spb_bpdu(ndo, stp_bpdu, (sizeof(struct stp_bpdu_) + mstp_len));
458             }
459         }
460         break;
461 
462     case STP_BPDU_TYPE_TOPO_CHANGE:
463         /* always empty message - just break out */
464         break;
465 
466     default:
467         break;
468     }
469     return;
470 
471 invalid:
472     nd_print_invalid(ndo);
473 }
474