From: David Härdeman Date: Fri, 9 Feb 2024 13:14:03 +0000 (+0100) Subject: odhcpd: add DNR (RFC 9463) support X-Git-Url: http://git.lede-project.org./?a=commitdiff_plain;h=6d342cc03bf72f27d0838942d009bf1c5df4f954;p=project%2Fodhcpd.git odhcpd: add DNR (RFC 9463) support Discovery of Network-designated Resolvers (DNR) allows devices on the network to discover encrypted DNS resolvers, which has so far required either manual configuration or other approaches (like systemd-resolved's "opportunistic" mode). To enable DNR, a new uci parameter has been added, which needs to contain at the very least, the priority (1-65535, lower = higher priority) and the server hostname (Authentication Domain Name, ADN, to use the wording of RFC9463): config dhcp 'lan' … list dnr '100 foobar.example.com' Optionally (and preferably), a comma-separated list of IP addresses and SvcParams can also be specified, like this (line wrapping added): config dhcp 'lan' … list dnr '100 resolver1.example.com fda7:ab54:69fb::1,fda7:ab54:69fb::2,10.0.0.1 alpn=dot port=853' list dnr '200 resolver2.example.com fda7:ab54:69fb::2,10.0.1.1,10.0.1.2 alpn=dot port=853' Client support is on it's way (e.g. in systemd PR #30952 or in the Windows Insiders program). Signed-off-by: David Härdeman --- diff --git a/README b/README index c8f2eb4..392ad2f 100644 --- a/README +++ b/README @@ -109,6 +109,8 @@ router list Routers to announce accepts IPv4 only dns list DNS servers to announce accepts IPv4 and IPv6 +dnr list disabled Encrypted DNS servers to announce + [ ...] dns_service bool 1 Announce the address of interface as DNS service if the list of dns is empty domain list Search domains to announce diff --git a/src/config.c b/src/config.c index 62d4857..2eba544 100644 --- a/src/config.c +++ b/src/config.c @@ -59,6 +59,7 @@ enum { IFACE_ATTR_NDP, IFACE_ATTR_ROUTER, IFACE_ATTR_DNS, + IFACE_ATTR_DNR, IFACE_ATTR_DNS_SERVICE, IFACE_ATTR_DOMAIN, IFACE_ATTR_FILTER_CLASS, @@ -112,6 +113,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [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 }, @@ -206,6 +208,32 @@ static const struct { const char *name; uint8_t flag; } ra_flags[] = { { .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; @@ -244,6 +272,13 @@ static void clean_interface(struct interface *iface) 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); } @@ -533,6 +568,274 @@ static int parse_ntp_fqdn(uint16_t *dhcpv6_ntp_len, char *fqdn, uint8_t **dhcpv6 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; @@ -985,6 +1288,20 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr 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); diff --git a/src/dhcpv4.c b/src/dhcpv4.c index 65a4b41..1ae7254 100644 --- a/src/dhcpv4.c +++ b/src/dhcpv4.c @@ -608,6 +608,14 @@ static void handle_dhcpv4(void *addr, void *data, size_t len, 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) @@ -851,12 +859,73 @@ void dhcpv4_handle_msg(void *addr, void *data, size_t len, 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; } } diff --git a/src/dhcpv4.h b/src/dhcpv4.h index b378bc1..f02529f 100644 --- a/src/dhcpv4.h +++ b/src/dhcpv4.h @@ -60,6 +60,7 @@ enum dhcpv4_opt { DHCPV4_OPT_AUTHENTICATION = 90, DHCPV4_OPT_SEARCH_DOMAIN = 119, DHCPV4_OPT_FORCERENEW_NONCE_CAPABLE = 145, + DHCPV4_OPT_DNR = 162, DHCPV4_OPT_END = 255, }; diff --git a/src/dhcpv6.c b/src/dhcpv6.c index 58b7d9f..aea6ac2 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -182,6 +182,7 @@ enum { IOV_SNTP_ADDR, IOV_RELAY_MSG, IOV_DHCPV4O6_SERVER, + IOV_DNR, IOV_TOTAL }; @@ -383,7 +384,6 @@ static void handle_client_request(void *addr, void *data, size_t len, /* SNTP */ struct in6_addr *sntp_addr_ptr = iface->dhcpv6_sntp; size_t sntp_cnt = 0; - struct { uint16_t type; uint16_t len; @@ -393,35 +393,113 @@ static void handle_client_request(void *addr, void *data, size_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; } } @@ -477,6 +555,7 @@ static void handle_client_request(void *addr, void *data, size_t len, [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}, }; @@ -634,6 +713,7 @@ static void handle_client_request(void *addr, void *data, size_t len, 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); @@ -651,7 +731,8 @@ static void handle_client_request(void *addr, void *data, size_t len, 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); diff --git a/src/dhcpv6.h b/src/dhcpv6.h index b925928..2356340 100644 --- a/src/dhcpv6.h +++ b/src/dhcpv6.h @@ -63,6 +63,7 @@ #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 diff --git a/src/odhcpd.h b/src/odhcpd.h index 156cec0..473c95b 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -37,6 +37,9 @@ // 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)) @@ -242,6 +245,23 @@ struct dhcp_assignment { }; +// 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; @@ -370,6 +390,10 @@ struct interface { // 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; diff --git a/src/router.c b/src/router.c index 7f5658b..722f2e3 100644 --- a/src/router.c +++ b/src/router.c @@ -396,6 +396,7 @@ enum { IOV_RA_DNS, IOV_RA_SEARCH, IOV_RA_PREF64, + IOV_RA_DNR, IOV_RA_ADV_INTERVAL, IOV_RA_TOTAL, }; @@ -440,6 +441,15 @@ struct nd_opt_pref64_info { 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) { @@ -451,10 +461,11 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr 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; /* @@ -787,6 +798,51 @@ pref64_out: 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