From patchwork Mon Apr 11 13:14:53 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Michal Kazior X-Patchwork-Id: 608780 X-Patchwork-Delegate: blogic@openwrt.org Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from arrakis.dune.hu (caladan.dune.hu [78.24.191.180]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3qk9WK2146z9ssM for ; Mon, 11 Apr 2016 23:14:05 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=tieto.com header.i=@tieto.com header.b=xl0187Zh; dkim-atps=neutral Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id 60394B80CE9; Mon, 11 Apr 2016 15:13:27 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on arrakis.dune.hu X-Spam-Level: X-Spam-Status: No, score=-0.3 required=5.0 tests=BAYES_00,RDNS_NONE, T_DKIM_INVALID autolearn=no autolearn_force=no version=3.4.1 Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP; Mon, 11 Apr 2016 15:13:27 +0200 (CEST) Received: from arrakis.dune.hu (localhost [127.0.0.1]) by arrakis.dune.hu (Postfix) with ESMTP id 414D3B80CD0 for ; Mon, 11 Apr 2016 15:13:23 +0200 (CEST) X-policyd-weight: using cached result; rate: -4 Received: from mail-lf0-f46.google.com (unknown [209.85.215.46]) by arrakis.dune.hu (Postfix) with ESMTPS for ; Mon, 11 Apr 2016 15:13:22 +0200 (CEST) Received: by mail-lf0-f46.google.com with SMTP id g184so156235678lfb.3 for ; Mon, 11 Apr 2016 06:13:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tieto.com; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=YTnMoeOuDSVCFgWPVooZFH1JyykMzBbT+2WNX1tML5A=; b=xl0187ZhkQqFdoVe/+HEjG8YyhsJk3WJ/Yw5BkjvIFlqAwJzyY6+bUde2XSxs4M3LX 3/l7EF9I3YgggIKvpIi92c3kXbevMBKq/Mk2wnVeMZ3xfQnSQHy1HBK4LUgLRXuDTqy5 +BjnVTh+7zmetQcH2Bw43XoneIR9CxkwvL+eA= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=YTnMoeOuDSVCFgWPVooZFH1JyykMzBbT+2WNX1tML5A=; b=eV8cUCztEhzcxPKPbSdRXMTPrYLDYbC0AA8j8sDU+nf6Q00J1xmGPIlZ77qt7n7RdS MQkbH/a0YevHO6/4brzHZ/GUpC8yJMPv403IHdYyrZVgiqNRkJAwisolnygsA7DljwEz 0Yy3BRD1bw+P7Z4ofFwkh2pUzYghegoN4ZcMgZFeKogQYJg7P9kjgSOA55PBaRyiBJAR TXHjAAfNpAf9a7BsXKJJDayOHxIPEUUN65qKSCGb63evfdO8crQ9s+5I3TZrCAKFA0v5 XSzwotKNpWZzcD5WAJD/Esju8or9BkQLC45HQpumMOFtWEVwy6i/9ahvwn9ockcEm7iL 5O3w== X-Gm-Message-State: AOPr4FWuQQtQGEdoWlc1msS5ZYP6KA8vJ8jGOd5+SoqZTpwI8I+M5uCIFbtp8+k9B3toxiijC4SKpXqPPrjl+AIXD3UvIz74EeCMwiDAoSVfpRhp11fXIhA2rf7kvKy1MZbB8KZ1vO8qgwcDTvzwIdPC0p8QyfJcbwnvB8bqq7LarvtPvkFu4PCQh40LhN/vrqC7bMRO X-Received: by 10.25.73.136 with SMTP id w130mr4780556lfa.71.1460380402391; Mon, 11 Apr 2016 06:13:22 -0700 (PDT) Received: from localhost.localdomain ([91.198.246.10]) by smtp.gmail.com with ESMTPSA id l191sm4633896lfl.19.2016.04.11.06.13.21 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 11 Apr 2016 06:13:21 -0700 (PDT) From: Michal Kazior To: openwrt-devel@lists.openwrt.org Date: Mon, 11 Apr 2016 15:14:53 +0200 Message-Id: <1460380494-22667-2-git-send-email-michal.kazior@tieto.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1460380494-22667-1-git-send-email-michal.kazior@tieto.com> References: <1460380494-22667-1-git-send-email-michal.kazior@tieto.com> X-DomainID: tieto.com Subject: [OpenWrt-Devel] [RFC relayd 1/2] relayd: add ipv6 support X-BeenThere: openwrt-devel@lists.openwrt.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: OpenWrt Development List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: openwrt-devel-bounces@lists.openwrt.org Sender: "openwrt-devel" 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 --- 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 #include #include +#include #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 Enable local access using 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 #include #include +#include +#include #include #include @@ -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;