]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ndisc: Parse RFC9463 encrypted DNS (DNR) option
authorRonan Pigott <ronan@rjp.ie>
Sat, 20 Jan 2024 01:26:26 +0000 (18:26 -0700)
committerRonan Pigott <ronan@rjp.ie>
Mon, 21 Oct 2024 16:10:19 +0000 (09:10 -0700)
This option is equivalent to the V4/V6 DNR options for DHCP.

src/libsystemd-network/ndisc-option.c
src/libsystemd-network/ndisc-option.h
src/libsystemd-network/sd-ndisc-router.c
src/network/networkd-link.h
src/network/networkd-ndisc.c
src/network/networkd-ndisc.h
src/systemd/sd-ndisc-router.h

index af38c32be123342e3134ab991d33ec5c3babdd8b..afa07c92c53b58260a038c266683316dd5f72e41 100644 (file)
@@ -3,6 +3,7 @@
 #include <linux/ipv6.h>
 #include <netinet/icmp6.h>
 
+#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);
                 }
index d7bd86147b27ae14a8e7055f64c4e034f517cfed..5752533e0f7ebc0a875255124d68b966d1d17433 100644 (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);
index 9ca737d493970875e6cb9b6ff5f277fc0f2013ea..c978547f8152093cb551706e56786d9366660454 100644 (file)
@@ -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*);
index 27bc299bc5fa1d4c01effe330887d962a323dca7..5816acace198c1e749b519d1bd0b1ff117b79cfd 100644 (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;
index 81835c06e573544b89986ef203dfcab2476a3e86..6110bcdec90dcf88fa735049dc58c089aa00f509 100644 (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"
@@ -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;
 }
 
index 6094881ac5358afc9750ab2dc21be849b96b5448..2ee07d3f7272107fbe757fcefe1d51d3c3f89b00 100644 (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));
 }
index b07fefba3b9e4415b913c74d2590afd7a7627210..a7f1365381c041bf9ef7a1c42e1ac2f89c731270 100644 (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