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