proxy_rtadvd.c revision 23bcfa32fddbe29a8c4c40d3bcfa4693a555c177
/* $Id$ */
/** @file
* NAT Network - IPv6 router advertisement daemon.
*/
/*
* Copyright (C) 2013-2014 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);
}