ndisc: Parse RFC9463 encrypted DNS (DNR) option

This option is equivalent to the V4/V6 DNR options for DHCP.
This commit is contained in:
Ronan Pigott 2024-01-19 18:26:26 -07:00
parent e3d2befc13
commit bd73de3575
7 changed files with 373 additions and 1 deletions

View file

@ -2,6 +2,7 @@
#include <netinet/icmp6.h>
#include "dns-resolver-internal.h"
#include "dns-domain.h"
#include "ether-addr-util.h"
#include "hostname-util.h"
@ -88,6 +89,13 @@ static void ndisc_dnssl_done(sd_ndisc_dnssl *dnssl) {
strv_free(dnssl->domains);
}
static void ndisc_dnr_done(sd_ndisc_dnr *dnr) {
if (!dnr)
return;
sd_dns_resolver_unref(dnr->resolver);
}
sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) {
if (!option)
return NULL;
@ -108,6 +116,10 @@ sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) {
case SD_NDISC_OPTION_CAPTIVE_PORTAL:
free(option->captive_portal);
break;
case SD_NDISC_OPTION_ENCRYPTED_DNS:
ndisc_dnr_done(&option->encrypted_dns);
break;
}
return mfree(option);
@ -1269,6 +1281,147 @@ static int ndisc_option_build_prefix64(const sd_ndisc_option *option, usec_t tim
return 0;
}
int ndisc_option_add_encrypted_dns_internal(
Set **options,
size_t offset,
sd_dns_resolver *res,
usec_t lifetime,
usec_t valid_until) {
assert(options);
sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_ENCRYPTED_DNS, offset);
if (!p)
return -ENOMEM;
p->encrypted_dns = (sd_ndisc_dnr) {
.resolver = res,
.lifetime = lifetime,
.valid_until = valid_until,
};
return ndisc_option_consume(options, p);
}
static int ndisc_get_dns_name(const uint8_t *optval, size_t optlen, char **ret) {
_cleanup_free_ char *name = NULL;
int r;
assert(optval || optlen == 0);
assert(ret);
r = dns_name_from_wire_format(&optval, &optlen, &name);
if (r < 0)
return r;
if (r == 0 || optlen != 0)
return -EBADMSG;
*ret = TAKE_PTR(name);
return r;
}
static int ndisc_option_parse_encrypted_dns(Set **options, size_t offset, size_t len, const uint8_t *opt) {
int r;
assert(options);
assert(opt);
_cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {};
usec_t lifetime;
size_t ilen;
/* Every field up to and including adn must be present */
if (len < 2*8)
return -EBADMSG;
if (opt[0] != SD_NDISC_OPTION_ENCRYPTED_DNS)
return -EBADMSG;
size_t off = 2;
/* Priority */
res.priority = unaligned_read_be16(opt + off);
/* Alias mode is not allowed */
if (res.priority == 0)
return -EBADMSG;
off += sizeof(uint16_t);
/* Lifetime */
lifetime = unaligned_be32_sec_to_usec(opt + off, /* max_as_infinity = */ true);
off += sizeof(uint32_t);
/* adn field (length + dns-name) */
ilen = unaligned_read_be16(opt + off);
off += sizeof(uint16_t);
if (off + ilen > len)
return -EBADMSG;
r = ndisc_get_dns_name(opt + off, ilen, &res.auth_name);
if (r < 0)
return r;
if (dns_name_is_root(res.auth_name))
return -EBADMSG;
off += ilen;
/* This is the last field in adn-only mode, sans padding */
if (8 * DIV_ROUND_UP(off, 8) == len && memeqzero(opt + off, len - off))
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Received ADN-only encrypted DNS option, ignoring.");
/* Fields following the variable (octets) length adn field are no longer certain to be aligned. */
/* addrs (length + packed struct in6_addr) */
if (off + sizeof(uint16_t) > len)
return -EBADMSG;
ilen = unaligned_read_be16(opt + off);
off += sizeof(uint16_t);
if (off + ilen > len || ilen % (sizeof(struct in6_addr)) != 0)
return -EBADMSG;
size_t n_addrs = ilen / (sizeof(struct in6_addr));
if (n_addrs == 0)
return -EBADMSG;
res.addrs = new(union in_addr_union, n_addrs);
if (!res.addrs)
return -ENOMEM;
for (size_t i = 0; i < n_addrs; i++) {
union in_addr_union addr;
memcpy(&addr.in6, opt + off, sizeof(struct in6_addr));
if (in_addr_is_multicast(AF_INET6, &addr) ||
in_addr_is_localhost(AF_INET, &addr))
return -EBADMSG;
res.addrs[i] = addr;
off += sizeof(struct in6_addr);
}
res.n_addrs = n_addrs;
res.family = AF_INET6;
/* SvcParam field. (length + SvcParams) */
if (off + sizeof(uint16_t) > len)
return -EBADMSG;
ilen = unaligned_read_be16(opt + off);
off += sizeof(uint16_t);
if (off + ilen > len)
return -EBADMSG;
r = dnr_parse_svc_params(opt + off, ilen, &res);
if (r < 0)
return r;
if (r == 0) /* This indicates a valid message we don't support */
return -EOPNOTSUPP;
off += ilen;
/* the remaining padding bytes must be zeroed */
if (len - off >= 8 || !memeqzero(opt + off, len - off))
return -EBADMSG;
sd_dns_resolver *new_res = new(sd_dns_resolver, 1);
if (!new_res)
return -ENOMEM;
*new_res = TAKE_STRUCT(res);
return ndisc_option_add_encrypted_dns(options, offset, new_res, lifetime);
}
static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) {
assert(options);
assert(opt);
@ -1375,6 +1528,10 @@ int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) {
r = ndisc_option_parse_prefix64(&options, offset, length, opt);
break;
case SD_NDISC_OPTION_ENCRYPTED_DNS:
r = ndisc_option_parse_encrypted_dns(&options, offset, length, opt);
break;
default:
r = ndisc_option_parse_default(&options, offset, length, opt);
}

