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;
