odhcpd: add DNR (RFC 9463) support
authorDavid Härdeman <david@hardeman.nu>
Fri, 9 Feb 2024 13:14:03 +0000 (14:14 +0100)
committerdedeckeh <dedeckeh@gmail.com>
Sun, 29 Dec 2024 11:50:42 +0000 (12:50 +0100)
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 <david@hardeman.nu>
README
src/config.c
src/dhcpv4.c
src/dhcpv4.h
src/dhcpv6.c
src/dhcpv6.h
src/odhcpd.h
src/router.c

diff --git a/README b/README
index c8f2eb4234abe2628dba240f844fdb4a0db07dd5..392ad2f5085de54dae47f78ffd650dd89a3d2d23 100644 (file)
--- a/README
+++ b/README
@@ -109,6 +109,8 @@ router                      list    <local address>         Routers to announce
                                                        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
index 62d4857720aba118dd1260f53bbc4b5f5c7ac53c..2eba544ed7f76f92103c4bf8e40d1d33a490ad1f 100644 (file)
@@ -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);
index 65a4b41c6d40b8c52781c5700decaf14316d515b..1ae7254dbed0eefd495641f716e2c1ab9dda4eca 100644 (file)
@@ -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;
                }
        }
 
index b378bc17165f65f44fa8eb48690f74240e64a10f..f02529f4fa0fc4608f2da168838a5f83cf729e16 100644 (file)
@@ -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,
 };
 
index 58b7d9fa7a3fbcb49d4b3e17d515daa2d8b5e9d3..aea6ac256d762ed42169dfbdc767190dce7e0974 100644 (file)
@@ -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);
index b925928d09ae1d669da7a94b8c67fbfdb6a7dc3f..2356340a39645511b09799fb34a6d988f070b61c 100644 (file)
@@ -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
 
index 156cec09214d6e4b37ba406046736e401c446736..473c95bc233db24179ec285bb9cf628302f52024 100644 (file)
@@ -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;
index 7f5658b276e16b3256de9ac129080e214b4cdd0c..722f2e3f23cbbf51050ed808f9b148ae00f86cdc 100644 (file)
@@ -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