View file

@ -9,6 +9,7 @@
#include <sys/uio.h>
#include "sd-ndisc-protocol.h"
#include "sd-dns-resolver.h"
#include "icmp6-packet.h"
#include "macro.h"
@ -66,6 +67,12 @@ typedef struct sd_ndisc_prefix64 {
usec_t valid_until;
} sd_ndisc_prefix64;
typedef struct sd_ndisc_dnr {
sd_dns_resolver *resolver;
usec_t lifetime;
usec_t valid_until;
} sd_ndisc_dnr;
typedef struct sd_ndisc_option {
uint8_t type;
size_t offset;
@ -83,6 +90,7 @@ typedef struct sd_ndisc_option {
sd_ndisc_dnssl dnssl; /* SD_NDISC_OPTION_DNSSL */
char *captive_portal; /* SD_NDISC_OPTION_CAPTIVE_PORTAL */
sd_ndisc_prefix64 prefix64; /* SD_NDISC_OPTION_PREF64 */
sd_ndisc_dnr encrypted_dns; /* SD_NDISC_OPTION_ENCRYPTED_DNS */
};
} sd_ndisc_option;
@ -327,4 +335,26 @@ static inline int ndisc_option_set_prefix64(
return ndisc_option_add_prefix64_internal(options, 0, prefixlen, prefix, lifetime, valid_until);
}
int ndisc_option_add_encrypted_dns_internal(
Set **options,
size_t offset,
sd_dns_resolver *res,
usec_t lifetime,
usec_t valid_until);
static inline int ndisc_option_add_encrypted_dns(
Set **options,
size_t offset,
sd_dns_resolver *res,
usec_t lifetime) {
return ndisc_option_add_encrypted_dns_internal(options, offset, res, lifetime, USEC_INFINITY);
}
static inline int ndisc_option_set_encrypted_dns(
Set **options,
size_t offset,
sd_dns_resolver *res,
usec_t lifetime,
usec_t valid_until) {
return ndisc_option_add_encrypted_dns_internal(options, 0, res, lifetime, valid_until);
}
int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, Set *options, usec_t timestamp);

View file

@ -5,12 +5,15 @@
#include <netinet/icmp6.h>
#include "dns-resolver-internal.h"
#include "sd-ndisc.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "ndisc-internal.h"
#include "ndisc-router-internal.h"
#include "string-table.h"
#include "unaligned.h"
static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) {
if (!rt)
@ -91,6 +94,7 @@ DEFINE_GET_TIMESTAMP(route_get_lifetime);
DEFINE_GET_TIMESTAMP(rdnss_get_lifetime);
DEFINE_GET_TIMESTAMP(dnssl_get_lifetime);
DEFINE_GET_TIMESTAMP(prefix64_get_lifetime);
DEFINE_GET_TIMESTAMP(encrypted_dns_get_lifetime);
int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) {
const struct nd_router_advert *a;
@ -342,3 +346,6 @@ DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, lifetime, uint64_t);
DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefixlen, uint8_t);
DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefix, struct in6_addr);
DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, lifetime, uint64_t);
DEFINE_GETTER(encrypted_dns, SD_NDISC_OPTION_ENCRYPTED_DNS, lifetime, uint64_t);
DEFINE_GETTER(encrypted_dns, SD_NDISC_OPTION_ENCRYPTED_DNS, resolver, sd_dns_resolver*);

