[Dnsmasq-discuss] [PATCH] Add support for RFC 9463 encrypted DNS options
Joachim Lindenberg
dnsmasq at lindenberg.one
Thu Jul 30 14:19:09 UTC 2026
I have been looking into DNS configuration options of DHCP, ICMPv6 RA, DNR
(rfc9463) and DDR (rfc9462), and I consider all of them a security and
privacy risk. Can we please have a warning in documentation, that any
network provided DNS server can be non-trusted and has to be considered a
risk from the client perspective? Imho authentication of someone you don´t
know and talking encrypted to these doesn´t make it secure.
I encourage anyone to use other configuration means to configure their own
trusted DNS resolvers and talking to them authenticated and encrypted. My
own network uses a combination of bind, samba-ad-dc, pi-hole, and stubby
working together to provide DoT, DoH to clients (bind), local resources
(samba-ad-dc), filtering and DNSSEC validation (pi-hole), and DoT upstream
(stubby). My mobile clients are configured to trust network provided DNS
servers only for the minimum period to get past captive portals, and then
switch to DoT or DoH.
Regards,
Joachim
-----Ursprüngliche Nachricht-----
Von: Dnsmasq-discuss <dnsmasq-discuss-bounces at lists.thekelleys.org.uk> Im
Auftrag von YU Jincheng
Gesendet: Dienstag, 28. Juli 2026 17:08
An: dnsmasq-discuss at lists.thekelleys.org.uk
Betreff: [Dnsmasq-discuss] [PATCH] Add support for RFC 9463 encrypted DNS
options
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-na
+me>[,<IPv4-address>...[,<SvcParam>...]]
+for DHCPv4 and
+.B
+--dhcp-option=option6:encrypted-dns,<priority>,<authentication-domain-n
+ame>[,<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)
_______________________________________________
Dnsmasq-discuss mailing list
Dnsmasq-discuss at lists.thekelleys.org.uk
https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss
More information about the Dnsmasq-discuss
mailing list