summaryrefslogtreecommitdiffstats
path: root/src/VBox/NetworkServices/NAT/proxy_rtadvd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/NetworkServices/NAT/proxy_rtadvd.c')
-rw-r--r--src/VBox/NetworkServices/NAT/proxy_rtadvd.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/NAT/proxy_rtadvd.c b/src/VBox/NetworkServices/NAT/proxy_rtadvd.c
new file mode 100644
index 00000000..849ef789
--- /dev/null
+++ b/src/VBox/NetworkServices/NAT/proxy_rtadvd.c
@@ -0,0 +1,417 @@
+/* $Id: proxy_rtadvd.c $ */
+/** @file
+ * NAT Network - IPv6 router advertisement daemon.
+ */
+
+/*
+ * Copyright (C) 2013-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define LOG_GROUP LOG_GROUP_NAT_SERVICE
+
+#include "winutils.h"
+
+#include "proxy.h"
+
+#include "lwip/opt.h"
+#include "lwip/sys.h"
+#include "lwip/stats.h"
+#include "lwip/timers.h"
+
+#include "lwip/inet_chksum.h"
+#include "lwip/icmp6.h"
+#include "lwip/nd6.h"
+
+#include "lwip/raw.h"
+
+#include <string.h>
+
+
+static void proxy_rtadvd_timer(void *);
+static void proxy_rtadvd_send_multicast(struct netif *);
+static void proxy_rtadvd_fill_payload(struct netif *, int);
+
+static u8_t rtadvd_recv(void *, struct raw_pcb *, struct pbuf *, ip6_addr_t *);
+
+
+/* ff02::1 - link-local all nodes multicast address */
+static ip6_addr_t allnodes_linklocal = {
+ { PP_HTONL(0xff020000UL), 0, 0, PP_HTONL(0x00000001UL) }
+};
+
+
+/*
+ * Unsolicited Router Advertisement payload.
+ *
+ * NB: Since ICMP checksum covers pseudo-header with destination
+ * address (link-local allnodes multicast in this case) this payload
+ * cannot be used for solicited replies to unicast addresses.
+ */
+static unsigned int unsolicited_ra_payload_length;
+static u8_t unsolicited_ra_payload[
+ sizeof(struct ra_header)
+ /* reserves enough space for NETIF_MAX_HWADDR_LEN */
+ + sizeof(struct lladdr_option)
+ /* we only announce one prefix */
+ + sizeof(struct prefix_option) * 1
+];
+
+
+static int ndefaults = 0;
+
+static struct raw_pcb *rtadvd_pcb;
+
+
+void
+proxy_rtadvd_start(struct netif *proxy_netif)
+{
+#if 0 /* XXX */
+ ndefaults = rtmon_get_defaults();
+#else
+ ndefaults = g_proxy_options->ipv6_defroute;
+#endif
+ if (ndefaults < 0) {
+ DPRINTF0(("rtadvd: failed to read IPv6 routing table, aborting\n"));
+ return;
+ }
+
+ proxy_rtadvd_fill_payload(proxy_netif, ndefaults > 0);
+
+ rtadvd_pcb = raw_new_ip6(IP6_NEXTH_ICMP6);
+ if (rtadvd_pcb == NULL) {
+ DPRINTF0(("rtadvd: failed to allocate pcb, aborting\n"));
+ return;
+ }
+
+ /*
+ * We cannot use raw_bind_ip6() since raw_input() doesn't grok
+ * multicasts. We are going to use ip6_output_if() directly.
+ */
+ raw_recv_ip6(rtadvd_pcb, rtadvd_recv, proxy_netif);
+
+ sys_timeout(3 * 1000, proxy_rtadvd_timer, proxy_netif);
+}
+
+
+static int quick_ras = 2;
+
+
+/**
+ * lwIP thread callback invoked when we start/stop advertising default
+ * route.
+ */
+void
+proxy_rtadvd_do_quick(void *arg)
+{
+ struct netif *proxy_netif = (struct netif *)arg;
+
+ quick_ras = 2;
+ sys_untimeout(proxy_rtadvd_timer, proxy_netif);
+ proxy_rtadvd_timer(proxy_netif); /* sends and re-arms */
+}
+
+
+static void
+proxy_rtadvd_timer(void *arg)
+{
+ struct netif *proxy_netif = (struct netif *)arg;
+ int newdefs;
+ u32_t delay;
+
+#if 0 /* XXX */
+ newdefs = rtmon_get_defaults();
+#else
+ newdefs = g_proxy_options->ipv6_defroute;
+#endif
+ if (newdefs != ndefaults && newdefs != -1) {
+ ndefaults = newdefs;
+ proxy_rtadvd_fill_payload(proxy_netif, ndefaults > 0);
+ }
+
+ proxy_rtadvd_send_multicast(proxy_netif);
+
+ if (quick_ras > 0) {
+ --quick_ras;
+ delay = 16 * 1000;
+ }
+ else {
+ delay = 600 * 1000;
+ }
+
+ sys_timeout(delay, proxy_rtadvd_timer, proxy_netif);
+}
+
+
+/*
+ * This should be folded into icmp6/nd6 input, but I don't want to
+ * solve this in general, making it configurable, etc.
+ *
+ * Cf. RFC 4861:
+ * 6.1.1. Validation of Router Solicitation Messages
+ */
+static u8_t
+rtadvd_recv(void *arg, struct raw_pcb *pcb, struct pbuf *p, ip6_addr_t *addr)
+{
+ enum raw_recv_status { RAW_RECV_CONTINUE = 0, RAW_RECV_CONSUMED = 1 };
+
+ struct netif *proxy_netif = (struct netif *)arg;
+ struct ip6_hdr *ip6_hdr;
+ struct icmp6_hdr *icmp6_hdr;
+ struct lladdr_option *lladdr_opt;
+ void *option;
+ u8_t opttype, optlen8;
+
+ LWIP_UNUSED_ARG(pcb);
+ LWIP_UNUSED_ARG(addr);
+
+ /* save a pointer to IP6 header and skip to ICMP6 payload */
+ ip6_hdr = (struct ip6_hdr *)p->payload;
+ pbuf_header(p, -ip_current_header_tot_len());
+
+ if (p->len < sizeof(struct icmp6_hdr)) {
+ ICMP6_STATS_INC(icmp6.lenerr);
+ goto drop;
+ }
+
+ if (ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->tot_len,
+ ip6_current_src_addr(),
+ ip6_current_dest_addr()) != 0)
+ {
+ ICMP6_STATS_INC(icmp6.chkerr);
+ goto drop;
+ }
+
+ icmp6_hdr = (struct icmp6_hdr *)p->payload;
+ if (icmp6_hdr->type != ICMP6_TYPE_RS) {
+ pbuf_header(p, ip_current_header_tot_len()); /* restore payload ptr */
+ return RAW_RECV_CONTINUE; /* not interested */
+ }
+
+ /* only now that we know it's ICMP6_TYPE_RS we can check IP6 hop limit */
+ if (IP6H_HOPLIM(ip6_hdr) != 255) {
+ ICMP6_STATS_INC(icmp6.proterr);
+ goto drop;
+ }
+
+ /* future, backward-incompatible changes may use different Code values. */
+ if (icmp6_hdr->code != 0) {
+ ICMP6_STATS_INC(icmp6.proterr);
+ goto drop;
+ }
+
+ /* skip past rs_header, nothing interesting in it */
+ if (p->len < sizeof(struct rs_header)) {
+ ICMP6_STATS_INC(icmp6.lenerr);
+ goto drop;
+ }
+ pbuf_header(p, -(s16_t)sizeof(struct rs_header));
+
+ lladdr_opt = NULL;
+ while (p->len > 0) {
+ int optlen;
+
+ if (p->len < 8) {
+ ICMP6_STATS_INC(icmp6.lenerr);
+ goto drop;
+ }
+
+ option = p->payload;
+ opttype = ((u8_t *)option)[0];
+ optlen8 = ((u8_t *)option)[1]; /* in units of 8 octets */
+
+ if (optlen8 == 0) {
+ ICMP6_STATS_INC(icmp6.proterr);
+ goto drop;
+ }
+
+ optlen = (unsigned int)optlen8 << 3;
+ if (p->len < optlen) {
+ ICMP6_STATS_INC(icmp6.lenerr);
+ goto drop;
+ }
+
+ if (opttype == ND6_OPTION_TYPE_SOURCE_LLADDR) {
+ if (lladdr_opt != NULL) { /* duplicate */
+ ICMP6_STATS_INC(icmp6.proterr);
+ goto drop;
+ }
+ lladdr_opt = (struct lladdr_option *)option;
+ }
+
+ pbuf_header(p, -optlen);
+ }
+
+ if (ip6_addr_isany(ip6_current_src_addr())) {
+ if (lladdr_opt != NULL) {
+ ICMP6_STATS_INC(icmp6.proterr);
+ goto drop;
+ }
+
+ /* reply with multicast RA */
+ }
+ else {
+ /*
+ * XXX: Router is supposed to update its Neighbor Cache (6.2.6),
+ * but it's hidden inside nd6.c.
+ */
+
+ /* may reply with either unicast or multicast RA */
+ }
+ /* we just always reply with multicast RA */
+
+ pbuf_free(p); /* NB: this invalidates lladdr_option */
+
+ sys_untimeout(proxy_rtadvd_timer, proxy_netif);
+ proxy_rtadvd_timer(proxy_netif); /* sends and re-arms */
+
+ return RAW_RECV_CONSUMED;
+
+ drop:
+ pbuf_free(p);
+ ICMP6_STATS_INC(icmp6.drop);
+ return RAW_RECV_CONSUMED;
+}
+
+
+static void
+proxy_rtadvd_send_multicast(struct netif *proxy_netif)
+{
+ struct pbuf *ph, *pp;
+ err_t error;
+
+ ph = pbuf_alloc(PBUF_IP, 0, PBUF_RAM);
+ if (ph == NULL) {
+ DPRINTF0(("%s: failed to allocate RA header pbuf\n", __func__));
+ return;
+ }
+
+ pp = pbuf_alloc(PBUF_RAW, unsolicited_ra_payload_length, PBUF_ROM);
+ if (pp == NULL) {
+ DPRINTF0(("%s: failed to allocate RA payload pbuf\n", __func__));
+ pbuf_free(ph);
+ return;
+ }
+ pp->payload = unsolicited_ra_payload;
+ pbuf_chain(ph, pp);
+
+ error = ip6_output_if(ph,
+ netif_ip6_addr(proxy_netif, 0), /* src: link-local */
+ &allnodes_linklocal, /* dst */
+ 255, /* hop limit */
+ 0, /* traffic class */
+ IP6_NEXTH_ICMP6,
+ proxy_netif);
+ if (error != ERR_OK) {
+ DPRINTF0(("%s: failed to send RA (err=%d)\n", __func__, error));
+ }
+
+ pbuf_free(pp);
+ pbuf_free(ph);
+}
+
+
+/*
+ * XXX: TODO: Only ra_header::router_lifetime (and hence
+ * ra_header::chksum) need to be changed, so we can precompute it once
+ * and then only update these two fields.
+ */
+static void
+proxy_rtadvd_fill_payload(struct netif *proxy_netif, int is_default)
+{
+ struct pbuf *p;
+ struct ra_header *ra_hdr;
+ struct lladdr_option *lladdr_opt;
+ struct prefix_option *pfx_opt;
+ unsigned int lladdr_optlen;
+
+ LWIP_ASSERT("netif hwaddr too long",
+ proxy_netif->hwaddr_len <= NETIF_MAX_HWADDR_LEN);
+
+ /* type + length + ll addr + round up to 8 octets */
+ lladdr_optlen = (2 + proxy_netif->hwaddr_len + 7) & ~0x7;
+
+ /* actual payload length */
+ unsolicited_ra_payload_length =
+ sizeof(struct ra_header)
+ + lladdr_optlen
+ + sizeof(struct prefix_option) * 1;
+
+ /* Set fields. */
+ ra_hdr = (struct ra_header *)unsolicited_ra_payload;
+ lladdr_opt = (struct lladdr_option *)((u8_t *)ra_hdr + sizeof(struct ra_header));
+ pfx_opt = (struct prefix_option *)((u8_t *)lladdr_opt + lladdr_optlen);
+
+ memset(unsolicited_ra_payload, 0, sizeof(unsolicited_ra_payload));
+
+ ra_hdr->type = ICMP6_TYPE_RA;
+
+#if 0
+ /*
+ * "M" flag. Tell guests to use stateful DHCP6. Disabled here
+ * since we don't provide stateful server.
+ */
+ ra_hdr->flags |= ND6_RA_FLAG_MANAGED_ADDR_CONFIG;
+#endif
+ /*
+ * XXX: TODO: Disable "O" flag for now to match disabled stateless
+ * server. We don't yet get IPv6 nameserver addresses from
+ * HostDnsService, so we have nothing to say, don't tell guests to
+ * come asking.
+ */
+#if 0
+ /*
+ * "O" flag. Tell guests to use DHCP6 for DNS and the like. This
+ * is served by simple stateless server (RFC 3736).
+ *
+ * XXX: "STATEFUL" in the flag name was probably a bug in RFC2461.
+ * It's present in the text, but not in the router configuration
+ * variable name. It's dropped in the text in RFC4861.
+ */
+ ra_hdr->flags |= ND6_RA_FLAG_OTHER_STATEFUL_CONFIG;
+#endif
+
+ if (is_default) {
+ ra_hdr->router_lifetime = PP_HTONS(1200); /* seconds */
+ }
+ else {
+ ra_hdr->router_lifetime = 0;
+ }
+
+ lladdr_opt->type = ND6_OPTION_TYPE_SOURCE_LLADDR;
+ lladdr_opt->length = lladdr_optlen >> 3; /* in units of 8 octets */
+ memcpy(lladdr_opt->addr, proxy_netif->hwaddr, proxy_netif->hwaddr_len);
+
+ pfx_opt->type = ND6_OPTION_TYPE_PREFIX_INFO;
+ pfx_opt->length = 4;
+ pfx_opt->prefix_length = 64;
+ pfx_opt->flags = ND6_PREFIX_FLAG_ON_LINK
+ | ND6_PREFIX_FLAG_AUTONOMOUS;
+ pfx_opt->valid_lifetime = ~0U; /* infinite */
+ pfx_opt->preferred_lifetime = ~0U; /* infinite */
+ pfx_opt->prefix.addr[0] = netif_ip6_addr(proxy_netif, 1)->addr[0];
+ pfx_opt->prefix.addr[1] = netif_ip6_addr(proxy_netif, 1)->addr[1];
+
+
+ /* we need a temp pbuf to calculate the checksum */
+ p = pbuf_alloc(PBUF_IP, unsolicited_ra_payload_length, PBUF_ROM);
+ if (p == NULL) {
+ DPRINTF0(("rtadvd: failed to allocate RA pbuf\n"));
+ return;
+ }
+ p->payload = unsolicited_ra_payload;
+
+ ra_hdr->chksum = ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->len,
+ /* src addr: netif's link-local */
+ netif_ip6_addr(proxy_netif, 0),
+ /* dst addr */
+ &allnodes_linklocal);
+ pbuf_free(p);
+}