This option is equivalent to the V4/V6 DNR options for DHCP.
#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"
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;
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);
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);
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);
}
#include <sys/uio.h>
#include "sd-ndisc-protocol.h"
+#include "sd-dns-resolver.h"
#include "icmp6-packet.h"
#include "macro.h"
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;
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;
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);
#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)
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;
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*);
Set *ndisc_captive_portals;
Set *ndisc_pref64;
Set *ndisc_redirects;
+ Set *ndisc_dnr;
uint32_t ndisc_mtu;
unsigned ndisc_messages;
bool ndisc_configured:1;
#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"
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;
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;
NDiscRDNSS *rdnss;
NDiscCaptivePortal *cp;
NDiscPREF64 *p64;
+ NDiscDNR *dnr;
Address *address;
Route *route;
int r, ret = 0;
* 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);
NDiscDNSSL *dnssl;
NDiscRDNSS *rdnss;
NDiscPREF64 *p64;
+ NDiscDNR *dnr;
Address *address;
Route *route;
int r;
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;
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;
}
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);
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;
}
#pragma once
#include "conf-parser.h"
+#include "dns-resolver-internal.h"
#include "time-util.h"
typedef struct Address Address;
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));
}
_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);
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