[OpenWrt-Devel] [RFC relayd 1/2] relayd: add ipv6 support
Michal Kazior
michal.kazior at tieto.com
Mon Apr 11 09:14:53 EDT 2016
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 <michal.kazior at tieto.com>
---
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
openwrt-devel at lists.openwrt.org
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel
More information about the openwrt-devel
mailing list