[Dnsmasq-discuss] [PATCH] Add support for RFC 9463 encrypted DNS options
YU Jincheng
shana at zju.edu.cn
Tue Jul 28 15:07:46 UTC 2026
Add structured encrypted-dns configuration for DHCPv4 option 162 and
DHCPv6 option 144. Encode the authentication domain name, addresses,
and SvcParams, while retaining numeric option forms as raw data.
Preserve multiple resolver instances. Concatenate and fragment DHCPv4
data as required by RFC 3396, and derive IPv6 RA encrypted DNS options
from matching DHCPv6 configuration.
Tested on Linux with default, NO_DHCP6, and NO_DHCP builds, ASan/UBSan
configuration tests, wire-format harnesses, and DHCPDISCOVER/DHCPOFFER
exchanges in isolated network namespaces.
Signed-off-by: YU Jincheng <shana at zju.edu.cn>
---
man/dnsmasq.8 | 27 +++
src/dhcp-common.c | 10 +-
src/dhcp-protocol.h | 1 +
src/dhcp6-protocol.h | 1 +
src/dnsmasq.h | 2 +
src/option.c | 443 ++++++++++++++++++++++++++++++++++++++++++-
src/radv-protocol.h | 1 +
src/radv.c | 73 +++++++
src/rfc2131.c | 53 ++++++
9 files changed, 607 insertions(+), 4 deletions(-)
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index fcb079d..36b5958 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -1369,6 +1369,33 @@ to option 120 are handled as per RFC 3361. Dotted-quad IP addresses
which are followed by a slash and then a netmask size are encoded as
described in RFC 3442.
+The \fBencrypted-dns\fP option has special processing as specified by
+RFC 9463. Its format is
+.B --dhcp-option=option:encrypted-dns,<priority>,<authentication-domain-name>[,<IPv4-address>...[,<SvcParam>...]]
+for DHCPv4 and
+.B --dhcp-option=option6:encrypted-dns,<priority>,<authentication-domain-name>[,<IPv6-address>...[,<SvcParam>...]]
+for DHCPv6. IPv6 addresses must be enclosed in square brackets, as for
+other DHCPv6 options. Omitting the addresses and SvcParams selects
+ADN-only mode. Otherwise, at least one address is required. Multiple
+instances may be configured; they are advertised as separate encrypted
+DNS resolvers and ordered by the client using the priority field.
+The supported SvcParams are \fBmandatory\fP, \fBalpn\fP,
+\fBno-default-alpn\fP, \fBport\fP, and \fBdohpath\fP. An unregistered
+SvcParam may be supplied as \fBkeyNNNN=<value>\fP; the value is used
+verbatim as its wire-format value. In a configuration file, commas
+which belong to a SvcParam value, including lists for \fBmandatory\fP
+and \fBalpn\fP, must be quoted, for example
+.B dhcp-option=option:encrypted-dns,1,resolver.example,192.0.2.1,alpn="dot,doq,h2",dohpath=/dns-query{?dns}.
+The \fBipv4hint\fP and \fBipv6hint\fP SvcParams are rejected because
+RFC 9463 forbids them in these options.
+
+DHCPv4 encrypted DNS data longer than one DHCP option is split and
+concatenated according to RFC 3396. When router advertisements are
+enabled, matching DHCPv6 \fBencrypted-dns\fP options are also emitted as
+RFC 9463 IPv6 RA Encrypted DNS options. Their lifetime is derived in the
+same way as the RDNSS and DNSSL lifetimes and is at least three maximum
+router-advertisement intervals.
+
IPv6 options are specified using the \fBoption6:\fP
keyword, followed by the option number or option name. The IPv6 option
name space is disjoint from the IPv4 option name space. IPv6 addresses
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index fa92381..897fda7 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -187,7 +187,8 @@ struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *con
{
struct dhcp_opt *tmp;
for (tmp = opts; tmp; tmp = tmp->next)
- if (tmp->opt == opt->opt && opt->netid && (tmp->flags & DHOPT_TAGOK))
+ if (!(opt->flags & DHOPT_DNR) &&
+ tmp->opt == opt->opt && opt->netid && (tmp->flags & DHOPT_TAGOK))
break;
if (!tmp)
opt->flags |= DHOPT_TAGOK;
@@ -201,7 +202,8 @@ struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *con
pxe_ok(opt, pxemode))
{
for (tmp = opts; tmp; tmp = tmp->next)
- if (tmp->opt == opt->opt && (tmp->flags & DHOPT_TAGOK))
+ if (tmp->opt == opt->opt && (tmp->flags & DHOPT_TAGOK) &&
+ (!(opt->flags & DHOPT_DNR) || tmp->netid))
break;
if (!tmp)
opt->flags |= DHOPT_TAGOK;
@@ -211,7 +213,7 @@ struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *con
/* Finally, eliminate duplicate options later in the chain, and therefore earlier in the config file. */
for (opt = opts; opt; opt = opt->next)
- if (opt->flags & DHOPT_TAGOK)
+ if ((opt->flags & DHOPT_TAGOK) && !(opt->flags & DHOPT_DNR))
for (tmp = opt->next; tmp; tmp = tmp->next)
if (tmp->opt == opt->opt)
tmp->flags &= ~DHOPT_TAGOK;
@@ -717,6 +719,7 @@ static const struct opttab_t {
{ "classless-static-route", 121, 0 },
{ "vendor-id-encap", 125, 0 },
{ "tftp-server-address", 150, OT_ADDR_LIST },
+ { "encrypted-dns", 162, OT_DNR }, /* RFC 9463 */
{ "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */
{ NULL, 0, 0 }
};
@@ -753,6 +756,7 @@ static const struct opttab_t opttab6[] = {
{ "bootfile-url", 59, OT_NAME },
{ "bootfile-param", 60, OT_CSTRING },
{ "captive-portal", 103, OT_NAME }, /* RFC 8910 */
+ { "encrypted-dns", 144, OT_DNR }, /* RFC 9463 */
{ NULL, 0, 0 }
};
#endif
diff --git a/src/dhcp-protocol.h b/src/dhcp-protocol.h
index adf8d08..cf335d8 100644
--- a/src/dhcp-protocol.h
+++ b/src/dhcp-protocol.h
@@ -67,6 +67,7 @@
#define OPTION_VENDOR_IDENT 124
#define OPTION_VENDOR_IDENT_OPT 125
#define OPTION_MUD_URL_V4 161
+#define OPTION_V4_DNR 162
#define OPTION_END 255
#define SUBOPT_CIRCUIT_ID 1
diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h
index d55f147..8aeaf6a 100644
--- a/src/dhcp6-protocol.h
+++ b/src/dhcp6-protocol.h
@@ -64,6 +64,7 @@
#define OPTION6_NTP_SERVER 56
#define OPTION6_CLIENT_MAC 79
#define OPTION6_MUD_URL 112
+#define OPTION6_DNR 144
#define NTP_SUBOPTION_SRV_ADDR 1
#define NTP_SUBOPTION_MC_ADDR 2
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index ecff18d..014e3ea 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -835,6 +835,7 @@ struct frec {
#define OT_DEC 0x0400
#define OT_TIME 0x0200
#define OT_DHCP6_VENDOR 0x0100
+#define OT_DNR 0x0080
/* actions in the daemon->helper RPC */
#define ACTION_DEL 1
@@ -985,6 +986,7 @@ struct dhcp_opt {
#define DHOPT_ADDR6 8192
#define DHOPT_VENDOR_PXE 16384
#define DHOPT_PXE_OPT 32768
+#define DHOPT_DNR 65536
struct dhcp_boot {
char *file, *sname, *tftp_sname;
diff --git a/src/option.c b/src/option.c
index 2341dfb..d40b0fe 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1452,6 +1452,428 @@ static void dhcp_opt_free(struct dhcp_opt *opt)
free(opt);
}
+struct dnr_svcparam {
+ u16 key, len;
+ unsigned char *val;
+};
+
+static int dnr_key(char *name, u16 *key)
+{
+ int val;
+
+ if (strcmp(name, "mandatory") == 0)
+ val = 0;
+ else if (strcmp(name, "alpn") == 0)
+ val = 1;
+ else if (strcmp(name, "no-default-alpn") == 0)
+ val = 2;
+ else if (strcmp(name, "port") == 0)
+ val = 3;
+ else if (strcmp(name, "ipv4hint") == 0)
+ val = 4;
+ else if (strcmp(name, "ipv6hint") == 0)
+ val = 6;
+ else if (strcmp(name, "dohpath") == 0)
+ val = 7;
+ else if (strncmp(name, "key", 3) == 0 && atoi_check16(name + 3, &val))
+ ;
+ else
+ return 0;
+
+ *key = val;
+ return 1;
+}
+
+static int dnr_dohpath(char *path)
+{
+ char *open, *close, *var, *end, *scan;
+ int found = 0;
+
+ if (!path || *path != '/')
+ return 0;
+
+ for (scan = path; *scan; scan = close + 1)
+ {
+ char *stray = strchr(scan, '}');
+
+ open = strchr(scan, '{');
+ if (stray && (!open || stray < open))
+ return 0;
+ if (!open)
+ break;
+ if (!(close = strchr(open + 1, '}')))
+ return 0;
+ if (memchr(open + 1, '{', close - open - 1))
+ return 0;
+
+ var = open + 1;
+ if (strchr("+#./;?&", *var))
+ var++;
+ if (var == close)
+ return 0;
+
+ while (var < close)
+ {
+ end = var;
+ while (end < close && *end != ',')
+ end++;
+ if (end == var)
+ return 0;
+ if (end - var >= 3 && memcmp(var, "dns", 3) == 0 &&
+ (end - var == 3 || var[3] == ':' || var[3] == '*'))
+ found = 1;
+ if (end < close && end + 1 == close)
+ return 0;
+ var = end + 1;
+ }
+ }
+
+ return found;
+}
+
+static int dnr_svcparam_cmp(const void *a, const void *b)
+{
+ const struct dnr_svcparam *aa = a, *bb = b;
+
+ return aa->key < bb->key ? -1 : aa->key > bb->key;
+}
+
+static void dnr_add_address(unsigned char **addresses, size_t *addr_len,
+ size_t *addr_alloc, void *address, size_t len)
+{
+ if (*addr_len + len > *addr_alloc)
+ {
+ unsigned char *new;
+ size_t new_alloc = *addr_alloc ? *addr_alloc * 2 : 4 * IN6ADDRSZ;
+
+ while (new_alloc < *addr_len + len)
+ new_alloc *= 2;
+ new = opt_malloc(new_alloc);
+ if (*addresses)
+ {
+ memcpy(new, *addresses, *addr_len);
+ free(*addresses);
+ }
+ *addresses = new;
+ *addr_alloc = new_alloc;
+ }
+
+ memcpy(*addresses + *addr_len, address, len);
+ *addr_len += len;
+}
+
+static int parse_dnr_opt(char *errstr, struct dhcp_opt *new, char *arg, int is6)
+{
+ struct dnr_svcparam *params = NULL;
+ unsigned char *addresses = NULL, *p, *adn_wire = NULL;
+ char *adn, *token, *next, *canon = NULL;
+ size_t addr_len = 0, addr_alloc = 0, param_len = 0, adn_len;
+ int priority, nparams = 0, allocated_params = 0, maxparams = 0, have_http = 0;
+ int have_alpn = 0, have_dohpath = 0, have_no_default = 0;
+ int ret = 0, i, j;
+
+#define DNR_ERR(x) do { strcpy(errstr, (x)); goto out; } while (0)
+
+ if (!arg)
+ DNR_ERR(_("missing priority in encrypted-dns option"));
+
+ token = arg;
+ if (!(adn = split(token)) || !atoi_check16(token, &priority))
+ DNR_ERR(_("bad priority in encrypted-dns option"));
+
+ if (!(next = split(adn)) && !*adn)
+ DNR_ERR(_("missing authentication name in encrypted-dns option"));
+
+ if (!(canon = canonicalise_opt(adn)) || !*canon)
+ DNR_ERR(_("bad authentication name in encrypted-dns option"));
+
+ adn_wire = opt_malloc(strlen(canon) + 2);
+ p = do_rfc1035_name(adn_wire, canon, NULL);
+ *p++ = 0;
+ adn_len = p - adn_wire;
+ if (adn_len > 255)
+ DNR_ERR(_("authentication name too long in encrypted-dns option"));
+
+ token = next;
+
+ /* Addresses precede SvcParams. Once a SvcParam is seen, all remaining
+ fields must also be SvcParams. */
+ while (token)
+ {
+ struct in_addr addr4;
+ struct in6_addr addr6;
+ char *value, *name;
+ u16 key;
+
+ next = split(token);
+ unhide_metas(token);
+
+ if (nparams == 0)
+ {
+ name = token;
+ if (is6)
+ {
+ size_t len = strlen(name);
+ if (len > 1 && name[0] == '[' && name[len - 1] == ']')
+ {
+ name[len - 1] = 0;
+ name++;
+ if (inet_pton(AF_INET6, name, &addr6))
+ {
+ if (IN6_IS_ADDR_UNSPECIFIED(&addr6) ||
+ IN6_IS_ADDR_LOOPBACK(&addr6) ||
+ IN6_IS_ADDR_MULTICAST(&addr6))
+ DNR_ERR(_("invalid IPv6 address in encrypted-dns option"));
+ dnr_add_address(&addresses, &addr_len, &addr_alloc,
+ &addr6, IN6ADDRSZ);
+ token = next;
+ continue;
+ }
+ }
+ }
+ else if (inet_pton(AF_INET, name, &addr4))
+ {
+ u32 a = ntohl(addr4.s_addr);
+ if (a == INADDR_ANY || IN_MULTICAST(a) ||
+ (a & 0xff000000) == 0x7f000000)
+ DNR_ERR(_("invalid IPv4 address in encrypted-dns option"));
+ if (addr_len + INADDRSZ > 255)
+ DNR_ERR(_("too many IPv4 addresses in encrypted-dns option"));
+ dnr_add_address(&addresses, &addr_len, &addr_alloc,
+ &addr4, INADDRSZ);
+ token = next;
+ continue;
+ }
+ }
+
+ name = token;
+ if ((value = strchr(name, '=')))
+ *(value++) = 0;
+
+ if (!dnr_key(name, &key))
+ DNR_ERR(_("bad SvcParam in encrypted-dns option"));
+ if (key == 4 || key == 6)
+ DNR_ERR(_("address hints are forbidden in encrypted-dns option"));
+ if (key == 65535)
+ DNR_ERR(_("invalid SvcParam key in encrypted-dns option"));
+
+ if (nparams == maxparams)
+ {
+ struct dnr_svcparam *newparams;
+ maxparams += 8;
+ newparams = opt_malloc(maxparams * sizeof(*newparams));
+ if (params)
+ {
+ memcpy(newparams, params, nparams * sizeof(*newparams));
+ free(params);
+ }
+ params = newparams;
+ }
+
+ for (i = 0; i < nparams; i++)
+ if (params[i].key == key)
+ DNR_ERR(_("duplicate SvcParam in encrypted-dns option"));
+
+ params[nparams].key = key;
+ params[nparams].len = 0;
+ params[nparams].val = NULL;
+ allocated_params = nparams + 1;
+
+ if (key == 0) /* mandatory */
+ {
+ char *item, *list_next;
+ u16 *keys;
+ int nkeys = 0;
+
+ if (!value || !*value)
+ DNR_ERR(_("bad mandatory SvcParam in encrypted-dns option"));
+
+ params[nparams].val = opt_malloc((strlen(value) + 1) * sizeof(*keys));
+ keys = (u16 *)params[nparams].val;
+ for (item = value; item; item = list_next)
+ {
+ u16 mandatory_key;
+ list_next = split_chr(item, ',');
+ if (!dnr_key(item, &mandatory_key) || mandatory_key == 0 ||
+ mandatory_key == 4 || mandatory_key == 6 ||
+ mandatory_key == 65535)
+ DNR_ERR(_("bad mandatory SvcParam in encrypted-dns option"));
+ for (i = 0; i < nkeys; i++)
+ if (keys[i] == mandatory_key)
+ DNR_ERR(_("duplicate mandatory key in encrypted-dns option"));
+ keys[nkeys++] = mandatory_key;
+ }
+
+ /* RFC 9460 requires the wire-format key list to be sorted. */
+ for (i = 1; i < nkeys; i++)
+ {
+ u16 k = keys[i];
+ for (j = i; j > 0 && keys[j - 1] > k; j--)
+ keys[j] = keys[j - 1];
+ keys[j] = k;
+ }
+
+ params[nparams].len = nkeys * 2;
+ p = params[nparams].val;
+ for (i = 0; i < nkeys; i++)
+ PUTSHORT(keys[i], p);
+ }
+ else if (key == 1) /* alpn */
+ {
+ char *item, *list_next;
+ size_t len = 0;
+
+ if (!value || !*value)
+ DNR_ERR(_("bad alpn SvcParam in encrypted-dns option"));
+ params[nparams].val = opt_malloc(strlen(value) + 2);
+ p = params[nparams].val;
+
+ for (item = value; item; item = list_next)
+ {
+ size_t alpn_len;
+ list_next = split_chr(item, ',');
+ alpn_len = strlen(item);
+ if (alpn_len == 0 || alpn_len > 255)
+ DNR_ERR(_("bad alpn SvcParam in encrypted-dns option"));
+ *p++ = alpn_len;
+ memcpy(p, item, alpn_len);
+ p += alpn_len;
+ len += alpn_len + 1;
+ if (strcmp(item, "h2") == 0 ||
+ strcmp(item, "http/1.1") == 0 ||
+ strcmp(item, "h3") == 0 ||
+ strncmp(item, "h3-", 3) == 0)
+ have_http = 1;
+ }
+ if (len > 65535)
+ DNR_ERR(_("alpn SvcParam too long in encrypted-dns option"));
+ params[nparams].len = len;
+ have_alpn = 1;
+ }
+ else if (key == 2) /* no-default-alpn */
+ {
+ if (value && *value)
+ DNR_ERR(_("bad no-default-alpn SvcParam in encrypted-dns option"));
+ have_no_default = 1;
+ }
+ else if (key == 3) /* port */
+ {
+ int port;
+ if (!value || !atoi_check16(value, &port))
+ DNR_ERR(_("bad port SvcParam in encrypted-dns option"));
+ params[nparams].len = 2;
+ params[nparams].val = opt_malloc(2);
+ p = params[nparams].val;
+ PUTSHORT(port, p);
+ }
+ else
+ {
+ if (!value)
+ value = "";
+ if (key == 7)
+ {
+ /* Full URI-template expansion belongs at the client, but the
+ template must be relative and contain the dns variable. */
+ if (!dnr_dohpath(value))
+ DNR_ERR(_("bad dohpath SvcParam in encrypted-dns option"));
+ have_dohpath = 1;
+ }
+ if (strlen(value) > 65535)
+ DNR_ERR(_("SvcParam too long in encrypted-dns option"));
+ params[nparams].len = strlen(value);
+ params[nparams].val = (unsigned char *)opt_string_alloc(value);
+ }
+
+ nparams++;
+ token = next;
+ }
+
+ if (nparams && addr_len == 0)
+ DNR_ERR(_("SvcParams require an address in encrypted-dns option"));
+ if (have_no_default && !have_alpn)
+ DNR_ERR(_("no-default-alpn requires alpn in encrypted-dns option"));
+ if (have_http && !have_dohpath)
+ DNR_ERR(_("HTTP ALPN requires dohpath in encrypted-dns option"));
+
+ if (nparams > 1)
+ qsort(params, nparams, sizeof(*params), dnr_svcparam_cmp);
+
+ /* Every key named by mandatory must also be present. */
+ if (nparams && params[0].key == 0)
+ for (i = 0; i < params[0].len; i += 2)
+ {
+ u16 key;
+ p = params[0].val + i;
+ GETSHORT(key, p);
+ for (j = 1; j < nparams && params[j].key != key; j++);
+ if (j == nparams)
+ DNR_ERR(_("mandatory key is absent in encrypted-dns option"));
+ }
+
+ for (i = 0; i < nparams; i++)
+ param_len += 4 + params[i].len;
+
+ if ((!is6 && adn_len + addr_len + param_len + 4 > 65535) ||
+ (is6 && adn_len + addr_len + param_len + 6 > 65535))
+ DNR_ERR(_("encrypted-dns option too long"));
+
+ new->len = 6 + adn_len + addr_len + param_len;
+ if (addr_len == 0)
+ new->len = (is6 ? 4 : 5) + adn_len;
+ new->val = opt_malloc(new->len);
+ p = new->val;
+
+ if (!is6)
+ {
+ /* DNR Instance Data Length excludes its own two-octet field. */
+ PUTSHORT(new->len - 2, p);
+ PUTSHORT(priority, p);
+ *(p++) = adn_len;
+ }
+ else
+ {
+ PUTSHORT(priority, p);
+ PUTSHORT(adn_len, p);
+ }
+
+ memcpy(p, adn_wire, adn_len);
+ p += adn_len;
+
+ if (addr_len)
+ {
+ if (is6)
+ PUTSHORT(addr_len, p);
+ else
+ *(p++) = addr_len;
+ memcpy(p, addresses, addr_len);
+ p += addr_len;
+
+ for (i = 0; i < nparams; i++)
+ {
+ PUTSHORT(params[i].key, p);
+ PUTSHORT(params[i].len, p);
+ if (params[i].len)
+ {
+ memcpy(p, params[i].val, params[i].len);
+ p += params[i].len;
+ }
+ }
+ }
+
+ new->flags |= DHOPT_DNR;
+ ret = 1;
+
+out:
+ free(adn_wire);
+ free(canon);
+ free(addresses);
+ for (i = 0; i < allocated_params; i++)
+ free(params[i].val);
+ free(params);
+ return ret;
+#undef DNR_ERR
+}
+
/* This is too insanely large to keep in-line in the switch */
static int parse_dhcp_opt(char *errstr, char *arg, int flags)
{
@@ -1462,6 +1884,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
u16 opt_len = 0;
int is6 = 0;
int option_ok = 0;
+ int dnr_named = 0;
new->len = 0;
new->flags = flags;
@@ -1493,6 +1916,8 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
/* option:<optname> must follow tag and vendor string. */
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
+ if (opt_len & OT_DNR)
+ dnr_named = 1;
}
break;
}
@@ -1516,6 +1941,8 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
opt_len = lookup_dhcp_len(AF_INET6, new->opt);
if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
option_ok = 1;
+ if (opt_len & OT_DNR)
+ dnr_named = 1;
}
}
/* option6:<opt>|<optname> must follow tag and vendor string. */
@@ -1576,6 +2003,19 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
/* option may be missing with rfc3925 match */
if (!option_ok)
goto_err(_("bad dhcp-option"));
+
+ if (dnr_named)
+ {
+ if (flags == DHOPT_MATCH ||
+ (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE | DHOPT_RFC3925)))
+ goto_err(_("inappropriate encrypted-dns option"));
+ if (!parse_dnr_opt(errstr, new, comma, is6))
+ goto on_error;
+ goto parsed_value;
+ }
+
+ /* Numeric forms retain the traditional raw-data behaviour. */
+ opt_len &= ~OT_DNR;
if (comma)
{
@@ -2002,7 +2442,8 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
}
}
- if (!is6 &&
+parsed_value:
+ if (!is6 && !(new->flags & DHOPT_DNR) &&
((new->len > 255) ||
(new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) ||
(new->len > 250 && (new->flags & DHOPT_RFC3925))))
diff --git a/src/radv-protocol.h b/src/radv-protocol.h
index 06eb418..e6799b9 100644
--- a/src/radv-protocol.h
+++ b/src/radv-protocol.h
@@ -53,3 +53,4 @@ struct prefix_opt {
#define ICMP6_OPT_RT_INFO 24
#define ICMP6_OPT_RDNSS 25
#define ICMP6_OPT_DNSSL 31
+#define ICMP6_OPT_DNR 144
diff --git a/src/radv.c b/src/radv.c
index 00c2aa7..49cb49e 100644
--- a/src/radv.c
+++ b/src/radv.c
@@ -68,6 +68,67 @@ static struct ra_interface *find_iface_param(char *iface);
static int hop_limit;
+static void add_ra_dnr(struct dhcp_opt *opt, unsigned int lifetime)
+{
+ unsigned char *p = opt->val, *out;
+ unsigned int adn_len, addr_len = 0, svc_len = 0;
+ size_t base, len;
+
+ if (opt->len < 4)
+ return;
+
+ adn_len = ((unsigned int)p[2] << 8) | p[3];
+ base = 4 + adn_len;
+ if (base > (size_t)opt->len)
+ return;
+
+ if (base != (size_t)opt->len)
+ {
+ if (base + 2 > (size_t)opt->len)
+ return;
+ addr_len = ((unsigned int)p[base] << 8) | p[base + 1];
+ if (addr_len == 0 || (addr_len & 15) != 0 ||
+ base + 2 + addr_len > (size_t)opt->len)
+ return;
+ svc_len = opt->len - base - 2 - addr_len;
+ }
+
+ /* Type, length, priority, lifetime, ADN length and ADN. The full
+ form additionally has address length, addresses, SvcParams length
+ and SvcParams. */
+ len = 10 + adn_len;
+ if (addr_len)
+ len += 4 + addr_len + svc_len;
+ len = (len + 7) & ~(size_t)7;
+ if (len > 255 * 8)
+ {
+ my_syslog(MS_DHCP | LOG_WARNING,
+ _("cannot send IPv6 RA encrypted-dns option: option too long"));
+ return;
+ }
+
+ if (!(out = expand(len)))
+ return;
+
+ memset(out, 0, len);
+ *(out++) = ICMP6_OPT_DNR;
+ *(out++) = len / 8;
+ memcpy(out, p, 2); /* Service Priority */
+ out += 2;
+ PUTLONG(lifetime, out);
+ memcpy(out, p + 2, 2 + adn_len); /* ADN Length and ADN */
+ out += 2 + adn_len;
+
+ if (addr_len)
+ {
+ memcpy(out, p + base, 2 + addr_len);
+ out += 2 + addr_len;
+ PUTSHORT(svc_len, out);
+ if (svc_len)
+ memcpy(out, p + base + 2 + addr_len, svc_len);
+ }
+}
+
void ra_init(time_t now)
{
struct icmp6_filter filter;
@@ -458,6 +519,18 @@ static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_ad
/* netids match and not encapsulated? */
if (!(opt_cfg->flags & DHOPT_TAGOK))
continue;
+
+ if (opt_cfg->opt == OPTION6_DNR && (opt_cfg->flags & DHOPT_DNR))
+ {
+ unsigned int lifetime = min_pref_time;
+
+ /* RFC 9463 recommends at least three maximum RA intervals. */
+ if (lifetime != 0xffffffff &&
+ lifetime < 3 * parm.adv_interval)
+ lifetime = 3 * parm.adv_interval;
+ add_ra_dnr(opt_cfg, lifetime);
+ continue;
+ }
if (opt_cfg->opt == OPTION6_DNS_SERVER)
{
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 7b58e69..d21d6d1 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -2604,6 +2604,50 @@ static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_v
return 0;
}
+static void do_dnr_options(struct dhcp_packet *mess, unsigned char *end,
+ unsigned char *req_options,
+ struct dhcp_opt *config_opts, int leasequery)
+{
+ struct dhcp_opt *opt;
+ unsigned char buf[255];
+ int len = 0;
+
+ for (opt = config_opts; opt; opt = opt->next)
+ if ((opt->flags & (DHOPT_TAGOK | DHOPT_DNR)) == (DHOPT_TAGOK | DHOPT_DNR) &&
+ !((!(opt->flags & DHOPT_FORCE) || leasequery) &&
+ !in_list(req_options, opt->opt)))
+ {
+ int offset;
+
+ for (offset = 0; offset < opt->len; )
+ {
+ int copy = opt->len - offset;
+
+ if (copy > 255 - len)
+ copy = 255 - len;
+ memcpy(buf + len, opt->val + offset, copy);
+ len += copy;
+ offset += copy;
+
+ if (len == 255)
+ {
+ unsigned char *p = free_space(mess, end, OPTION_V4_DNR, len);
+ if (!p)
+ return;
+ memcpy(p, buf, len);
+ len = 0;
+ }
+ }
+ }
+
+ if (len)
+ {
+ unsigned char *p = free_space(mess, end, OPTION_V4_DNR, len);
+ if (p)
+ memcpy(p, buf, len);
+ }
+}
+
static void do_options(struct dhcp_context *context,
struct dhcp_packet *mess,
unsigned char *end,
@@ -2629,6 +2673,7 @@ static void do_options(struct dhcp_context *context,
unsigned char f0 = 0, s0 = 0;
int done_file = 0, done_server = 0;
int done_vendor_class = 0;
+ int done_dnr = 0;
struct dhcp_netid *tagif;
struct dhcp_netid_list *id_list;
@@ -2911,6 +2956,14 @@ static void do_options(struct dhcp_context *context,
/* vendor-class comes from elsewhere for PXE */
if (pxe_arch != -1 && optno == OPTION_VENDOR_ID)
continue;
+
+ if (opt->flags & DHOPT_DNR)
+ {
+ if (!done_dnr)
+ do_dnr_options(mess, end, req_options, config_opts, leasequery);
+ done_dnr = 1;
+ continue;
+ }
/* always force null-term for filename and servername - buggy PXE again. */
len = do_opt(opt, NULL, context,
--
2.50.1 (Apple Git-155)
More information about the Dnsmasq-discuss
mailing list