#include <stddef.h>
#include <resolv.h>
#include <sys/timerfd.h>
+#include <arpa/inet.h>
#include "odhcpd.h"
#include "dhcpv6.h"
-static const char *excluded_class = "HOMENET";
-
static void relay_client_request(struct sockaddr_in6 *source,
const void *data, size_t len, struct interface *iface);
static void relay_server_response(uint8_t *data, size_t len);
static void handle_dhcpv6(void *addr, void *data, size_t len,
- struct interface *iface);
+ struct interface *iface, void *dest);
static void handle_client_request(void *addr, void *data, size_t len,
- struct interface *iface);
+ struct interface *iface, void *dest_addr);
}
// Configure multicast settings
- if (enable && iface->dhcpv6 && !iface->master) {
+ if (enable && iface->dhcpv6) {
int sock = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if (sock < 0) {
syslog(LOG_ERR, "Failed to create DHCPv6 server socket: %s",
return setup_dhcpv6_ia_interface(iface, enable);
}
+enum {
+ IOV_NESTED = 0,
+ IOV_DEST,
+ IOV_MAXRT,
+#define IOV_STAT IOV_MAXRT
+ IOV_DNS,
+ IOV_DNS_ADDR,
+ IOV_SEARCH,
+ IOV_SEARCH_DOMAIN,
+ IOV_PDBUF,
+#define IOV_REFRESH IOV_PDBUF
+ IOV_CERID,
+ IOV_DHCPV6_RAW,
+ IOV_RELAY_MSG,
+ IOV_TOTAL
+};
static void handle_nested_message(uint8_t *data, size_t len,
- uint8_t **opts, uint8_t **end, struct iovec iov[6])
+ uint8_t **opts, uint8_t **end, struct iovec iov[IOV_TOTAL - 1])
{
struct dhcpv6_relay_header *hdr = (struct dhcpv6_relay_header*)data;
- if (iov[0].iov_base == NULL) {
- iov[0].iov_base = data;
- iov[0].iov_len = len;
+ if (iov[IOV_NESTED].iov_base == NULL) {
+ iov[IOV_NESTED].iov_base = data;
+ iov[IOV_NESTED].iov_len = len;
}
if (len < sizeof(struct dhcpv6_client_header))
return;
if (hdr->msg_type != DHCPV6_MSG_RELAY_FORW) {
- iov[0].iov_len = data - (uint8_t*)iov[0].iov_base;
+ iov[IOV_NESTED].iov_len = data - (uint8_t*)iov[IOV_NESTED].iov_base;
struct dhcpv6_client_header *hdr = (void*)data;
*opts = (uint8_t*)&hdr[1];
*end = data + len;
uint8_t *odata;
dhcpv6_for_each_option(hdr->options, data + len, otype, olen, odata) {
if (otype == DHCPV6_OPT_RELAY_MSG) {
- iov[7].iov_base = odata + olen;
- iov[7].iov_len = (((uint8_t*)iov[0].iov_base) + iov[0].iov_len)
- - (odata + olen);
+ iov[IOV_RELAY_MSG].iov_base = odata + olen;
+ iov[IOV_RELAY_MSG].iov_len = (((uint8_t*)iov[IOV_NESTED].iov_base) +
+ iov[IOV_NESTED].iov_len) - (odata + olen);
handle_nested_message(odata, olen, opts, end, iov);
return;
}
}
}
-
// Simple DHCPv6-server for information requests
static void handle_client_request(void *addr, void *data, size_t len,
- struct interface *iface)
+ struct interface *iface, void *dest_addr)
{
struct dhcpv6_client_header *hdr = data;
+
if (len < sizeof(*hdr))
return;
uint16_t duid_type;
uint16_t hardware_type;
uint8_t mac[6];
- uint16_t solmaxrt_type;
- uint16_t solmaxrt_length;
- uint32_t solmaxrt_value;
uint16_t clientid_type;
uint16_t clientid_length;
uint8_t clientid_buf[130];
.serverid_length = htons(10),
.duid_type = htons(3),
.hardware_type = htons(1),
- .solmaxrt_type = htons(DHCPV6_OPT_SOL_MAX_RT),
- .solmaxrt_length = htons(4),
- .solmaxrt_value = htonl(60),
.clientid_type = htons(DHCPV6_OPT_CLIENTID),
.clientid_buf = {0}
};
struct __attribute__((packed)) {
uint16_t type;
uint16_t len;
+ uint32_t value;
+ } maxrt = {htons(DHCPV6_OPT_SOL_MAX_RT), htons(sizeof(maxrt) - 4),
+ htonl(60)};
+
+ struct __attribute__((packed)) {
+ uint16_t type;
+ uint16_t len;
uint16_t value;
} stat = {htons(DHCPV6_OPT_STATUS), htons(sizeof(stat) - 4),
- htons(DHCPV6_STATUS_NOADDRSAVAIL)};
+ htons(DHCPV6_STATUS_USEMULTICAST)};
struct __attribute__((packed)) {
uint16_t type;
} refresh = {htons(DHCPV6_OPT_INFO_REFRESH), htons(sizeof(uint32_t)),
htonl(600)};
- struct odhcpd_ipaddr ipaddr;
- struct in6_addr *dns_addr = iface->dns;
+ struct in6_addr dns_addr, *dns_addr_ptr = iface->dns;
size_t dns_cnt = iface->dns_cnt;
- if (dns_cnt == 0 && odhcpd_get_interface_addresses(iface->ifindex, &ipaddr, 1) == 1) {
- dns_addr = &ipaddr.addr;
+ if ((dns_cnt == 0) &&
+ !odhcpd_get_linklocal_interface_address(iface->ifindex, &dns_addr)) {
+ dns_addr_ptr = &dns_addr;
dns_cnt = 1;
}
struct {
uint16_t type;
uint16_t len;
- } dns = {htons(DHCPV6_OPT_DNS_SERVERS), htons(dns_cnt * sizeof(*dns_addr))};
+ } dns = {htons(DHCPV6_OPT_DNS_SERVERS), htons(dns_cnt * sizeof(*dns_addr_ptr))};
} search = {htons(DHCPV6_OPT_DNS_DOMAIN), htons(search_len)};
+ struct dhcpv6_cer_id cerid = {
+#ifdef EXT_CER_ID
+ .type = htons(EXT_CER_ID),
+#endif
+ .len = htons(36),
+ .addr = iface->dhcpv6_pd_cer,
+ };
+
uint8_t pdbuf[512];
- struct iovec iov[] = {{NULL, 0},
- {&dest, (uint8_t*)&dest.clientid_type - (uint8_t*)&dest},
- {&dns, (dns_cnt) ? sizeof(dns) : 0},
- {dns_addr, dns_cnt * sizeof(*dns_addr)},
- {&search, (search_len) ? sizeof(search) : 0},
- {search_domain, search_len},
- {pdbuf, 0},
- {NULL, 0}};
+ struct iovec iov[IOV_TOTAL] = {
+ [IOV_NESTED] = {NULL, 0},
+ [IOV_DEST] = {&dest, (uint8_t*)&dest.clientid_type - (uint8_t*)&dest},
+ [IOV_MAXRT] = {&maxrt, sizeof(maxrt)},
+ [IOV_DNS] = {&dns, (dns_cnt) ? sizeof(dns) : 0},
+ [IOV_DNS_ADDR] = {dns_addr_ptr, dns_cnt * sizeof(*dns_addr_ptr)},
+ [IOV_SEARCH] = {&search, (search_len) ? sizeof(search) : 0},
+ [IOV_SEARCH_DOMAIN] = {search_domain, search_len},
+ [IOV_PDBUF] = {pdbuf, 0},
+ [IOV_CERID] = {&cerid, 0},
+ [IOV_DHCPV6_RAW] = {iface->dhcpv6_raw, iface->dhcpv6_raw_len},
+ [IOV_RELAY_MSG] = {NULL, 0}
+ };
uint8_t *opts = (uint8_t*)&hdr[1], *opts_end = (uint8_t*)data + len;
if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW)
if (opts[-4] == DHCPV6_MSG_ADVERTISE || opts[-4] == DHCPV6_MSG_REPLY || opts[-4] == DHCPV6_MSG_RELAY_REPL)
return;
+ if (!IN6_IS_ADDR_MULTICAST((struct in6_addr *)dest_addr) && iov[IOV_NESTED].iov_len == 0 &&
+ (opts[-4] == DHCPV6_MSG_SOLICIT || opts[-4] == DHCPV6_MSG_CONFIRM ||
+ opts[-4] == DHCPV6_MSG_REBIND || opts[-4] == DHCPV6_MSG_INFORMATION_REQUEST))
+ return;
+
if (opts[-4] == DHCPV6_MSG_SOLICIT) {
dest.msg_type = DHCPV6_MSG_ADVERTISE;
} else if (opts[-4] == DHCPV6_MSG_INFORMATION_REQUEST) {
- iov[6].iov_base = &refresh;
- iov[6].iov_len = sizeof(refresh);
+ iov[IOV_REFRESH].iov_base = &refresh;
+ iov[IOV_REFRESH].iov_len = sizeof(refresh);
+
+ // Return inf max rt option in reply to information request
+ maxrt.type = htons(DHCPV6_OPT_INF_MAX_RT);
}
// Go through options and find what we need
if (otype == DHCPV6_OPT_CLIENTID && olen <= 130) {
dest.clientid_length = htons(olen);
memcpy(dest.clientid_buf, odata, olen);
- iov[1].iov_len += 4 + olen;
+ iov[IOV_DEST].iov_len += 4 + olen;
} else if (otype == DHCPV6_OPT_SERVERID) {
if (olen != ntohs(dest.serverid_length) ||
memcmp(odata, &dest.duid_type, olen))
return; // Not for us
- } else if (otype == DHCPV6_OPT_USER_CLASS) {
+ } else if (iface->filter_class && otype == DHCPV6_OPT_USER_CLASS) {
uint8_t *c = odata, *cend = &odata[olen];
for (; &c[2] <= cend && &c[2 + (c[0] << 8) + c[1]] <= cend; c = &c[2 + (c[0] << 8) + c[1]]) {
- size_t elen = strlen(excluded_class);
- if (((((size_t)c[0]) << 8) | c[1]) == elen && !memcmp(&c[2], excluded_class, elen))
+ size_t elen = strlen(iface->filter_class);
+ if (((((size_t)c[0]) << 8) | c[1]) == elen && !memcmp(&c[2], iface->filter_class, elen))
return; // Ignore from homenet
}
+ } else if (otype == DHCPV6_OPT_IA_PD) {
+#ifdef EXT_CER_ID
+ iov[IOV_CERID].iov_len = sizeof(cerid);
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&cerid.addr)) {
+ struct odhcpd_ipaddr addrs[32];
+ ssize_t len = odhcpd_get_interface_addresses(0, addrs,
+ ARRAY_SIZE(addrs));
+
+ for (ssize_t i = 0; i < len; ++i)
+ if (IN6_IS_ADDR_UNSPECIFIED(&cerid.addr)
+ || memcmp(&addrs[i].addr, &cerid.addr, sizeof(cerid.addr)) < 0)
+ cerid.addr = addrs[i].addr;
+ }
+#endif
}
}
+ if (!IN6_IS_ADDR_MULTICAST((struct in6_addr *)dest_addr) && iov[IOV_NESTED].iov_len == 0 &&
+ (opts[-4] == DHCPV6_MSG_REQUEST || opts[-4] == DHCPV6_MSG_RENEW ||
+ opts[-4] == DHCPV6_MSG_RELEASE || opts[-4] == DHCPV6_MSG_DECLINE)) {
+ iov[IOV_STAT].iov_base = &stat;
+ iov[IOV_STAT].iov_len = sizeof(stat);
+
+ for (ssize_t i = IOV_STAT + 1; i < IOV_TOTAL; ++i)
+ iov[i].iov_len = 0;
+
+ odhcpd_send(iface->dhcpv6_event.uloop.fd, addr, iov, ARRAY_SIZE(iov), iface);
+ return;
+ }
+
if (opts[-4] != DHCPV6_MSG_INFORMATION_REQUEST) {
- iov[6].iov_len = dhcpv6_handle_ia(pdbuf, sizeof(pdbuf), iface, addr, &opts[-4], opts_end);
- if (iov[6].iov_len == 0 && opts[-4] == DHCPV6_MSG_REBIND)
+ ssize_t ialen = dhcpv6_handle_ia(pdbuf, sizeof(pdbuf), iface, addr, &opts[-4], opts_end);
+ iov[IOV_PDBUF].iov_len = ialen;
+ if (ialen < 0 || (ialen == 0 && (opts[-4] == DHCPV6_MSG_REBIND || opts[-4] == DHCPV6_MSG_CONFIRM)))
return;
}
- if (iov[0].iov_len > 0) // Update length
- update_nested_message(data, len, iov[1].iov_len + iov[2].iov_len +
- iov[3].iov_len + iov[4].iov_len + iov[5].iov_len +
- iov[6].iov_len - (4 + opts_end - opts));
+ if (iov[IOV_NESTED].iov_len > 0) // Update length
+ update_nested_message(data, len, iov[IOV_DEST].iov_len + iov[IOV_MAXRT].iov_len +
+ iov[IOV_DNS].iov_len + iov[IOV_DNS_ADDR].iov_len +
+ iov[IOV_SEARCH].iov_len + iov[IOV_SEARCH_DOMAIN].iov_len +
+ iov[IOV_PDBUF].iov_len + iov[IOV_CERID].iov_len +
+ iov[IOV_DHCPV6_RAW].iov_len - (4 + opts_end - opts));
odhcpd_send(iface->dhcpv6_event.uloop.fd, addr, iov, ARRAY_SIZE(iov), iface);
}
// Central DHCPv6-relay handler
static void handle_dhcpv6(void *addr, void *data, size_t len,
- struct interface *iface)
+ struct interface *iface, void *dest_addr)
{
if (iface->dhcpv6 == RELAYD_SERVER) {
- handle_client_request(addr, data, len, iface);
+ handle_client_request(addr, data, len, iface, dest_addr);
} else if (iface->dhcpv6 == RELAYD_RELAY) {
if (iface->master)
relay_server_response(data, len);
if (is_authenticated)
return; // Impossible to rewrite
- struct odhcpd_ipaddr ip;
const struct in6_addr *rewrite = iface->dns;
+ struct in6_addr addr;
size_t rewrite_cnt = iface->dns_cnt;
if (rewrite_cnt == 0) {
- if (odhcpd_get_interface_addresses(iface->ifindex, &ip, 1) < 1)
+ if (odhcpd_get_linklocal_interface_address(iface->ifindex, &addr))
return; // Unable to get interface address
- rewrite = &ip.addr;
+ rewrite = &addr;
rewrite_cnt = 1;
}
struct sockaddr_in6 dhcpv6_servers = {AF_INET6,
htons(DHCPV6_SERVER_PORT), 0, ALL_DHCPV6_SERVERS, 0};
struct iovec iov[2] = {{&hdr, sizeof(hdr)}, {(void*)data, len}};
- odhcpd_send(iface->dhcpv6_event.uloop.fd, &dhcpv6_servers, iov, 2, master);
+ odhcpd_send(master->dhcpv6_event.uloop.fd, &dhcpv6_servers, iov, 2, master);
}