1784088dfSchristos /* 2784088dfSchristos * Copyright (c) 2016 Antonin Décimo, Jean-Raphaël Gaglione 3784088dfSchristos * 4784088dfSchristos * Redistribution and use in source and binary forms, with or without 5784088dfSchristos * modification, are permitted provided that the following conditions 6784088dfSchristos * are met: 7784088dfSchristos * 1. Redistributions of source code must retain the above copyright 8784088dfSchristos * notice, this list of conditions and the following disclaimer. 9784088dfSchristos * 2. Redistributions in binary form must reproduce the above copyright 10784088dfSchristos * notice, this list of conditions and the following disclaimer in the 11784088dfSchristos * documentation and/or other materials provided with the distribution. 12784088dfSchristos * 3. Neither the name of the project nor the names of its contributors 13784088dfSchristos * may be used to endorse or promote products derived from this software 14784088dfSchristos * without specific prior written permission. 15784088dfSchristos * 16784088dfSchristos * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 17784088dfSchristos * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18784088dfSchristos * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19784088dfSchristos * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 20784088dfSchristos * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21784088dfSchristos * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22784088dfSchristos * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23784088dfSchristos * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24784088dfSchristos * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25784088dfSchristos * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26784088dfSchristos * SUCH DAMAGE. 27784088dfSchristos */ 28784088dfSchristos 29fdccd7e4Schristos #include <sys/cdefs.h> 30fdccd7e4Schristos #ifndef lint 31*26ba0b50Schristos __RCSID("$NetBSD: print-hncp.c,v 1.8 2024/09/02 16:15:31 christos Exp $"); 32fdccd7e4Schristos #endif 33fdccd7e4Schristos 34dc860a36Sspz /* \summary: Home Networking Control Protocol (HNCP) printer */ 35dc860a36Sspz 36c74ad251Schristos #include <config.h> 37784088dfSchristos 38c74ad251Schristos #include "netdissect-stdinc.h" 39784088dfSchristos 40784088dfSchristos #include <string.h> 41784088dfSchristos 42784088dfSchristos #include "netdissect.h" 43784088dfSchristos #include "addrtoname.h" 44784088dfSchristos #include "extract.h" 45784088dfSchristos 46784088dfSchristos static void 47784088dfSchristos hncp_print_rec(netdissect_options *ndo, 48784088dfSchristos const u_char *cp, u_int length, int indent); 49784088dfSchristos 50784088dfSchristos void 51784088dfSchristos hncp_print(netdissect_options *ndo, 52784088dfSchristos const u_char *cp, u_int length) 53784088dfSchristos { 54c74ad251Schristos ndo->ndo_protocol = "hncp"; 55c74ad251Schristos ND_PRINT("hncp (%u)", length); 56784088dfSchristos hncp_print_rec(ndo, cp, length, 1); 57784088dfSchristos } 58784088dfSchristos 59784088dfSchristos /* RFC7787 */ 60784088dfSchristos #define DNCP_REQUEST_NETWORK_STATE 1 61784088dfSchristos #define DNCP_REQUEST_NODE_STATE 2 62784088dfSchristos #define DNCP_NODE_ENDPOINT 3 63784088dfSchristos #define DNCP_NETWORK_STATE 4 64784088dfSchristos #define DNCP_NODE_STATE 5 65784088dfSchristos #define DNCP_PEER 8 66784088dfSchristos #define DNCP_KEEP_ALIVE_INTERVAL 9 67784088dfSchristos #define DNCP_TRUST_VERDICT 10 68784088dfSchristos 69784088dfSchristos /* RFC7788 */ 70784088dfSchristos #define HNCP_HNCP_VERSION 32 71784088dfSchristos #define HNCP_EXTERNAL_CONNECTION 33 72784088dfSchristos #define HNCP_DELEGATED_PREFIX 34 73784088dfSchristos #define HNCP_PREFIX_POLICY 43 74817e9a7eSchristos #define HNCP_DHCPV4_DATA 37 /* This is correct, see RFC 7788 Errata ID 5113. */ 75817e9a7eSchristos #define HNCP_DHCPV6_DATA 38 /* idem */ 76784088dfSchristos #define HNCP_ASSIGNED_PREFIX 35 77784088dfSchristos #define HNCP_NODE_ADDRESS 36 78784088dfSchristos #define HNCP_DNS_DELEGATED_ZONE 39 79784088dfSchristos #define HNCP_DOMAIN_NAME 40 80784088dfSchristos #define HNCP_NODE_NAME 41 81784088dfSchristos #define HNCP_MANAGED_PSK 42 82784088dfSchristos 83784088dfSchristos /* See type_mask in hncp_print_rec below */ 84784088dfSchristos #define RANGE_DNCP_RESERVED 0x10000 85784088dfSchristos #define RANGE_HNCP_UNASSIGNED 0x10001 86784088dfSchristos #define RANGE_DNCP_PRIVATE_USE 0x10002 87784088dfSchristos #define RANGE_DNCP_FUTURE_USE 0x10003 88784088dfSchristos 89784088dfSchristos static const struct tok type_values[] = { 90784088dfSchristos { DNCP_REQUEST_NETWORK_STATE, "Request network state" }, 91784088dfSchristos { DNCP_REQUEST_NODE_STATE, "Request node state" }, 92784088dfSchristos { DNCP_NODE_ENDPOINT, "Node endpoint" }, 93784088dfSchristos { DNCP_NETWORK_STATE, "Network state" }, 94784088dfSchristos { DNCP_NODE_STATE, "Node state" }, 95784088dfSchristos { DNCP_PEER, "Peer" }, 96784088dfSchristos { DNCP_KEEP_ALIVE_INTERVAL, "Keep-alive interval" }, 97784088dfSchristos { DNCP_TRUST_VERDICT, "Trust-Verdict" }, 98784088dfSchristos 99784088dfSchristos { HNCP_HNCP_VERSION, "HNCP-Version" }, 100784088dfSchristos { HNCP_EXTERNAL_CONNECTION, "External-Connection" }, 101784088dfSchristos { HNCP_DELEGATED_PREFIX, "Delegated-Prefix" }, 102784088dfSchristos { HNCP_PREFIX_POLICY, "Prefix-Policy" }, 103784088dfSchristos { HNCP_DHCPV4_DATA, "DHCPv4-Data" }, 104784088dfSchristos { HNCP_DHCPV6_DATA, "DHCPv6-Data" }, 105784088dfSchristos { HNCP_ASSIGNED_PREFIX, "Assigned-Prefix" }, 106784088dfSchristos { HNCP_NODE_ADDRESS, "Node-Address" }, 107784088dfSchristos { HNCP_DNS_DELEGATED_ZONE, "DNS-Delegated-Zone" }, 108784088dfSchristos { HNCP_DOMAIN_NAME, "Domain-Name" }, 109784088dfSchristos { HNCP_NODE_NAME, "Node-Name" }, 110784088dfSchristos { HNCP_MANAGED_PSK, "Managed-PSK" }, 111784088dfSchristos 112784088dfSchristos { RANGE_DNCP_RESERVED, "Reserved" }, 113784088dfSchristos { RANGE_HNCP_UNASSIGNED, "Unassigned" }, 114784088dfSchristos { RANGE_DNCP_PRIVATE_USE, "Private use" }, 115784088dfSchristos { RANGE_DNCP_FUTURE_USE, "Future use" }, 116784088dfSchristos 117784088dfSchristos { 0, NULL} 118784088dfSchristos }; 119784088dfSchristos 120784088dfSchristos #define DH4OPT_DNS_SERVERS 6 /* RFC2132 */ 121784088dfSchristos #define DH4OPT_NTP_SERVERS 42 /* RFC2132 */ 122784088dfSchristos #define DH4OPT_DOMAIN_SEARCH 119 /* RFC3397 */ 123784088dfSchristos 124784088dfSchristos static const struct tok dh4opt_str[] = { 125784088dfSchristos { DH4OPT_DNS_SERVERS, "DNS-server" }, 126784088dfSchristos { DH4OPT_NTP_SERVERS, "NTP-server"}, 127784088dfSchristos { DH4OPT_DOMAIN_SEARCH, "DNS-search" }, 128784088dfSchristos { 0, NULL } 129784088dfSchristos }; 130784088dfSchristos 131784088dfSchristos #define DH6OPT_DNS_SERVERS 23 /* RFC3646 */ 132784088dfSchristos #define DH6OPT_DOMAIN_LIST 24 /* RFC3646 */ 133784088dfSchristos #define DH6OPT_SNTP_SERVERS 31 /* RFC4075 */ 134784088dfSchristos 135784088dfSchristos static const struct tok dh6opt_str[] = { 136784088dfSchristos { DH6OPT_DNS_SERVERS, "DNS-server" }, 137784088dfSchristos { DH6OPT_DOMAIN_LIST, "DNS-search-list" }, 138784088dfSchristos { DH6OPT_SNTP_SERVERS, "SNTP-servers" }, 139784088dfSchristos { 0, NULL } 140784088dfSchristos }; 141784088dfSchristos 142dc860a36Sspz /* 143dc860a36Sspz * For IPv4-mapped IPv6 addresses, length of the prefix that precedes 144dc860a36Sspz * the 4 bytes of IPv4 address at the end of the IPv6 address. 145dc860a36Sspz */ 146dc860a36Sspz #define IPV4_MAPPED_HEADING_LEN 12 147dc860a36Sspz 148dc860a36Sspz /* 149dc860a36Sspz * Is an IPv6 address an IPv4-mapped address? 150dc860a36Sspz */ 151c74ad251Schristos static int 152dc860a36Sspz is_ipv4_mapped_address(const u_char *addr) 153dc860a36Sspz { 154dc860a36Sspz /* The value of the prefix */ 155dc860a36Sspz static const u_char ipv4_mapped_heading[IPV4_MAPPED_HEADING_LEN] = 156dc860a36Sspz { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }; 157dc860a36Sspz 158dc860a36Sspz return memcmp(addr, ipv4_mapped_heading, IPV4_MAPPED_HEADING_LEN) == 0; 159dc860a36Sspz } 160dc860a36Sspz 161784088dfSchristos static const char * 162c74ad251Schristos format_nid(netdissect_options *ndo, const u_char *data) 163784088dfSchristos { 164817e9a7eSchristos static char buf[4][sizeof("01:01:01:01")]; 165784088dfSchristos static int i = 0; 166784088dfSchristos i = (i + 1) % 4; 167817e9a7eSchristos snprintf(buf[i], sizeof(buf[i]), "%02x:%02x:%02x:%02x", 168c74ad251Schristos GET_U_1(data), GET_U_1(data + 1), GET_U_1(data + 2), 169c74ad251Schristos GET_U_1(data + 3)); 170784088dfSchristos return buf[i]; 171784088dfSchristos } 172784088dfSchristos 173784088dfSchristos static const char * 174c74ad251Schristos format_256(netdissect_options *ndo, const u_char *data) 175784088dfSchristos { 176817e9a7eSchristos static char buf[4][sizeof("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")]; 177784088dfSchristos static int i = 0; 178784088dfSchristos i = (i + 1) % 4; 179817e9a7eSchristos snprintf(buf[i], sizeof(buf[i]), "%016" PRIx64 "%016" PRIx64 "%016" PRIx64 "%016" PRIx64, 180c74ad251Schristos GET_BE_U_8(data), 181c74ad251Schristos GET_BE_U_8(data + 8), 182c74ad251Schristos GET_BE_U_8(data + 16), 183c74ad251Schristos GET_BE_U_8(data + 24) 184784088dfSchristos ); 185784088dfSchristos return buf[i]; 186784088dfSchristos } 187784088dfSchristos 188784088dfSchristos static const char * 189dc860a36Sspz format_interval(const uint32_t n) 190784088dfSchristos { 191dc860a36Sspz static char buf[4][sizeof("0000000.000s")]; 192784088dfSchristos static int i = 0; 193784088dfSchristos i = (i + 1) % 4; 194dc860a36Sspz snprintf(buf[i], sizeof(buf[i]), "%u.%03us", n / 1000, n % 1000); 195784088dfSchristos return buf[i]; 196784088dfSchristos } 197784088dfSchristos 198784088dfSchristos static const char * 199784088dfSchristos format_ip6addr(netdissect_options *ndo, const u_char *cp) 200784088dfSchristos { 201dc860a36Sspz if (is_ipv4_mapped_address(cp)) 202c74ad251Schristos return GET_IPADDR_STRING(cp + IPV4_MAPPED_HEADING_LEN); 203784088dfSchristos else 204c74ad251Schristos return GET_IP6ADDR_STRING(cp); 205784088dfSchristos } 206784088dfSchristos 207784088dfSchristos static int 208784088dfSchristos print_prefix(netdissect_options *ndo, const u_char *prefix, u_int max_length) 209784088dfSchristos { 210784088dfSchristos int plenbytes; 211dc860a36Sspz char buf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::/128")]; 212dc860a36Sspz 213c74ad251Schristos if (GET_U_1(prefix) >= 96 && max_length >= IPV4_MAPPED_HEADING_LEN + 1 && 214c74ad251Schristos is_ipv4_mapped_address(prefix + 1)) { 215c74ad251Schristos nd_ipv4 addr; 216dc860a36Sspz u_int plen; 217dc860a36Sspz 218c74ad251Schristos plen = GET_U_1(prefix) - 96; 219dc860a36Sspz if (32 < plen) 220dc860a36Sspz return -1; 221dc860a36Sspz max_length -= 1; 222dc860a36Sspz 223dc860a36Sspz memset(&addr, 0, sizeof(addr)); 224dc860a36Sspz plenbytes = (plen + 7) / 8; 225dc860a36Sspz if (max_length < (u_int)plenbytes + IPV4_MAPPED_HEADING_LEN) 226dc860a36Sspz return -3; 227c74ad251Schristos memcpy(&addr, prefix + IPV4_MAPPED_HEADING_LEN + 1, plenbytes); 228dc860a36Sspz if (plen % 8) { 229dc860a36Sspz ((u_char *)&addr)[plenbytes - 1] &= 230dc860a36Sspz ((0xff00 >> (plen % 8)) & 0xff); 231dc860a36Sspz } 232c74ad251Schristos snprintf(buf, sizeof(buf), "%s/%u", ipaddr_string(ndo, (const u_char *)&addr), plen); /* local buffer, not packet data; don't use GET_IPADDR_STRING() */ 233dc860a36Sspz plenbytes += 1 + IPV4_MAPPED_HEADING_LEN; 234784088dfSchristos } else { 235dc860a36Sspz plenbytes = decode_prefix6(ndo, prefix, max_length, buf, sizeof(buf)); 236817e9a7eSchristos if (plenbytes < 0) 237817e9a7eSchristos return plenbytes; 238784088dfSchristos } 239784088dfSchristos 240c74ad251Schristos ND_PRINT("%s", buf); 241784088dfSchristos return plenbytes; 242784088dfSchristos } 243784088dfSchristos 244784088dfSchristos static int 245784088dfSchristos print_dns_label(netdissect_options *ndo, 246784088dfSchristos const u_char *cp, u_int max_length, int print) 247784088dfSchristos { 248784088dfSchristos u_int length = 0; 249784088dfSchristos while (length < max_length) { 250c74ad251Schristos u_int lab_length = GET_U_1(cp + length); 251c74ad251Schristos length++; 252784088dfSchristos if (lab_length == 0) 253784088dfSchristos return (int)length; 254784088dfSchristos if (length > 1 && print) 255c74ad251Schristos ND_PRINT("."); 256784088dfSchristos if (length+lab_length > max_length) { 257784088dfSchristos if (print) 258c74ad251Schristos nd_printjnp(ndo, cp+length, max_length-length); 259784088dfSchristos break; 260784088dfSchristos } 261784088dfSchristos if (print) 262c74ad251Schristos nd_printjnp(ndo, cp+length, lab_length); 263784088dfSchristos length += lab_length; 264784088dfSchristos } 265784088dfSchristos if (print) 266c74ad251Schristos ND_PRINT("[|DNS]"); 267784088dfSchristos return -1; 268784088dfSchristos } 269784088dfSchristos 270784088dfSchristos static int 271784088dfSchristos dhcpv4_print(netdissect_options *ndo, 272784088dfSchristos const u_char *cp, u_int length, int indent) 273784088dfSchristos { 274784088dfSchristos u_int i, t; 275c74ad251Schristos const uint8_t *tlv, *value; 276784088dfSchristos uint8_t type, optlen; 277784088dfSchristos 278784088dfSchristos i = 0; 279784088dfSchristos while (i < length) { 28072c96ff3Schristos if (i + 2 > length) 28172c96ff3Schristos return -1; 282784088dfSchristos tlv = cp + i; 283c74ad251Schristos type = GET_U_1(tlv); 284c74ad251Schristos optlen = GET_U_1(tlv + 1); 285784088dfSchristos value = tlv + 2; 286784088dfSchristos 287c74ad251Schristos ND_PRINT("\n"); 288784088dfSchristos for (t = indent; t > 0; t--) 289c74ad251Schristos ND_PRINT("\t"); 290784088dfSchristos 291c74ad251Schristos ND_PRINT("%s", tok2str(dh4opt_str, "Unknown", type)); 292c74ad251Schristos ND_PRINT(" (%u)", optlen + 2 ); 29372c96ff3Schristos if (i + 2 + optlen > length) 29472c96ff3Schristos return -1; 295784088dfSchristos 296784088dfSchristos switch (type) { 297784088dfSchristos case DH4OPT_DNS_SERVERS: 298784088dfSchristos case DH4OPT_NTP_SERVERS: { 299784088dfSchristos if (optlen < 4 || optlen % 4 != 0) { 300784088dfSchristos return -1; 301784088dfSchristos } 302784088dfSchristos for (t = 0; t < optlen; t += 4) 303c74ad251Schristos ND_PRINT(" %s", GET_IPADDR_STRING(value + t)); 304784088dfSchristos } 305784088dfSchristos break; 306784088dfSchristos case DH4OPT_DOMAIN_SEARCH: { 307784088dfSchristos const u_char *tp = value; 308784088dfSchristos while (tp < value + optlen) { 309c74ad251Schristos ND_PRINT(" "); 310c74ad251Schristos if ((tp = fqdn_print(ndo, tp, value + optlen)) == NULL) 311784088dfSchristos return -1; 312784088dfSchristos } 313784088dfSchristos } 314784088dfSchristos break; 315784088dfSchristos } 316784088dfSchristos 317784088dfSchristos i += 2 + optlen; 318784088dfSchristos } 319784088dfSchristos return 0; 320784088dfSchristos } 321784088dfSchristos 322784088dfSchristos static int 323784088dfSchristos dhcpv6_print(netdissect_options *ndo, 324784088dfSchristos const u_char *cp, u_int length, int indent) 325784088dfSchristos { 326784088dfSchristos u_int i, t; 327784088dfSchristos const u_char *tlv, *value; 328784088dfSchristos uint16_t type, optlen; 329784088dfSchristos 330784088dfSchristos i = 0; 331784088dfSchristos while (i < length) { 33272c96ff3Schristos if (i + 4 > length) 33372c96ff3Schristos return -1; 334784088dfSchristos tlv = cp + i; 335c74ad251Schristos type = GET_BE_U_2(tlv); 336c74ad251Schristos optlen = GET_BE_U_2(tlv + 2); 337784088dfSchristos value = tlv + 4; 338784088dfSchristos 339c74ad251Schristos ND_PRINT("\n"); 340784088dfSchristos for (t = indent; t > 0; t--) 341c74ad251Schristos ND_PRINT("\t"); 342784088dfSchristos 343c74ad251Schristos ND_PRINT("%s", tok2str(dh6opt_str, "Unknown", type)); 344c74ad251Schristos ND_PRINT(" (%u)", optlen + 4 ); 34572c96ff3Schristos if (i + 4 + optlen > length) 34672c96ff3Schristos return -1; 347784088dfSchristos 348784088dfSchristos switch (type) { 349784088dfSchristos case DH6OPT_DNS_SERVERS: 350784088dfSchristos case DH6OPT_SNTP_SERVERS: { 351784088dfSchristos if (optlen % 16 != 0) { 352c74ad251Schristos nd_print_invalid(ndo); 353784088dfSchristos return -1; 354784088dfSchristos } 355784088dfSchristos for (t = 0; t < optlen; t += 16) 356c74ad251Schristos ND_PRINT(" %s", GET_IP6ADDR_STRING(value + t)); 357784088dfSchristos } 358784088dfSchristos break; 359784088dfSchristos case DH6OPT_DOMAIN_LIST: { 360784088dfSchristos const u_char *tp = value; 361784088dfSchristos while (tp < value + optlen) { 362c74ad251Schristos ND_PRINT(" "); 363c74ad251Schristos if ((tp = fqdn_print(ndo, tp, value + optlen)) == NULL) 364784088dfSchristos return -1; 365784088dfSchristos } 366784088dfSchristos } 367784088dfSchristos break; 368784088dfSchristos } 369784088dfSchristos 370784088dfSchristos i += 4 + optlen; 371784088dfSchristos } 372784088dfSchristos return 0; 373784088dfSchristos } 374784088dfSchristos 375784088dfSchristos /* Determine in-line mode */ 376784088dfSchristos static int 377784088dfSchristos is_in_line(netdissect_options *ndo, int indent) 378784088dfSchristos { 379784088dfSchristos return indent - 1 >= ndo->ndo_vflag && ndo->ndo_vflag < 3; 380784088dfSchristos } 381784088dfSchristos 382784088dfSchristos static void 383784088dfSchristos print_type_in_line(netdissect_options *ndo, 384784088dfSchristos uint32_t type, int count, int indent, int *first_one) 385784088dfSchristos { 386784088dfSchristos if (count > 0) { 387784088dfSchristos if (*first_one) { 388784088dfSchristos *first_one = 0; 389784088dfSchristos if (indent > 1) { 390784088dfSchristos u_int t; 391c74ad251Schristos ND_PRINT("\n"); 392784088dfSchristos for (t = indent; t > 0; t--) 393c74ad251Schristos ND_PRINT("\t"); 394784088dfSchristos } else { 395c74ad251Schristos ND_PRINT(" "); 396784088dfSchristos } 397784088dfSchristos } else { 398c74ad251Schristos ND_PRINT(", "); 399784088dfSchristos } 400c74ad251Schristos ND_PRINT("%s", tok2str(type_values, "Easter Egg", type)); 401784088dfSchristos if (count > 1) 402c74ad251Schristos ND_PRINT(" (x%d)", count); 403784088dfSchristos } 404784088dfSchristos } 405784088dfSchristos 406c74ad251Schristos static void 407784088dfSchristos hncp_print_rec(netdissect_options *ndo, 408784088dfSchristos const u_char *cp, u_int length, int indent) 409784088dfSchristos { 410784088dfSchristos const int in_line = is_in_line(ndo, indent); 411784088dfSchristos int first_one = 1; 412784088dfSchristos 413784088dfSchristos u_int i, t; 414784088dfSchristos 415dc860a36Sspz uint32_t last_type_mask = 0xffffffffU; 416784088dfSchristos int last_type_count = -1; 417784088dfSchristos 418c74ad251Schristos const uint8_t *tlv, *value; 419784088dfSchristos uint16_t type, bodylen; 420784088dfSchristos uint32_t type_mask; 421784088dfSchristos 422784088dfSchristos i = 0; 423784088dfSchristos while (i < length) { 424784088dfSchristos tlv = cp + i; 425784088dfSchristos 426784088dfSchristos if (!in_line) { 427c74ad251Schristos ND_PRINT("\n"); 428784088dfSchristos for (t = indent; t > 0; t--) 429c74ad251Schristos ND_PRINT("\t"); 430784088dfSchristos } 431784088dfSchristos 432c74ad251Schristos ND_TCHECK_4(tlv); 433784088dfSchristos if (i + 4 > length) 434784088dfSchristos goto invalid; 435784088dfSchristos 436c74ad251Schristos type = GET_BE_U_2(tlv); 437c74ad251Schristos bodylen = GET_BE_U_2(tlv + 2); 438784088dfSchristos value = tlv + 4; 439c74ad251Schristos ND_TCHECK_LEN(value, bodylen); 440784088dfSchristos if (i + bodylen + 4 > length) 441784088dfSchristos goto invalid; 442784088dfSchristos 443784088dfSchristos type_mask = 444784088dfSchristos (type == 0) ? RANGE_DNCP_RESERVED: 445784088dfSchristos (44 <= type && type <= 511) ? RANGE_HNCP_UNASSIGNED: 446784088dfSchristos (768 <= type && type <= 1023) ? RANGE_DNCP_PRIVATE_USE: 447784088dfSchristos RANGE_DNCP_FUTURE_USE; 448784088dfSchristos if (type == 6 || type == 7) 449784088dfSchristos type_mask = RANGE_DNCP_FUTURE_USE; 450784088dfSchristos 451784088dfSchristos /* defined types */ 452784088dfSchristos { 453784088dfSchristos t = 0; 454784088dfSchristos while (1) { 455784088dfSchristos u_int key = type_values[t++].v; 456784088dfSchristos if (key > 0xffff) 457784088dfSchristos break; 458784088dfSchristos if (key == type) { 459784088dfSchristos type_mask = type; 460784088dfSchristos break; 461784088dfSchristos } 462784088dfSchristos } 463784088dfSchristos } 464784088dfSchristos 465784088dfSchristos if (in_line) { 466784088dfSchristos if (last_type_mask == type_mask) { 467784088dfSchristos last_type_count++; 468784088dfSchristos } else { 469784088dfSchristos print_type_in_line(ndo, last_type_mask, last_type_count, indent, &first_one); 470784088dfSchristos last_type_mask = type_mask; 471784088dfSchristos last_type_count = 1; 472784088dfSchristos } 473784088dfSchristos 474784088dfSchristos goto skip_multiline; 475784088dfSchristos } 476784088dfSchristos 477c74ad251Schristos ND_PRINT("%s", tok2str(type_values, "Easter Egg (42)", type_mask) ); 478784088dfSchristos if (type_mask > 0xffff) 479c74ad251Schristos ND_PRINT(": type=%u", type ); 480c74ad251Schristos ND_PRINT(" (%u)", bodylen + 4 ); 481784088dfSchristos 482784088dfSchristos switch (type_mask) { 483784088dfSchristos 484784088dfSchristos case DNCP_REQUEST_NETWORK_STATE: { 485784088dfSchristos if (bodylen != 0) 486c74ad251Schristos nd_print_invalid(ndo); 487784088dfSchristos } 488784088dfSchristos break; 489784088dfSchristos 490784088dfSchristos case DNCP_REQUEST_NODE_STATE: { 491784088dfSchristos const char *node_identifier; 492dc860a36Sspz if (bodylen != 4) { 493c74ad251Schristos nd_print_invalid(ndo); 494dc860a36Sspz break; 495dc860a36Sspz } 496c74ad251Schristos node_identifier = format_nid(ndo, value); 497c74ad251Schristos ND_PRINT(" NID: %s", node_identifier); 498784088dfSchristos } 499784088dfSchristos break; 500784088dfSchristos 501784088dfSchristos case DNCP_NODE_ENDPOINT: { 502784088dfSchristos const char *node_identifier; 503784088dfSchristos uint32_t endpoint_identifier; 504dc860a36Sspz if (bodylen != 8) { 505c74ad251Schristos nd_print_invalid(ndo); 506dc860a36Sspz break; 507dc860a36Sspz } 508c74ad251Schristos node_identifier = format_nid(ndo, value); 509c74ad251Schristos endpoint_identifier = GET_BE_U_4(value + 4); 510c74ad251Schristos ND_PRINT(" NID: %s EPID: %08x", 511784088dfSchristos node_identifier, 512784088dfSchristos endpoint_identifier 513c74ad251Schristos ); 514784088dfSchristos } 515784088dfSchristos break; 516784088dfSchristos 517784088dfSchristos case DNCP_NETWORK_STATE: { 518784088dfSchristos uint64_t hash; 519dc860a36Sspz if (bodylen != 8) { 520c74ad251Schristos nd_print_invalid(ndo); 521dc860a36Sspz break; 522dc860a36Sspz } 523c74ad251Schristos hash = GET_BE_U_8(value); 524c74ad251Schristos ND_PRINT(" hash: %016" PRIx64, hash); 525784088dfSchristos } 526784088dfSchristos break; 527784088dfSchristos 528784088dfSchristos case DNCP_NODE_STATE: { 529dc860a36Sspz const char *node_identifier, *interval; 530784088dfSchristos uint32_t sequence_number; 531784088dfSchristos uint64_t hash; 532dc860a36Sspz if (bodylen < 20) { 533c74ad251Schristos nd_print_invalid(ndo); 534dc860a36Sspz break; 535dc860a36Sspz } 536c74ad251Schristos node_identifier = format_nid(ndo, value); 537c74ad251Schristos sequence_number = GET_BE_U_4(value + 4); 538c74ad251Schristos interval = format_interval(GET_BE_U_4(value + 8)); 539c74ad251Schristos hash = GET_BE_U_8(value + 12); 540c74ad251Schristos ND_PRINT(" NID: %s seqno: %u %s hash: %016" PRIx64, 541784088dfSchristos node_identifier, 542784088dfSchristos sequence_number, 543dc860a36Sspz interval, 544784088dfSchristos hash 545c74ad251Schristos ); 546784088dfSchristos hncp_print_rec(ndo, value+20, bodylen-20, indent+1); 547784088dfSchristos } 548784088dfSchristos break; 549784088dfSchristos 550784088dfSchristos case DNCP_PEER: { 551784088dfSchristos const char *peer_node_identifier; 552784088dfSchristos uint32_t peer_endpoint_identifier, endpoint_identifier; 553dc860a36Sspz if (bodylen != 12) { 554c74ad251Schristos nd_print_invalid(ndo); 555dc860a36Sspz break; 556dc860a36Sspz } 557c74ad251Schristos peer_node_identifier = format_nid(ndo, value); 558c74ad251Schristos peer_endpoint_identifier = GET_BE_U_4(value + 4); 559c74ad251Schristos endpoint_identifier = GET_BE_U_4(value + 8); 560c74ad251Schristos ND_PRINT(" Peer-NID: %s Peer-EPID: %08x Local-EPID: %08x", 561784088dfSchristos peer_node_identifier, 562784088dfSchristos peer_endpoint_identifier, 563784088dfSchristos endpoint_identifier 564c74ad251Schristos ); 565784088dfSchristos } 566784088dfSchristos break; 567784088dfSchristos 568784088dfSchristos case DNCP_KEEP_ALIVE_INTERVAL: { 569784088dfSchristos uint32_t endpoint_identifier; 570784088dfSchristos const char *interval; 571dc860a36Sspz if (bodylen < 8) { 572c74ad251Schristos nd_print_invalid(ndo); 573dc860a36Sspz break; 574dc860a36Sspz } 575c74ad251Schristos endpoint_identifier = GET_BE_U_4(value); 576c74ad251Schristos interval = format_interval(GET_BE_U_4(value + 4)); 577c74ad251Schristos ND_PRINT(" EPID: %08x Interval: %s", 578784088dfSchristos endpoint_identifier, 579784088dfSchristos interval 580c74ad251Schristos ); 581784088dfSchristos } 582784088dfSchristos break; 583784088dfSchristos 584784088dfSchristos case DNCP_TRUST_VERDICT: { 585dc860a36Sspz if (bodylen <= 36) { 586c74ad251Schristos nd_print_invalid(ndo); 587dc860a36Sspz break; 588dc860a36Sspz } 589c74ad251Schristos ND_PRINT(" Verdict: %u Fingerprint: %s Common Name: ", 590c74ad251Schristos GET_U_1(value), 591c74ad251Schristos format_256(ndo, value + 4)); 592c74ad251Schristos nd_printjnp(ndo, value + 36, bodylen - 36); 593784088dfSchristos } 594784088dfSchristos break; 595784088dfSchristos 596784088dfSchristos case HNCP_HNCP_VERSION: { 597784088dfSchristos uint16_t capabilities; 598784088dfSchristos uint8_t M, P, H, L; 599dc860a36Sspz if (bodylen < 5) { 600c74ad251Schristos nd_print_invalid(ndo); 601dc860a36Sspz break; 602dc860a36Sspz } 603c74ad251Schristos capabilities = GET_BE_U_2(value + 2); 604784088dfSchristos M = (uint8_t)((capabilities >> 12) & 0xf); 605784088dfSchristos P = (uint8_t)((capabilities >> 8) & 0xf); 606784088dfSchristos H = (uint8_t)((capabilities >> 4) & 0xf); 607784088dfSchristos L = (uint8_t)(capabilities & 0xf); 608c74ad251Schristos ND_PRINT(" M: %u P: %u H: %u L: %u User-agent: ", 609784088dfSchristos M, P, H, L 610c74ad251Schristos ); 611c74ad251Schristos nd_printjnp(ndo, value + 4, bodylen - 4); 612784088dfSchristos } 613784088dfSchristos break; 614784088dfSchristos 615784088dfSchristos case HNCP_EXTERNAL_CONNECTION: { 616784088dfSchristos /* Container TLV */ 617784088dfSchristos hncp_print_rec(ndo, value, bodylen, indent+1); 618784088dfSchristos } 619784088dfSchristos break; 620784088dfSchristos 621784088dfSchristos case HNCP_DELEGATED_PREFIX: { 622784088dfSchristos int l; 623c74ad251Schristos if (bodylen < 9 || bodylen < 9 + (GET_U_1(value + 8) + 7) / 8) { 624c74ad251Schristos nd_print_invalid(ndo); 625dc860a36Sspz break; 626dc860a36Sspz } 627c74ad251Schristos ND_PRINT(" VLSO: %s PLSO: %s Prefix: ", 628c74ad251Schristos format_interval(GET_BE_U_4(value)), 629c74ad251Schristos format_interval(GET_BE_U_4(value + 4)) 630c74ad251Schristos ); 631dc860a36Sspz l = print_prefix(ndo, value + 8, bodylen - 8); 632dc860a36Sspz if (l == -1) { 633c74ad251Schristos ND_PRINT("(length is invalid)"); 634dc860a36Sspz break; 635dc860a36Sspz } 636dc860a36Sspz if (l < 0) { 637dc860a36Sspz /* 638dc860a36Sspz * We've already checked that we've captured the 639dc860a36Sspz * entire TLV, based on its length, so this will 640dc860a36Sspz * either be -1, meaning "the prefix length is 641dc860a36Sspz * greater than the longest possible address of 642dc860a36Sspz * that type" (i.e., > 32 for IPv4 or > 128 for 643dc860a36Sspz * IPv6", or -3, meaning "the prefix runs past 644dc860a36Sspz * the end of the TLV". 645dc860a36Sspz */ 646c74ad251Schristos nd_print_invalid(ndo); 647dc860a36Sspz break; 648dc860a36Sspz } 649784088dfSchristos l += 8 + (-l & 3); 650784088dfSchristos 651784088dfSchristos if (bodylen >= l) 652784088dfSchristos hncp_print_rec(ndo, value + l, bodylen - l, indent+1); 653784088dfSchristos } 654784088dfSchristos break; 655784088dfSchristos 656784088dfSchristos case HNCP_PREFIX_POLICY: { 657784088dfSchristos uint8_t policy; 658dc860a36Sspz int l; 659dc860a36Sspz if (bodylen < 1) { 660c74ad251Schristos nd_print_invalid(ndo); 661dc860a36Sspz break; 662dc860a36Sspz } 663c74ad251Schristos policy = GET_U_1(value); 664c74ad251Schristos ND_PRINT(" type: "); 665784088dfSchristos if (policy == 0) { 666dc860a36Sspz if (bodylen != 1) { 667c74ad251Schristos nd_print_invalid(ndo); 668dc860a36Sspz break; 669dc860a36Sspz } 670c74ad251Schristos ND_PRINT("Internet connectivity"); 671784088dfSchristos } else if (policy >= 1 && policy <= 128) { 672c74ad251Schristos ND_PRINT("Dest-Prefix: "); 673dc860a36Sspz l = print_prefix(ndo, value, bodylen); 674dc860a36Sspz if (l == -1) { 675c74ad251Schristos ND_PRINT("(length is invalid)"); 676dc860a36Sspz break; 677dc860a36Sspz } 678dc860a36Sspz if (l < 0) { 679dc860a36Sspz /* 680dc860a36Sspz * We've already checked that we've captured the 681dc860a36Sspz * entire TLV, based on its length, so this will 682dc860a36Sspz * either be -1, meaning "the prefix length is 683dc860a36Sspz * greater than the longest possible address of 684dc860a36Sspz * that type" (i.e., > 32 for IPv4 or > 128 for 685dc860a36Sspz * IPv6", or -3, meaning "the prefix runs past 686dc860a36Sspz * the end of the TLV". 687dc860a36Sspz */ 688c74ad251Schristos nd_print_invalid(ndo); 689dc860a36Sspz break; 690dc860a36Sspz } 691784088dfSchristos } else if (policy == 129) { 692c74ad251Schristos ND_PRINT("DNS domain: "); 693784088dfSchristos print_dns_label(ndo, value+1, bodylen-1, 1); 694784088dfSchristos } else if (policy == 130) { 695c74ad251Schristos ND_PRINT("Opaque UTF-8: "); 696c74ad251Schristos nd_printjnp(ndo, value + 1, bodylen - 1); 697784088dfSchristos } else if (policy == 131) { 698dc860a36Sspz if (bodylen != 1) { 699c74ad251Schristos nd_print_invalid(ndo); 700dc860a36Sspz break; 701dc860a36Sspz } 702c74ad251Schristos ND_PRINT("Restrictive assignment"); 703784088dfSchristos } else if (policy >= 132) { 704c74ad251Schristos ND_PRINT("Unknown (%u)", policy); /* Reserved for future additions */ 705784088dfSchristos } 706784088dfSchristos } 707784088dfSchristos break; 708784088dfSchristos 709784088dfSchristos case HNCP_DHCPV4_DATA: { 710dc860a36Sspz if (bodylen == 0) { 711c74ad251Schristos nd_print_invalid(ndo); 712dc860a36Sspz break; 713dc860a36Sspz } 714784088dfSchristos if (dhcpv4_print(ndo, value, bodylen, indent+1) != 0) 715784088dfSchristos goto invalid; 716784088dfSchristos } 717784088dfSchristos break; 718784088dfSchristos 719784088dfSchristos case HNCP_DHCPV6_DATA: { 720dc860a36Sspz if (bodylen == 0) { 721c74ad251Schristos nd_print_invalid(ndo); 722dc860a36Sspz break; 723dc860a36Sspz } 724dc860a36Sspz if (dhcpv6_print(ndo, value, bodylen, indent+1) != 0) { 725c74ad251Schristos nd_print_invalid(ndo); 726dc860a36Sspz break; 727dc860a36Sspz } 728784088dfSchristos } 729784088dfSchristos break; 730784088dfSchristos 731784088dfSchristos case HNCP_ASSIGNED_PREFIX: { 732784088dfSchristos uint8_t prty; 733784088dfSchristos int l; 734c74ad251Schristos if (bodylen < 6 || bodylen < 6 + (GET_U_1(value + 5) + 7) / 8) { 735c74ad251Schristos nd_print_invalid(ndo); 736dc860a36Sspz break; 737dc860a36Sspz } 738c74ad251Schristos prty = GET_U_1(value + 4) & 0xf; 739c74ad251Schristos ND_PRINT(" EPID: %08x Prty: %u", 740c74ad251Schristos GET_BE_U_4(value), 741784088dfSchristos prty 742c74ad251Schristos ); 743c74ad251Schristos ND_PRINT(" Prefix: "); 744dc860a36Sspz if ((l = print_prefix(ndo, value + 5, bodylen - 5)) < 0) { 745c74ad251Schristos nd_print_invalid(ndo); 746dc860a36Sspz break; 747dc860a36Sspz } 748784088dfSchristos l += 5; 749784088dfSchristos l += -l & 3; 750784088dfSchristos 751784088dfSchristos if (bodylen >= l) 752784088dfSchristos hncp_print_rec(ndo, value + l, bodylen - l, indent+1); 753784088dfSchristos } 754784088dfSchristos break; 755784088dfSchristos 756784088dfSchristos case HNCP_NODE_ADDRESS: { 757784088dfSchristos uint32_t endpoint_identifier; 758784088dfSchristos const char *ip_address; 759dc860a36Sspz if (bodylen < 20) { 760c74ad251Schristos nd_print_invalid(ndo); 761dc860a36Sspz break; 762dc860a36Sspz } 763c74ad251Schristos endpoint_identifier = GET_BE_U_4(value); 764784088dfSchristos ip_address = format_ip6addr(ndo, value + 4); 765c74ad251Schristos ND_PRINT(" EPID: %08x IP Address: %s", 766784088dfSchristos endpoint_identifier, 767784088dfSchristos ip_address 768c74ad251Schristos ); 769784088dfSchristos 770784088dfSchristos hncp_print_rec(ndo, value + 20, bodylen - 20, indent+1); 771784088dfSchristos } 772784088dfSchristos break; 773784088dfSchristos 774784088dfSchristos case HNCP_DNS_DELEGATED_ZONE: { 775784088dfSchristos const char *ip_address; 776784088dfSchristos int len; 777dc860a36Sspz if (bodylen < 17) { 778c74ad251Schristos nd_print_invalid(ndo); 779dc860a36Sspz break; 780dc860a36Sspz } 781784088dfSchristos ip_address = format_ip6addr(ndo, value); 782c74ad251Schristos ND_PRINT(" IP-Address: %s %c%c%c ", 783784088dfSchristos ip_address, 784c74ad251Schristos (GET_U_1(value + 16) & 4) ? 'l' : '-', 785c74ad251Schristos (GET_U_1(value + 16) & 2) ? 'b' : '-', 786c74ad251Schristos (GET_U_1(value + 16) & 1) ? 's' : '-' 787c74ad251Schristos ); 788784088dfSchristos len = print_dns_label(ndo, value+17, bodylen-17, 1); 789dc860a36Sspz if (len < 0) { 790c74ad251Schristos nd_print_invalid(ndo); 791dc860a36Sspz break; 792dc860a36Sspz } 793784088dfSchristos len += 17; 794784088dfSchristos len += -len & 3; 795784088dfSchristos if (bodylen >= len) 796784088dfSchristos hncp_print_rec(ndo, value+len, bodylen-len, indent+1); 797784088dfSchristos } 798784088dfSchristos break; 799784088dfSchristos 800784088dfSchristos case HNCP_DOMAIN_NAME: { 801dc860a36Sspz if (bodylen == 0) { 802c74ad251Schristos nd_print_invalid(ndo); 803dc860a36Sspz break; 804dc860a36Sspz } 805c74ad251Schristos ND_PRINT(" Domain: "); 806784088dfSchristos print_dns_label(ndo, value, bodylen, 1); 807784088dfSchristos } 808784088dfSchristos break; 809784088dfSchristos 810784088dfSchristos case HNCP_NODE_NAME: { 811784088dfSchristos u_int l; 812dc860a36Sspz if (bodylen < 17) { 813c74ad251Schristos nd_print_invalid(ndo); 814dc860a36Sspz break; 815dc860a36Sspz } 816c74ad251Schristos l = GET_U_1(value + 16); 817dc860a36Sspz if (bodylen < 17 + l) { 818c74ad251Schristos nd_print_invalid(ndo); 819dc860a36Sspz break; 820dc860a36Sspz } 821c74ad251Schristos ND_PRINT(" IP-Address: %s Name: ", 822784088dfSchristos format_ip6addr(ndo, value) 823c74ad251Schristos ); 824784088dfSchristos if (l < 64) { 825c74ad251Schristos ND_PRINT("\""); 826c74ad251Schristos nd_printjnp(ndo, value + 17, l); 827c74ad251Schristos ND_PRINT("\""); 828784088dfSchristos } else { 829c74ad251Schristos nd_print_invalid(ndo); 830784088dfSchristos } 831784088dfSchristos l += 17; 832c74ad251Schristos l = roundup2(l, 4); 833784088dfSchristos if (bodylen >= l) 834784088dfSchristos hncp_print_rec(ndo, value + l, bodylen - l, indent+1); 835784088dfSchristos } 836784088dfSchristos break; 837784088dfSchristos 838784088dfSchristos case HNCP_MANAGED_PSK: { 839dc860a36Sspz if (bodylen < 32) { 840c74ad251Schristos nd_print_invalid(ndo); 841dc860a36Sspz break; 842dc860a36Sspz } 843c74ad251Schristos ND_PRINT(" PSK: %s", format_256(ndo, value)); 844784088dfSchristos hncp_print_rec(ndo, value + 32, bodylen - 32, indent+1); 845784088dfSchristos } 846784088dfSchristos break; 847784088dfSchristos 848784088dfSchristos case RANGE_DNCP_RESERVED: 849784088dfSchristos case RANGE_HNCP_UNASSIGNED: 850784088dfSchristos case RANGE_DNCP_PRIVATE_USE: 851784088dfSchristos case RANGE_DNCP_FUTURE_USE: 852784088dfSchristos break; 853784088dfSchristos 854784088dfSchristos } 855784088dfSchristos skip_multiline: 856784088dfSchristos 857c74ad251Schristos i += 4 + roundup2(bodylen, 4); 858784088dfSchristos } 859784088dfSchristos print_type_in_line(ndo, last_type_mask, last_type_count, indent, &first_one); 860784088dfSchristos 861784088dfSchristos return; 862784088dfSchristos 863784088dfSchristos trunc: 864c74ad251Schristos nd_print_trunc(ndo); 865784088dfSchristos return; 866784088dfSchristos 867784088dfSchristos invalid: 868c74ad251Schristos nd_print_invalid(ndo); 869784088dfSchristos } 870