From 8df5d49e283c7e242edb1bb5716a201f85fec321 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Fr=C3=A9d=C3=A9ric=20Perrin?= Date: Sat, 15 Dec 2012 00:08:08 +0100 Subject: [PATCH 1/1] Initial commit --- Makefile | 9 ++ ndp6.c | 303 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+) create mode 100644 Makefile create mode 100644 ndp6.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..30f055f --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +CFLAGS=-Wall -std=c99 -g +LDLIBS=-lpcap + +EXECUTABLE=ndp6 + +all: $(EXECUTABLE) + +clean: + rm -f *.o $(EXECUTABLE) diff --git a/ndp6.c b/ndp6.c new file mode 100644 index 0000000..19bbee2 --- /dev/null +++ b/ndp6.c @@ -0,0 +1,303 @@ +#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); +} -- 2.43.0