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