]> gitweb.fperrin.net Git - ndp6.git/commitdiff
Initial commit master
authorFrédéric Perrin <frederic.perrin@resel.fr>
Fri, 14 Dec 2012 23:08:08 +0000 (00:08 +0100)
committerFrédéric Perrin <frederic.perrin@resel.fr>
Fri, 14 Dec 2012 23:08:08 +0000 (00:08 +0100)
Makefile [new file with mode: 0644]
ndp6.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..19bbee2
--- /dev/null
+++ b/ndp6.c
@@ -0,0 +1,303 @@
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+#include <net/ethernet.h>
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
+#include <pcap/pcap.h>
+
+#include <err.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+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);
+}