diff options
Diffstat (limited to 'src/VBox/NetworkServices/NAT/proxy_dhcp6ds.c')
-rw-r--r-- | src/VBox/NetworkServices/NAT/proxy_dhcp6ds.c | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/src/VBox/NetworkServices/NAT/proxy_dhcp6ds.c b/src/VBox/NetworkServices/NAT/proxy_dhcp6ds.c new file mode 100644 index 00000000..ac831346 --- /dev/null +++ b/src/VBox/NetworkServices/NAT/proxy_dhcp6ds.c @@ -0,0 +1,317 @@ +/* $Id: proxy_dhcp6ds.c $ */ +/** @file + * NAT Network - Simple stateless DHCPv6 (RFC 3736) server. + */ + +/* + * 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 "dhcp6.h" +#include "proxy.h" + +#include <string.h> + +#include "lwip/opt.h" +#include "lwip/mld6.h" +#include "lwip/udp.h" + + +static void dhcp6ds_recv(void *, struct udp_pcb *, struct pbuf *, ip6_addr_t *, u16_t); + + +/* ff02::1:2 - "All_DHCP_Relay_Agents_and_Servers" link-scoped multicast */ +static /* const */ ip6_addr_t all_dhcp_relays_and_servers = { + { PP_HTONL(0xff020000UL), 0, 0, PP_HTONL(0x00010002UL) } +}; + +/* ff05::1:3 - "All_DHCP_Servers" site-scoped multicast */ +static /* const */ ip6_addr_t all_dhcp_servers = { + { PP_HTONL(0xff050000UL), 0, 0, PP_HTONL(0x00010003UL) } +}; + + +static struct udp_pcb *dhcp6ds_pcb; + +/* prebuilt Server ID option */ +#define DUID_LL_LEN (/* duid type */ 2 + /* hw type */ 2 + /* ether addr */ 6) +static u8_t dhcp6ds_serverid[/* opt */ 2 + /* optlen */ 2 + DUID_LL_LEN]; + +/* prebuilt DNS Servers option */ +static u8_t dhcp6ds_dns[/* opt */ 2 + /* optlen */ 2 + /* IPv6 addr */ 16]; + + +/** + * Initialize DHCP6 server. + * + * Join DHCP6 multicast groups. + * Create and bind server pcb. + * Prebuild fixed parts of reply. + */ +err_t +dhcp6ds_init(struct netif *proxy_netif) +{ + ip6_addr_t *pxaddr, *pxaddr_nonlocal; + int i; + err_t error; + + LWIP_ASSERT1(proxy_netif != NULL); + LWIP_ASSERT1(proxy_netif->hwaddr_len == 6); /* ethernet */ + + pxaddr = netif_ip6_addr(proxy_netif, 0); /* link local */ + + /* + * XXX: TODO: This is a leftover from testing with IPv6 mapped + * loopback with a special IPv6->IPv4 mapping hack in pxudp.c + */ + /* advertise ourself as DNS resolver - will be proxied to host */ + pxaddr_nonlocal = NULL; + for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; ++i) { + if (ip6_addr_ispreferred(netif_ip6_addr_state(proxy_netif, i)) + && !ip6_addr_islinklocal(netif_ip6_addr(proxy_netif, i))) + { + pxaddr_nonlocal = netif_ip6_addr(proxy_netif, i); + break; + } + } + LWIP_ASSERT1(pxaddr_nonlocal != NULL); /* must be configured on the netif */ + + + error = mld6_joingroup(pxaddr, &all_dhcp_relays_and_servers); + if (error != ERR_OK) { + DPRINTF0(("%s: failed to join All_DHCP_Relay_Agents_and_Servers: %s\n", + __func__, proxy_lwip_strerr(error))); + goto err; + } + + error = mld6_joingroup(pxaddr, &all_dhcp_servers); + if (error != ERR_OK) { + DPRINTF0(("%s: failed to join All_DHCP_Servers: %s\n", + __func__, proxy_lwip_strerr(error))); + goto err1; + } + + + dhcp6ds_pcb = udp_new_ip6(); + if (dhcp6ds_pcb == NULL) { + DPRINTF0(("%s: failed to allocate PCB\n", __func__)); + error = ERR_MEM; + goto err2; + } + + udp_recv_ip6(dhcp6ds_pcb, dhcp6ds_recv, NULL); + + error = udp_bind_ip6(dhcp6ds_pcb, pxaddr, DHCP6_SERVER_PORT); + if (error != ERR_OK) { + DPRINTF0(("%s: failed to bind PCB\n", __func__)); + goto err3; + } + + +#define OPT_SET(buf, off, c) do { \ + u16_t _s = PP_HTONS(c); \ + memcpy(&(buf)[off], &_s, sizeof(u16_t)); \ + } while (0) + +#define SERVERID_SET(off, c) OPT_SET(dhcp6ds_serverid, (off), (c)) +#define DNSSRV_SET(off, c) OPT_SET(dhcp6ds_dns, (off), (c)) + + SERVERID_SET(0, DHCP6_OPTION_SERVERID); + SERVERID_SET(2, DUID_LL_LEN); + SERVERID_SET(4, DHCP6_DUID_LL); + SERVERID_SET(6, ARES_HRD_ETHERNET); + memcpy(&dhcp6ds_serverid[8], proxy_netif->hwaddr, 6); + + DNSSRV_SET(0, DHCP6_OPTION_DNS_SERVERS); + DNSSRV_SET(2, 16); /* one IPv6 address */ + /* + * XXX: TODO: This is a leftover from testing with IPv6 mapped + * loopback with a special IPv6->IPv4 mapping hack in pxudp.c + */ + memcpy(&dhcp6ds_dns[4], pxaddr_nonlocal, sizeof(ip6_addr_t)); + +#undef SERVERID_SET +#undef DNSSRV_SET + + return ERR_OK; + + + err3: + udp_remove(dhcp6ds_pcb); + dhcp6ds_pcb = NULL; + err2: + mld6_leavegroup(pxaddr, &all_dhcp_servers); + err1: + mld6_leavegroup(pxaddr, &all_dhcp_relays_and_servers); + err: + return error; +} + + +static u8_t dhcp6ds_reply_buf[1024]; + +static void +dhcp6ds_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, + ip6_addr_t *addr, u16_t port) +{ + u8_t msg_header[4]; + unsigned int msg_type, msg_tid; + int copied; + size_t roff; + struct pbuf *q; + err_t error; + + LWIP_UNUSED_ARG(arg); + LWIP_ASSERT1(p != NULL); + + copied = pbuf_copy_partial(p, msg_header, sizeof(msg_header), 0); + if (copied != sizeof(msg_header)) { + DPRINTF(("%s: message header truncated\n", __func__)); + pbuf_free(p); + return; + } + pbuf_header(p, -(s16_t)sizeof(msg_header)); + + msg_type = msg_header[0]; + msg_tid = (msg_header[1] << 16) | (msg_header[2] << 8) | msg_header[3]; + DPRINTF(("%s: type %u, tid 0x%6x\n", __func__, msg_type, msg_tid)); + if (msg_type != DHCP6_INFORMATION_REQUEST) { /** @todo ? RELAY_FORW */ + pbuf_free(p); + return; + } + + roff = 0; + + msg_header[0] = DHCP6_REPLY; + memcpy(dhcp6ds_reply_buf + roff, msg_header, sizeof(msg_header)); + roff += sizeof(msg_header); + + + /* loop over options */ + while (p->tot_len > 0) { + u16_t opt, optlen; + + /* fetch option code */ + copied = pbuf_copy_partial(p, &opt, sizeof(opt), 0); + if (copied != sizeof(opt)) { + DPRINTF(("%s: option header truncated\n", __func__)); + pbuf_free(p); + return; + } + pbuf_header(p, -(s16_t)sizeof(opt)); + opt = ntohs(opt); + + /* fetch option length */ + copied = pbuf_copy_partial(p, &optlen, sizeof(optlen), 0); + if (copied != sizeof(optlen)) { + DPRINTF(("%s: option %u length truncated\n", __func__, opt)); + pbuf_free(p); + return; + } + pbuf_header(p, -(s16_t)sizeof(optlen)); + optlen = ntohs(optlen); + + /* enough data? */ + if (optlen > p->tot_len) { + DPRINTF(("%s: option %u truncated: expect %u, got %u\n", + __func__, opt, optlen, p->tot_len)); + pbuf_free(p); + return; + } + + DPRINTF2(("%s: option %u length %u\n", __func__, opt, optlen)); + + if (opt == DHCP6_OPTION_CLIENTID) { + u16_t s; + + /* "A DUID can be no more than 128 octets long (not + including the type code)." */ + if (optlen > 130) { + DPRINTF(("%s: client DUID too long: %u\n", __func__, optlen)); + pbuf_free(p); + return; + } + + s = PP_HTONS(DHCP6_OPTION_CLIENTID); + memcpy(dhcp6ds_reply_buf + roff, &s, sizeof(s)); + roff += sizeof(s); + + s = ntohs(optlen); + memcpy(dhcp6ds_reply_buf + roff, &s, sizeof(s)); + roff += sizeof(s); + + pbuf_copy_partial(p, dhcp6ds_reply_buf + roff, optlen, 0); + roff += optlen; + } + else if (opt == DHCP6_OPTION_ORO) { + u16_t *opts; + int i, nopts; + + if (optlen % 2 != 0) { + DPRINTF2(("%s: Option Request of odd length\n", __func__)); + goto bad_oro; + } + nopts = optlen / 2; + + opts = (u16_t *)malloc(optlen); + if (opts == NULL) { + DPRINTF2(("%s: failed to allocate space for Option Request\n", + __func__)); + goto bad_oro; + } + + pbuf_copy_partial(p, opts, optlen, 0); + for (i = 0; i < nopts; ++i) { + opt = ntohs(opts[i]); + DPRINTF2(("> request option %u\n", opt)); + }; + free(opts); + + bad_oro: /* empty */; + } + + pbuf_header(p, -optlen); /* go to next option */ + } + pbuf_free(p); /* done */ + + + memcpy(dhcp6ds_reply_buf + roff, dhcp6ds_serverid, sizeof(dhcp6ds_serverid)); + roff += sizeof(dhcp6ds_serverid); + + memcpy(dhcp6ds_reply_buf + roff, dhcp6ds_dns, sizeof(dhcp6ds_dns)); + roff += sizeof(dhcp6ds_dns); + + Assert(roff == (u16_t)roff); + q = pbuf_alloc(PBUF_RAW, (u16_t)roff, PBUF_RAM); + if (q == NULL) { + DPRINTF(("%s: pbuf_alloc(%d) failed\n", __func__, (int)roff)); + return; + } + + error = pbuf_take(q, dhcp6ds_reply_buf, (u16_t)roff); + if (error != ERR_OK) { + DPRINTF(("%s: pbuf_take(%d) failed: %s\n", + __func__, (int)roff, proxy_lwip_strerr(error))); + pbuf_free(q); + return; + } + + error = udp_sendto_ip6(pcb, q, addr, port); + if (error != ERR_OK) { + DPRINTF(("%s: udp_sendto failed: %s\n", + __func__, proxy_lwip_strerr(error))); + } + + pbuf_free(q); +} |