From: Ronan Pigott Date: Sat, 20 Jan 2024 01:26:26 +0000 (-0700) Subject: ndisc: Parse RFC9463 encrypted DNS (DNR) option X-Git-Tag: v257-rc1~171^2~6 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=0c90d1d2f2433451c43e6b69c02186195e2d317b;p=thirdparty%2Fsystemd.git ndisc: Parse RFC9463 encrypted DNS (DNR) option This option is equivalent to the V4/V6 DNR options for DHCP. --- diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c index af38c32be12..afa07c92c53 100644 --- a/src/libsystemd-network/ndisc-option.c +++ b/src/libsystemd-network/ndisc-option.c @@ -3,6 +3,7 @@ #include #include +#include "dns-resolver-internal.h" #include "dns-domain.h" #include "ether-addr-util.h" #include "hostname-util.h" @@ -89,6 +90,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; @@ -109,6 +117,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); @@ -1270,6 +1282,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); @@ -1376,6 +1529,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); } diff --git a/src/libsystemd-network/ndisc-option.h b/src/libsystemd-network/ndisc-option.h index d7bd86147b2..5752533e0f7 100644 --- a/src/libsystemd-network/ndisc-option.h +++ b/src/libsystemd-network/ndisc-option.h @@ -9,6 +9,7 @@ #include #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); diff --git a/src/libsystemd-network/sd-ndisc-router.c b/src/libsystemd-network/sd-ndisc-router.c index 9ca737d4939..c978547f815 100644 --- a/src/libsystemd-network/sd-ndisc-router.c +++ b/src/libsystemd-network/sd-ndisc-router.c @@ -8,9 +8,12 @@ #include "sd-ndisc.h" #include "alloc-util.h" +#include "dns-domain.h" +#include "dns-resolver-internal.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*); diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 27bc299bc5f..5816acace19 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -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; diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 81835c06e57..6110bcdec90 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -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" @@ -1828,6 +1829,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; + + 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 = { .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; @@ -1879,6 +2018,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; @@ -1891,6 +2033,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; @@ -1989,6 +2132,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); @@ -2016,6 +2167,7 @@ static int ndisc_setup_expire(Link *link) { NDiscDNSSL *dnssl; NDiscRDNSS *rdnss; NDiscPREF64 *p64; + NDiscDNR *dnr; Address *address; Route *route; int r; @@ -2068,6 +2220,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; @@ -2324,6 +2479,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; } @@ -2507,7 +2670,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); @@ -2517,6 +2680,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; } diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index 6094881ac53..2ee07d3f727 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -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)); } diff --git a/src/systemd/sd-ndisc-router.h b/src/systemd/sd-ndisc-router.h index b07fefba3b9..a7f1365381c 100644 --- a/src/systemd/sd-ndisc-router.h +++ b/src/systemd/sd-ndisc-router.h @@ -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