#include <time.h>
#include <fcntl.h>
#include <errno.h>
+#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <resolv.h>
htons(DHCPV6_OPT_DNS_DOMAIN),
htons(DHCPV6_OPT_NTP_SERVER),
htons(DHCPV6_OPT_SIP_SERVER_A),
- htons(DHCPV6_OPT_SIP_SERVER_D)};
+ htons(DHCPV6_OPT_SIP_SERVER_D),
+ htons(DHCPV6_OPT_PD_EXCLUDE)};
odhcp6c_add_state(STATE_ORO, oro, sizeof(oro));
// Build IA_PDs
size_t ia_pd_entries, ia_pd_len = 0;
- void *ia_pd = NULL;
struct odhcp6c_entry *e = odhcp6c_get_state(STATE_IA_PD, &ia_pd_entries);
ia_pd_entries /= sizeof(*e);
struct dhcpv6_ia_hdr hdr_ia_pd = {
1, 0, 0
};
- struct dhcpv6_ia_prefix pref = {
- .type = htons(DHCPV6_OPT_IA_PREFIX),
- .len = htons(25), .prefix = request_prefix
- };
-
- struct dhcpv6_ia_prefix p[ia_pd_entries];
+ uint8_t *ia_pd = alloca(ia_pd_entries * (sizeof(struct dhcpv6_ia_prefix) + 10));
for (size_t i = 0; i < ia_pd_entries; ++i) {
- p[i].type = htons(DHCPV6_OPT_IA_PREFIX);
- p[i].len = htons(sizeof(p[i]) - 4U);
- p[i].preferred = 0;
- p[i].valid = 0;
- p[i].prefix = e[i].length;
- p[i].addr = e[i].target;
+ uint8_t ex_len = 0;
+ if (e[i].priority > 0)
+ ex_len = ((e[i].priority - e[i].length - 1) / 8) + 6;
+
+ struct dhcpv6_ia_prefix p = {
+ .type = htons(DHCPV6_OPT_IA_PREFIX),
+ .len = htons(sizeof(p) - 4U + ex_len),
+ .prefix = e[i].length,
+ .addr = e[i].target
+ };
+
+ memcpy(ia_pd + ia_pd_len, &p, sizeof(p));
+ ia_pd_len += sizeof(p);
+
+ if (ex_len) {
+ ia_pd[ia_pd_len++] = 0;
+ ia_pd[ia_pd_len++] = DHCPV6_OPT_PD_EXCLUDE;
+ ia_pd[ia_pd_len++] = 0;
+ ia_pd[ia_pd_len++] = ex_len - 4;
+ ia_pd[ia_pd_len++] = e[i].priority;
+
+ uint32_t excl = ntohl(e[i].router.s6_addr32[1]);
+ excl >>= (64 - e[i].priority);
+ excl <<= 8 - ((e[i].priority - e[i].length) % 8);
+
+ for (size_t i = ex_len - 5; i > 0; --i, excl >>= 8)
+ ia_pd[ia_pd_len + i] = excl & 0xff;
+ ia_pd_len += ex_len - 5;
+ }
}
- ia_pd = p;
- ia_pd_len = sizeof(p);
- hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
- if (request_prefix > 0 &&
+ struct dhcpv6_ia_prefix pref = {
+ .type = htons(DHCPV6_OPT_IA_PREFIX),
+ .len = htons(25), .prefix = request_prefix
+ };
+ if (request_prefix > 0 && ia_pd_len == 0 &&
(type == DHCPV6_MSG_SOLICIT ||
type == DHCPV6_MSG_REQUEST)) {
- ia_pd = &pref;
+ ia_pd = (uint8_t*)&pref;
ia_pd_len = sizeof(pref);
- hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
}
+ hdr_ia_pd.len = htons(ntohs(hdr_ia_pd.len) + ia_pd_len);
// Build IA_NAs
size_t ia_na_entries, ia_na_len = 0;
entry.length = prefix->prefix;
entry.target = prefix->addr;
- odhcp6c_update_entry(STATE_IA_PD, &entry);
+ // Parse PD-exclude
+ bool ok = true;
+ uint16_t stype, slen;
+ uint8_t *sdata;
+ dhcpv6_for_each_option(odata + sizeof(*prefix) - 4U,
+ odata + olen, stype, slen, sdata) {
+ if (stype != DHCPV6_OPT_PD_EXCLUDE || slen < 2)
+ continue;
+
+ uint8_t elen = sdata[0];
+ if (elen > 64)
+ elen = 64;
+
+ if (elen <= 32 || elen <= entry.length) {
+ ok = false;
+ continue;
+ }
+
+
+ uint8_t bytes = ((elen - entry.length - 1) / 8) + 1;
+ if (slen <= bytes) {
+ ok = false;
+ continue;
+ }
+
+ uint32_t exclude = 0;
+ do {
+ exclude = exclude << 8 | sdata[bytes];
+ } while (--bytes);
+
+ exclude >>= 8 - ((elen - entry.length) % 8);
+ exclude <<= 64 - elen;
+
+ // Abusing router & priority fields for exclusion
+ entry.router = entry.target;
+ entry.router.s6_addr32[1] |= htonl(exclude);
+ entry.priority = elen;
+ }
+
+ if (ok)
+ odhcp6c_update_entry(STATE_IA_PD, &entry);
+
+ entry.priority = 0;
+ memset(&entry.router, 0, sizeof(entry.router));
} else if (otype == DHCPV6_OPT_IA_ADDR) {
struct dhcpv6_ia_addr *addr = (void*)&odata[-4];
if (olen + 4U < sizeof(*addr))
}
}
+enum entry_type {
+ ENTRY_ADDRESS,
+ ENTRY_HOST,
+ ENTRY_ROUTE,
+ ENTRY_PREFIX
+};
-static void entry_to_env(const char *name, const void *data, size_t len, bool host, bool route)
+static void entry_to_env(const char *name, const void *data, size_t len, enum entry_type type)
{
size_t buf_len = strlen(name);
const struct odhcp6c_entry *e = data;
for (size_t i = 0; i < len / sizeof(*e); ++i) {
inet_ntop(AF_INET6, &e[i].target, &buf[buf_len], INET6_ADDRSTRLEN);
buf_len += strlen(&buf[buf_len]);
- if (!host) {
+ if (type != ENTRY_HOST) {
buf_len += snprintf(&buf[buf_len], 6, "/%hhu", e[i].length);
- if (route) {
+ if (type == ENTRY_ROUTE) {
buf[buf_len++] = ',';
if (!IN6_IS_ADDR_UNSPECIFIED(&e[i].router)) {
inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
} else {
buf_len += snprintf(&buf[buf_len], 24, ",%u,%u", e[i].preferred, e[i].valid);
}
+
+ if (type == ENTRY_PREFIX && e[i].priority) {
+ // priority and router are abused for prefix exclusion
+ buf[buf_len++] = ',';
+ inet_ntop(AF_INET6, &e[i].router, &buf[buf_len], INET6_ADDRSTRLEN);
+ buf_len += strlen(&buf[buf_len]);
+ buf_len += snprintf(&buf[buf_len], 24, "/%u", e[i].priority);
+ }
}
buf[buf_len++] = ' ';
}
fqdn_to_env("SNTP_FQDN", sntp_dns, sntp_dns_len);
fqdn_to_env("SIP_DOMAIN", sip_fqdn, sip_fqdn_len);
bin_to_env(custom, custom_len);
- entry_to_env("PREFIXES", prefix, prefix_len, false, false);
- entry_to_env("ADDRESSES", address, address_len, false, false);
- entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, false, false);
- entry_to_env("RA_ROUTES", ra_route, ra_route_len, false, true);
- entry_to_env("RA_DNS", ra_dns, ra_dns_len, true, false);
+ entry_to_env("PREFIXES", prefix, prefix_len, ENTRY_PREFIX);
+ entry_to_env("ADDRESSES", address, address_len, ENTRY_ADDRESS);
+ entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, ENTRY_ADDRESS);
+ entry_to_env("RA_ROUTES", ra_route, ra_route_len, ENTRY_ROUTE);
+ entry_to_env("RA_DNS", ra_dns, ra_dns_len, ENTRY_HOST);
argv[2] = (char*)status;
execv(argv[0], argv);