This adds basic IPv6 support framework. Things
like, e.g. DHCPv6 will not work (yeT) though
because link-local support requires additional
changes (including kernel).

Signed-off-by: Michal Kazior <[email protected]>
---
 dhcp.c   |  30 +--
 main.c   | 730 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 relayd.h |  59 +++++-
 route.c  | 184 +++++++++++-----
 4 files changed, 876 insertions(+), 127 deletions(-)

diff --git a/dhcp.c b/dhcp.c
index aefe34fd80e6..a915cf88e97f 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -55,32 +55,6 @@ struct dhcp_header {
        uint8_t option_data[];
 } __packed;
 
-static uint16_t
-chksum(uint16_t sum, const uint8_t *data, uint16_t len)
-{
-       const uint8_t *last;
-       uint16_t t;
-
-       last = data + len - 1;
-
-       while(data < last) {
-               t = (data[0] << 8) + data[1];
-               sum += t;
-               if(sum < t)
-                       sum++;
-               data += 2;
-       }
-
-       if(data == last) {
-               t = (data[0] << 8) + 0;
-               sum += t;
-               if(sum < t)
-                       sum++;
-       }
-
-       return sum;
-}
-
 static void
 parse_dhcp_options(struct relayd_host *host, struct dhcp_header *dhcp, int len)
 {
@@ -99,7 +73,7 @@ parse_dhcp_options(struct relayd_host *host, struct 
dhcp_header *dhcp, int len)
                        if (!memcmp(opt->data, host->ipaddr, 4))
                                relayd_add_host_route(host, dest, 0);
                        else
-                               relayd_add_pending_route(opt->data, dest, 0, 
10000);
+                               relayd_add_pending_route(opt->data, dest, 0, 
10000, AF_INET);
                        break;
                case DHCP_OPTION_ROUTES:
                        DPRINTF(2, "Found a DHCP static routes option, 
len=%d\n", opt->len);
@@ -150,7 +124,7 @@ bool relayd_handle_dhcp_packet(struct relayd_interface 
*rif, void *data, int len
                return true;
 
        if (dhcp->op == 2) {
-               host = relayd_refresh_host(rif, pkt->eth.ether_shost, (void *) 
&pkt->iph.saddr);
+               host = relayd_refresh_host(rif, pkt->eth.ether_shost, (void *) 
&pkt->iph.saddr, AF_INET);
                if (host && parse)
                        parse_dhcp_options(host, dhcp, udplen - sizeof(struct 
udphdr));
        }
diff --git a/main.c b/main.c
index b3c13f7f7a49..e1d1dc997b6f 100644
--- a/main.c
+++ b/main.c
@@ -28,6 +28,7 @@
 #include <errno.h>
 #include <signal.h>
 #include <string.h>
+#include <linux/filter.h>
 
 #include "relayd.h"
 
@@ -41,6 +42,7 @@ static int inet_sock;
 static int forward_bcast;
 static int forward_dhcp;
 static int parse_dhcp;
+static int ipv6;
 
 uint8_t local_addr[4];
 int local_route_table;
@@ -48,16 +50,296 @@ int local_route_table;
 struct relayd_pending_route {
        struct relayd_route rt;
        struct uloop_timeout timeout;
-       uint8_t gateway[4];
+       union {
+               uint8_t gateway[16];
+               uint16_t gateway16[8];
+       };
 };
 
-static struct relayd_host *find_host_by_ipaddr(struct relayd_interface *rif, 
const uint8_t *ipaddr)
+/* Generated with: tcpdump -dd ip6 proto 58 or ip6 multicast */
+static struct sock_filter ip6bpf[] = {
+       { 0x28, 0, 0, 0x0000000c },
+       { 0x15, 0, 8, 0x000086dd },
+       { 0x30, 0, 0, 0x00000014 },
+       { 0x15, 5, 0, 0x0000003a },
+       { 0x15, 0, 2, 0x0000002c },
+       { 0x30, 0, 0, 0x00000036 },
+       { 0x15, 2, 0, 0x0000003a },
+       { 0x30, 0, 0, 0x00000026 },
+       { 0x15, 0, 1, 0x000000ff },
+       { 0x6, 0, 0, 0x00040000 },
+       { 0x6, 0, 0, 0x00000000 },
+};
+static const struct sock_fprog ip6bpf_prog = {
+       sizeof(ip6bpf) / sizeof(ip6bpf[0]),
+       ip6bpf,
+};
+
+uint16_t chksum(uint16_t sum, const uint8_t *data, uint16_t len)
+{
+       const uint8_t *last;
+       uint16_t t;
+
+       last = data + len - 1;
+
+       while(data < last) {
+               t = (data[0] << 8) + data[1];
+               sum += t;
+               if(sum < t)
+                       sum++;
+               data += 2;
+       }
+
+       if(data == last) {
+               t = (data[0] << 8) + 0;
+               sum += t;
+               if(sum < t)
+                       sum++;
+       }
+
+       return sum;
+}
+
+static void icmp6_chksum(struct ether_header *eth,
+                        struct ip6_hdr *ip6,
+                        struct icmp6_hdr *icmp6,
+                        int pktlen)
+{
+       unsigned char sumbuf[16 + 16 + 4 + 4];
+       uint16_t sum;
+       unsigned int uplen;
+
+       uplen = pktlen - ((void *)icmp6 - (void *)eth);
+       uplen = htonl(uplen);
+       memcpy(&sumbuf[0],  &ip6->ip6_src, 16);
+       memcpy(&sumbuf[16], &ip6->ip6_dst, 16);
+       memcpy(&sumbuf[32], &uplen, 4);
+       memset(&sumbuf[36], 0, 3);
+       memcpy(&sumbuf[39], &ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt, 1);
+
+       icmp6->icmp6_cksum = 0;
+       uplen = ntohl(uplen);
+       sum = chksum(0, (void *)sumbuf, 40);
+       sum = chksum(sum, (void *)icmp6, uplen);
+
+       if (sum == 0)
+               sum = 0xffff;
+
+       icmp6->icmp6_cksum = htons(~sum);
+}
+
+static void udp6_chksum(struct ether_header *eth,
+                       struct ip6_hdr *ip6,
+                       struct udphdr *udp,
+                       int pktlen)
+{
+       unsigned char sumbuf[16 + 16 + 4 + 4];
+       uint16_t sum;
+       unsigned int uplen;
+
+       uplen = pktlen - ((void *)udp - (void *)eth);
+       uplen = htonl(uplen);
+       memcpy(&sumbuf[0],  &ip6->ip6_src, 16);
+       memcpy(&sumbuf[16], &ip6->ip6_dst, 16);
+       memcpy(&sumbuf[32], &uplen, 4);
+       memset(&sumbuf[36], 0, 3);
+       memcpy(&sumbuf[39], &ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt, 1);
+
+       udp->check = 0;
+       uplen = ntohl(uplen);
+       sum = chksum(0, (void *)sumbuf, 40);
+       sum = chksum(sum, (void *)udp, uplen);
+
+       if (sum == 0)
+               sum = 0xffff;
+
+       udp->check = htons(~sum);
+}
+
+static int icmp6_getopt(struct nd_opt_hdr *nd_optbuf,
+                       int nd_optlen,
+                       int nd_opt_type,
+                       void **opt,
+                       int *optlen)
+{
+       int len;
+
+       while (nd_optlen >= sizeof(*nd_optbuf)) {
+               len = nd_optbuf->nd_opt_len;
+               len *= 8;
+               if (len == 0)
+                       return -1;
+
+               if (nd_optbuf->nd_opt_type != nd_opt_type) {
+                       nd_optbuf = (void *)nd_optbuf + len;
+                       nd_optlen -= len;
+                       continue;
+               }
+
+               if (len > nd_optlen)
+                       return -1;
+
+               *opt = (void *)nd_optbuf + sizeof(*nd_optbuf);
+               *optlen = len;
+               return 0;
+       }
+
+       return -1;
+}
+
+static void send_na(struct relayd_interface *rif,
+                   const uint8_t ethsrc[ETH_ALEN],
+                   const uint8_t ethdst[ETH_ALEN],
+                   const uint8_t ipsrc[16],
+                   const uint8_t ipdst[16],
+                   const uint8_t tp[16],
+                   const uint8_t th[ETH_ALEN])
+{
+       struct na_packet pkt;
+       size_t len;
+
+       memset(&pkt, 0, sizeof(pkt));
+
+       pkt.eth.ether_type = htons(ETHERTYPE_IPV6);
+       memcpy(pkt.eth.ether_shost, ethsrc, ETH_ALEN);
+       memcpy(pkt.eth.ether_dhost, ethdst, ETH_ALEN);
+
+       len = sizeof(pkt.na) + sizeof(pkt.tlla) + sizeof(pkt.addr);
+
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000);
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len);
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6;
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_hlim = 255;
+       memcpy(&pkt.ip6.ip6_src, ipsrc, 16);
+       memcpy(&pkt.ip6.ip6_dst, ipdst, 16);
+       pkt.na.nd_na_hdr.icmp6_type = ND_NEIGHBOR_ADVERT;
+       pkt.na.nd_na_hdr.icmp6_code = 0;
+       pkt.na.nd_na_hdr.icmp6_cksum = 0;
+       pkt.na.nd_na_hdr.icmp6_dataun.icmp6_un_data32[0] = 0;
+       memcpy(&pkt.na.nd_na_target, tp, 16);
+       pkt.tlla.nd_opt_type = ND_OPT_TARGET_LINKADDR;
+       pkt.tlla.nd_opt_len = 1;
+       memcpy(&pkt.addr, th, ETH_ALEN);
+       icmp6_chksum(&pkt.eth, &pkt.ip6, &pkt.na.nd_na_hdr, sizeof(pkt));
+
+       DPRINTF(2, "%s: sending ICMP6 NA to "IP6_FMT", "IP6_FMT" is at 
("MAC_FMT")\n",
+               rif->ifname,
+               IP6_BUF(&pkt.ip6.ip6_dst),
+               IP6_BUF(&pkt.na.nd_na_target),
+               MAC_BUF(&pkt.addr));
+
+       sendto(rif->icmp6_fd.fd, &pkt, sizeof(pkt), 0,
+               (struct sockaddr *) &rif->sll, sizeof(rif->sll));
+}
+
+static void send_ns(struct relayd_interface *rif,
+                   const uint8_t ethsrc[ETH_ALEN],
+                   const uint8_t ethdst[ETH_ALEN],
+                   const uint8_t ipsrc[16],
+                   const uint8_t ipdst[16],
+                   const uint8_t tp[16],
+                   const uint8_t sh[ETH_ALEN])
+{
+       struct ns_packet pkt;
+       size_t len;
+
+       memset(&pkt, 0, sizeof(pkt));
+
+       pkt.eth.ether_type = htons(ETHERTYPE_IPV6);
+       memcpy(pkt.eth.ether_shost, ethsrc, ETH_ALEN);
+       memcpy(pkt.eth.ether_dhost, ethdst, ETH_ALEN);
+
+       len = sizeof(pkt.ns) + sizeof(pkt.slla) + sizeof(pkt.addr);
+
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_flow = htonl(0x60000000);
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_plen = htons(len);
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_nxt = IPPROTO_ICMPV6;
+       pkt.ip6.ip6_ctlun.ip6_un1.ip6_un1_hlim = 255;
+       memcpy(&pkt.ip6.ip6_src, ipsrc, 16);
+       memcpy(&pkt.ip6.ip6_dst, ipdst, 16);
+       pkt.ns.nd_ns_hdr.icmp6_type = ND_NEIGHBOR_SOLICIT;
+       pkt.ns.nd_ns_hdr.icmp6_code = 0;
+       pkt.ns.nd_ns_hdr.icmp6_cksum = 0;
+       pkt.ns.nd_ns_hdr.icmp6_dataun.icmp6_un_data32[0] = 0;
+       memcpy(&pkt.ns.nd_ns_target, tp, 16);
+       pkt.slla.nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+       pkt.slla.nd_opt_len = 1;
+       memcpy(&pkt.addr, sh, ETH_ALEN);
+       icmp6_chksum(&pkt.eth, &pkt.ip6, &pkt.ns.nd_ns_hdr, sizeof(pkt));
+
+       DPRINTF(2, "%s: sending ICMP6 NS who-has "IP6_FMT", tell "IP6_FMT" 
("MAC_FMT")\n",
+               rif->ifname,
+               IP6_BUF(&pkt.ns.nd_ns_target),
+               IP6_BUF(&pkt.ip6.ip6_src),
+               MAC_BUF(&pkt.addr));
+
+       sendto(rif->icmp6_fd.fd, &pkt, sizeof(pkt), 0,
+               (struct sockaddr *) &rif->sll, sizeof(rif->sll));
+}
+
+static void relay_na(struct relayd_interface *rif,
+                    struct ether_header *eth,
+                    struct ip6_hdr *ip6,
+                    struct icmp6_hdr *icmp6,
+                    struct ether_addr *etha,
+                    int pktlen,
+                    const uint8_t sha[ETH_ALEN],
+                    const uint8_t tha[ETH_ALEN])
+{
+       struct nd_neighbor_advert *na = (void *)icmp6;
+
+       memcpy(eth->ether_dhost, tha, ETH_ALEN);
+       memcpy(etha, sha, ETH_ALEN);
+
+       DPRINTF(2, "%s: relying NA to "IP6_FMT", "IP6_FMT" is at ("MAC_FMT")\n",
+               rif->ifname,
+               IP6_BUF(&ip6->ip6_src),
+               IP6_BUF(&na->nd_na_target),
+               MAC_BUF(etha));
+
+       icmp6_chksum(eth, ip6, icmp6, pktlen);
+       sendto(rif->icmp6_fd.fd, eth, pktlen,  0,
+               (struct sockaddr *) &rif->sll, sizeof(rif->sll));
+}
+
+static void relay_ns(struct relayd_interface *from_rif,
+                    struct ether_header *eth,
+                    struct ip6_hdr *ip6,
+                    struct nd_neighbor_solicit *ns,
+                    struct ether_addr *etha,
+                    int pktlen)
+{
+       struct relayd_interface *rif;
+
+       list_for_each_entry(rif, &interfaces, list) {
+               if (rif == from_rif)
+                       continue;
+
+               memcpy(eth->ether_shost, rif->sll.sll_addr, ETH_ALEN);
+               memcpy(etha, rif->sll.sll_addr, ETH_ALEN);
+
+               DPRINTF(2, "%s: relying ICMP6 NS "IP6_FMT", tell "IP6_FMT" 
("MAC_FMT")\n",
+                       rif->ifname,
+                       IP6_BUF(&ns->nd_ns_target),
+                       IP6_BUF(&ip6->ip6_src),
+                       MAC_BUF(etha));
+
+               icmp6_chksum(eth, ip6, &ns->nd_ns_hdr, pktlen);
+               sendto(rif->icmp6_fd.fd, eth, pktlen, 0,
+                       (struct sockaddr *) &rif->sll, sizeof(rif->sll));
+       }
+}
+
+static struct relayd_host *find_host_by_ipaddr(struct relayd_interface *rif,
+                                              const uint8_t *ipaddr,
+                                              int af)
 {
        struct relayd_host *host;
+       int addrlen = AF2ADDRLEN(af);
 
        if (!rif) {
                list_for_each_entry(rif, &interfaces, list) {
-                       host = find_host_by_ipaddr(rif, ipaddr);
+                       host = find_host_by_ipaddr(rif, ipaddr, af);
                        if (!host)
                                continue;
 
@@ -67,7 +349,7 @@ static struct relayd_host *find_host_by_ipaddr(struct 
relayd_interface *rif, con
        }
 
        list_for_each_entry(host, &rif->hosts, list) {
-               if (memcmp(ipaddr, host->ipaddr, sizeof(host->ipaddr)) != 0)
+               if (memcmp(ipaddr, host->ipaddr, addrlen) != 0)
                        continue;
 
                return host;
@@ -79,6 +361,7 @@ static void add_arp(struct relayd_host *host)
 {
        struct sockaddr_in *sin;
        struct arpreq arp;
+       int addrlen = AF2ADDRLEN(host->af);
 
        strncpy(arp.arp_dev, host->rif->ifname, sizeof(arp.arp_dev));
        arp.arp_flags = ATF_COM;
@@ -87,8 +370,8 @@ static void add_arp(struct relayd_host *host)
        memcpy(arp.arp_ha.sa_data, host->lladdr, ETH_ALEN);
 
        sin = (struct sockaddr_in *) &arp.arp_pa;
-       sin->sin_family = AF_INET;
-       memcpy(&sin->sin_addr, host->ipaddr, sizeof(host->ipaddr));
+       sin->sin_family = host->af;
+       memcpy(&sin->sin_addr, host->ipaddr, addrlen);
 
        ioctl(inet_sock, SIOCSARP, &arp);
 }
@@ -105,9 +388,10 @@ static void timeout_host_route(struct uloop_timeout 
*timeout)
 void relayd_add_host_route(struct relayd_host *host, const uint8_t *dest, 
uint8_t mask)
 {
        struct relayd_route *rt;
+       int addrlen = AF2ADDRLEN(host->af);
 
        list_for_each_entry(rt, &host->routes, list) {
-               if (!memcmp(rt->dest, dest, sizeof(rt->dest)) && rt->mask == 
mask)
+               if (!memcmp(rt->dest, dest, addrlen) && rt->mask == mask)
                        return;
        }
 
@@ -116,8 +400,9 @@ void relayd_add_host_route(struct relayd_host *host, const 
uint8_t *dest, uint8_
                return;
 
        list_add(&rt->list, &host->routes);
-       memcpy(rt->dest, dest, sizeof(rt->dest));
+       memcpy(rt->dest, dest, addrlen);
        rt->mask = mask;
+       rt->af = host->af;
        relayd_add_route(host, rt);
 }
 
@@ -125,8 +410,17 @@ static void del_host(struct relayd_host *host)
 {
        struct relayd_route *route, *tmp;
 
-       DPRINTF(1, "%s: deleting host "IP_FMT" ("MAC_FMT")\n", 
host->rif->ifname,
-               IP_BUF(host->ipaddr), MAC_BUF(host->lladdr));
+       switch (host->af) {
+               DPRINTF(1, "%s: deleting host "IP_FMT" ("MAC_FMT")\n",
+                       host->rif->ifname, IP_BUF(host->ipaddr),
+                       MAC_BUF(host->lladdr));
+               break;
+       case AF_INET6:
+               DPRINTF(1, "%s: deleting host "IP6_FMT" ("MAC_FMT")\n",
+                       host->rif->ifname, IP6_BUF(host->ipaddr16),
+                       MAC_BUF(host->lladdr));
+               break;
+       }
 
        list_for_each_entry_safe(route, tmp, &host->routes, list) {
                relayd_del_route(host, route);
@@ -177,13 +471,14 @@ static void send_arp_request(struct relayd_interface 
*rif, const uint8_t *ipaddr
                (struct sockaddr *) &rif->sll, sizeof(rif->sll));
 }
 
-void relayd_add_pending_route(const uint8_t *gateway, const uint8_t *dest, 
uint8_t mask, int timeout)
+void relayd_add_pending_route(const uint8_t *gateway, const uint8_t *dest, 
uint8_t mask, int timeout, int af)
 {
        struct relayd_pending_route *rt;
        struct relayd_interface *rif;
        struct relayd_host *host;
+       int addrlen = AF2ADDRLEN(af);
 
-       host = find_host_by_ipaddr(NULL, gateway);
+       host = find_host_by_ipaddr(NULL, gateway, af);
        if (host) {
                relayd_add_host_route(host, dest, mask);
                return;
@@ -193,17 +488,32 @@ void relayd_add_pending_route(const uint8_t *gateway, 
const uint8_t *dest, uint8
        if (!rt)
                return;
 
-       memcpy(rt->gateway, gateway, sizeof(rt->gateway));
-       memcpy(rt->rt.dest, dest, sizeof(rt->rt.dest));
+       memcpy(rt->gateway, gateway, addrlen);
+       memcpy(rt->rt.dest, dest, addrlen);
        rt->rt.mask = mask;
+       rt->rt.af = af;
        list_add(&rt->rt.list, &pending_routes);
        if (timeout <= 0)
                return;
 
        rt->timeout.cb = timeout_host_route;
        uloop_timeout_set(&rt->timeout, 10000);
-       list_for_each_entry(rif, &interfaces, list) {
-               send_arp_request(rif, gateway);
+
+       switch (af) {
+       case AF_INET:
+               list_for_each_entry(rif, &interfaces, list)
+                       send_arp_request(rif, gateway);
+               break;
+       case AF_INET6:
+               list_for_each_entry(rif, &interfaces, list)
+                       send_ns(rif,
+                               rif->sll.sll_addr,
+                               ETH_IP6_ALLNODES,
+                               DUMMY_IP6,
+                               IP6_ALLNODES,
+                               gateway,
+                               rif->sll.sll_addr);
+               break;
        }
 }
 
@@ -263,8 +573,21 @@ static void host_entry_timeout(struct uloop_timeout 
*timeout)
         * giving up on it.
         */
        if (host->rif->managed && host->cleanup_pending < host_ping_tries) {
-               list_for_each_entry(rif, &interfaces, list) {
-                       send_arp_request(rif, host->ipaddr);
+               switch (host->af) {
+               case AF_INET:
+                       list_for_each_entry(rif, &interfaces, list)
+                               send_arp_request(rif, host->ipaddr);
+                       break;
+               case AF_INET6:
+                       list_for_each_entry(rif, &interfaces, list)
+                               send_ns(rif,
+                                       rif->sll.sll_addr,
+                                       ETH_IP6_ALLNODES,
+                                       DUMMY_IP6,
+                                       IP6_ALLNODES,
+                                       host->ipaddr,
+                                       rif->sll.sll_addr);
+                       break;
                }
                host->cleanup_pending++;
                uloop_timeout_set(&host->timeout, 1000);
@@ -273,18 +596,31 @@ static void host_entry_timeout(struct uloop_timeout 
*timeout)
        del_host(host);
 }
 
-static struct relayd_host *add_host(struct relayd_interface *rif, const 
uint8_t *lladdr, const uint8_t *ipaddr)
+static struct relayd_host *add_host(struct relayd_interface *rif,
+                                   const uint8_t *lladdr,
+                                   const uint8_t *ipaddr,
+                                   int af)
 {
        struct relayd_host *host;
        struct relayd_pending_route *route, *rtmp;
+       int addrlen = AF2ADDRLEN(af);
 
-       DPRINTF(1, "%s: adding host "IP_FMT" ("MAC_FMT")\n", rif->ifname,
+       switch (af) {
+       case AF_INET:
+               DPRINTF(1, "%s: adding host "IP_FMT" ("MAC_FMT")\n", 
rif->ifname,
                        IP_BUF(ipaddr), MAC_BUF(lladdr));
+               break;
+       case AF_INET6:
+               DPRINTF(1, "%s: adding host "IP6_FMT" ("MAC_FMT")\n", 
rif->ifname,
+                       IP6_BUF(ipaddr), MAC_BUF(lladdr));
+               break;
+       }
 
        host = calloc(1, sizeof(*host));
        INIT_LIST_HEAD(&host->routes);
        host->rif = rif;
-       memcpy(host->ipaddr, ipaddr, sizeof(host->ipaddr));
+       host->af = af;
+       memcpy(host->ipaddr, ipaddr, addrlen);
        memcpy(host->lladdr, lladdr, sizeof(host->lladdr));
        list_add(&host->list, &rif->hosts);
        host->timeout.cb = host_entry_timeout;
@@ -295,7 +631,10 @@ static struct relayd_host *add_host(struct 
relayd_interface *rif, const uint8_t
                relayd_add_route(host, NULL);
 
        list_for_each_entry_safe(route, rtmp, &pending_routes, rt.list) {
-               if (memcmp(route->gateway, ipaddr, 4) != 0)
+               if (route->rt.af != af)
+                       continue;
+
+               if (memcmp(route->gateway, ipaddr, addrlen) != 0)
                        continue;
 
                relayd_add_host_route(host, route->rt.dest, route->rt.mask);
@@ -310,7 +649,7 @@ static struct relayd_host *add_host(struct relayd_interface 
*rif, const uint8_t
        return host;
 }
 
-static void send_gratuitous_arp(struct relayd_interface *rif, const uint8_t 
*spa)
+static void send_gratuitous_arp(struct relayd_interface *rif, const uint8_t 
*spa, int af)
 {
        struct relayd_interface *to_rif;
 
@@ -318,18 +657,43 @@ static void send_gratuitous_arp(struct relayd_interface 
*rif, const uint8_t *spa
                if (rif == to_rif)
                        continue;
 
-               send_arp_reply(to_rif, spa, NULL, spa);
+               switch (af) {
+               case AF_INET:
+                       send_arp_reply(to_rif, spa, NULL, spa);
+                       break;
+               case AF_INET6:
+                       send_na(to_rif,
+                               to_rif->sll.sll_addr,
+                               ETH_IP6_ALLNODES,
+                               DUMMY_IP6,
+                               IP6_ALLNODES,
+                               spa,
+                               to_rif->sll.sll_addr);
+                       break;
+               }
        }
 }
 
-
-struct relayd_host *relayd_refresh_host(struct relayd_interface *rif, const 
uint8_t *lladdr, const uint8_t *ipaddr)
+struct relayd_host *relayd_refresh_host(struct relayd_interface *rif,
+                                       const uint8_t *lladdr,
+                                       const uint8_t *ipaddr,
+                                       int af)
 {
        struct relayd_host *host;
 
-       host = find_host_by_ipaddr(rif, ipaddr);
+       switch (af) {
+       case AF_INET6:
+               if (IN6_IS_ADDR_MULTICAST(ipaddr)) {
+                       DPRINTF(1, "%s: ignoring multicast host "IP6_FMT" 
("MAC_FMT")\n",
+                               rif->ifname, IP6_BUF(ipaddr), MAC_BUF(lladdr));
+                       return NULL;
+               }
+               break;
+       }
+
+       host = find_host_by_ipaddr(rif, ipaddr, af);
        if (!host) {
-               host = find_host_by_ipaddr(NULL, ipaddr);
+               host = find_host_by_ipaddr(NULL, ipaddr, af);
 
                /* 
                 * When we suddenly see the host appearing on a different 
interface,
@@ -343,11 +707,11 @@ struct relayd_host *relayd_refresh_host(struct 
relayd_interface *rif, const uint
                        return NULL;
                }
 
-               host = add_host(rif, lladdr, ipaddr);
+               host = add_host(rif, lladdr, ipaddr, af);
        } else {
                host->cleanup_pending = false;
                uloop_timeout_set(&host->timeout, host_timeout * 1000);
-               send_gratuitous_arp(rif, ipaddr);
+               send_gratuitous_arp(rif, ipaddr, af);
        }
 
        return host;
@@ -390,16 +754,16 @@ static void recv_arp_request(struct relayd_interface 
*rif, struct arp_packet *pk
        if (!memcmp(pkt->arp.arp_spa, "\x00\x00\x00\x00", 4))
                return;
 
-       host = find_host_by_ipaddr(NULL, pkt->arp.arp_spa);
+       host = find_host_by_ipaddr(NULL, pkt->arp.arp_spa, AF_INET);
        if (!host || host->rif != rif)
-               relayd_refresh_host(rif, pkt->eth.ether_shost, 
pkt->arp.arp_spa);
+               relayd_refresh_host(rif, pkt->eth.ether_shost, 
pkt->arp.arp_spa, AF_INET);
 
        if (local_route_table && !memcmp(pkt->arp.arp_tpa, local_addr, 
sizeof(local_addr))) {
                send_arp_reply(rif, local_addr, pkt->arp.arp_sha, 
pkt->arp.arp_spa);
                return;
        }
 
-       host = find_host_by_ipaddr(NULL, pkt->arp.arp_tpa);
+       host = find_host_by_ipaddr(NULL, pkt->arp.arp_tpa, AF_INET);
 
        /*
         * If a host is being pinged because of a timeout, do not use the cached
@@ -424,9 +788,9 @@ static void recv_arp_reply(struct relayd_interface *rif, 
struct arp_packet *pkt)
                IP_BUF(pkt->arp.arp_tpa));
 
        if (memcmp(pkt->arp.arp_sha, rif->sll.sll_addr, ETH_ALEN) != 0)
-               relayd_refresh_host(rif, pkt->arp.arp_sha, pkt->arp.arp_spa);
+               relayd_refresh_host(rif, pkt->arp.arp_sha, pkt->arp.arp_spa, 
AF_INET);
 
-       host = find_host_by_ipaddr(NULL, pkt->arp.arp_tpa);
+       host = find_host_by_ipaddr(NULL, pkt->arp.arp_tpa, AF_INET);
        if (!host)
                return;
 
@@ -436,6 +800,233 @@ static void recv_arp_reply(struct relayd_interface *rif, 
struct arp_packet *pkt)
        send_arp_reply(host->rif, pkt->arp.arp_spa, host->lladdr, host->ipaddr);
 }
 
+static void relayd_handle_icmp6_ns(struct relayd_interface *rif,
+                                  struct ether_header *eth,
+                                  struct ip6_hdr *ip6,
+                                  struct icmp6_hdr *icmp6,
+                                  int pktlen)
+{
+       struct nd_neighbor_solicit *ns = (void *)icmp6;
+       struct relayd_host *host;
+       struct ether_addr *etha;
+       void *opt;
+       int optlen;
+       int len = pktlen;
+
+       len -= sizeof(*eth);
+       len -= ((void *)icmp6 - (void *)ip6);
+       if (len < sizeof(*ns))
+               return;
+
+       if (icmp6_getopt((void *)ns + sizeof(*ns), len - sizeof(*ns),
+                        ND_OPT_SOURCE_LINKADDR, &opt, &optlen) < 0)
+               return;
+
+       if (optlen != 8)
+               return;
+
+       etha = opt;
+
+       DPRINTF(2, "%s: ICMP6 NS "IP6_FMT", tell "IP6_FMT" ("MAC_FMT")\n",
+               rif->ifname,
+               IP6_BUF(&ns->nd_ns_target),
+               IP6_BUF(&ip6->ip6_src),
+               MAC_BUF(etha));
+
+       host = find_host_by_ipaddr(NULL, (void *)&ip6->ip6_src, AF_INET6);
+
+       if (!host || host->rif != rif)
+               relayd_refresh_host(rif, etha->ether_addr_octet, (void 
*)&ip6->ip6_src, AF_INET6);
+
+       /* TODO: handle local_route_table? */
+
+       host = find_host_by_ipaddr(NULL, (void *)&ns->nd_ns_target, AF_INET6);
+
+       /*
+        * If a host is being pinged because of a timeout, do not use the cached
+        * entry here. That way we can avoid giving out stale data in case the 
node
+        * has moved. We shouldn't relay requests here either, as we might miss 
our
+        * chance to create a host route.
+        */
+       if (host && host->cleanup_pending)
+               return;
+
+       relay_ns(rif, eth, ip6, ns, etha, pktlen);
+}
+
+static void relayd_handle_icmp6_na(struct relayd_interface *rif,
+                                  struct ether_header *eth,
+                                  struct ip6_hdr *ip6,
+                                  struct icmp6_hdr *icmp6,
+                                  int pktlen)
+{
+       struct nd_neighbor_advert *na = (void *)icmp6;
+       struct relayd_host *host;
+       struct ether_addr *etha;
+       void *opt;
+       int optlen;
+       int len = pktlen;
+
+       len -= sizeof(*eth);
+       len -= ((void *)icmp6 - (void *)ip6);
+       if (len < sizeof(*na))
+               return;
+
+       if (icmp6_getopt((void *)na + sizeof(*na), len - sizeof(*na),
+                        ND_OPT_TARGET_LINKADDR, &opt, &optlen) < 0)
+               return;
+
+       if (optlen != 8)
+               return;
+
+       etha = opt;
+
+       DPRINTF(2, "%s: ICMP6 NA "IP6_FMT" from "MAC_FMT", deliver to 
"IP6_FMT"\n",
+               rif->ifname,
+               IP6_BUF(&na->nd_na_target),
+               MAC_BUF(etha),
+               IP6_BUF(&ip6->ip6_dst));
+
+       if (memcmp(etha, rif->sll.sll_addr, ETH_ALEN) != 0)
+               relayd_refresh_host(rif, etha->ether_addr_octet, (void 
*)&na->nd_na_target, AF_INET6);
+
+       host = find_host_by_ipaddr(NULL, (void *)&ip6->ip6_dst, AF_INET6);
+       if (!host)
+               return;
+
+       if (host->rif == rif)
+               return;
+
+       relay_na(host->rif, eth, ip6, icmp6, etha, pktlen,
+                host->rif->sll.sll_addr, host->lladdr);
+}
+
+static bool relayd_handle_icmp6_neigh(struct relayd_interface *rif,
+                                     void *pkt, int pktlen)
+{
+       struct ether_header *eth;
+       struct ip6_hdr *ip6;
+       struct icmp6_hdr *icmp6;
+
+       if (pktlen < sizeof(*eth) + sizeof(*ip6) + sizeof(*icmp6))
+               return false;
+
+       eth = pkt;
+       ip6 = pkt + sizeof(*eth);
+       icmp6 = pkt + sizeof(*eth) + sizeof(*ip6);
+
+       /* TODO: IPv6 extension header is not supported */
+       if (ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_ICMPV6)
+               return false;
+
+       switch (icmp6->icmp6_type) {
+       case ND_NEIGHBOR_SOLICIT:
+               relayd_handle_icmp6_ns(rif, eth, ip6, icmp6, pktlen);
+               return true;
+       case ND_NEIGHBOR_ADVERT:
+               relayd_handle_icmp6_na(rif, eth, ip6, icmp6, pktlen);
+               return true;
+       }
+
+       return false;
+}
+
+static bool relayd_handle_ip6_mcast(struct relayd_interface *from_rif,
+                                   void *pkt, int pktlen)
+{
+       struct relayd_interface *rif;
+       struct ether_header *eth;
+       struct ip6_hdr *ip6;
+       struct icmp6_hdr *icmp6;
+       struct udphdr *udp;
+       void *payload;
+       bool bad;
+
+       if (pktlen < sizeof(*eth) + sizeof(*ip6))
+               return false;
+
+       eth = pkt;
+       ip6 = pkt + sizeof(*eth);
+       payload = pkt + sizeof(*eth) + sizeof(*ip6);
+
+       if (eth->ether_dhost[0] != 0x33 ||
+           eth->ether_dhost[1] != 0x33)
+               return false;
+
+       list_for_each_entry(rif, &interfaces, list) {
+               if (rif == from_rif)
+                       continue;
+
+               DPRINTF(3, "%s: forwarding ipv6 packet to %s\n",
+                       from_rif->ifname, rif->ifname);
+               memcpy(eth->ether_shost, rif->sll.sll_addr, ETH_ALEN);
+               bad = false;
+
+               /* TODO: I don't really understand why I need to recalculate it.
+                * DHCPv6 server doesn't seem to consume the packet unless I
+                * recompute it. Sniffing suggests it arrives at a different
+                * checksum..
+                *
+                * TODO: This doesn't support IPv6 extension headers.
+                */
+               switch (ip6->ip6_ctlun.ip6_un1.ip6_un1_nxt) {
+               case IPPROTO_ICMPV6:
+                       if (pktlen < sizeof(*eth) + sizeof(*ip6) + 
sizeof(*icmp6)) {
+                               DPRINTF(1, "received malformed ICMPv6 
packet\n");
+                               bad = true;
+                               break;
+                       }
+                       icmp6_chksum(eth, ip6, payload, pktlen);
+                       break;
+               case IPPROTO_UDP:
+                       if (pktlen < sizeof(*eth) + sizeof(*ip6) + 
sizeof(*udp)) {
+                               DPRINTF(1, "received malformed UDPv6 packet\n");
+                               bad = true;
+                               break;
+                       }
+                       udp6_chksum(eth, ip6, payload, pktlen);
+                       break;
+               }
+
+               if (bad)
+                       continue;
+
+               send(rif->icmp6_fd.fd, eth, pktlen, 0);
+       }
+
+       return true;
+}
+
+static void recv_packet_icmp6(struct uloop_fd *fd, unsigned int events)
+{
+       struct relayd_interface *rif = container_of(fd, struct 
relayd_interface, icmp6_fd);
+       static char pktbuf[4096];
+       int pktlen;
+
+       do {
+               if (rif->fd.error)
+                       uloop_end();
+
+               pktlen = recv(rif->icmp6_fd.fd, pktbuf, sizeof(pktbuf), 0);
+               if (pktlen < 0) {
+                       if (errno == EINTR)
+                               continue;
+
+                       break;
+               }
+
+               if (!pktlen)
+                       break;
+
+               if (relayd_handle_icmp6_neigh(rif, pktbuf, pktlen))
+                       continue;
+
+               if (relayd_handle_ip6_mcast(rif, pktbuf, pktlen))
+                       continue;
+
+       } while (1);
+}
+
 static void recv_packet(struct uloop_fd *fd, unsigned int events)
 {
        struct relayd_interface *rif = container_of(fd, struct 
relayd_interface, fd);
@@ -521,8 +1112,10 @@ static int init_interface(struct relayd_interface *rif)
 {
        struct sockaddr_ll *sll = &rif->sll;
        struct sockaddr_in *sin;
+       struct packet_mreq mreq;
        struct ifreq ifr;
        int fd = rif->fd.fd;
+       // int val;
 #ifdef PACKET_RECV_TYPE
        unsigned int pkt_type;
 #endif
@@ -595,7 +1188,41 @@ static int init_interface(struct relayd_interface *rif)
 #endif
 
        uloop_fd_add(&rif->bcast_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER);
+
+       if (!ipv6)
+               goto skip_ipv6;
+
+       fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6));
+       if (fd < 0)
+               return 0;
+
+       rif->icmp6_fd.fd = fd;
+       rif->icmp6_fd.cb = recv_packet_icmp6;
+
+       memcpy(&rif->ip6_sll, &rif->sll, sizeof(rif->ip6_sll));
+       sll = &rif->ip6_sll;
+       sll->sll_protocol = htons(ETH_P_IPV6);
+
+       if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
+                      &ip6bpf_prog, sizeof(ip6bpf_prog))) {
+               perror("setsockopt(SO_ATTACH_FILTER)");
+               return -1;
+       }
+
+       if (bind(fd, (struct sockaddr *)sll, sizeof(struct sockaddr_ll)) < 0) {
+               perror("bind(ETH_P_IPV6)");
+               return -1;
+       }
+
+       mreq.mr_ifindex = sll->sll_ifindex;
+       mreq.mr_type = PACKET_MR_PROMISC;
+       setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
+
+       uloop_fd_add(&rif->icmp6_fd, ULOOP_READ | ULOOP_EDGE_TRIGGER);
+
+skip_ipv6:
        relayd_add_interface_routes(rif);
+
        return 0;
 }
 
@@ -604,9 +1231,24 @@ static void ping_static_routes(void)
        struct relayd_pending_route *rt;
        struct relayd_interface *rif;
 
-       list_for_each_entry(rt, &pending_routes, rt.list)
-               list_for_each_entry(rif, &interfaces, list)
-                       send_arp_request(rif, rt->gateway);
+       list_for_each_entry(rt, &pending_routes, rt.list) {
+               switch (rt->rt.af) {
+               case AF_INET:
+                       list_for_each_entry(rif, &interfaces, list)
+                               send_arp_request(rif, rt->gateway);
+                       break;
+               case AF_INET6:
+                       list_for_each_entry(rif, &interfaces, list)
+                               send_ns(rif,
+                                       rif->sll.sll_addr,
+                                       ETH_IP6_ALLNODES,
+                                       DUMMY_IP6,
+                                       IP6_ALLNODES,
+                                       rt->gateway,
+                                       rif->sll.sll_addr);
+                       break;
+               }
+       }
 }
 
 static int init_interfaces(void)
@@ -698,6 +1340,7 @@ static int usage(const char *progname)
                        "       -D              Enable DHCP forwarding\n"
                        "       -P              Disable DHCP options parsing\n"
                        "       -L <ipaddr>     Enable local access using 
<ipaddr> as source address\n"
+                       "       -6              Enable IPv6 support\n"
                        "\n",
                progname);
        return -1;
@@ -728,7 +1371,7 @@ int main(int argc, char **argv)
        parse_dhcp = 1;
        uloop_init();
 
-       while ((ch = getopt(argc, argv, "I:i:t:p:BDPdT:G:R:L:")) != -1) {
+       while ((ch = getopt(argc, argv, "I:i:t:p:BDP6dT:G:R:L:")) != -1) {
                switch(ch) {
                case 'I':
                        managed = true;
@@ -763,6 +1406,9 @@ int main(int argc, char **argv)
                case 'P':
                        parse_dhcp = 0;
                        break;
+               case '6':
+                       ipv6 = 1;
+                       break;
                case 'T':
                        route_table = atoi(optarg);
                        if (route_table <= 0)
@@ -773,7 +1419,7 @@ int main(int argc, char **argv)
                                fprintf(stderr, "Address '%s' not found\n", 
optarg);
                                return 1;
                        }
-                       relayd_add_pending_route((uint8_t *) &addr.s_addr, 
(const uint8_t *) "\x00\x00\x00\x00", 0, 0);
+                       relayd_add_pending_route((uint8_t *) &addr.s_addr, 
(const uint8_t *) "\x00\x00\x00\x00", 0, 0, AF_INET);
                        break;
                case 'L':
                        if (!inet_aton(optarg, &addr)) {
@@ -808,7 +1454,7 @@ int main(int argc, char **argv)
                        if (mask < 0 || mask > 32)
                                return usage(argv[0]);
 
-                       relayd_add_pending_route((uint8_t *) &addr.s_addr, 
(uint8_t *) &addr2.s_addr, mask, 0);
+                       relayd_add_pending_route((uint8_t *) &addr.s_addr, 
(uint8_t *) &addr2.s_addr, mask, 0, AF_INET);
                        break;
                case '?':
                default:
@@ -835,7 +1481,7 @@ int main(int argc, char **argv)
        if (local_addr_valid)
                local_route_table = route_table++;
 
-       if (relayd_rtnl_init() < 0)
+       if (relayd_rtnl_init(ipv6) < 0)
                return 1;
 
        if (init_interfaces() < 0)
diff --git a/relayd.h b/relayd.h
index d7ad212edb68..aeffeda722f1 100644
--- a/relayd.h
+++ b/relayd.h
@@ -24,6 +24,8 @@
 #include <netinet/if_ether.h>
 #include <netinet/ip.h>
 #include <netinet/udp.h>
+#include <netinet/ip6.h>
+#include <netinet/icmp6.h>
 
 #include <linux/if_packet.h>
 #include <linux/rtnetlink.h>
@@ -46,6 +48,7 @@
 #endif
 
 #define __uc(c) ((unsigned char *)(c))
+#define __us(s) ((unsigned short *)(s))
 
 #define MAC_FMT        "%02x:%02x:%02x:%02x:%02x:%02x"
 #define MAC_BUF(_c) __uc(_c)[0], __uc(_c)[1], __uc(_c)[2], __uc(_c)[3], 
__uc(_c)[4], __uc(_c)[5]
@@ -53,7 +56,24 @@
 #define IP_FMT "%d.%d.%d.%d"
 #define IP_BUF(_c) __uc(_c)[0], __uc(_c)[1], __uc(_c)[2], __uc(_c)[3]
 
+#define IP6_FMT        "%x:%x:%x:%x:%x:%x:%x:%x"
+#define IP6_BUF(_s) ntohs(__us(_s)[0]), \
+                   ntohs(__us(_s)[1]), \
+                   ntohs(__us(_s)[2]), \
+                   ntohs(__us(_s)[3]), \
+                   ntohs(__us(_s)[4]), \
+                   ntohs(__us(_s)[5]), \
+                   ntohs(__us(_s)[6]), \
+                   ntohs(__us(_s)[7])
+
+#define AF2ADDRLEN(af) ((af) == AF_INET  ? 4 : \
+                       (af) == AF_INET6 ? 16 : \
+                       -1)
+
 #define DUMMY_IP ((uint8_t *) "\x01\x01\x01\x01")
+#define DUMMY_IP6 ((uint8_t *) 
"\xfe\x80\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01")
+#define ETH_IP6_ALLNODES ((uint8_t *) "\x33\x33\x00\x01\x00\x01")
+#define IP6_ALLNODES ((uint8_t *) 
"\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01")
 
 #define DHCP_FLAG_BROADCAST    (1 << 15)
 
@@ -61,11 +81,14 @@ struct relayd_interface {
        struct list_head list;
        struct uloop_fd fd;
        struct uloop_fd bcast_fd;
+       struct uloop_fd icmp6_fd;
        struct sockaddr_ll sll;
        struct sockaddr_ll bcast_sll;
+       struct sockaddr_ll ip6_sll;
        char ifname[IFNAMSIZ];
        struct list_head hosts;
        uint8_t src_ip[4];
+       uint8_t src_ip6[16];
        bool managed;
        int rt_table;
 };
@@ -75,15 +98,23 @@ struct relayd_host {
        struct list_head routes;
        struct relayd_interface *rif;
        uint8_t lladdr[ETH_ALEN];
-       uint8_t ipaddr[4];
+       union {
+               uint8_t ipaddr[16];
+               uint16_t ipaddr16[8];
+       };
        struct uloop_timeout timeout;
        int cleanup_pending;
+       int af;
 };
 
 struct relayd_route {
        struct list_head list;
-       uint8_t dest[4];
+       union {
+               uint8_t dest[16];
+               uint16_t dest16[8];
+       };
        uint8_t mask;
+       int af;
 };
 
 struct arp_packet {
@@ -91,6 +122,22 @@ struct arp_packet {
        struct ether_arp arp;
 } __packed;
 
+struct ns_packet {
+       struct ether_header eth;
+       struct ip6_hdr ip6;
+       struct nd_neighbor_solicit ns;
+       struct nd_opt_hdr slla;
+       uint8_t addr[ETH_ALEN];
+} __packed;
+
+struct na_packet {
+       struct ether_header eth;
+       struct ip6_hdr ip6;
+       struct nd_neighbor_advert na;
+       struct nd_opt_hdr tlla;
+       uint8_t addr[ETH_ALEN];
+} __packed;
+
 struct rtnl_req {
        struct nlmsghdr nl;
        struct rtmsg rt;
@@ -114,17 +161,19 @@ static inline void relayd_del_route(struct relayd_host 
*host, struct relayd_rout
        rtnl_route_set(host, route, false);
 }
 
+uint16_t chksum(uint16_t sum, const uint8_t *data, uint16_t len);
 void relayd_add_interface_routes(struct relayd_interface *rif);
 void relayd_del_interface_routes(struct relayd_interface *rif);
 
-int relayd_rtnl_init(void);
+int relayd_rtnl_init(int ipv6);
 void relayd_rtnl_done(void);
 
 struct relayd_host *relayd_refresh_host(struct relayd_interface *rif,
                                        const uint8_t *lladdr,
-                                       const uint8_t *ipaddr);
+                                       const uint8_t *ipaddr,
+                                       int af);
 void relayd_add_host_route(struct relayd_host *host, const uint8_t *ipaddr, 
uint8_t mask);
-void relayd_add_pending_route(const uint8_t *gateway, const uint8_t *dest, 
uint8_t mask, int timeout);
+void relayd_add_pending_route(const uint8_t *gateway, const uint8_t *dest, 
uint8_t mask, int timeout, int af);
 
 void relayd_forward_bcast_packet(struct relayd_interface *from_rif, void 
*packet, int len);
 bool relayd_handle_dhcp_packet(struct relayd_interface *rif, void *data, int 
len, bool forward, bool parse);
diff --git a/route.c b/route.c
index c552d1f271f5..fc15e9945aaf 100644
--- a/route.c
+++ b/route.c
@@ -32,6 +32,10 @@
 
 static struct uloop_fd rtnl_sock;
 static unsigned int rtnl_seq, rtnl_dump_seq;
+static int neigh_dump_af[2];
+static int neigh_dump_cnt;
+static int neigh_dump_idx;
+static int ipv6;
 int route_table = 16800;
 
 static void rtnl_flush(void)
@@ -44,6 +48,16 @@ static void rtnl_flush(void)
 
        write(fd, "-1", 2);
        close(fd);
+
+       if (!ipv6)
+               return;
+
+       fd = open("/proc/sys/net/ipv6/route/flush", O_WRONLY);
+       if (fd < 0)
+               return;
+
+       write(fd, "-1", 2);
+       close(fd);
 }
 
 enum {
@@ -126,27 +140,47 @@ rtnl_rule_request(struct relayd_interface *rif, int flags)
 
        send(rtnl_sock.fd, &req, req.nl.nlmsg_len, 0);
        rtnl_flush();
+
+       if (!ipv6)
+               return;
+
+       req.rt.rtm_family = AF_INET6;
+       send(rtnl_sock.fd, &req, req.nl.nlmsg_len, 0);
+       rtnl_flush();
 }
 
-struct rtnl_addr {
+struct rtnl_addr4 {
        struct rtattr rta;
        uint8_t ipaddr[4];
 } __packed;
 
-static struct rtnl_addr *
-rtnl_add_addr(struct rtnl_addr *addr, int *len, int type, const uint8_t 
*ipaddr)
+struct rtnl_addr6 {
+       struct rtattr rta;
+       uint8_t ipaddr[16];
+} __packed;
+
+static void *
+rtnl_add_addr(void *addrs, int *len, int type, const uint8_t *ipaddr, int 
addrlen)
 {
-       addr->rta.rta_type = type;
-       memcpy(addr->ipaddr, ipaddr, 4);
-       *len += sizeof(*addr);
-       return addr + 1;
+       struct rtattr *rta;
+       size_t offset;
+
+       rta = addrs;
+       rta->rta_type = type;
+       rta->rta_len = sizeof(*rta) + addrlen;
+       memcpy(addrs + sizeof(*rta), ipaddr, addrlen);
+       offset = sizeof(*rta) + addrlen;
+       *len += offset;
+
+       return addrs + offset;
 }
 
 static void
 rtnl_route_request(struct relayd_interface *rif, struct relayd_host *host,
                   struct relayd_route *route, bool add)
 {
-       static struct {
+       int addrlen = AF2ADDRLEN(host->af);
+       struct {
                struct nlmsghdr nl;
                struct rtmsg rt;
                struct {
@@ -157,11 +191,14 @@ rtnl_route_request(struct relayd_interface *rif, struct 
relayd_host *host,
                        struct rtattr rta;
                        int ifindex;
                } __packed dev;
-               struct rtnl_addr addr[3];
+               union {
+                       struct rtnl_addr4 addr4[3];
+                       struct rtnl_addr6 addr6[3];
+               } __packed addrs;
        } __packed req = {
                .rt = {
-                       .rtm_family = AF_INET,
-                       .rtm_dst_len = 32,
+                       .rtm_family = host->af,
+                       .rtm_dst_len = addrlen * 8,
                        .rtm_table = RT_TABLE_MAIN,
                },
                .table.rta = {
@@ -172,12 +209,9 @@ rtnl_route_request(struct relayd_interface *rif, struct 
relayd_host *host,
                        .rta_type = RTA_OIF,
                        .rta_len = sizeof(req.dev),
                },
-               .addr[0].rta.rta_len = sizeof(struct rtnl_addr),
-               .addr[1].rta.rta_len = sizeof(struct rtnl_addr),
-               .addr[2].rta.rta_len = sizeof(struct rtnl_addr),
        };
-       int pktlen = sizeof(req) - sizeof(req.addr);
-       struct rtnl_addr *addr = &req.addr[0];
+       int pktlen = sizeof(req) - sizeof(req.addrs);
+       void *addrs = &req.addrs;
        const char *ifname = "loopback";
 
        req.dev.ifindex = host->rif->sll.sll_ifindex;
@@ -204,24 +238,41 @@ rtnl_route_request(struct relayd_interface *rif, struct 
relayd_host *host,
                ifname = rif->ifname;
 
        if (route) {
-               DPRINTF(2, "%s: add route to "IP_FMT"/%d via "IP_FMT" (%s)\n", 
ifname,
-                       IP_BUF(route->dest), route->mask, IP_BUF(host->ipaddr),
-                       host->rif->ifname);
+               switch (host->af) {
+               case AF_INET:
+                       DPRINTF(2, "%s: add route to "IP_FMT"/%d via "IP_FMT" 
(%s)\n", ifname,
+                               IP_BUF(route->dest), route->mask, 
IP_BUF(host->ipaddr),
+                               host->rif->ifname);
+                       break;
+               case AF_INET6:
+                       DPRINTF(2, "%s: add route to "IP6_FMT"/%d via "IP6_FMT" 
(%s)\n", ifname,
+                               IP6_BUF(route->dest16), route->mask, 
IP6_BUF(host->ipaddr16),
+                               host->rif->ifname);
+                       break;
+               }
 
                req.rt.rtm_dst_len = route->mask;
                if (route->mask)
-                       addr = rtnl_add_addr(addr, &pktlen, RTA_DST, 
route->dest);
-               addr = rtnl_add_addr(addr, &pktlen, RTA_GATEWAY, host->ipaddr);
+                       addrs = rtnl_add_addr(addrs, &pktlen, RTA_DST, 
route->dest, addrlen);
+               addrs = rtnl_add_addr(addrs, &pktlen, RTA_GATEWAY, 
host->ipaddr, addrlen);
        } else {
-               DPRINTF(2, "%s: add host route to "IP_FMT" (%s)\n", ifname,
-                       IP_BUF(host->ipaddr), host->rif->ifname);
-               addr = rtnl_add_addr(addr, &pktlen, RTA_DST, host->ipaddr);
-               req.rt.rtm_dst_len = 32;
+               switch (host->af) {
+               case AF_INET:
+                       DPRINTF(2, "%s: add host route to "IP_FMT" (%s)\n", 
ifname,
+                               IP_BUF(host->ipaddr), host->rif->ifname);
+                       break;
+               case AF_INET6:
+                       DPRINTF(2, "%s: add host route to "IP6_FMT" (%s)\n", 
ifname,
+                               IP6_BUF(host->ipaddr16), host->rif->ifname);
+                       break;
+               }
+               addrs = rtnl_add_addr(addrs, &pktlen, RTA_DST, host->ipaddr, 
addrlen);
+               req.rt.rtm_dst_len = addrlen * 8;
        }
 
        /* local route */
        if (!rif)
-               addr = rtnl_add_addr(addr, &pktlen, RTA_PREFSRC, local_addr);
+               addrs = rtnl_add_addr(addrs, &pktlen, RTA_PREFSRC, local_addr, 
addrlen);
 
        req.nl.nlmsg_len = pktlen;
        if (route)
@@ -272,7 +323,8 @@ static void rtnl_parse_newneigh(struct nlmsghdr *h)
        struct rtattr *rta;
        int len;
 
-       if (r->ndm_family != AF_INET)
+       if (r->ndm_family != AF_INET &&
+           r->ndm_family != AF_INET6)
                return;
 
        list_for_each_entry(rif, &interfaces, list) {
@@ -302,9 +354,45 @@ found_interface:
        if (!memcmp(lladdr, "\x00\x00\x00\x00\x00\x00", ETH_ALEN))
                return;
 
-       DPRINTF(1, "%s: Found ARP cache entry for host "IP_FMT" ("MAC_FMT")\n",
-               rif->ifname, IP_BUF(ipaddr), MAC_BUF(lladdr));
-       relayd_refresh_host(rif, lladdr, ipaddr);
+       switch (r->ndm_family) {
+       case AF_INET:
+               DPRINTF(1, "%s: Found ARP cache entry for host "IP_FMT" 
("MAC_FMT")\n",
+                       rif->ifname, IP_BUF(ipaddr), MAC_BUF(lladdr));
+               break;
+       case AF_INET6:
+               DPRINTF(1, "%s: Found ARP cache entry for host "IP6_FMT" 
("MAC_FMT")\n",
+                       rif->ifname, IP6_BUF(ipaddr), MAC_BUF(lladdr));
+               break;
+       }
+
+       relayd_refresh_host(rif, lladdr, ipaddr, r->ndm_family);
+}
+
+static void rtnl_dump_request(int nlmsg_type, int af)
+{
+       struct {
+               struct nlmsghdr nlh;
+               struct rtgenmsg g;
+       } req = {
+               .nlh = {
+                       .nlmsg_len = sizeof(req),
+                       .nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST,
+                       .nlmsg_pid = 0,
+               },
+               .g.rtgen_family = af,
+       };
+       req.nlh.nlmsg_type = nlmsg_type;
+       req.nlh.nlmsg_seq = rtnl_seq;
+       send(rtnl_sock.fd, &req, sizeof(req), 0);
+       rtnl_dump_seq = rtnl_seq++;
+}
+
+static void rtnl_dump_next(void)
+{
+       if (neigh_dump_idx >= neigh_dump_cnt)
+               return;
+
+       rtnl_dump_request(RTM_GETNEIGH, neigh_dump_af[neigh_dump_idx++]);
 }
 
 static void rtnl_parse_packet(void *data, int len)
@@ -312,6 +400,10 @@ static void rtnl_parse_packet(void *data, int len)
        struct nlmsghdr *h;
 
        for (h = data; NLMSG_OK(h, len); h = NLMSG_NEXT(h, len)) {
+               if (h->nlmsg_type == NLMSG_DONE &&
+                   h->nlmsg_seq == rtnl_dump_seq)
+                       rtnl_dump_next();
+
                if (h->nlmsg_type == NLMSG_DONE ||
                    h->nlmsg_type == NLMSG_ERROR)
                        return;
@@ -360,29 +452,12 @@ static void rtnl_cb(struct uloop_fd *fd, unsigned int 
events)
        } while (1);
 }
 
-static void rtnl_dump_request(int nlmsg_type)
-{
-       static struct {
-               struct nlmsghdr nlh;
-               struct rtgenmsg g;
-       } req = {
-               .nlh = {
-                       .nlmsg_len = sizeof(req),
-                       .nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST,
-                       .nlmsg_pid = 0,
-               },
-               .g.rtgen_family = AF_INET,
-       };
-       req.nlh.nlmsg_type = nlmsg_type;
-       req.nlh.nlmsg_seq = rtnl_seq;
-       send(rtnl_sock.fd, &req, sizeof(req), 0);
-       rtnl_seq++;
-}
-
-int relayd_rtnl_init(void)
+int relayd_rtnl_init(int ipv6_flag)
 {
        struct sockaddr_nl snl_local = {};
 
+       ipv6 = ipv6_flag;
+
        rtnl_sock.fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
        if (rtnl_sock.fd < 0) {
                perror("socket(AF_NETLINK)");
@@ -400,9 +475,14 @@ int relayd_rtnl_init(void)
        rtnl_sock.cb = rtnl_cb;
        uloop_fd_add(&rtnl_sock, ULOOP_READ | ULOOP_EDGE_TRIGGER);
 
+       neigh_dump_idx = 0;
+       neigh_dump_cnt = 0;
+       neigh_dump_af[neigh_dump_cnt++] = AF_INET;
+       if (ipv6)
+               neigh_dump_af[neigh_dump_cnt++] = AF_INET6;
+
        rtnl_seq = time(NULL);
-       rtnl_dump_seq = rtnl_seq;
-       rtnl_dump_request(RTM_GETNEIGH);
+       rtnl_dump_next();
        rtnl_rule_request(NULL, RULE_F_ADD);
 
        return 0;
-- 
2.1.4
_______________________________________________
openwrt-devel mailing list
[email protected]
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel

Reply via email to