#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void usage(void) { fprintf(stderr, "ndp6-pcap [-d] -i interface -p network/prefixlen...\n"); exit(1); } /* * list of networks proxied by this programme */ struct network { struct in6_addr prefix; int plen; } *proxiednets; int nbnet = 0; /* * NA payload, always including the ether address of the proxying interface */ struct adv { struct nd_neighbor_advert na; struct nd_opt_hdr lladdr_opt; struct ether_addr lladdr; /* no padding: sizeof(nd_opt_hdr + ether_addr) % 8 == 0 */ } __packed adv; struct ether_addr mylladdr; /* link-layer address of the outside interface */ uint32_t myscope; /* link-local scope of the outside interface */ int sender; /* socket for sending NA */ int debug = 0; void trace(int level, char *message, ...); uint16_t getscope(struct sockaddr_in6 *sin6); int samenet(struct in6_addr *a, struct in6_addr *b, int plen); int proxied(struct in6_addr *a); void reply_ns(u_char *args, const struct pcap_pkthdr *pkt, const u_char *frame); void reply_ns(u_char *args, const struct pcap_pkthdr *pkt, const u_char *frame) { int minsize = sizeof(struct ether_header) + sizeof(struct ip6_hdr) + sizeof(struct nd_neighbor_solicit); if (pkt->caplen < minsize) { trace(LOG_NOTICE, "received small packet: %d < %d\n", pkt->caplen, minsize); return; } struct ip6_hdr *ns_packet; ns_packet = (struct ip6_hdr *)(frame + sizeof(struct ether_header)); if (! IN6_IS_ADDR_LINKLOCAL(&ns_packet->ip6_src)) { trace(LOG_NOTICE, "received packet from non link-local sender"); return; } struct nd_neighbor_solicit *ns; ns = (struct nd_neighbor_solicit *)(frame + sizeof(struct ether_header) + sizeof(struct ip6_hdr)); if (debug) { char src[INET6_ADDRSTRLEN], tgt[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &ns_packet->ip6_src, src, INET6_ADDRSTRLEN); inet_ntop(AF_INET6, &ns->nd_ns_target, tgt, INET6_ADDRSTRLEN); trace(LOG_DEBUG, "received packet from %s, target %s", src, tgt); } if (! proxied(&ns->nd_ns_target)) { trace(LOG_DEBUG, "target address is not to be proxied"); return; } /* our reply */ bzero(&adv, sizeof(struct adv)); adv.na.nd_na_type = ND_NEIGHBOR_ADVERT; adv.na.nd_na_flags_reserved = ND_NA_FLAG_SOLICITED; adv.na.nd_na_target = ns->nd_ns_target; adv.lladdr_opt.nd_opt_type = ND_OPT_TARGET_LINKADDR; adv.lladdr_opt.nd_opt_len = 1; /* sizeof lladdr in octets */ adv.lladdr = mylladdr; struct sockaddr_in6 dstsaddr; bzero(&dstsaddr, sizeof(struct sockaddr_in6)); dstsaddr.sin6_family = AF_INET6; dstsaddr.sin6_addr = ns_packet->ip6_src; dstsaddr.sin6_scope_id = myscope; if (sendto(sender, &adv, sizeof(struct adv), 0, (struct sockaddr *)&dstsaddr, sizeof(struct sockaddr_in6)) < 0) trace(LOG_ERR, "error sending reply: %m"); else trace(LOG_DEBUG, "reply sent"); } uint8_t pl2m[8] = { 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe }; int samenet(struct in6_addr *a, struct in6_addr *b, int plen) { if (memcmp(a, b, plen / 8)) return 0; uint8_t m = pl2m[plen % 8]; return (a->s6_addr[plen / 8] & m) == (b->s6_addr[plen / 8] & m); } /* returns 1 if the address given is one for which we proxy */ int proxied(struct in6_addr *a) { for (int i = 0; ! IN6_IS_ADDR_UNSPECIFIED(&proxiednets[i].prefix); i++) if (samenet(a, &proxiednets[i].prefix, proxiednets[i].plen)) return 1; return 0; } int main(int argc, char **argv) { pcap_t *pcap; struct bpf_program filter; char errbuf[PCAP_ERRBUF_SIZE]; char *intfname = NULL; int ch; while ((ch = getopt(argc, argv, "di:p:")) != -1) { switch (ch) { case 'd': { debug = 1; break; } case 'i': { intfname = optarg; break; } case 'p': { char *netname = strdup(optarg); char *plena = strchr(netname, '/'); if (! plena) errx(1, "can't parse %s as a network name", netname); *plena = '\0'; plena++; nbnet++; proxiednets = realloc(proxiednets, (nbnet+1) * sizeof(struct network)); if (! proxiednets) err(1, "realloc(proxiednets, %d)", nbnet); trace(LOG_INFO, "will proxy for %s/%s (%d)", netname, plena, nbnet); struct network *net = &proxiednets[nbnet - 1]; if (inet_pton(AF_INET6, netname, &net->prefix) <= 0) err(1, "could not parse %s", netname); net->plen = atoi(plena); if (net->plen < 0 || net->plen > 128) err(1, "bad prefix length: %d", net->plen); free(netname); break; } default: warnx("unkown option -- %c", ch); usage(); break; } } proxiednets[nbnet].prefix = in6addr_any; proxiednets[nbnet].plen = 0; if (! intfname) errx(1, "no outside interface given"); if (nbnet == 0) errx(1, "no network to be proxied given"); if (! debug) setlogmask(LOG_UPTO(LOG_NOTICE)); /* get scope id for LL address, and the link address */ struct ifaddrs *headifa; int scopefound = 0; int lladdrfound = 0; if (getifaddrs(&headifa) < 0) err(1, "getifaddrs"); for (struct ifaddrs *ifa = headifa; ifa; ifa = ifa->ifa_next) { if (strcmp(ifa->ifa_name, intfname)) continue; if (! ifa->ifa_addr) continue; if (ifa->ifa_addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; if (! IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) continue; myscope = getscope(sin6); scopefound = 1; trace(LOG_DEBUG, "scope for %s is %d", intfname, myscope); } else if (ifa->ifa_addr->sa_family == AF_LINK) { struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr; if (sdl->sdl_type != IFT_ETHER) continue; mylladdr = *((struct ether_addr *) LLADDR(sdl)); lladdrfound = 1; trace(LOG_DEBUG, "ethernet address for %s is %s", intfname, ether_ntoa(&mylladdr)); } } freeifaddrs(headifa); if (! lladdrfound || ! scopefound) errx(1, "could not find scope and link address for %s", intfname); /* setup the sending socket for the NA replies */ if ((sender = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0) err(1, "creating socket"); if (shutdown(sender, SHUT_RD) < 0) err(1, "half-closing the sending socket"); /* the NA we send are link-local */ int hoplimit = 255; if (setsockopt(sender, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *) &hoplimit, sizeof(hoplimit)) == -1) err(1, "set hoplimit"); /* setup pcap */ if (! (pcap = pcap_open_live(intfname, BUFSIZ, 1, 1, errbuf))) errx(1, "pcap_open_live: %s", errbuf); /* pcap filter. we're looking at an offset of the ip6 header because * pcap doesn't support looking at the icmp6 type */ char *filter_exp; asprintf(&filter_exp, "icmp6 and ip6[%zd] = %d", sizeof(struct ip6_hdr) + offsetof(struct icmp6_hdr, icmp6_type), ND_NEIGHBOR_SOLICIT); if (! filter_exp) err(1, "creating filter"); if (pcap_compile(pcap, &filter, filter_exp, 1, 0) < 0) errx(1, "pcap_compile: %s", pcap_geterr(pcap)); if (pcap_setfilter(pcap, &filter) < 0) errx(1, "pcap_setfilter: %s", pcap_geterr(pcap)); /* fork and start pcap's event loop */ if (!debug && daemon(0, 0) < 0) err(1, "daemon"); pcap_loop(pcap, -1, reply_ns, NULL); /* not reached */ return 0; } /* * XXX: taken from ifconfig: FreeBSD doesn't return the scope id in the * scope_id field, but embedded in the address itself * * fixed in http://svnweb.freebsd.org/base?view=revision&revision=243187 */ uint16_t getscope(struct sockaddr_in6 *sin6) { if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && sin6->sin6_scope_id == 0) { sin6->sin6_scope_id = ntohs(*(uint16_t *)&sin6->sin6_addr.s6_addr[2]); sin6->sin6_addr.s6_addr[2] = sin6->sin6_addr.s6_addr[3] = 0; } return sin6->sin6_scope_id; } void trace(int priority, char *message, ...) { va_list argp; va_start(argp, message); if (debug) { vfprintf(stderr, message, argp); fprintf(stderr, "\n"); } else { vsyslog(priority, message, argp); } va_end(argp); }