passt: Introduce a DHCPv6 server
This implementation, similarly to the IPv4 DHCP one, hands out a single address, which is the same as the upstream address for the host. This avoids the need for address translation as long as the client runs a DHCPv6 client. The NDP "Managed" flag is now set in Router Advertisements. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
This commit is contained in:
502
dhcpv6.c
Normal file
502
dhcpv6.c
Normal file
@@ -0,0 +1,502 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
/* PASST - Plug A Simple Socket Transport
|
||||
*
|
||||
* dhcpv6.c - Minimalistic DHCPv6 server for PASST
|
||||
*
|
||||
* Copyright (c) 2021 Red Hat GmbH
|
||||
* Author: Stefano Brivio <sbrivio@redhat.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/ipv6.h>
|
||||
#include <linux/udp.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_arp.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include "passt.h"
|
||||
#include "tap.h"
|
||||
#include "util.h"
|
||||
|
||||
/**
|
||||
* struct opt_hdr - DHCPv6 option header
|
||||
* @t: Option type
|
||||
* @l: Option length, network order
|
||||
*/
|
||||
struct opt_hdr {
|
||||
uint16_t t;
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
# define OPT_CLIENTID 1
|
||||
# define OPT_SERVERID 2
|
||||
# define OPT_IA_NA 3
|
||||
# define OPT_IA_TA 4
|
||||
# define OPT_IAAADR 5
|
||||
# define OPT_STATUS_CODE 13
|
||||
# define STATUS_NOTONLINK 4
|
||||
# define OPT_DNS_SERVERS 23
|
||||
#else
|
||||
# define OPT_CLIENTID __bswap_constant_16(1)
|
||||
# define OPT_SERVERID __bswap_constant_16(2)
|
||||
# define OPT_IA_NA __bswap_constant_16(3)
|
||||
# define OPT_IA_TA __bswap_constant_16(4)
|
||||
# define OPT_IAAADR __bswap_constant_16(5)
|
||||
# define OPT_STATUS_CODE __bswap_constant_16(13)
|
||||
# define STATUS_NOTONLINK __bswap_constant_16(4)
|
||||
# define OPT_DNS_SERVERS __bswap_constant_16(23)
|
||||
#endif
|
||||
#define STR_NOTONLINK "Prefix not appropriate for link."
|
||||
|
||||
uint16_t l;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
# define OPT_SIZE_CONV(x) (x)
|
||||
#else
|
||||
# define OPT_SIZE_CONV(x) (__bswap_constant_16(x))
|
||||
#endif
|
||||
#define OPT_SIZE(x) OPT_SIZE_CONV(sizeof(struct opt_##x) - \
|
||||
sizeof(struct opt_hdr))
|
||||
|
||||
/**
|
||||
* struct opt_client_id - DHCPv6 Client Identifier option
|
||||
* @hdr: Option header
|
||||
* @duid: Client DUID, up to 128 bytes (cf. RFC 8415, 11.1.)
|
||||
*/
|
||||
struct opt_client_id {
|
||||
struct opt_hdr hdr;
|
||||
uint8_t duid[128];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct opt_server_id - DHCPv6 Server Identifier option
|
||||
* @hdr: Option header
|
||||
* @duid_type: Type of server DUID, network order
|
||||
* @duid_hw: IANA hardware type, network order
|
||||
* @duid_time: Time reference, network order
|
||||
* @duid_lladdr: Link-layer address (MAC address)
|
||||
*/
|
||||
struct opt_server_id {
|
||||
struct opt_hdr hdr;
|
||||
uint16_t duid_type;
|
||||
#define DUID_TYPE_LLT 1
|
||||
|
||||
uint16_t duid_hw;
|
||||
uint32_t duid_time;
|
||||
uint8_t duid_lladdr[ETH_ALEN];
|
||||
};
|
||||
|
||||
static const struct opt_server_id server_id_const = {
|
||||
{ OPT_SERVERID, OPT_SIZE(server_id) },
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
DUID_TYPE_LLT, ARPHRD_ETHER,
|
||||
#else
|
||||
__bswap_constant_16(DUID_TYPE_LLT), __bswap_constant_16(ARPHRD_ETHER),
|
||||
#endif
|
||||
0, { 0 }
|
||||
};
|
||||
|
||||
/**
|
||||
* struct opt_ia_na - Identity Association for Non-temporary Addresses Option
|
||||
* @hdr: Option header
|
||||
* @iaid: Unique identifier for IA_NA, network order
|
||||
* @t1: Rebind interval for this server (always infinity)
|
||||
* @t2: Rebind interval for any server (always infinity)
|
||||
*/
|
||||
struct opt_ia_na {
|
||||
struct opt_hdr hdr;
|
||||
uint32_t iaid;
|
||||
uint32_t t1;
|
||||
uint32_t t2;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct opt_ia_ta - Identity Association for Temporary Addresses Option
|
||||
* @hdr: Option header
|
||||
* @iaid: Unique identifier for IA_TA, network order
|
||||
*/
|
||||
struct opt_ia_ta {
|
||||
struct opt_hdr hdr;
|
||||
uint32_t iaid;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct opt_ia_addr - IA Address Option
|
||||
* @hdr: Option header
|
||||
* @addr: Leased IPv6 address
|
||||
* @pref_lifetime: Preferred lifetime, network order (always infinity)
|
||||
* @valid_lifetime: Valid lifetime, network order (always infinity)
|
||||
*/
|
||||
struct opt_ia_addr {
|
||||
struct opt_hdr hdr;
|
||||
struct in6_addr addr;
|
||||
uint32_t pref_lifetime;
|
||||
uint32_t valid_lifetime;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct opt_status_code - Status Code Option (used for NotOnLink error only)
|
||||
* @hdr: Option header
|
||||
* @code: Numeric code for status, network order
|
||||
* @status_msg: Text string suitable for display, not NULL-terminated
|
||||
*/
|
||||
struct opt_status_code {
|
||||
struct opt_hdr hdr;
|
||||
uint16_t code;
|
||||
char status_msg[sizeof(STR_NOTONLINK) - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct opt_dns_servers - DNS Recursive Name Server option (RFC 3646)
|
||||
* @hdr: Option header
|
||||
* @addr: IPv6 DNS address
|
||||
*/
|
||||
struct opt_dns_servers {
|
||||
struct opt_hdr hdr;
|
||||
struct in6_addr addr;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct msg_hdr - DHCPv6 client/server message header
|
||||
* @type: DHCP message type
|
||||
* @xid: Transaction ID for message exchange
|
||||
*/
|
||||
struct msg_hdr {
|
||||
uint32_t type:8;
|
||||
#define TYPE_SOLICIT 1
|
||||
#define TYPE_ADVERTISE 2
|
||||
#define TYPE_REQUEST 3
|
||||
#define TYPE_CONFIRM 4
|
||||
#define TYPE_RENEW 5
|
||||
#define TYPE_REBIND 6
|
||||
#define TYPE_REPLY 7
|
||||
#define TYPE_RELEASE 8
|
||||
#define TYPE_DECLINE 9
|
||||
#define TYPE_INFORMATION_REQUEST 11
|
||||
|
||||
uint32_t xid:24;
|
||||
} __attribute__((__packed__));
|
||||
|
||||
static const struct udphdr uh_resp = {
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
547, 546, 0, 0,
|
||||
#else
|
||||
__bswap_constant_16(547), __bswap_constant_16(546), 0, 0,
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* struct resp_t - Normal advertise and reply message
|
||||
* @uh: UDP header
|
||||
* @hdr: DHCP message header
|
||||
* @server_id: Server Identifier option
|
||||
* @ia_na: Non-temporary Address option
|
||||
* @ia_addr: Address for IA_NA
|
||||
* @dns_servers: DNS Recursive Name Server option
|
||||
* @client_id: Client Identifier, variable length, must be at the end
|
||||
*/
|
||||
static struct resp_t {
|
||||
struct udphdr uh;
|
||||
struct msg_hdr hdr;
|
||||
|
||||
struct opt_server_id server_id;
|
||||
struct opt_ia_na ia_na;
|
||||
struct opt_ia_addr ia_addr;
|
||||
struct opt_dns_servers dns_servers;
|
||||
struct opt_client_id client_id;
|
||||
} __attribute__((__packed__)) resp = {
|
||||
uh_resp,
|
||||
{ 0 },
|
||||
server_id_const,
|
||||
|
||||
{ { OPT_IA_NA, OPT_SIZE_CONV(sizeof(struct opt_ia_na) +
|
||||
sizeof(struct opt_ia_addr) -
|
||||
sizeof(struct opt_hdr)) },
|
||||
1, (uint32_t)~0U, (uint32_t)~0U
|
||||
},
|
||||
|
||||
{ { OPT_IAAADR, OPT_SIZE(ia_addr) },
|
||||
IN6ADDR_ANY_INIT, (uint32_t)~0U, (uint32_t)~0U
|
||||
},
|
||||
|
||||
{ { OPT_DNS_SERVERS, OPT_SIZE(dns_servers), },
|
||||
IN6ADDR_ANY_INIT
|
||||
},
|
||||
|
||||
{ { OPT_CLIENTID, 0, },
|
||||
{ 0 }
|
||||
},
|
||||
};
|
||||
|
||||
static const struct opt_status_code sc_not_on_link = {
|
||||
{ OPT_STATUS_CODE, OPT_SIZE(status_code), },
|
||||
STATUS_NOTONLINK, STR_NOTONLINK
|
||||
};
|
||||
|
||||
/**
|
||||
* struct resp_not_on_link_t - NotOnLink error (mandated by RFC 8415, 18.3.2.)
|
||||
* @uh: UDP header
|
||||
* @hdr: DHCP message header
|
||||
* @server_id: Server Identifier option
|
||||
* @var: Payload: IA_NA from client, status code, client ID
|
||||
*/
|
||||
static struct resp_not_on_link_t {
|
||||
struct udphdr uh;
|
||||
struct msg_hdr hdr;
|
||||
|
||||
struct opt_server_id server_id;
|
||||
|
||||
uint8_t var[sizeof(struct opt_ia_na) + sizeof(struct opt_status_code) +
|
||||
sizeof(struct opt_client_id)];
|
||||
} __attribute__((__packed__)) resp_not_on_link = {
|
||||
uh_resp,
|
||||
{ TYPE_REPLY, 0 },
|
||||
server_id_const,
|
||||
{ 0, },
|
||||
};
|
||||
|
||||
/**
|
||||
* dhcpv6_opt() - Get option from DHCPv6 message
|
||||
* @o: First option header to check
|
||||
* @type: Option type to look up, network order
|
||||
* @len: Remaining length, host order, modified on return
|
||||
*
|
||||
* Return: pointer to option header, or NULL on malformed or missing option
|
||||
*/
|
||||
static struct opt_hdr *dhcpv6_opt(struct opt_hdr *o, uint16_t type, size_t *len)
|
||||
{
|
||||
while (*len >= sizeof(struct opt_hdr)) {
|
||||
if (ntohs(o->l) > *len)
|
||||
return NULL;
|
||||
|
||||
if (o->t == type)
|
||||
return o;
|
||||
|
||||
*len -= ntohs(o->l) + sizeof(struct opt_hdr);
|
||||
o = (struct opt_hdr *)((uint8_t *)(o + 1) + ntohs(o->l));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* dhcpv6_ia_notonlink() - Check if any IA contains non-appropriate addresses
|
||||
* @o: First option header to check for IAs
|
||||
* @len: Remaining message length, host order
|
||||
* @addr: Address we want to lease to the client
|
||||
*
|
||||
* Return: pointer to non-appropriate IA_NA or IA_TA, if any, NULL otherwise
|
||||
*/
|
||||
static struct opt_hdr *dhcpv6_ia_notonlink(struct opt_hdr *o, size_t len,
|
||||
struct in6_addr *addr)
|
||||
{
|
||||
struct opt_hdr *ia, *ia_addr;
|
||||
struct in6_addr *req_addr;
|
||||
size_t __len;
|
||||
int ia_type;
|
||||
|
||||
ia_type = OPT_IA_NA;
|
||||
ia_ta:
|
||||
__len = len;
|
||||
ia = o;
|
||||
|
||||
while ((ia = dhcpv6_opt(ia, ia_type, &__len))) {
|
||||
size_t ia_len = ntohs(ia->l) - sizeof(struct opt_hdr);
|
||||
|
||||
if (ia_len > __len)
|
||||
return NULL;
|
||||
|
||||
if (ia_type == OPT_IA_NA) {
|
||||
struct opt_ia_na *opts = (struct opt_ia_na *)ia + 1;
|
||||
|
||||
ia_addr = (struct opt_hdr *)opts;
|
||||
} else if (ia_type == OPT_IA_TA) {
|
||||
struct opt_ia_ta *opts = (struct opt_ia_ta *)ia + 1;
|
||||
|
||||
ia_addr = (struct opt_hdr *)opts;
|
||||
}
|
||||
|
||||
while ((ia_addr = dhcpv6_opt(ia_addr, OPT_IAAADR, &ia_len))) {
|
||||
struct opt_ia_addr *next;
|
||||
|
||||
req_addr = (struct in6_addr *)(ia_addr + 1);
|
||||
if (memcmp(addr, req_addr, sizeof(*addr)))
|
||||
return ia;
|
||||
|
||||
next = (struct opt_ia_addr *)ia_addr + 1;
|
||||
ia_addr = (struct opt_hdr *)next;
|
||||
}
|
||||
|
||||
if (!ia_addr)
|
||||
break;
|
||||
|
||||
ia = ia_addr;
|
||||
}
|
||||
|
||||
if (ia_type == OPT_IA_NA) {
|
||||
ia_type = OPT_IA_TA;
|
||||
goto ia_ta;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* dhcpv6() - Check if this is a DHCPv6 message, reply as needed
|
||||
* @c: Execution context
|
||||
* @eh: Packet buffer, Ethernet header
|
||||
* @len: Total L2 packet length
|
||||
*
|
||||
* Return: 0 if it's not a DHCPv6 message, 1 if handled, -1 on failure
|
||||
*/
|
||||
int dhcpv6(struct ctx *c, struct ethhdr *eh, size_t len)
|
||||
{
|
||||
struct ipv6hdr *ip6h = (struct ipv6hdr *)(eh + 1);
|
||||
struct opt_hdr *ia, *bad_ia, *client_id, *server_id;
|
||||
struct msg_hdr *mh;
|
||||
struct udphdr *uh;
|
||||
uint8_t proto;
|
||||
size_t mlen;
|
||||
size_t n;
|
||||
|
||||
uh = (struct udphdr *)ipv6_l4hdr(ip6h, &proto);
|
||||
if (!uh || proto != IPPROTO_UDP || uh->dest != htons(547))
|
||||
return 0;
|
||||
|
||||
if (!IN6_IS_ADDR_MULTICAST(&ip6h->daddr))
|
||||
return -1;
|
||||
|
||||
mlen = len - ((intptr_t)uh - (intptr_t)eh) - sizeof(*uh);
|
||||
|
||||
if (mlen != ntohs(uh->len) - sizeof(*uh) ||
|
||||
mlen < sizeof(struct msg_hdr))
|
||||
return -1;
|
||||
|
||||
c->addr6_guest = ip6h->saddr;
|
||||
|
||||
mh = (struct msg_hdr *)(uh + 1);
|
||||
mlen -= sizeof(struct msg_hdr);
|
||||
|
||||
n = mlen;
|
||||
client_id = dhcpv6_opt((struct opt_hdr *)(mh + 1), OPT_CLIENTID, &n);
|
||||
if (!client_id || ntohs(client_id->l) > ntohs(OPT_SIZE(client_id)))
|
||||
return -1;
|
||||
|
||||
n = mlen;
|
||||
server_id = dhcpv6_opt((struct opt_hdr *)(mh + 1), OPT_SERVERID, &n);
|
||||
|
||||
n = mlen;
|
||||
ia = dhcpv6_opt((struct opt_hdr *)(mh + 1), OPT_IA_NA, &n);
|
||||
if (ia && ntohs(ia->l) < ntohs(OPT_SIZE(ia_na)))
|
||||
return -1;
|
||||
|
||||
resp.hdr.type = TYPE_REPLY;
|
||||
switch (mh->type) {
|
||||
case TYPE_REQUEST:
|
||||
case TYPE_RENEW:
|
||||
if (!server_id ||
|
||||
memcmp(&resp.server_id, server_id, sizeof(resp.server_id)))
|
||||
return -1;
|
||||
/* Falls through */
|
||||
case TYPE_CONFIRM:
|
||||
if (mh->type == TYPE_CONFIRM && server_id)
|
||||
return -1;
|
||||
|
||||
if ((bad_ia = dhcpv6_ia_notonlink((struct opt_hdr *)(mh + 1),
|
||||
mlen, &c->addr6))) {
|
||||
n = OPT_IA_NA ? sizeof(struct opt_ia_na) :
|
||||
sizeof(struct opt_ia_ta);
|
||||
memcpy(&resp_not_on_link.var, bad_ia, n);
|
||||
|
||||
memcpy(&resp_not_on_link.var + n, &sc_not_on_link,
|
||||
sizeof(sc_not_on_link));
|
||||
n += sizeof(sc_not_on_link);
|
||||
|
||||
memcpy(&resp_not_on_link.var + n, client_id,
|
||||
sizeof(struct opt_hdr) + client_id->l);
|
||||
n += sizeof(struct opt_hdr) + client_id->l;
|
||||
|
||||
n = offsetof(struct resp_not_on_link_t, var) + n;
|
||||
resp_not_on_link.uh.len = htons(n);
|
||||
|
||||
resp_not_on_link.hdr.xid = mh->xid;
|
||||
|
||||
tap_ip_send(c, &c->gw6, IPPROTO_UDP,
|
||||
(char *)&resp_not_on_link, n);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
info("DHCPv6: received REQUEST/RENEW/CONFIRM, sending REPLY");
|
||||
break;
|
||||
case TYPE_INFORMATION_REQUEST:
|
||||
if (server_id &&
|
||||
memcmp(&resp.server_id, server_id, sizeof(resp.server_id)))
|
||||
return -1;
|
||||
|
||||
n = mlen;
|
||||
if (ia || dhcpv6_opt((struct opt_hdr *)(mh + 1), OPT_IA_TA, &n))
|
||||
return -1;
|
||||
|
||||
info("DHCPv6: received INFORMATION_REQUEST, sending REPLY");
|
||||
break;
|
||||
case TYPE_REBIND:
|
||||
if (!server_id ||
|
||||
memcmp(&resp.server_id, server_id, sizeof(resp.server_id)))
|
||||
return -1;
|
||||
|
||||
info("DHCPv6: received REBIND, sending REPLY");
|
||||
break;
|
||||
case TYPE_SOLICIT:
|
||||
if (server_id)
|
||||
return -1;
|
||||
|
||||
resp.hdr.type = TYPE_ADVERTISE;
|
||||
|
||||
info("DHCPv6: received SOLICIT, sending ADVERTISE");
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
if (ia)
|
||||
resp.ia_na.iaid = ((struct opt_ia_na *)ia)->iaid;
|
||||
|
||||
memcpy(&resp.client_id, client_id,
|
||||
ntohs(client_id->l) + sizeof(struct opt_hdr));
|
||||
resp.uh.len = htons(n = offsetof(struct resp_t, client_id) +
|
||||
sizeof(struct opt_hdr) + ntohs(client_id->l));
|
||||
|
||||
resp.hdr.xid = mh->xid;
|
||||
tap_ip_send(c, &c->gw6, IPPROTO_UDP, (char *)&resp, n);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* dhcpv6() - Initialise DUID and addresses for DHCPv6 server
|
||||
* @c: Execution context
|
||||
*/
|
||||
void dhcpv6_init(struct ctx *c)
|
||||
{
|
||||
struct tm y2k = { 0, 0, 0, 1, 0, 100, 0, 0, 0, 0, NULL };
|
||||
uint32_t duid_time;
|
||||
|
||||
duid_time = htonl(difftime(time(NULL), mktime(&y2k)));
|
||||
|
||||
resp.server_id.duid_time = duid_time;
|
||||
resp_not_on_link.server_id.duid_time = duid_time;
|
||||
|
||||
memcpy(resp.server_id.duid_lladdr, c->mac, sizeof(c->mac));
|
||||
memcpy(resp_not_on_link.server_id.duid_lladdr, c->mac, sizeof(c->mac));
|
||||
|
||||
resp.ia_addr.addr = c->addr6;
|
||||
resp.dns_servers.addr = c->dns6;
|
||||
}
|
Reference in New Issue
Block a user