1 /* 2 * Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997 3 * The Regents of the University of California. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that: (1) source code distributions 7 * retain the above copyright notice and this paragraph in its entirety, (2) 8 * distributions including binary code include the above copyright notice and 9 * this paragraph in its entirety in the documentation or other materials 10 * provided with the distribution, and (3) all advertising materials mentioning 11 * features or use of this software display the following acknowledgement: 12 * ``This product includes software developed by the University of California, 13 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of 14 * the University nor the names of its contributors may be used to endorse 15 * or promote products derived from this software without specific prior 16 * written permission. 17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED 18 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 20 * 21 * Code by Gert Doering, SpaceNet GmbH, gert@space.net 22 * 23 * Reference documentation: 24 * https://web.archive.org/web/20000914194913/http://www.cisco.com/univercd/cc/td/doc/product/lan/trsrb/frames.pdf 25 */ 26 27 #include <sys/cdefs.h> 28 #ifndef lint 29 __RCSID("$NetBSD: print-cdp.c,v 1.10 2024/09/02 16:15:30 christos Exp $"); 30 #endif 31 32 /* \summary: Cisco Discovery Protocol (CDP) printer */ 33 34 #include <config.h> 35 36 #include "netdissect-stdinc.h" 37 38 #include <string.h> 39 40 #define ND_LONGJMP_FROM_TCHECK 41 #include "netdissect.h" 42 #include "addrtoname.h" 43 #include "extract.h" 44 #include "nlpid.h" 45 46 47 #define CDP_HEADER_LEN 4 48 #define CDP_HEADER_VERSION_OFFSET 0 49 #define CDP_HEADER_TTL_OFFSET 1 50 #define CDP_HEADER_CHECKSUM_OFFSET 2 51 52 #define CDP_TLV_HEADER_LEN 4 53 #define CDP_TLV_TYPE_OFFSET 0 54 #define CDP_TLV_LEN_OFFSET 2 55 56 static const struct tok cdp_capability_values[] = { 57 { 0x01, "Router" }, 58 { 0x02, "Transparent Bridge" }, 59 { 0x04, "Source Route Bridge" }, 60 { 0x08, "L2 Switch" }, 61 { 0x10, "L3 capable" }, 62 { 0x20, "IGMP snooping" }, 63 { 0x40, "L1 capable" }, 64 { 0, NULL } 65 }; 66 67 static void cdp_print_addr(netdissect_options *, const u_char *, u_int); 68 static void cdp_print_prefixes(netdissect_options *, const u_char *, u_int); 69 70 static void 71 cdp_print_string(netdissect_options *ndo, 72 const u_char *cp, const u_int len) 73 { 74 ND_PRINT("'"); 75 (void)nd_printn(ndo, cp, len, NULL); 76 ND_PRINT("'"); 77 } 78 79 static void 80 cdp_print_power(netdissect_options *ndo, 81 const u_char *cp, const u_int len) 82 { 83 u_int val = 0; 84 85 switch (len) { 86 case 1: 87 val = GET_U_1(cp); 88 break; 89 case 2: 90 val = GET_BE_U_2(cp); 91 break; 92 case 3: 93 val = GET_BE_U_3(cp); 94 break; 95 } 96 ND_PRINT("%1.2fW", val / 1000.0); 97 } 98 99 static void 100 cdp_print_capability(netdissect_options *ndo, 101 const u_char *cp, const u_int len _U_) 102 { 103 uint32_t val = GET_BE_U_4(cp); 104 105 ND_PRINT("(0x%08x): %s", val, 106 bittok2str(cdp_capability_values, "none", val)); 107 } 108 109 /* Rework the version string to get a nice indentation. */ 110 static void 111 cdp_print_version(netdissect_options *ndo, 112 const u_char *cp, const u_int len) 113 { 114 unsigned i; 115 116 ND_PRINT("\n\t "); 117 for (i = 0; i < len; i++) { 118 u_char c = GET_U_1(cp + i); 119 120 if (c == '\n') 121 ND_PRINT("\n\t "); 122 else 123 fn_print_char(ndo, c); 124 } 125 } 126 127 static void 128 cdp_print_uint16(netdissect_options *ndo, 129 const u_char *cp, const u_int len _U_) 130 { 131 ND_PRINT("%u", GET_BE_U_2(cp)); 132 } 133 134 static void 135 cdp_print_duplex(netdissect_options *ndo, 136 const u_char *cp, const u_int len _U_) 137 { 138 ND_PRINT("%s", GET_U_1(cp) ? "full": "half"); 139 } 140 141 /* https://www.cisco.com/c/en/us/td/docs/voice_ip_comm/cata/186/2_12_m/english/release/notes/186rn21m.html 142 * plus more details from other sources 143 * 144 * There are apparently versions of the request with both 145 * 2 bytes and 3 bytes of value. The 3 bytes of value 146 * appear to be a 1-byte application type followed by a 147 * 2-byte VLAN ID; the 2 bytes of value are unknown 148 * (they're 0x20 0x00 in some captures I've seen; that 149 * is not a valid VLAN ID, as VLAN IDs are 12 bits). 150 * 151 * The replies all appear to be 3 bytes long. 152 */ 153 static void 154 cdp_print_ata186(netdissect_options *ndo, 155 const u_char *cp, const u_int len) 156 { 157 if (len == 2) 158 ND_PRINT("unknown 0x%04x", GET_BE_U_2(cp)); 159 else 160 ND_PRINT("app %u, vlan %u", GET_U_1(cp), GET_BE_U_2(cp + 1)); 161 } 162 163 static void 164 cdp_print_mtu(netdissect_options *ndo, 165 const u_char *cp, const u_int len _U_) 166 { 167 ND_PRINT("%u bytes", GET_BE_U_4(cp)); 168 } 169 170 static void 171 cdp_print_uint8x(netdissect_options *ndo, 172 const u_char *cp, const u_int len _U_) 173 { 174 ND_PRINT("0x%02x", GET_U_1(cp)); 175 } 176 177 static void 178 cdp_print_phys_loc(netdissect_options *ndo, 179 const u_char *cp, const u_int len) 180 { 181 ND_PRINT("0x%02x", GET_U_1(cp)); 182 if (len > 1) { 183 ND_PRINT("/"); 184 (void)nd_printn(ndo, cp + 1, len - 1, NULL); 185 } 186 } 187 188 struct cdp_tlvinfo { 189 const char *name; 190 void (*printer)(netdissect_options *ndo, const u_char *, u_int); 191 int min_len, max_len; 192 }; 193 194 #define T_DEV_ID 0x01 195 #define T_MAX 0x17 196 static const struct cdp_tlvinfo cdptlvs[T_MAX + 1] = { 197 /* 0x00 */ 198 [ T_DEV_ID ] = { "Device-ID", cdp_print_string, -1, -1 }, 199 [ 0x02 ] = { "Address", cdp_print_addr, -1, -1 }, 200 [ 0x03 ] = { "Port-ID", cdp_print_string, -1, -1 }, 201 [ 0x04 ] = { "Capability", cdp_print_capability, 4, 4 }, 202 [ 0x05 ] = { "Version String", cdp_print_version, -1, -1 }, 203 [ 0x06 ] = { "Platform", cdp_print_string, -1, -1 }, 204 [ 0x07 ] = { "Prefixes", cdp_print_prefixes, -1, -1 }, 205 /* not documented */ 206 [ 0x08 ] = { "Protocol-Hello option", NULL, -1, -1 }, 207 /* CDPv2 */ 208 [ 0x09 ] = { "VTP Management Domain", cdp_print_string, -1, -1 }, 209 /* CDPv2 */ 210 [ 0x0a ] = { "Native VLAN ID", cdp_print_uint16, 2, 2 }, 211 /* CDPv2 */ 212 [ 0x0b ] = { "Duplex", cdp_print_duplex, 1, 1 }, 213 /* 0x0c */ 214 /* 0x0d */ 215 /* incomplete doc. */ 216 [ 0x0e ] = { "ATA-186 VoIP VLAN assignment", cdp_print_ata186, 3, 3 }, 217 /* incomplete doc. */ 218 [ 0x0f ] = { "ATA-186 VoIP VLAN request", cdp_print_ata186, 2, 3 }, 219 /* not documented */ 220 [ 0x10 ] = { "power consumption", cdp_print_power, 1, 3 }, 221 /* not documented */ 222 [ 0x11 ] = { "MTU", cdp_print_mtu, 4, 4 }, 223 /* not documented */ 224 [ 0x12 ] = { "AVVID trust bitmap", cdp_print_uint8x, 1, 1 }, 225 /* not documented */ 226 [ 0x13 ] = { "AVVID untrusted ports CoS", cdp_print_uint8x, 1, 1 }, 227 /* not documented */ 228 [ 0x14 ] = { "System Name", cdp_print_string, -1, -1 }, 229 /* not documented */ 230 [ 0x15 ] = { "System Object ID (not decoded)", NULL, -1, -1 }, 231 [ 0x16 ] = { "Management Addresses", cdp_print_addr, 4, -1 }, 232 /* not documented */ 233 [ 0x17 ] = { "Physical Location", cdp_print_phys_loc, 1, -1 }, 234 }; 235 236 void 237 cdp_print(netdissect_options *ndo, 238 const u_char *tptr, u_int length) 239 { 240 u_int orig_length = length; 241 uint16_t checksum; 242 243 ndo->ndo_protocol = "cdp"; 244 245 if (length < CDP_HEADER_LEN) { 246 ND_PRINT(" (packet length %u < %u)", length, CDP_HEADER_LEN); 247 goto invalid; 248 } 249 ND_PRINT("CDPv%u, ttl: %us", 250 GET_U_1(tptr + CDP_HEADER_VERSION_OFFSET), 251 GET_U_1(tptr + CDP_HEADER_TTL_OFFSET)); 252 checksum = GET_BE_U_2(tptr + CDP_HEADER_CHECKSUM_OFFSET); 253 if (ndo->ndo_vflag) 254 ND_PRINT(", checksum: 0x%04x (unverified), length %u", 255 checksum, orig_length); 256 tptr += CDP_HEADER_LEN; 257 length -= CDP_HEADER_LEN; 258 259 while (length) { 260 u_int type, len; 261 const struct cdp_tlvinfo *info; 262 const char *name; 263 u_char covered = 0; 264 265 if (length < CDP_TLV_HEADER_LEN) { 266 ND_PRINT(" (remaining packet length %u < %u)", 267 length, CDP_TLV_HEADER_LEN); 268 goto invalid; 269 } 270 type = GET_BE_U_2(tptr + CDP_TLV_TYPE_OFFSET); 271 len = GET_BE_U_2(tptr + CDP_TLV_LEN_OFFSET); /* object length includes the 4 bytes header length */ 272 info = type <= T_MAX ? &cdptlvs[type] : NULL; 273 name = (info && info->name) ? info->name : "unknown field type"; 274 if (len < CDP_TLV_HEADER_LEN) { 275 if (ndo->ndo_vflag) 276 ND_PRINT("\n\t%s (0x%02x), TLV length: %u byte%s (too short)", 277 name, type, len, PLURAL_SUFFIX(len)); 278 else 279 ND_PRINT(", %s TLV length %u too short", 280 name, len); 281 goto invalid; 282 } 283 if (len > length) { 284 ND_PRINT(" (TLV length %u > %u)", len, length); 285 goto invalid; 286 } 287 tptr += CDP_TLV_HEADER_LEN; 288 length -= CDP_TLV_HEADER_LEN; 289 len -= CDP_TLV_HEADER_LEN; 290 291 /* In non-verbose mode just print Device-ID. */ 292 if (!ndo->ndo_vflag && type == T_DEV_ID) 293 ND_PRINT(", Device-ID "); 294 else if (ndo->ndo_vflag) 295 ND_PRINT("\n\t%s (0x%02x), value length: %u byte%s: ", 296 name, type, len, PLURAL_SUFFIX(len)); 297 298 if (info) { 299 if ((info->min_len > 0 && len < (unsigned)info->min_len) || 300 (info->max_len > 0 && len > (unsigned)info->max_len)) 301 ND_PRINT(" (malformed TLV)"); 302 else if (ndo->ndo_vflag || type == T_DEV_ID) { 303 if (info->printer) 304 info->printer(ndo, tptr, len); 305 else 306 ND_TCHECK_LEN(tptr, len); 307 /* 308 * When the type is defined without a printer, 309 * do not print the hex dump. 310 */ 311 covered = 1; 312 } 313 } 314 315 if (ndo->ndo_vflag && !covered) { 316 ND_TCHECK_LEN(tptr, len); 317 print_unknown_data(ndo, tptr, "\n\t ", len); 318 } 319 tptr += len; 320 length -= len; 321 } 322 if (ndo->ndo_vflag < 1) 323 ND_PRINT(", length %u", orig_length); 324 325 return; 326 invalid: 327 nd_print_invalid(ndo); 328 ND_TCHECK_LEN(tptr, length); 329 } 330 331 /* 332 * Protocol type values. 333 * 334 * PT_NLPID means that the protocol type field contains an OSI NLPID. 335 * 336 * PT_IEEE_802_2 means that the protocol type field contains an IEEE 802.2 337 * LLC header that specifies that the payload is for that protocol. 338 */ 339 #define PT_NLPID 1 /* OSI NLPID */ 340 #define PT_IEEE_802_2 2 /* IEEE 802.2 LLC header */ 341 342 static void 343 cdp_print_addr(netdissect_options *ndo, 344 const u_char * p, u_int l) 345 { 346 u_int num; 347 static const u_char prot_ipv6[] = { 348 0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00, 0x86, 0xdd 349 }; 350 351 if (l < 4) { 352 ND_PRINT(" (not enough space for num)"); 353 goto invalid; 354 } 355 num = GET_BE_U_4(p); 356 p += 4; 357 l -= 4; 358 359 while (num) { 360 u_int pt, pl, al; 361 362 if (l < 2) { 363 ND_PRINT(" (not enough space for PT+PL)"); 364 goto invalid; 365 } 366 pt = GET_U_1(p); /* type of "protocol" field */ 367 pl = GET_U_1(p + 1); /* length of "protocol" field */ 368 p += 2; 369 l -= 2; 370 371 if (l < pl + 2) { 372 ND_PRINT(" (not enough space for P+AL)"); 373 goto invalid; 374 } 375 /* Skip the protocol for now. */ 376 al = GET_BE_U_2(p + pl); /* address length */ 377 378 if (pt == PT_NLPID && pl == 1 && GET_U_1(p) == NLPID_IP && 379 al == 4) { 380 /* 381 * IPv4: protocol type = NLPID, protocol length = 1 382 * (1-byte NLPID), protocol = 0xcc (NLPID for IPv4), 383 * address length = 4 384 */ 385 p += pl + 2; 386 l -= pl + 2; 387 /* p is just beyond al now. */ 388 if (l < al) { 389 ND_PRINT(" (not enough space for A)"); 390 goto invalid; 391 } 392 ND_PRINT("IPv4 (%u) %s", num, GET_IPADDR_STRING(p)); 393 p += al; 394 l -= al; 395 } else if (pt == PT_IEEE_802_2 && pl == 8 && 396 memcmp(p, prot_ipv6, 8) == 0 && al == 16) { 397 /* 398 * IPv6: protocol type = IEEE 802.2 header, 399 * protocol length = 8 (size of LLC+SNAP header), 400 * protocol = LLC+SNAP header with the IPv6 401 * Ethertype, address length = 16 402 */ 403 p += pl + 2; 404 l -= pl + 2; 405 /* p is just beyond al now. */ 406 if (l < al) { 407 ND_PRINT(" (not enough space for A)"); 408 goto invalid; 409 } 410 ND_PRINT("IPv6 (%u) %s", num, GET_IP6ADDR_STRING(p)); 411 p += al; 412 l -= al; 413 } else { 414 /* 415 * Generic case: just print raw data 416 */ 417 ND_PRINT("pt=0x%02x, pl=%u, pb=", pt, pl); 418 while (pl != 0) { 419 ND_PRINT(" %02x", GET_U_1(p)); 420 p++; 421 l--; 422 pl--; 423 } 424 ND_PRINT(", al=%u, a=", al); 425 p += 2; 426 l -= 2; 427 /* p is just beyond al now. */ 428 if (l < al) { 429 ND_PRINT(" (not enough space for A)"); 430 goto invalid; 431 } 432 while (al != 0) { 433 ND_PRINT(" %02x", GET_U_1(p)); 434 p++; 435 l--; 436 al--; 437 } 438 } 439 num--; 440 if (num) 441 ND_PRINT(" "); 442 } 443 if (l) 444 ND_PRINT(" (%u bytes of stray data)", l); 445 return; 446 447 invalid: 448 ND_TCHECK_LEN(p, l); 449 } 450 451 static void 452 cdp_print_prefixes(netdissect_options *ndo, 453 const u_char * p, u_int l) 454 { 455 if (l % 5) { 456 ND_PRINT(" [length %u is not a multiple of 5]", l); 457 goto invalid; 458 } 459 460 ND_PRINT(" IPv4 Prefixes (%u):", l / 5); 461 462 while (l > 0) { 463 ND_PRINT(" %u.%u.%u.%u/%u", 464 GET_U_1(p), GET_U_1(p + 1), GET_U_1(p + 2), 465 GET_U_1(p + 3), GET_U_1(p + 4)); 466 l -= 5; 467 p += 5; 468 } 469 return; 470 471 invalid: 472 ND_TCHECK_LEN(p, l); 473 } 474