View file

@ -167,6 +167,7 @@ typedef struct Link {
Set *ndisc_captive_portals;
Set *ndisc_pref64;
Set *ndisc_redirects;
Set *ndisc_dnr;
uint32_t ndisc_mtu;
unsigned ndisc_messages;
bool ndisc_configured:1;

View file

@ -22,6 +22,7 @@
#include "networkd-route.h"
#include "networkd-state-file.h"
#include "networkd-sysctl.h"
#include "sort-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
@ -1784,6 +1785,144 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) {
return 0;
}
static NDiscDNR* ndisc_dnr_free(NDiscDNR *x) {
if (!x)
return NULL;
sd_dns_resolver_done(&x->resolver);
return mfree(x);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(NDiscDNR*, ndisc_dnr_free);
static int ndisc_dnr_compare_func(const NDiscDNR *a, const NDiscDNR *b) {
return CMP(a->resolver.priority, b->resolver.priority) ||
strcmp_ptr(a->resolver.auth_name, b->resolver.auth_name) ||
CMP(a->resolver.transports, b->resolver.transports) ||
CMP(a->resolver.port, b->resolver.port) ||
strcmp_ptr(a->resolver.dohpath, b->resolver.dohpath) ||
CMP(a->resolver.family, b->resolver.family) ||
CMP(a->resolver.n_addrs, b->resolver.n_addrs) ||
memcmp(a->resolver.addrs, b->resolver.addrs, sizeof(a->resolver.addrs[0]) * a->resolver.n_addrs);
}
static void ndisc_dnr_hash_func(const NDiscDNR *x, struct siphash *state) {
assert(x);
siphash24_compress_resolver(&x->resolver, state);
}
DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR(
ndisc_dnr_hash_ops,
NDiscDNR,
ndisc_dnr_hash_func,
ndisc_dnr_compare_func,
ndisc_dnr_free);
static int sd_dns_resolver_copy(const sd_dns_resolver *a, sd_dns_resolver *b) {
int r;
assert(a);
assert(b);
_cleanup_(sd_dns_resolver_done) sd_dns_resolver c = {
.priority = a->priority,
.transports = a->transports,
.port = a->port,
/* .auth_name */
.family = a->family,
/* .addrs */
/* .n_addrs */
/* .dohpath */
};
/* auth_name */
r = strdup_to(&c.auth_name, a->auth_name);
if (r < 0)
return r;
/* addrs, n_addrs */
c.addrs = newdup(union in_addr_union, a->addrs, a->n_addrs);
if (!c.addrs)
return r;
c.n_addrs = a->n_addrs;
/* dohpath */
r = strdup_to(&c.dohpath, a->dohpath);
if (r < 0)
return r;
*b = TAKE_STRUCT(c);
return 0;
}
static int ndisc_router_process_encrypted_dns(Link *link, sd_ndisc_router *rt) {
int r = 0;
assert(link);
assert(link->network);
assert(rt);
struct in6_addr router;
usec_t lifetime_usec;
sd_dns_resolver *res;
_cleanup_(ndisc_dnr_freep) NDiscDNR *new_entry = NULL;
r = sd_ndisc_router_get_sender_address(rt, &router);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m");
r = sd_ndisc_router_encrypted_dns_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m");
r = sd_ndisc_router_encrypted_dns_get_resolver(rt, &res);
if (r < 0)
return log_link_warning_errno(link, r, "Failed to get encrypted dns resolvers: %m");
NDiscDNR *dnr, d = (NDiscDNR){ .resolver = *res };
if (lifetime_usec == 0) {
dnr = set_remove(link->ndisc_dnr, &d);
if (dnr) {
ndisc_dnr_free(dnr);
link_dirty(link);
return 0;
}
}
dnr = set_get(link->ndisc_dnr, &d);
if (dnr) {
dnr->router = router;
dnr->lifetime_usec = lifetime_usec;
return 0;
}
new_entry = new(NDiscDNR, 1);
if (!new_entry)
return log_oom();
*new_entry = (NDiscDNR) {
.router = router,
/* .resolver, */
.lifetime_usec = lifetime_usec,
};
r = sd_dns_resolver_copy(res, &new_entry->resolver);
if (r < 0)
return log_oom();
/* Not sorted by priority */
r = set_ensure_put(&link->ndisc_dnr, &ndisc_dnr_hash_ops, new_entry);
if (r < 0)
return log_oom();
assert(r > 0);
TAKE_PTR(new_entry);
link_dirty(link);
return 0;
}
static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
size_t n_captive_portal = 0;
int r;
@ -1835,6 +1974,9 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
case SD_NDISC_OPTION_PREF64:
r = ndisc_router_process_pref64(link, rt);
break;
case SD_NDISC_OPTION_ENCRYPTED_DNS:
r = ndisc_router_process_encrypted_dns(link, rt);
break;
}
if (r < 0 && r != -EBADMSG)
return r;
@ -1847,6 +1989,7 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t
NDiscRDNSS *rdnss;
NDiscCaptivePortal *cp;
NDiscPREF64 *p64;
NDiscDNR *dnr;
Address *address;
Route *route;
int r, ret = 0;
@ -1945,6 +2088,14 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t
* the 'updated' flag. */
}
SET_FOREACH(dnr, link->ndisc_dnr) {
if (dnr->lifetime_usec > timestamp_usec)
continue; /* The resolver is still valid */
ndisc_dnr_free(set_remove(link->ndisc_dnr, dnr));
updated = true;
}
if (updated)
link_dirty(link);
@ -1972,6 +2123,7 @@ static int ndisc_setup_expire(Link *link) {
NDiscDNSSL *dnssl;
NDiscRDNSS *rdnss;
NDiscPREF64 *p64;
NDiscDNR *dnr;
Address *address;
Route *route;
int r;
@ -2024,6 +2176,9 @@ static int ndisc_setup_expire(Link *link) {
SET_FOREACH(p64, link->ndisc_pref64)
lifetime_usec = MIN(lifetime_usec, p64->lifetime_usec);
SET_FOREACH(dnr, link->ndisc_dnr)
lifetime_usec = MIN(lifetime_usec, dnr->lifetime_usec);
if (lifetime_usec == USEC_INFINITY)
return 0;
@ -2288,6 +2443,14 @@ static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *n
p64->router = current_address;
}
NDiscDNR *dnr;
SET_FOREACH(dnr, link->ndisc_dnr) {
if (!in6_addr_equal(&dnr->router, &original_address))
continue;
dnr->router = current_address;
}
return 0;
}
@ -2471,7 +2634,7 @@ int ndisc_stop(Link *link) {
void ndisc_flush(Link *link) {
assert(link);
/* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */
/* Remove all addresses, routes, RDNSS, DNSSL, DNR, and Captive Portal entries, without exception. */
(void) ndisc_drop_outdated(link, /* router = */ NULL, /* timestamp_usec = */ USEC_INFINITY);
(void) ndisc_drop_redirect(link, /* router = */ NULL);
@ -2481,6 +2644,7 @@ void ndisc_flush(Link *link) {
link->ndisc_captive_portals = set_free(link->ndisc_captive_portals);
link->ndisc_pref64 = set_free(link->ndisc_pref64);
link->ndisc_redirects = set_free(link->ndisc_redirects);
link->ndisc_dnr = set_free(link->ndisc_dnr);
link->ndisc_mtu = 0;
}

View file

@ -2,6 +2,7 @@
#pragma once
#include "conf-parser.h"
#include "dns-resolver-internal.h"
#include "time-util.h"
typedef struct Address Address;
@ -49,6 +50,12 @@ typedef struct NDiscPREF64 {
struct in6_addr prefix;
} NDiscPREF64;
typedef struct NDiscDNR {
struct in6_addr router;
usec_t lifetime_usec;
sd_dns_resolver resolver;
} NDiscDNR;
static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) {
return ((char*) n) + ALIGN(sizeof(NDiscDNSSL));
}

View file

@ -28,6 +28,7 @@
_SD_BEGIN_DECLARATIONS;
typedef struct sd_ndisc_router sd_ndisc_router;
typedef struct sd_dns_resolver sd_dns_resolver;
sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt);
sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt);
@ -87,6 +88,11 @@ int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret);
int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret);
int sd_ndisc_router_prefix64_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);
/* Specific option access: SD_NDISC_OPTION_ENCRYPTED_DNS */
int sd_ndisc_router_encrypted_dns_get_resolver(sd_ndisc_router *rt, sd_dns_resolver **ret);
int sd_ndisc_router_encrypted_dns_get_lifetime(sd_ndisc_router *rt, uint64_t *ret);
int sd_ndisc_router_encrypted_dns_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret);
_SD_END_DECLARATIONS;
#endif