accepts IPv4 only
dns list <local address> DNS servers to announce
accepts IPv4 and IPv6
+dnr list disabled Encrypted DNS servers to announce
+ <priority> <domain name> [<comma separated IP addresses> <SvcParams (key=value)>...]
dns_service bool 1 Announce the address of interface as DNS service
if the list of dns is empty
domain list <local search domain> Search domains to announce
IFACE_ATTR_NDP,
IFACE_ATTR_ROUTER,
IFACE_ATTR_DNS,
+ IFACE_ATTR_DNR,
IFACE_ATTR_DNS_SERVICE,
IFACE_ATTR_DOMAIN,
IFACE_ATTR_FILTER_CLASS,
[IFACE_ATTR_NDP] = { .name = "ndp", .type = BLOBMSG_TYPE_STRING },
[IFACE_ATTR_ROUTER] = { .name = "router", .type = BLOBMSG_TYPE_ARRAY },
[IFACE_ATTR_DNS] = { .name = "dns", .type = BLOBMSG_TYPE_ARRAY },
+ [IFACE_ATTR_DNR] = { .name = "dnr", .type = BLOBMSG_TYPE_ARRAY },
[IFACE_ATTR_DNS_SERVICE] = { .name = "dns_service", .type = BLOBMSG_TYPE_BOOL },
[IFACE_ATTR_DOMAIN] = { .name = "domain", .type = BLOBMSG_TYPE_ARRAY },
[IFACE_ATTR_FILTER_CLASS] = { .name = "filter_class", .type = BLOBMSG_TYPE_STRING },
{ .name = NULL, },
};
+// https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml
+enum svc_param_keys {
+ DNR_SVC_MANDATORY,
+ DNR_SVC_ALPN,
+ DNR_SVC_NO_DEFAULT_ALPN,
+ DNR_SVC_PORT,
+ DNR_SVC_IPV4HINT,
+ DNR_SVC_ECH,
+ DNR_SVC_IPV6HINT,
+ DNR_SVC_DOHPATH,
+ DNR_SVC_OHTTP,
+ DNR_SVC_MAX,
+};
+
+static const char *svc_param_key_names[DNR_SVC_MAX] = {
+ [DNR_SVC_MANDATORY] = "mandatory",
+ [DNR_SVC_ALPN] = "alpn",
+ [DNR_SVC_NO_DEFAULT_ALPN] = "no-default-alpn",
+ [DNR_SVC_PORT] = "port",
+ [DNR_SVC_IPV4HINT] = "ipv4hint",
+ [DNR_SVC_ECH] = "ech",
+ [DNR_SVC_IPV6HINT] = "ipv6hint",
+ [DNR_SVC_DOHPATH] = "dohpath",
+ [DNR_SVC_OHTTP] = "ohttp",
+};
+
static void set_interface_defaults(struct interface *iface)
{
iface->ignore = true;
free(iface->dhcpv4_ntp);
free(iface->dhcpv6_ntp);
free(iface->dhcpv6_sntp);
+ for (unsigned i = 0; i < iface->dnr_cnt; i++) {
+ free(iface->dnr[i].adn);
+ free(iface->dnr[i].addr4);
+ free(iface->dnr[i].addr6);
+ free(iface->dnr[i].svc);
+ }
+ free(iface->dnr);
memset(&iface->ra, 0, sizeof(*iface) - offsetof(struct interface, ra));
set_interface_defaults(iface);
}
return 0;
}
+/* Parse DNR Options */
+static int parse_dnr_str(char *str, struct interface *iface)
+{
+ struct dnr_options dnr = {0};
+ size_t adn_len;
+ uint8_t adn_buf[256] = {0};
+ char *saveptr1, *saveptr2;
+
+ char *priority;
+ priority = strtok_r(str, " \f\n\r\t\v", &saveptr1);
+ if (!priority) {
+ goto err;
+ } else if (sscanf(priority, "%" SCNu16, &dnr.priority) != 1) {
+ syslog(LOG_ERR, "Unable to parse priority '%s'", priority);
+ goto err;
+ } else if (dnr.priority == 0) {
+ syslog(LOG_ERR, "Invalid priority '%s'", priority);
+ goto err;
+ }
+
+ char *adn;
+ adn = strtok_r(NULL, " \f\n\r\t\v", &saveptr1);
+ if (!adn)
+ goto err;
+
+ adn_len = strlen(adn);
+ if (adn_len > 0 && adn[adn_len - 1] == '.')
+ adn[adn_len - 1] = '\0';
+
+ if (adn_len >= sizeof(adn_buf)) {
+ syslog(LOG_ERR, "Hostname '%s' too long", adn);
+ goto err;
+ }
+
+ adn_len = dn_comp(adn, adn_buf, sizeof(adn_buf), NULL, NULL);
+ if (adn_len <= 0) {
+ syslog(LOG_ERR, "Unable to parse hostname '%s'", adn);
+ goto err;
+ }
+
+ dnr.adn = malloc(adn_len);
+ if (!dnr.adn)
+ goto err;
+ memcpy(dnr.adn, adn_buf, adn_len);
+ dnr.adn_len = adn_len;
+
+ char *addrs;
+ addrs = strtok_r(NULL, " \f\n\r\t\v", &saveptr1);
+ if (!addrs)
+ // ADN-Only mode
+ goto done;
+
+ for (char *addr = strtok_r(addrs, ",", &saveptr2); addr; addr = strtok_r(NULL, ",", &saveptr2)) {
+ struct in6_addr addr6, *tmp6;
+ struct in_addr addr4, *tmp4;
+ size_t new_sz;
+
+ if (inet_pton(AF_INET6, addr, &addr6) == 1) {
+ new_sz = (dnr.addr6_cnt + 1) * sizeof(*dnr.addr6);
+ if (new_sz > UINT16_MAX)
+ continue;
+ tmp6 = realloc(dnr.addr6, new_sz);
+ if (!tmp6)
+ goto err;
+ dnr.addr6 = tmp6;
+ memcpy(&dnr.addr6[dnr.addr6_cnt], &addr6, sizeof(*dnr.addr6));
+ dnr.addr6_cnt++;
+
+ } else if (inet_pton(AF_INET, addr, &addr4) == 1) {
+ new_sz = (dnr.addr4_cnt + 1) * sizeof(*dnr.addr4);
+ if (new_sz > UINT8_MAX)
+ continue;
+ tmp4 = realloc(dnr.addr4, new_sz);
+ if (!tmp4)
+ goto err;
+ dnr.addr4 = tmp4;
+ memcpy(&dnr.addr4[dnr.addr4_cnt], &addr4, sizeof(*dnr.addr4));
+ dnr.addr4_cnt++;
+
+ } else {
+ syslog(LOG_ERR, "Unable to parse IP address '%s'", addr);
+ goto err;
+ }
+ }
+
+ char *svc_vals[DNR_SVC_MAX] = { NULL, };
+ for (char *svc_tok = strtok_r(NULL, " \f\n\r\t\v", &saveptr1); svc_tok; svc_tok = strtok_r(NULL, " \f\n\r\t\v", &saveptr1)) {
+ uint16_t svc_id;
+ char *svc_key, *svc_val;
+
+ svc_key = strtok_r(svc_tok, "=", &saveptr2);
+ svc_val = strtok_r(NULL, "=", &saveptr2);
+
+ for (svc_id = 0; svc_id < DNR_SVC_MAX; svc_id++)
+ if (!strcmp(svc_key, svc_param_key_names[svc_id]))
+ break;
+
+ if (svc_id >= DNR_SVC_MAX) {
+ syslog(LOG_ERR, "Invalid SvcParam '%s'", svc_key);
+ goto err;
+ }
+
+ svc_vals[svc_id] = svc_val ? svc_val : "";
+ }
+
+ /* SvcParamKeys must be in increasing order, RFC9460 §2.2 */
+ for (uint16_t svc_key = 0; svc_key < DNR_SVC_MAX; svc_key++) {
+ uint16_t svc_key_be = ntohs(svc_key);
+ uint16_t svc_val_len, svc_val_len_be;
+ char *svc_val_str = svc_vals[svc_key];
+ uint8_t *tmp;
+
+ if (!svc_val_str)
+ continue;
+
+ switch (svc_key) {
+ case DNR_SVC_MANDATORY:
+ uint16_t mkeys[DNR_SVC_MAX];
+
+ svc_val_len = 0;
+ for (char *mkey_str = strtok_r(svc_val_str, ",", &saveptr2); mkey_str; mkey_str = strtok_r(NULL, ",", &saveptr2)) {
+ uint16_t mkey;
+
+ for (mkey = 0; mkey < DNR_SVC_MAX; mkey++)
+ if (!strcmp(mkey_str, svc_param_key_names[mkey]))
+ break;
+
+ if (mkey >= DNR_SVC_MAX || !svc_vals[mkey]) {
+ syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'mandatory'", mkey_str);
+ goto err;
+ }
+
+ mkeys[svc_val_len++] = ntohs(mkey);
+ }
+
+ svc_val_len *= sizeof(uint16_t);
+ svc_val_len_be = ntohs(svc_val_len);
+
+ tmp = realloc(dnr.svc, dnr.svc_len + 4 + svc_val_len);
+ if (!tmp)
+ goto err;
+
+ dnr.svc = tmp;
+ memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be));
+ memcpy(dnr.svc + dnr.svc_len + 2, &svc_val_len_be, sizeof(svc_val_len_be));
+ memcpy(dnr.svc + dnr.svc_len + 4, mkeys, svc_val_len);
+ dnr.svc_len += 4 + svc_val_len;
+ break;
+
+ case DNR_SVC_ALPN:
+ size_t len_off;
+
+ tmp = realloc(dnr.svc, dnr.svc_len + 4);
+ if (!tmp)
+ goto err;
+
+ dnr.svc = tmp;
+ memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be));
+ /* the length is not known yet */
+ len_off = dnr.svc_len + sizeof(svc_key_be);
+ dnr.svc_len += 4;
+
+ svc_val_len = 0;
+ for (char *alpn_id_str = strtok_r(svc_val_str, ",", &saveptr2); alpn_id_str; alpn_id_str = strtok_r(NULL, ",", &saveptr2)) {
+ size_t alpn_id_len;
+
+ alpn_id_len = strlen(alpn_id_str);
+ if (alpn_id_len > UINT8_MAX) {
+ syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'alpn'", alpn_id_str);
+ goto err;
+ }
+
+ tmp = realloc(dnr.svc, dnr.svc_len + 1 + alpn_id_len);
+ if (!tmp)
+ goto err;
+ dnr.svc = tmp;
+
+ dnr.svc[dnr.svc_len] = alpn_id_len;
+ memcpy(dnr.svc + dnr.svc_len + 1, alpn_id_str, alpn_id_len);
+ dnr.svc_len += 1 + alpn_id_len;
+ svc_val_len += 1 + alpn_id_len;
+ }
+
+ svc_val_len_be = ntohs(svc_val_len);
+ memcpy(dnr.svc + len_off, &svc_val_len_be, sizeof(svc_val_len_be));
+ break;
+
+ case DNR_SVC_PORT:
+ uint16_t port;
+
+ if (sscanf(svc_val_str, "%" SCNu16, &port) != 1) {
+ syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'port'", svc_val_str);
+ goto err;
+ }
+
+ port = ntohs(port);
+ svc_val_len_be = ntohs(2);
+
+ tmp = realloc(dnr.svc, dnr.svc_len + 6);
+ if (!tmp)
+ goto err;
+
+ dnr.svc = tmp;
+ memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be));
+ memcpy(dnr.svc + dnr.svc_len + 2, &svc_val_len_be, sizeof(svc_val_len_be));
+ memcpy(dnr.svc + dnr.svc_len + 4, &port, sizeof(port));
+ dnr.svc_len += 6;
+ break;
+
+ case DNR_SVC_NO_DEFAULT_ALPN:
+ /* fall through */
+
+ case DNR_SVC_OHTTP:
+ if (strlen(svc_val_str) > 0) {
+ syslog(LOG_ERR, "Invalid value '%s' for SvcParam 'port'", svc_val_str);
+ goto err;
+ }
+ /* fall through */
+
+ case DNR_SVC_DOHPATH:
+ /* plain string */
+ svc_val_len = strlen(svc_val_str);
+ svc_val_len_be = ntohs(svc_val_len);
+ tmp = realloc(dnr.svc, dnr.svc_len + 4 + svc_val_len);
+ if (!tmp)
+ goto err;
+
+ dnr.svc = tmp;
+ memcpy(dnr.svc + dnr.svc_len, &svc_key_be, sizeof(svc_key_be));
+ dnr.svc_len += sizeof(svc_key_be);
+ memcpy(dnr.svc + dnr.svc_len, &svc_val_len_be, sizeof(svc_val_len_be));
+ dnr.svc_len += sizeof(svc_val_len_be);
+ memcpy(dnr.svc + dnr.svc_len, svc_val_str, svc_val_len);
+ dnr.svc_len += svc_val_len;
+ break;
+
+ case DNR_SVC_ECH:
+ syslog(LOG_ERR, "SvcParam 'ech' is not implemented");
+ goto err;
+
+ case DNR_SVC_IPV4HINT:
+ /* fall through */
+
+ case DNR_SVC_IPV6HINT:
+ syslog(LOG_ERR, "SvcParam '%s' is not allowed", svc_param_key_names[svc_key]);
+ goto err;
+ }
+ }
+
+done:
+ struct dnr_options *tmp;
+ tmp = realloc(iface->dnr, (iface->dnr_cnt + 1) * sizeof(dnr));
+ if (!tmp)
+ goto err;
+
+ iface->dnr = tmp;
+ memcpy(iface->dnr + iface->dnr_cnt, &dnr, sizeof(dnr));
+ iface->dnr_cnt++;
+ return 0;
+
+err:
+ free(dnr.adn);
+ free(dnr.addr4);
+ free(dnr.addr6);
+ free(dnr.svc);
+ return -1;
+}
+
int config_parse_interface(void *data, size_t len, const char *name, bool overwrite)
{
struct odhcpd_ipaddr *addrs = NULL;
if ((c = tb[IFACE_ATTR_RA_DNS]))
iface->ra_dns = blobmsg_get_bool(c);
+ if ((c = tb[IFACE_ATTR_DNR])) {
+ struct blob_attr *cur;
+ unsigned rem;
+
+ blobmsg_for_each_attr(cur, c, rem) {
+ if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, false))
+ continue;
+
+ if (parse_dnr_str(blobmsg_get_string(cur), iface))
+ syslog(LOG_ERR, "Invalid %s value configured for interface '%s'",
+ iface_attrs[IFACE_ATTR_DNR].name, iface->name);
+ }
+ }
+
if ((c = tb[IFACE_ATTR_RA_PREF64])) {
const char *str = blobmsg_get_string(c);
char *astr = malloc(strlen(str) + 1);
dhcpv4_handle_msg(addr, data, len, iface, dest_addr, dhcpv4_send_reply, &sock);
}
+/* DNR */
+struct dhcpv4_dnr {
+ uint16_t len;
+ uint16_t priority;
+ uint8_t adn_len;
+ uint8_t body[];
+};
+
void dhcpv4_handle_msg(void *addr, void *data, size_t len,
struct interface *iface, _unused void *dest_addr,
send_reply_cb_t send_reply, void *opaque)
dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNSSERVER,
4 * iface->dhcpv4_dns_cnt, iface->dhcpv4_dns);
- if (a && iface->dhcpv4_ntp_cnt != 0) {
- for (size_t opts = 0; opts < a->reqopts_len; opts++) {
- if (a->reqopts[opts] == DHCPV4_OPT_NTPSERVER) {
- dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NTPSERVER,
- 4 * iface->dhcpv4_ntp_cnt, iface->dhcpv4_ntp);
+ for (size_t opt = 0; a && opt < a->reqopts_len; opt++) {
+ switch (a->reqopts[opt]) {
+ case DHCPV4_OPT_NTPSERVER:
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NTPSERVER,
+ 4 * iface->dhcpv4_ntp_cnt, iface->dhcpv4_ntp);
+ break;
+
+ case DHCPV4_OPT_DNR:
+ struct dhcpv4_dnr *dnrs;
+ size_t dnrs_len = 0;
+
+ for (size_t i = 0; i < iface->dnr_cnt; i++) {
+ struct dnr_options *dnr = &iface->dnr[i];
+
+ if (dnr->addr4_cnt == 0 && dnr->addr6_cnt > 0)
+ continue;
+
+ dnrs_len += sizeof(struct dhcpv4_dnr);
+ dnrs_len += dnr->adn_len;
+
+ if (dnr->addr4_cnt > 0 || dnr->svc_len > 0) {
+ dnrs_len += sizeof(uint8_t);
+ dnrs_len += dnr->addr4_cnt * sizeof(*dnr->addr4);
+ dnrs_len += dnr->svc_len;
+ }
+ }
+
+ dnrs = alloca(dnrs_len);
+ uint8_t *pos = (uint8_t *)dnrs;
+
+ for (size_t i = 0; i < iface->dnr_cnt; i++) {
+ struct dnr_options *dnr = &iface->dnr[i];
+ struct dhcpv4_dnr *d4dnr = (struct dhcpv4_dnr *)pos;
+ uint16_t d4dnr_len = sizeof(uint16_t) + sizeof(uint8_t) + dnr->adn_len;
+ uint16_t d4dnr_priority_be = htons(dnr->priority);
+ uint16_t d4dnr_len_be;
+
+ if (dnr->addr4_cnt == 0 && dnr->addr6_cnt > 0)
+ continue;
+
+ /* memcpy as the struct is unaligned */
+ memcpy(&d4dnr->priority, &d4dnr_priority_be, sizeof(d4dnr_priority_be));
+
+ d4dnr->adn_len = dnr->adn_len;
+ pos = d4dnr->body;
+ memcpy(pos, dnr->adn, dnr->adn_len);
+ pos += dnr->adn_len;
+
+ if (dnr->addr4_cnt > 0 || dnr->svc_len > 0) {
+ uint8_t addr4_len = dnr->addr4_cnt * sizeof(*dnr->addr4);
+
+ *(pos++) = addr4_len;
+ memcpy(pos, dnr->addr4, addr4_len);
+ pos += addr4_len;
+ memcpy(pos, dnr->svc, dnr->svc_len);
+ pos += dnr->svc_len;
+
+ d4dnr_len += sizeof(addr4_len) + addr4_len + dnr->svc_len;
+ }
+
+ d4dnr_len_be = htons(d4dnr_len);
+ memcpy(&d4dnr->len, &d4dnr_len_be, sizeof(d4dnr_len_be));
}
+
+ dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNR,
+ dnrs_len, dnrs);
+ break;
}
}
DHCPV4_OPT_AUTHENTICATION = 90,
DHCPV4_OPT_SEARCH_DOMAIN = 119,
DHCPV4_OPT_FORCERENEW_NONCE_CAPABLE = 145,
+ DHCPV4_OPT_DNR = 162,
DHCPV4_OPT_END = 255,
};
IOV_SNTP_ADDR,
IOV_RELAY_MSG,
IOV_DHCPV4O6_SERVER,
+ IOV_DNR,
IOV_TOTAL
};
/* SNTP */
struct in6_addr *sntp_addr_ptr = iface->dhcpv6_sntp;
size_t sntp_cnt = 0;
-
struct {
uint16_t type;
uint16_t len;
uint8_t *ntp_ptr = iface->dhcpv6_ntp;
uint16_t ntp_len = iface->dhcpv6_ntp_len;
size_t ntp_cnt = 0;
-
struct {
uint16_t type;
uint16_t len;
} ntp;
+ /* DNR */
+ struct dhcpv6_dnr {
+ uint16_t type;
+ uint16_t len;
+ uint16_t priority;
+ uint16_t adn_len;
+ uint8_t body[];
+ };
+ struct dhcpv6_dnr *dnrs = NULL;
+ size_t dnrs_len = 0;
+
uint16_t otype, olen;
- uint16_t *reqopts = NULL;
uint8_t *odata;
- size_t reqopts_len = 0;
+ uint16_t *reqopts = NULL;
+ size_t reqopts_cnt = 0;
+ /* FIXME: this should be merged with the second loop further down */
dhcpv6_for_each_option(opts, opts_end, otype, olen, odata) {
+ /* Requested options, array of uint16_t, RFC 8415 §21.7 */
if (otype == DHCPV6_OPT_ORO) {
- reqopts_len = olen;
+ reqopts_cnt = olen / sizeof(uint16_t);
reqopts = (uint16_t *)odata;
+ break;
}
}
- for(size_t opt = 0; opt < reqopts_len/2; opt++) {
- if (iface->dhcpv6_sntp_cnt != 0 &&
- DHCPV6_OPT_SNTP_SERVERS == ntohs(reqopts[opt])) {
+ /* Requested options */
+ for (size_t i = 0; i < reqopts_cnt; i++) {
+ uint16_t opt = ntohs(reqopts[i]);
+
+ switch (opt) {
+ case DHCPV6_OPT_SNTP_SERVERS:
sntp_cnt = iface->dhcpv6_sntp_cnt;
dhcpv6_sntp.type = htons(DHCPV6_OPT_SNTP_SERVERS);
dhcpv6_sntp.len = htons(sntp_cnt * sizeof(*sntp_addr_ptr));
- } else if (iface->dhcpv6_ntp_cnt != 0 &&
- DHCPV6_OPT_NTP_SERVERS == ntohs(reqopts[opt])) {
+ break;
+
+ case DHCPV6_OPT_NTP_SERVERS:
ntp_cnt = iface->dhcpv6_ntp_cnt;
ntp.type = htons(DHCPV6_OPT_NTP_SERVERS);
ntp.len = htons(ntp_len);
+ break;
+
+ case DHCPV6_OPT_DNR:
+ for (size_t i = 0; i < iface->dnr_cnt; i++) {
+ struct dnr_options *dnr = &iface->dnr[i];
+
+ if (dnr->addr6_cnt == 0 && dnr->addr4_cnt > 0)
+ continue;
+
+ dnrs_len += sizeof(struct dhcpv6_dnr);
+ dnrs_len += dnr->adn_len;
+
+ if (dnr->addr6_cnt > 0 || dnr->svc_len > 0) {
+ dnrs_len += sizeof(uint16_t);
+ dnrs_len += dnr->addr6_cnt * sizeof(*dnr->addr6);
+ dnrs_len += dnr->svc_len;
+ }
+ }
+
+ dnrs = alloca(dnrs_len);
+ uint8_t *pos = (uint8_t *)dnrs;
+
+ for (size_t i = 0; i < iface->dnr_cnt; i++) {
+ struct dnr_options *dnr = &iface->dnr[i];
+ struct dhcpv6_dnr *d6dnr = (struct dhcpv6_dnr *)pos;
+ uint16_t d6dnr_type_be = htons(DHCPV6_OPT_DNR);
+ uint16_t d6dnr_len = 2 * sizeof(uint16_t) + dnr->adn_len;
+ uint16_t d6dnr_len_be;
+ uint16_t d6dnr_priority_be = htons(dnr->priority);
+ uint16_t d6dnr_adn_len_be = htons(dnr->adn_len);
+
+ if (dnr->addr6_cnt == 0 && dnr->addr4_cnt > 0)
+ continue;
+
+ /* memcpy as the struct is unaligned */
+ memcpy(&d6dnr->type, &d6dnr_type_be, sizeof(d6dnr_type_be));
+ memcpy(&d6dnr->priority, &d6dnr_priority_be, sizeof(d6dnr_priority_be));
+ memcpy(&d6dnr->adn_len, &d6dnr_adn_len_be, sizeof(d6dnr_adn_len_be));
+
+ pos = d6dnr->body;
+ memcpy(pos, dnr->adn, dnr->adn_len);
+ pos += dnr->adn_len;
+
+ if (dnr->addr6_cnt > 0 || dnr->svc_len > 0) {
+ uint16_t addr6_len = dnr->addr6_cnt * sizeof(*dnr->addr6);
+ uint16_t addr6_len_be = htons(addr6_len);
+
+ memcpy(pos, &addr6_len_be, sizeof(addr6_len_be));
+ pos += sizeof(addr6_len_be);
+ memcpy(pos, dnr->addr6, addr6_len);
+ pos += addr6_len;
+ memcpy(pos, dnr->svc, dnr->svc_len);
+ pos += dnr->svc_len;
+
+ d6dnr_len += sizeof(addr6_len_be) + addr6_len + dnr->svc_len;
+ }
+
+ d6dnr_len_be = htons(d6dnr_len);
+ memcpy(&d6dnr->len, &d6dnr_len_be, sizeof(d6dnr_len_be));
+ }
+ break;
}
}
[IOV_NTP_ADDR] = {ntp_ptr, (ntp_cnt) ? ntp_len : 0},
[IOV_SNTP] = {&dhcpv6_sntp, (sntp_cnt) ? sizeof(dhcpv6_sntp) : 0},
[IOV_SNTP_ADDR] = {sntp_addr_ptr, sntp_cnt * sizeof(*sntp_addr_ptr)},
+ [IOV_DNR] = {dnrs, dnrs_len},
[IOV_RELAY_MSG] = {NULL, 0},
[IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0},
};
dest.msg_type = DHCPV6_MSG_DHCPV4_RESPONSE;
} else
#endif /* DHCPV4_SUPPORT */
+
if (hdr->msg_type != DHCPV6_MSG_INFORMATION_REQUEST) {
ssize_t ialen = dhcpv6_ia_handle_IAs(pdbuf, sizeof(pdbuf), iface, addr, (const void *)hdr, opts_end);
iov[IOV_DHCPV4O6_SERVER].iov_len +
iov[IOV_CERID].iov_len + iov[IOV_DHCPV6_RAW].iov_len +
iov[IOV_NTP].iov_len + iov[IOV_NTP_ADDR].iov_len +
- iov[IOV_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len -
+ iov[IOV_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len +
+ iov[IOV_DNR].iov_len -
(4 + opts_end - opts));
syslog(LOG_DEBUG, "Sending a DHCPv6-%s on %s", iov[IOV_NESTED].iov_len ? "relay-reply" : "reply", iface->name);
#define DHCPV6_OPT_INF_MAX_RT 83
#define DHCPV6_OPT_DHCPV4_MSG 87
#define DHCPV6_OPT_4O6_SERVER 88
+#define DHCPV6_OPT_DNR 144
#define DHCPV6_DUID_VENDOR 2
// RFC 8781 defines PREF64 option
#define ND_OPT_PREF64 38
+// RFC 9463 - Discovery of Network-designated Resolvers (DNR)
+#define ND_OPT_DNR 144
+
#define INFINITE_VALID(x) ((x) == 0)
#define _unused __attribute__((unused))
};
+// DNR - RFC9463
+struct dnr_options {
+ uint16_t priority;
+
+ uint8_t *adn;
+ uint16_t adn_len;
+
+ struct in_addr *addr4;
+ size_t addr4_cnt;
+ struct in6_addr *addr6;
+ size_t addr6_cnt;
+
+ uint8_t *svc;
+ uint16_t svc_len;
+};
+
+
struct interface {
struct avl_node avl;
// SNTP
struct in6_addr *dhcpv6_sntp;
size_t dhcpv6_sntp_cnt;
+
+ // DNR
+ struct dnr_options *dnr;
+ size_t dnr_cnt;
};
extern struct avl_tree interfaces;
IOV_RA_DNS,
IOV_RA_SEARCH,
IOV_RA_PREF64,
+ IOV_RA_DNR,
IOV_RA_ADV_INTERVAL,
IOV_RA_TOTAL,
};
uint32_t addr[3];
};
+struct nd_opt_dnr_info {
+ uint8_t type;
+ uint8_t len;
+ uint16_t priority;
+ uint32_t lifetime;
+ uint16_t adn_len;
+ uint8_t body[];
+};
+
/* Router Advert server mode */
static int send_router_advert(struct interface *iface, const struct in6_addr *from)
{
struct nd_opt_search_list *search = NULL;
struct nd_opt_route_info *routes = NULL;
struct nd_opt_pref64_info *pref64 = NULL;
+ struct nd_opt_dnr_info *dnrs = NULL;
struct nd_opt_adv_interval adv_interval;
struct iovec iov[IOV_RA_TOTAL];
struct sockaddr_in6 dest;
- size_t dns_sz = 0, search_sz = 0, pref64_sz = 0;
+ size_t dns_sz = 0, search_sz = 0, pref64_sz = 0, dnrs_sz = 0;
size_t pfxs_cnt = 0, routes_cnt = 0;
ssize_t valid_addr_cnt = 0, invalid_addr_cnt = 0;
/*
iov[IOV_RA_PREF64].iov_base = (char *)pref64;
iov[IOV_RA_PREF64].iov_len = pref64_sz;
+ if (iface->dnr_cnt) {
+ size_t dnr_sz[iface->dnr_cnt];
+
+ for (unsigned i = 0; i < iface->dnr_cnt; i++) {
+ dnr_sz[i] = sizeof(struct nd_opt_dnr_info) + iface->dnr[i].adn_len;
+ if (iface->dnr[i].addr6_cnt > 0 || iface->dnr[i].svc_len > 0) {
+ dnr_sz[i] += 2 + iface->dnr[i].addr6_cnt * sizeof(struct in6_addr);
+ dnr_sz[i] += 2 + iface->dnr[i].svc_len;
+ }
+ dnr_sz[i] = (dnr_sz[i] + 7) & ~7;
+ dnrs_sz += dnr_sz[i];
+ }
+
+ /* dnrs are sized in multiples of 8, so each dnr should be aligned */
+ dnrs = alloca(dnrs_sz);
+ memset(dnrs, 0, dnrs_sz);
+
+ uint8_t *pos = (uint8_t *)dnrs;
+ for (unsigned i = 0; i < iface->dnr_cnt; pos += dnr_sz[i], i++) {
+ struct nd_opt_dnr_info *dnr = (struct nd_opt_dnr_info *)pos;
+ size_t dnr_addr6_sz = iface->dnr[i].addr6_cnt * sizeof(struct in6_addr);
+ uint8_t *tmp = dnr->body;
+
+ dnr->type = ND_OPT_DNR;
+ dnr->len = dnr_sz[i] / 8;
+ dnr->priority = htons(iface->dnr[i].priority);
+ dnr->lifetime = htonl(lifetime);
+
+ dnr->adn_len = htons(iface->dnr[i].adn_len);
+ memcpy(tmp, iface->dnr[i].adn, iface->dnr[i].adn_len);
+ tmp += iface->dnr[i].adn_len;
+
+ *(tmp++) = dnr_addr6_sz >> 8;
+ *(tmp++) = dnr_addr6_sz & 0xff;
+ memcpy(tmp, iface->dnr[i].addr6, dnr_addr6_sz);
+ tmp += dnr_addr6_sz;
+
+ *(tmp++) = iface->dnr[i].svc_len >> 8;
+ *(tmp++) = iface->dnr[i].svc_len & 0xff;
+ memcpy(tmp, iface->dnr[i].svc, iface->dnr[i].svc_len);
+ }
+ }
+ iov[IOV_RA_DNR].iov_base = (char *)dnrs;
+ iov[IOV_RA_DNR].iov_len = dnrs_sz;
+
/*
* RFC7084 § 4.3 :
* L-3: An IPv6 CE router MUST advertise itself as a router for the