1
0
mirror of https://github.com/systemd/systemd synced 2024-07-01 07:34:28 +00:00

Compare commits

...

34 Commits

Author SHA1 Message Date
Ronan Pigott
bfc29a2c5b
Merge 87cc325527 into 0e445aaa85 2024-06-28 17:55:50 -07:00
Daan De Meyer
0e445aaa85 repart: Log more about filesystem sector size 2024-06-29 02:09:55 +02:00
Luca Boccassi
1c74db68e4
Merge pull request #33518 from DaanDeMeyer/aarch64
Various integration test fixes
2024-06-29 02:09:21 +02:00
Daan De Meyer
436474dd43 TEST-54-CREDS: Use UEFI firmware if available
On aarch64, SMBIOS is only available when using UEFI, so let's make
sure that the creds test uses UEFI when available so that it can
read creds from SMBIOS when running in a virtual machine.
2024-06-28 16:23:27 +02:00
Daan De Meyer
f392be9e77 TEST-18-FAILUREACTION: Set auto firmware
This test runs in nspawn by default but will still run in qemu when
tests are run unprivileged so make sure we use UEFI if available to
avoid hangs when using the linux firmware.
2024-06-28 16:23:25 +02:00
Daan De Meyer
3cf38516bb TEST-09-REBOOT: Set auto firmware
This test runs in nspawn by default but will still run in qemu when
tests are run unprivileged so make sure we use UEFI if available to
avoid hangs when using the linux firmware.
2024-06-28 16:21:39 +02:00
Daan De Meyer
7c04137a6f TEST-06-SELINUX: Explicitly pull in autorelabel.service 2024-06-28 15:50:35 +02:00
Daan De Meyer
98f2a332cb TEST-70-TPM2: Use UEFI firmware if available
On x86 this doesn't matter but on aarch64 we need to make sure UEFI
is used so that /sys/kernel/security/tpm0/binary_bios_measurements
is there which is required for TEST-70-TPM2.
2024-06-28 15:47:33 +02:00
Daan De Meyer
24284098a4 TEST-45-TIMEDATE: Use syslog identifier journalctl match
Using a unit match is racy whereas a syslog identifier match is
reliable.
2024-06-28 14:51:10 +02:00
Daan De Meyer
72d121b601 TEST-64-UDEV-STORAGE: Make nvme_subsystem expected pci symlinks more generic
When running the test on aarch64 the symlinks look as follows:

"""
[root@H ~]# ls /dev/disk/by-path
platform-4010000000.pcie-pci-0000:00:04.0-scsi-0:0:0:0       platform-4010000000.pcie-pci-0000:00:04.0-scsi-0:0:0:0-part1  platform-4010000000.pcie-pci-0000:00:05.0-nvme-16
platform-4010000000.pcie-pci-0000:00:04.0-scsi-0:0:0:0-part  platform-4010000000.pcie-pci-0000:00:04.0-scsi-0:0:0:0-part2  platform-4010000000.pcie-pci-0000:00:05.0-nvme-17
"""

So let's make the PCI patterns a little more generic so they match
both the x86 and the aarch64 paths.
2024-06-28 14:20:34 +02:00
Daan De Meyer
3cdc8da5ad mkosi: Install pciutils 2024-06-28 13:41:12 +02:00
Daan De Meyer
32f3617fd7 test: pull in multi-user.target explicitly
Let's make sure we pull in multi-user.target so that we get a console
when a test fails and we're running meson test with --interactive.
2024-06-28 13:34:37 +02:00
Daan De Meyer
4e469c0af2 TEST-64-UDEV-STORAGE: Fix python 3.9 compatibility
Using double quotes in f-strings only works from python 3.12 onwards.
Use single quotes to make sure python 3.9 works as well.

Also clean up quotes a little in general.
2024-06-28 13:18:29 +02:00
Daan De Meyer
7f07f9c8cd TEST-64-UDEV-STORAGE: Use bus pci slot 1 instead of 0
Trying to use bus pci slot 0 fails on aarch64 so let's use 1 instead.

The error:

"""
qemu-system-aarch64: -device virtio-blk-pci,drive=drive0,scsi=off,bus=pci_bridge25: Unsupported PCI slot 0 for standard hotplug controller. Valid slots are between 1 and 31.
"""
2024-06-28 12:51:26 +02:00
Ronan Pigott
87cc325527 resolve: move sd-* api into libsystemd-network
This duplicates the svc param constants for the benefit of the
resolved-core library.
2024-06-27 11:41:24 -07:00
Ronan Pigott
db894b84b7 ndisc: implement ndisc_option_build_encrypted_dns
This is only used by the fuzzer so far.
2024-06-20 08:36:00 -07:00
Ronan Pigott
86cb6b7359 network: add dnr resolvers to networkctl status json output 2024-06-20 08:36:00 -07:00
Ronan Pigott
627780e9e9 test/fuzz: add dnr packets
The structure of DNR options is considerably more complicated than most
DHCP options, and as a result the fuzzer has poor coverage of these code
paths.

This adds some DNR packets to the fuzzing corpus, not with the intent of
capturing some specific edge case, but with the intent to rapidly
improve the fuzzers' coverage of these codepaths by giving it a valid
example to begin with.

Also include an ndisc router advert with a few Encrypted DNS options,
for the same purpose.
2024-06-20 08:36:00 -07:00
Ronan Pigott
4e30e3d2d5 network: Serialize ipv6ra DNR
Serialize DNR servers acquired by ipv6ra option, same as the V4/V6 DNR
DHCP options.
2024-06-20 08:36:00 -07:00
Ronan Pigott
9250f56b5b network: Introduce IPv6RA UseDNR= option
Same as the DHCP v4/v6 options, this controls the use of DNR received
from ipv6ra.
2024-06-20 08:36:00 -07:00
Ronan Pigott
bd73de3575 ndisc: Parse RFC9463 encrypted DNS (DNR) option
This option is equivalent to the V4/V6 DNR options for DHCP.
2024-06-20 08:36:00 -07:00
Ronan Pigott
e3d2befc13 test-network: add DHCPv6 DNR test
Same as the DHCPv4 test.
2024-06-20 08:36:00 -07:00
Ronan Pigott
59b867823f network: Serialize DHCPv6 DNR servers
This serializes DNR servers acquired by V6_DNR option, equivalent to the
V4_DNR option.
2024-06-20 08:36:00 -07:00
Ronan Pigott
cff8947fac network: Introduce UseDNR DHCPv6 option
This is equivalent to the DHCPv4 option introduced earlier.
2024-06-20 08:36:00 -07:00
Ronan Pigott
072071bc61 network: Parse RFC9463 DHCPv6 DNR option
Implement the parsing for V6_DNR DHCPv6 option. This does the same as
the DHCP V4_DNR option.
2024-06-20 08:36:00 -07:00
Ronan Pigott
47b87dc2ed dhcp6: use dns_name_from_wire_format
Convert some of the option parsing to use dns_name_from_wire_format,
introduced earlier. No change in behavior intended.
2024-06-20 08:36:00 -07:00
Ronan Pigott
aeed60a1fc test-dhcp6: terminate fqdn option
The encoded fqdn in this option must be properly terminated. We will
soon validate that this field is correctly encoded, so correct it in the
test.
2024-06-20 08:36:00 -07:00
Ronan Pigott
c42ed79c3a test-network: add test for DHCPv4 DNR
This will test that networkd/resolved can understand the V4_DNR DHCP
option.
2024-06-20 08:36:00 -07:00
Ronan Pigott
ea72b708ed network: Serialize DNR servers
Implement serialization/deserialization for DNR servers. This re-uses
the string format in place for user configuration of DoT servers, and as
a consequence non-DoT servers are discarded when recording the link
configuration, for correctness.

This also enables sd-resolved to use these servers as it would other DNS
servers.
2024-06-20 08:36:00 -07:00
Ronan Pigott
169f90c158 network: Add serialization for DoT resolvers
For now only DoT is supported, so DoT resolvers are represented using
the existing configuration format.
2024-06-20 08:36:00 -07:00
Ronan Pigott
9fcc5d2d78 network: Introduce UseDNR DHCPv4 option
This option will control the use of DNR for choosing DNS servers on the
link. Defaults to the value of UseDNS so that in most cases they will be
toggled together.
2024-06-20 08:36:00 -07:00
Ronan Pigott
3f484e344a network: parse RFC9463 DHCPv4 DNR option
This option is another way for DHCP servers to indicate preferred DNS
servers for the network, but includes more detailed info like the server
name, transport (DoT/DoH/DoQ etc.), and port.

Allow our DHCPv4 client to parse this option.
2024-06-20 08:36:00 -07:00
Ronan Pigott
2c313e2907 network: Introduce sd_dns_resolver
This type will be used to represent a "designated resolver", and the
necessary info for communicating with it. Beyond and address endpoint,
we may need to know the dns transport, authenticated domain name, DoH
path, etc.
2024-06-20 08:35:57 -07:00
Ronan Pigott
c9fa4f5aca dns: introduce dns_name_from_wire_format
This is implemented in various places, but it is better to share this
code.
2024-06-20 08:30:55 -07:00
60 changed files with 2055 additions and 140 deletions

View File

@ -2561,6 +2561,18 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseDNR=</varname></term>
<listitem>
<para>When true, designated resolvers advertised by the DHCP server will be used as encrypted
DNS servers. See <ulink url="https://datatracker.ietf.org/doc/html/rfc9463">RFC 9463</ulink>.</para>
<para>Defaults to unset, and the value for <varname>UseDNS=</varname> will be used.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseMTU=</varname></term>
<listitem>
@ -3053,6 +3065,7 @@ NFTSet=prefix:netdev:filter:eth_ipv4_prefix</programlisting>
<varlistentry>
<term><varname>UseDNS=</varname></term>
<term><varname>UseDNR=</varname></term>
<term><varname>UseNTP=</varname></term>
<term><varname>UseHostname=</varname></term>
<term><varname>UseDomains=</varname></term>
@ -3340,6 +3353,16 @@ Token=prefixstable:2002:da8:1::</programlisting></para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseDNR=</varname></term>
<listitem>
<para> When true, the DNR servers received in the Router Advertisement will be used. Defaults to
the value of <option>UseDNS=</option>.</para>
<xi:include href="version-info.xml" xpointer="v256"/>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>UseDomains=</varname></term>
<listitem>

View File

@ -57,6 +57,7 @@ Packages=
opensc
openssl
p11-kit
pciutils
python3
qrencode
radvd

View File

@ -55,6 +55,9 @@ struct sd_dhcp_lease {
DHCPServerData servers[_SD_DHCP_LEASE_SERVER_TYPE_MAX];
sd_dns_resolver *dnr;
size_t n_dnr;
struct sd_dhcp_route *static_routes;
size_t n_static_routes;
struct sd_dhcp_route *classless_routes;

View File

@ -4,6 +4,7 @@
#include <stdint.h>
#include "sd-dhcp-option.h"
#include "dns-resolver-internal.h"
#include "dhcp-protocol.h"
#include "hash-funcs.h"

View File

@ -8,6 +8,7 @@
#include <inttypes.h>
#include "sd-dhcp6-lease.h"
#include "dns-resolver-internal.h"
#include "dhcp6-option.h"
#include "dhcp6-protocol.h"
@ -38,6 +39,8 @@ struct sd_dhcp6_lease {
struct in6_addr *dns;
size_t dns_count;
sd_dns_resolver *dnr;
size_t n_dnr;
char **domains;
struct in6_addr *ntp;
size_t ntp_count;

View File

@ -206,6 +206,7 @@ bool dhcp6_option_can_request(uint16_t option) {
case SD_DHCP6_OPTION_V6_DOTS_RI:
case SD_DHCP6_OPTION_V6_DOTS_ADDRESS:
case SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF:
case SD_DHCP6_OPTION_V6_DNR:
return true;
default:
return false;
@ -820,74 +821,6 @@ int dhcp6_option_parse_addresses(
return 0;
}
static int parse_domain(const uint8_t **data, size_t *len, char **ret) {
_cleanup_free_ char *domain = NULL;
const uint8_t *optval;
size_t optlen, n = 0;
int r;
assert(data);
assert(len);
assert(*data || *len == 0);
assert(ret);
optval = *data;
optlen = *len;
if (optlen <= 1)
return -ENODATA;
for (;;) {
const char *label;
uint8_t c;
if (optlen == 0)
break;
c = *optval;
optval++;
optlen--;
if (c == 0)
/* End label */
break;
if (c > 63)
return -EBADMSG;
if (c > optlen)
return -EMSGSIZE;
/* Literal label */
label = (const char*) optval;
optval += c;
optlen -= c;
if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
if (n != 0)
domain[n++] = '.';
r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
n += r;
}
if (n > 0) {
if (!GREEDY_REALLOC(domain, n + 1))
return -ENOMEM;
domain[n] = '\0';
}
*ret = TAKE_PTR(domain);
*data = optval;
*len = optlen;
return n;
}
int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **ret) {
_cleanup_free_ char *domain = NULL;
int r;
@ -895,7 +828,7 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, size_t optlen, char **r
assert(optval || optlen == 0);
assert(ret);
r = parse_domain(&optval, &optlen, &domain);
r = dns_name_from_wire_format(&optval, &optlen, &domain);
if (r < 0)
return r;
if (r == 0)
@ -922,11 +855,11 @@ int dhcp6_option_parse_domainname_list(const uint8_t *optval, size_t optlen, cha
while (optlen > 0) {
_cleanup_free_ char *name = NULL;
r = parse_domain(&optval, &optlen, &name);
r = dns_name_from_wire_format(&optval, &optlen, &name);
if (r < 0)
return r;
if (r == 0)
continue;
if (dns_name_is_root(name)) /* root domain */
return -EBADMSG;
r = strv_consume(&names, TAKE_PTR(name));
if (r < 0)

View File

@ -0,0 +1,57 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <errno.h>
#include "sd-dns-resolver.h"
#include "macro.h"
#include "list.h"
#include "socket-netlink.h"
/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
enum {
DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 § 8 */
DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 § 7.1 */
DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 § 7.1 */
DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 § 7.2 */
DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 § 7.3 */
DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */
DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 § 7.3 */
DNS_SVC_PARAM_KEY_DOHPATH = 7, /* RFC 9461 */
DNS_SVC_PARAM_KEY_OHTTP = 8,
_DNS_SVC_PARAM_KEY_MAX_DEFINED,
DNS_SVC_PARAM_KEY_INVALID = 65535 /* RFC 9460 */
};
const char* dns_svc_param_key_to_string(int i) _const_;
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
/* Represents a "designated resolver" */
/* typedef struct sd_dns_resolver sd_dns_resolver; */
struct sd_dns_resolver {
uint16_t priority;
char *auth_name;
int family;
union in_addr_union *addrs;
size_t n_addrs;
sd_dns_alpn_flags transports;
uint16_t port;
char *dohpath;
};
void siphash24_compress_resolver(const sd_dns_resolver *res, struct siphash *state);
int dns_resolver_transports_to_strv(sd_dns_alpn_flags transports, char ***ret);
int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolvers,
struct in_addr_full ***ret_addrs, size_t *ret_n_addrs);
int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b);
int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *resolver);
int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolvers, char ***ret_names);
void dns_resolver_done_many(sd_dns_resolver *resolvers, size_t n);

View File

@ -23,6 +23,7 @@ sources = files(
'sd-dhcp-server.c',
'sd-dhcp6-client.c',
'sd-dhcp6-lease.c',
'sd-dns-resolver.c',
'sd-ipv4acd.c',
'sd-ipv4ll.c',
'sd-lldp-rx.c',

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,283 @@ 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_build_encrypted_dns(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) {
int r;
assert(option);
assert(option->type == SD_NDISC_OPTION_ENCRYPTED_DNS);
assert(ret);
size_t off, len, ilen, plen, poff;
/* Everything up to adn field is required, so we need at least 2*8 bytes */
_cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8);
if (!buf)
return -ENOMEM;
_cleanup_strv_free_ char **alpns = NULL;
const sd_dns_resolver *res = option->encrypted_dns.resolver;
be32_t lifetime = usec_to_be32_sec(MIN(option->encrypted_dns.lifetime,
usec_sub_unsigned(option->encrypted_dns.valid_until, timestamp)));
/* Type (Length field filled in last) */
buf[0] = option->type;
/* Priority */
off = 2;
unaligned_write_be16(buf + off, res->priority);
off += sizeof(be16_t);
/* Lifetime */
memcpy(buf + off, &lifetime, sizeof(be32_t));
off += sizeof(be32_t);
/* ADN */
//FIXME can the wire format be longer than this?
ilen = strlen(res->auth_name) + 2;
/* From now on, there isn't guaranteed to be enough space to put each field */
if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen))
return -ENOMEM;
r = dns_name_to_wire_format(res->auth_name, buf + off + sizeof(uint16_t), ilen, /* canonical = */ false);
if (r < 0)
return r;
unaligned_write_be16(buf + off, (uint16_t) r);
off += sizeof(uint16_t) + r;
/* ADN-only mode */
if (res->n_addrs == 0)
goto padding;
/* addrs */
if (size_multiply_overflow(sizeof(struct in6_addr), res->n_addrs))
return -ENOMEM;
ilen = res->n_addrs * sizeof(struct in6_addr);
if (!GREEDY_REALLOC(buf, off + sizeof(uint16_t) + ilen))
return -ENOMEM;
unaligned_write_be16(buf + off, ilen);
off += sizeof(uint16_t);
FOREACH_ARRAY(addr, res->addrs, res->n_addrs) {
memcpy(buf + off, &addr->in6, sizeof(struct in6_addr));
off += sizeof(struct in6_addr);
}
/* SvcParam, MUST appear in order */
poff = off + sizeof(uint16_t);
/* ALPN */
dns_resolver_transports_to_strv(res->transports, &alpns);
/* res needs to have at least one valid transport */
if (strv_isempty(alpns))
return -EINVAL;
plen = 0;
STRV_FOREACH(alpn, alpns)
plen += sizeof(uint8_t) + strlen(*alpn);
if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen))
return -ENOMEM;
unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_ALPN);
poff += sizeof(uint16_t);
unaligned_write_be16(buf + poff, plen);
poff += sizeof(uint16_t);
STRV_FOREACH(alpn, alpns) {
size_t alen = strlen(*alpn);
buf[poff++] = alen;
memcpy(buf + poff, *alpn, alen);
poff += alen;
}
/* port */
if (res->port > 0) {
plen = 2;
if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen))
return -ENOMEM;
unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_PORT);
poff += sizeof(uint16_t);
unaligned_write_be16(buf + poff, plen);
poff += sizeof(uint16_t);
unaligned_write_be16(buf + poff, res->port);
poff += sizeof(uint16_t);
}
/* dohpath */
if (res->dohpath) {
plen = strlen(res->dohpath);
if (!GREEDY_REALLOC(buf, poff + 2 * sizeof(uint16_t) + plen))
return -ENOMEM;
unaligned_write_be16(buf + poff, (uint16_t) DNS_SVC_PARAM_KEY_DOHPATH);
poff += sizeof(uint16_t);
unaligned_write_be16(buf + poff, plen);
poff += sizeof(uint16_t);
memcpy(buf + poff, res->dohpath, plen);
poff += plen;
}
unaligned_write_be16(buf + off, LESS_BY(poff, off));
off = poff;
padding:
len = DIV_ROUND_UP(off, 8);
if (!GREEDY_REALLOC(buf, 8*len))
return -ENOMEM;
memzero(buf + off, 8*len - off);
buf[1] = len;
*ret = TAKE_PTR(buf);
return 0;
}
static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) {
assert(options);
assert(opt);
@ -1375,6 +1664,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);
}
@ -1486,6 +1779,10 @@ int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr,
r = ndisc_option_build_prefix64(option, timestamp, &buf);
break;
case SD_NDISC_OPTION_ENCRYPTED_DNS:
r = ndisc_option_build_encrypted_dns(option, timestamp, &buf);
break;
default:
continue;
}

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

@ -14,6 +14,7 @@
#include "log.h"
#include "network-internal.h"
#include "parse-util.h"
#include "strv.h"
size_t serialize_in_addrs(FILE *f,
const struct in_addr *addresses,
@ -131,6 +132,99 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string) {
return size;
}
int serialize_dnr(FILE *f, const sd_dns_resolver *dnr, size_t n_dnr, bool *with_leading_space) {
int r;
bool _space = false;
if (!with_leading_space)
with_leading_space = &_space;
int n = 0;
_cleanup_strv_free_ char **names = NULL;
r = dns_resolvers_to_dot_strv(dnr, n_dnr, &names);
if (r < 0)
return r;
if (r > 0)
fputstrv(f, names, NULL, with_leading_space);
n += r;
return n;
}
static int coalesce_dnr(sd_dns_resolver *dnr, size_t n_dnr, int family, const char *auth_name,
union in_addr_union *addr) {
assert(dnr || n_dnr == 0);
assert(auth_name);
assert(addr);
/* Look through list of DNR for matching resolvers to add our addr to. Since DoT is assumed, no need
* to compare transports/dohpath/etc. */
FOREACH_ARRAY(res, dnr, n_dnr) {
if (family == res->family && streq(auth_name, res->auth_name)) {
if (!GREEDY_REALLOC(res->addrs, res->n_addrs + 1))
return -ENOMEM;
res->addrs[res->n_addrs++] = *addr;
return true;
}
}
return false;
}
/* Deserialized resolvers are assumed to offer DoT service. */
int deserialize_dnr(sd_dns_resolver **ret, const char *string) {
int r;
assert(ret);
assert(string);
sd_dns_resolver *dnr = NULL;
size_t n = 0;
CLEANUP_ARRAY(dnr, n, dns_resolver_done_many);
int priority = 0;
for (;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&string, &word, NULL, 0);
if (r < 0)
return r;
if (r == 0)
break;
uint16_t port;
int family;
_cleanup_free_ union in_addr_union *addr = new(union in_addr_union, 1);
_cleanup_free_ char *auth_name = NULL;
r = in_addr_port_ifindex_name_from_string_auto(word, &family, addr, &port, NULL, &auth_name);
if (r < 0)
return r;
r = coalesce_dnr(dnr, n, family, auth_name, addr);
if (r < 0)
return r;
if (r > 0)
continue;
if (!GREEDY_REALLOC(dnr, n+1))
return -ENOMEM;
priority = n+1;
dnr[n++] = (sd_dns_resolver) {
.priority = priority, /* not serialized, but this will preserve the order */
.auth_name = TAKE_PTR(auth_name),
.family = family,
.addrs = TAKE_PTR(addr),
.n_addrs = 1,
.transports = SD_DNS_ALPN_DOT,
.port = port,
};
}
*ret = TAKE_PTR(dnr);
return n;
}
void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) {
assert(f);
assert(key);

View File

@ -17,6 +17,9 @@ void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses,
bool *with_leading_space);
int deserialize_in6_addrs(struct in6_addr **addresses, const char *string);
int serialize_dnr(FILE *f, const sd_dns_resolver *dnr, size_t n_dnr, bool *with_leading_space);
int deserialize_dnr(sd_dns_resolver **ret, const char *string);
/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */
struct sd_dhcp_route;
struct sd_dhcp_lease;

View File

@ -11,6 +11,7 @@
#include <unistd.h>
#include "sd-dhcp-lease.h"
#include "dns-resolver-internal.h"
#include "alloc-util.h"
#include "dhcp-lease-internal.h"
@ -26,6 +27,7 @@
#include "network-common.h"
#include "network-internal.h"
#include "parse-util.h"
#include "sort-util.h"
#include "stdio-util.h"
#include "string-util.h"
#include "strv.h"
@ -229,6 +231,17 @@ int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **ret) {
return 0;
}
int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers) {
assert_return(lease, -EINVAL);
assert_return(ret_resolvers, -EINVAL);
if (!lease->dnr)
return -ENODATA;
*ret_resolvers = lease->dnr;
return lease->n_dnr;
}
int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, const struct in_addr **addr) {
assert_return(lease, -EINVAL);
assert_return(addr, -EINVAL);
@ -418,6 +431,7 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) {
for (sd_dhcp_lease_server_type_t i = 0; i < _SD_DHCP_LEASE_SERVER_TYPE_MAX; i++)
free(lease->servers[i].addr);
dns_resolver_done_many(lease->dnr, lease->n_dnr);
free(lease->static_routes);
free(lease->classless_routes);
free(lease->vendor_specific);
@ -559,6 +573,133 @@ static int lease_parse_sip_server(const uint8_t *option, size_t len, struct in_a
return lease_parse_in_addrs(option + 1, len - 1, ret, n_ret);
}
static int lease_parse_dns_name(const uint8_t *optval, size_t optlen, char **ret) {
_cleanup_free_ char *name = NULL;
int r;
assert(optval);
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 lease_parse_dnr(const uint8_t *option, size_t len, sd_dns_resolver **ret_dnr, size_t *ret_n_dnr) {
int r;
sd_dns_resolver *res_list = NULL;
size_t n_resolvers = 0;
CLEANUP_ARRAY(res_list, n_resolvers, dns_resolver_done_many);
assert(option || len == 0);
assert(ret_dnr);
_cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {};
size_t offset = 0;
while (offset < len) {
/* Instance Data length */
if (offset + 2 > len)
return -EBADMSG;
size_t ilen = unaligned_read_be16(option + offset);
if (offset + ilen + 2 > len)
return -EBADMSG;
offset += 2;
size_t iend = offset + ilen;
/* priority */
if (offset + 2 > len)
return -EBADMSG;
res.priority = unaligned_read_be16(option + offset);
offset += 2;
/* Authenticated Domain Name */
if (offset + 1 > len)
return -EBADMSG;
ilen = option[offset++];
if (offset + ilen > iend)
return -EBADMSG;
r = lease_parse_dns_name(option + offset, ilen, &res.auth_name);
if (r < 0)
return r;
if (dns_name_is_root(res.auth_name))
return -EBADMSG;
offset += ilen;
/* RFC9463 § 3.1.6: In ADN-only mode, server omits everything after the ADN.
* We don't support these, but they are not invalid. */
if (offset == iend) {
log_debug("Received ADN-only DNRv4 option, ignoring.");
sd_dns_resolver_done(&res);
continue;
}
/* IPv4 addrs */
if (offset + 1 > len)
return -EBADMSG;
ilen = option[offset++];
if (offset + ilen > iend)
return -EBADMSG;
size_t n_addrs;
_cleanup_free_ struct in_addr *addrs = NULL;
r = lease_parse_in_addrs(option + offset, ilen, &addrs, &n_addrs);
if (r < 0)
return r;
offset += ilen;
/* RFC9463 § 3.1.8: option MUST include at least one valid IP addr */
if (!n_addrs)
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 = (union in_addr_union) {.in = addrs[i]};
/* RFC9463 § 5.2 client MUST discard multicast and host loopback addresses */
if (in_addr_is_multicast(AF_INET, &addr) ||
in_addr_is_localhost(AF_INET, &addr))
return -EBADMSG;
res.addrs[i] = addr;
}
res.n_addrs = n_addrs;
res.family = AF_INET;
/* service params */
r = dnr_parse_svc_params(option + offset, iend-offset, &res);
if (r < 0)
return r;
if (r == 0) {
/* We can't use this record, but it was not invalid. */
log_debug("Received DNRv4 option with unsupported SvcParams, ignoring.");
sd_dns_resolver_done(&res);
continue;
}
offset = iend;
/* Append the latest resolver */
if (!GREEDY_REALLOC0(res_list, n_resolvers+1))
return -ENOMEM;
res_list[n_resolvers++] = TAKE_STRUCT(res);
}
typesafe_qsort(*ret_dnr, *ret_n_dnr, dns_resolver_prio_compare);
dns_resolver_done_many(*ret_dnr, *ret_n_dnr);
*ret_dnr = TAKE_PTR(res_list);
*ret_n_dnr = n_resolvers;
return n_resolvers;
}
static int lease_parse_static_routes(sd_dhcp_lease *lease, const uint8_t *option, size_t len) {
int r;
@ -879,6 +1020,15 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void
break;
}
case SD_DHCP_OPTION_V4_DNR:
r = lease_parse_dnr(option, len, &lease->dnr, &lease->n_dnr);
if (r < 0) {
log_debug_errno(r, "Failed to parse network-designated resolvers, ignoring: %m");
return 0;
}
break;
case SD_DHCP_OPTION_VENDOR_SPECIFIC:
if (len <= 0)
@ -1140,6 +1290,14 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) {
fputc('\n', f);
}
sd_dns_resolver *resolvers;
r = sd_dhcp_lease_get_dnr(lease, &resolvers);
if (r > 0) {
fputs("DNR=", f);
serialize_dnr(f, resolvers, r, NULL);
fputc('\n', f);
}
r = sd_dhcp_lease_get_ntp(lease, &addresses);
if (r > 0) {
fputs("NTP=", f);
@ -1248,6 +1406,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
*next_server = NULL,
*broadcast = NULL,
*dns = NULL,
*dnr = NULL,
*ntp = NULL,
*sip = NULL,
*pop3 = NULL,
@ -1285,6 +1444,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
"NEXT_SERVER", &next_server,
"BROADCAST", &broadcast,
"DNS", &dns,
"DNR", &dnr,
"NTP", &ntp,
"SIP", &sip,
"POP3", &pop3,
@ -1387,6 +1547,13 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) {
lease->servers[SD_DHCP_LEASE_DNS].size = r;
}
if (dnr) {
r = deserialize_dnr(&lease->dnr, dnr);
if (r < 0)
log_debug_errno(r, "Failed to deserialize DNR servers %s, ignoring: %m", dnr);
lease->n_dnr = r;
}
if (ntp) {
r = deserialize_in_addrs(&lease->servers[SD_DHCP_LEASE_NTP].addr, ntp);
if (r < 0)

View File

@ -9,6 +9,7 @@
#include "dhcp6-internal.h"
#include "dhcp6-lease-internal.h"
#include "network-common.h"
#include "sort-util.h"
#include "strv.h"
#include "unaligned.h"
@ -438,6 +439,95 @@ int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret) {
return strv_length(lease->domains);
}
static int dhcp6_lease_add_dnr(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
int r;
assert(lease);
_cleanup_(sd_dns_resolver_done) sd_dns_resolver res = {};
size_t offset = 0;
/* priority */
if (optlen - offset < sizeof(uint16_t))
return -EBADMSG;
res.priority = unaligned_read_be16(optval + offset);
offset += sizeof(uint16_t);
/* adn */
if (optlen - offset < sizeof(uint16_t))
return -EBADMSG;
size_t ilen = unaligned_read_be16(optval + offset);
offset += sizeof(uint16_t);
if (offset + ilen > optlen)
return -EBADMSG;
r = dhcp6_option_parse_domainname(optval + offset, ilen, &res.auth_name);
if (r < 0)
return r;
offset += ilen;
/* RFC9463 § 3.1.6: adn only mode */
if (offset == optlen)
return 0;
/* addrs */
if (optlen - offset < sizeof(uint16_t))
return -EBADMSG;
ilen = unaligned_read_be16(optval + offset);
offset += sizeof(uint16_t);
if (offset + ilen > optlen)
return -EBADMSG;
_cleanup_free_ struct in6_addr *addrs = NULL;
size_t n_addrs = 0;
r = dhcp6_option_parse_addresses(optval + offset, ilen, &addrs, &n_addrs);
if (r < 0)
return r;
if (n_addrs == 0)
return -EBADMSG;
offset += ilen;
res.addrs = new(union in_addr_union, n_addrs);
for (size_t i = 0; i < n_addrs; i++) {
union in_addr_union addr = (union in_addr_union) {.in6 = addrs[i]};
/* RFC9463 § 6.2 client MUST discard multicast and host loopback addresses */
if (in_addr_is_multicast(AF_INET6, &addr) ||
in_addr_is_localhost(AF_INET6, &addr))
return -EBADMSG;
res.addrs[i] = addr;
}
res.n_addrs = n_addrs;
res.family = AF_INET6;
/* svc params */
r = dnr_parse_svc_params(optval + offset, optlen-offset, &res);
if (r < 0)
return r;
/* Append this resolver */
if (!GREEDY_REALLOC(lease->dnr, lease->n_dnr+1))
return -ENOMEM;
lease->dnr[lease->n_dnr++] = TAKE_STRUCT(res);
typesafe_qsort(lease->dnr, lease->n_dnr, dns_resolver_prio_compare);
return 1;
}
int sd_dhcp6_lease_get_dnr(sd_dhcp6_lease *lease, sd_dns_resolver **ret) {
assert_return(lease, -EINVAL);
assert_return(ret, -EINVAL);
if (!lease->dnr)
return -ENODATA;
*ret = lease->dnr;
return lease->n_dnr;
}
int dhcp6_lease_add_ntp(sd_dhcp6_lease *lease, const uint8_t *optval, size_t optlen) {
int r;
@ -851,6 +941,15 @@ static int dhcp6_lease_parse_message(
irt = unaligned_be32_sec_to_usec(optval, /* max_as_infinity = */ false);
break;
case SD_DHCP6_OPTION_V6_DNR:
r = dhcp6_lease_add_dnr(lease, optval, optlen);
if (r < 0)
return log_dhcp6_client_errno(client, r, "Failed to parse DNR option, ignoring: %m");
if (r == 0)
log_dhcp6_client(client, "Received ADN-only DNRv6 option, ignoring.");
break;
case SD_DHCP6_OPTION_VENDOR_OPTS:
r = dhcp6_lease_add_vendor_option(lease, optval, optlen);
if (r < 0)
@ -904,6 +1003,7 @@ static sd_dhcp6_lease *dhcp6_lease_free(sd_dhcp6_lease *lease) {
dhcp6_ia_free(lease->ia_na);
dhcp6_ia_free(lease->ia_pd);
free(lease->dns);
dns_resolver_done_many(lease->dnr, lease->n_dnr);
free(lease->fqdn);
free(lease->captive_portal);
strv_free(lease->domains);

View File

@ -0,0 +1,388 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "dns-resolver-internal.h"
#include "macro.h"
#include "unaligned.h"
#include "socket-netlink.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
void sd_dns_resolver_done(sd_dns_resolver *res) {
assert(res);
res->auth_name = mfree(res->auth_name);
res->addrs = mfree(res->addrs);
res->dohpath = mfree(res->dohpath);
}
sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res) {
if (!res)
return NULL;
sd_dns_resolver_done(res);
return mfree(res);
}
void dns_resolver_done_many(sd_dns_resolver resolvers[], size_t n) {
assert(resolvers || n == 0);
FOREACH_ARRAY(res, resolvers, n)
sd_dns_resolver_done(res);
free(resolvers);
}
int dns_resolver_prio_compare(const sd_dns_resolver *a, const sd_dns_resolver *b) {
return CMP(ASSERT_PTR(a)->priority, ASSERT_PTR(b)->priority);
}
int sd_dns_resolver_get_priority(sd_dns_resolver *res, uint16_t *ret_priority) {
assert_return(res, -EINVAL);
assert_return(ret_priority, -EINVAL);
*ret_priority = res->priority;
return 0;
}
int sd_dns_resolver_get_adn(sd_dns_resolver *res, const char **ret_adn) {
assert_return(res, -EINVAL);
assert_return(ret_adn, -EINVAL);
/* Without adn only Do53 can be supported */
if (!res->auth_name)
return -ENODATA;
*ret_adn = res->auth_name;
return 0;
}
int sd_dns_resolver_get_inet_addresses(sd_dns_resolver *res, const struct in_addr **ret_addrs, size_t
*ret_n_addrs) {
assert_return(res, -EINVAL);
assert_return(ret_addrs, -EINVAL);
assert_return(ret_n_addrs, -EINVAL);
assert_return(res->family == AF_INET, -EINVAL);
/* ADN-only mode has no addrs */
if (res->n_addrs == 0)
return -ENODATA;
struct in_addr *addrs = new(struct in_addr, res->n_addrs);
if (!addrs)
return -ENOMEM;
for (size_t i = 0; i < res->n_addrs; i++)
addrs[i] = res->addrs[i].in;
*ret_addrs = addrs;
*ret_n_addrs = res->n_addrs;
return 0;
}
int sd_dns_resolver_get_inet6_addresses(sd_dns_resolver *res, const struct in6_addr **ret_addrs, size_t
*ret_n_addrs) {
assert_return(res, -EINVAL);
assert_return(ret_addrs, -EINVAL);
assert_return(ret_n_addrs, -EINVAL);
assert_return(res->family == AF_INET6, -EINVAL);
/* ADN-only mode has no addrs */
if (res->n_addrs == 0)
return -ENODATA;
struct in6_addr *addrs = new(struct in6_addr, res->n_addrs);
if (!addrs)
return -ENOMEM;
for (size_t i = 0; i < res->n_addrs; i++)
addrs[i] = res->addrs[i].in6;
*ret_addrs = addrs;
*ret_n_addrs = res->n_addrs;
return 0;
}
int sd_dns_resolver_get_transports(sd_dns_resolver *res, sd_dns_alpn_flags *ret_transports) {
assert_return(res, -EINVAL);
assert_return(ret_transports, -EINVAL);
/* ADN-only mode has no transports */
if (!res->transports)
return -ENODATA;
*ret_transports = res->transports;
return 0;
}
int sd_dns_resolver_get_port(sd_dns_resolver *res, uint16_t *ret_port) {
assert_return(res, -EINVAL);
assert_return(ret_port, -EINVAL);
/* port = 0 is the default port */
*ret_port = res->port;
return 0;
}
int sd_dns_resolver_get_dohpath(sd_dns_resolver *res, const char **ret_dohpath) {
assert_return(res, -EINVAL);
assert_return(ret_dohpath, -EINVAL);
/* only present in DoH resolvers */
if (!res->dohpath)
return -ENODATA;
*ret_dohpath = res->dohpath;
return 0;
}
void siphash24_compress_resolver(const sd_dns_resolver *res, struct siphash *state) {
assert(res);
siphash24_compress_typesafe(res->priority, state);
siphash24_compress_typesafe(res->transports, state);
siphash24_compress_typesafe(res->port, state);
siphash24_compress_string(res->auth_name, state);
siphash24_compress_string(res->dohpath, state);
siphash24_compress_typesafe(res->family, state);
FOREACH_ARRAY(addr, res->addrs, res->n_addrs)
siphash24_compress_typesafe(*addr, state);
}
static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
[DNS_SVC_PARAM_KEY_MANDATORY] = "mandatory",
[DNS_SVC_PARAM_KEY_ALPN] = "alpn",
[DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
[DNS_SVC_PARAM_KEY_PORT] = "port",
[DNS_SVC_PARAM_KEY_IPV4HINT] = "ipv4hint",
[DNS_SVC_PARAM_KEY_ECH] = "ech",
[DNS_SVC_PARAM_KEY_IPV6HINT] = "ipv6hint",
[DNS_SVC_PARAM_KEY_DOHPATH] = "dohpath",
[DNS_SVC_PARAM_KEY_OHTTP] = "ohttp",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
const char *p = dns_svc_param_key_to_string(i);
if (p)
return p;
return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
}
int dns_resolver_transports_to_strv(sd_dns_alpn_flags transports, char ***ret) {
_cleanup_strv_free_ char **ans = NULL;
assert(ret);
if (FLAGS_SET(transports, SD_DNS_ALPN_DO53)) {
/* Do53 has no ALPN, this flag is only for our own usage. */
}
if (FLAGS_SET(transports, SD_DNS_ALPN_HTTP_2_TLS))
if (strv_extend(&ans, "h2") < 0)
return -ENOMEM;
if (FLAGS_SET(transports, SD_DNS_ALPN_HTTP_3))
if (strv_extend(&ans, "h3") < 0)
return -ENOMEM;
if (FLAGS_SET(transports, SD_DNS_ALPN_DOT))
if (strv_extend(&ans, "dot") < 0)
return -ENOMEM;
if (FLAGS_SET(transports, SD_DNS_ALPN_DOQ))
if (strv_extend(&ans, "doq") < 0)
return -ENOMEM;
*ret = TAKE_PTR(ans);
return 0;
}
int dnr_parse_svc_params(const uint8_t *option, size_t len, sd_dns_resolver *resolver) {
size_t offset = 0;
int r;
assert(option || len == 0);
assert(resolver);
sd_dns_alpn_flags transports = 0;
uint16_t port = 0;
_cleanup_free_ char *dohpath = NULL;
bool alpn = false;
uint16_t lastkey = 0;
while (offset < len) {
if (offset + 4 > len)
return -EBADMSG;
uint16_t key = unaligned_read_be16(&option[offset]);
offset += 2;
/* RFC9460 § 2.2 SvcParam MUST appear in strictly increasing numeric order */
if (lastkey >= key)
return -EBADMSG;
lastkey = key;
uint16_t plen = unaligned_read_be16(&option[offset]);
offset += 2;
if (offset + plen > len)
return -EBADMSG;
switch (key) {
/* Mandatory keys must be understood by the client, otherwise the record should be discarded.
* Automatic mandatory keys must not appear in the mandatory parameter, so these are all
* supplementary. We don't understand any supplementary keys, so if the mandatory parameter
* is present, we cannot use this record.*/
case DNS_SVC_PARAM_KEY_MANDATORY:
if (plen > 0)
return -EBADMSG;
break;
case DNS_SVC_PARAM_KEY_ALPN:
if (plen == 0)
return 0;
alpn = true; /* alpn is required. Record that the requirement is met. */
size_t poff = offset;
size_t pend = offset + plen;
while (poff < pend) {
uint8_t alen = option[poff++];
if (poff + alen > len)
return -EBADMSG;
if (memcmp_nn((const char*) &option[poff], alen, "dot", STRLEN("dot")) == 0)
SET_FLAG(transports, SD_DNS_ALPN_DOT, true);
if (memcmp_nn((const char*) &option[poff], alen, "h2", STRLEN("h2")) == 0)
SET_FLAG(transports, SD_DNS_ALPN_HTTP_2_TLS, true);
if (memcmp_nn((const char*) &option[poff], alen, "h3", STRLEN("h3")) == 0)
SET_FLAG(transports, SD_DNS_ALPN_HTTP_3, true);
if (memcmp_nn((const char*) &option[poff], alen, "doq", STRLEN("doq")) == 0)
SET_FLAG(transports, SD_DNS_ALPN_DOQ, true);
poff += alen;
}
if (poff != pend)
return -EBADMSG;
break;
case DNS_SVC_PARAM_KEY_PORT:
if (plen != 2)
return -EBADMSG;
port = unaligned_read_be16(&option[offset]);
/* Server should indicate default port by omitting this param */
if (port == 0)
return -EBADMSG;
break;
/* RFC9463 § 5.1 service params MUST NOT include ipv4hint/ipv6hint */
case DNS_SVC_PARAM_KEY_IPV4HINT:
case DNS_SVC_PARAM_KEY_IPV6HINT:
return -EBADMSG;
case DNS_SVC_PARAM_KEY_DOHPATH:
r = make_cstring((const char*) &option[offset], plen,
MAKE_CSTRING_REFUSE_TRAILING_NUL, &dohpath);
if (ERRNO_IS_NEG_RESOURCE(r))
return r;
if (r < 0)
return -EBADMSG;
/* dohpath is a RFC6750 URI template. We don't parse these, but at least check the
* charset is reasonable. */
if (!in_charset(dohpath, URI_VALID "{}"))
return -EBADMSG;
break;
default:
break;
}
offset += plen;
}
if (offset != len)
return -EBADMSG;
/* DNR cannot be used without alpn */
if (!alpn)
return -EBADMSG;
/* RFC9461 § 5: If the [SvcParam] indicates support for HTTP, "dohpath" MUST be present. */
if (!dohpath && (FLAGS_SET(transports, SD_DNS_ALPN_HTTP_2_TLS) ||
FLAGS_SET(transports, SD_DNS_ALPN_HTTP_3)))
return -EBADMSG;
/* No useful transports */
if (!transports)
return 0;
resolver->transports = transports;
resolver->port = port;
free_and_replace(resolver->dohpath, dohpath);
return transports;
}
int dns_resolvers_to_dot_addrs(const sd_dns_resolver *resolvers, size_t n_resolvers,
struct in_addr_full ***ret_addrs, size_t *ret_n_addrs) {
assert(ret_addrs);
assert(ret_n_addrs);
assert(resolvers || n_resolvers == 0);
struct in_addr_full **addrs = NULL;
size_t n = 0;
CLEANUP_ARRAY(addrs, n, in_addr_full_array_free);
FOREACH_ARRAY(res, resolvers, n_resolvers) {
if (!FLAGS_SET(res->transports, SD_DNS_ALPN_DOT))
continue;
FOREACH_ARRAY(i, res->addrs, res->n_addrs) {
_cleanup_(in_addr_full_freep) struct in_addr_full *addr = NULL;
int r;
addr = new0(struct in_addr_full, 1);
if (!addr)
return -ENOMEM;
if (!GREEDY_REALLOC(addrs, n+1))
return -ENOMEM;
r = free_and_strdup(&addr->server_name, res->auth_name);
if (r < 0)
return r;
addr->family = res->family;
addr->port = res->port;
addr->address = *i;
addrs[n++] = TAKE_PTR(addr);
}
}
*ret_addrs = TAKE_PTR(addrs);
*ret_n_addrs = n;
return n;
}
int dns_resolvers_to_dot_strv(const sd_dns_resolver *resolvers, size_t n_resolvers, char ***ret_names) {
assert(ret_names);
int r;
_cleanup_strv_free_ char **names = NULL;
size_t len = 0;
struct in_addr_full **addrs = NULL;
size_t n = 0;
CLEANUP_ARRAY(addrs, n, in_addr_full_array_free);
r = dns_resolvers_to_dot_addrs(resolvers, n_resolvers, &addrs, &n);
if (r < 0)
return r;
FOREACH_ARRAY(addr, addrs, n) {
const char *name = in_addr_full_to_string(*addr);
if (!name)
return -ENOMEM;
r = strv_extend_with_size(&names, &len, name);
if (r < 0)
return r;
}
*ret_names = TAKE_PTR(names);
return len;
}

View File

@ -0,0 +1,43 @@
#ifndef SD_DNS_RESOLVER_H
#define SD_DNS_RESOLVER_H
#include <errno.h>
#include <netinet/in.h>
#include <stddef.h>
#include <stdint.h>
#include "_sd-common.h"
_SD_BEGIN_DECLARATIONS;
typedef struct sd_dns_resolver sd_dns_resolver;
/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids */
typedef enum sd_dns_alpn_flags {
/* There isn't really an alpn reserved for Do53 service, but designated resolvers may or may not offer
* Do53 service, so we should probably have a flag to represent this capability. Unfortunately DNR
* does not indicate the status to us.*/
SD_DNS_ALPN_DO53 = 1 << 0,
/* SD_DNS_ALPN_HTTP_1_1, "http/1.1" [RFC9112] */
SD_DNS_ALPN_HTTP_2_TLS = 1 << 1, /* "h2" [RFC9113] [RFC9461] */
/* SD_DNS_ALPN_HTTP_2_TCP, "h2c" [RFC9113] */
SD_DNS_ALPN_HTTP_3 = 1 << 2, /* "h3" [RFC9114] [RFC9461] */
SD_DNS_ALPN_DOT = 1 << 3, /* "dot" [RFC7858] [RFC9461] */
SD_DNS_ALPN_DOQ = 1 << 4 /* "doq" [RFC9250] [RFC9461] */
} sd_dns_alpn_flags;
int sd_dns_resolver_get_priority(sd_dns_resolver *res, uint16_t *ret_priority);
int sd_dns_resolver_get_adn(sd_dns_resolver *res, const char **ret_adn);
int sd_dns_resolver_get_inet_addresses(sd_dns_resolver *res, const struct in_addr **ret_addrs, size_t *n);
int sd_dns_resolver_get_inet6_addresses(sd_dns_resolver *res, const struct in6_addr **ret_addrs, size_t *n);
int sd_dns_resolver_get_transports(sd_dns_resolver *res, sd_dns_alpn_flags *ret_transports);
int sd_dns_resolver_get_port(sd_dns_resolver *res, uint16_t *ret_port);
int sd_dns_resolver_get_dohpath(sd_dns_resolver *res, const char **ret_dohpath);
void sd_dns_resolver_done(sd_dns_resolver *res);
sd_dns_resolver *sd_dns_resolver_unref(sd_dns_resolver *res);
_SD_DEFINE_POINTER_CLEANUP_FUNC (sd_dns_resolver, sd_dns_resolver_unref);
_SD_END_DECLARATIONS;
#endif /* SD_DNS_RESOLVER_H */

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

@ -165,10 +165,7 @@ TEST(parse_domain) {
domain = mfree(domain);
data = (uint8_t []) { 4, 't', 'e', 's', 't' };
assert_se(dhcp6_option_parse_domainname(data, 5, &domain) >= 0);
assert_se(domain);
assert_se(streq(domain, "test"));
domain = mfree(domain);
assert_se(dhcp6_option_parse_domainname(data, 5, &domain) < 0);
data = (uint8_t []) { 0 };
assert_se(dhcp6_option_parse_domainname(data, 1, &domain) < 0);
@ -745,8 +742,8 @@ static const uint8_t msg_reply[] = {
0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b,
0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
/* Client FQDN */
0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12,
0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a',
0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x13,
0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
/* Vendor specific options */
0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09,
0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES,
@ -827,8 +824,8 @@ static const uint8_t msg_advertise[] = {
0x00, SD_DHCP6_OPTION_DOMAIN, 0x00, 0x0b,
0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
/* Client FQDN */
0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x12,
0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a',
0x00, SD_DHCP6_OPTION_CLIENT_FQDN, 0x00, 0x13,
0x01, 0x06, 'c', 'l', 'i', 'e', 'n', 't', 0x03, 'l', 'a', 'b', 0x05, 'i', 'n', 't', 'r', 'a', 0x00,
/* Vendor specific options */
0x00, SD_DHCP6_OPTION_VENDOR_OPTS, 0x00, 0x09,
0x00, 0x00, 0x00, 0x20, 0x00, 0xf7, 0x00, 0x01, VENDOR_SUBOPTION_BYTES,

View File

@ -3,6 +3,8 @@
#include <netinet/in.h>
#include "dns-resolver-internal.h"
#include "conf-parser.h"
#include "dhcp-duid-internal.h"
#include "in-addr-util.h"

View File

@ -1556,6 +1556,13 @@ static int dhcp4_configure(Link *link) {
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for SIP server: %m");
}
if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_V4_DNR);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for DNR: %m");
}
if (link->network->dhcp_use_captive_portal) {
r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_DHCP_CAPTIVE_PORTAL);
if (r < 0)

View File

@ -646,6 +646,12 @@ static int dhcp6_configure(Link *link) {
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request domains: %m");
}
if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_V6_DNR);
if (r < 0)
return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request DNR: %m");
}
if (link->network->dhcp6_use_captive_portal > 0) {
r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_CAPTIVE_PORTAL);
if (r < 0)

View File

@ -94,6 +94,37 @@ bool link_get_use_dns(Link *link, NetworkConfigSource proto) {
return true;
}
bool link_get_use_dnr(Link *link, NetworkConfigSource proto) {
int n;
assert(link);
if (!link->network)
return false;
switch (proto) {
case NETWORK_CONFIG_SOURCE_DHCP4:
n = link->network->dhcp_use_dnr;
break;
case NETWORK_CONFIG_SOURCE_DHCP6:
n = link->network->dhcp6_use_dnr;
break;
case NETWORK_CONFIG_SOURCE_NDISC:
n = link->network->ndisc_use_dnr;
break;
default:
assert_not_reached();
}
/* If set explicitly, use that */
if (n >= 0)
return n;
/* Otherwise, default to the same as the UseDNS setting. After all,
* this is just another way for the server to tell us about DNS configuration. */
return link_get_use_dns(link, proto);
}
int config_parse_domains(
const char *unit,
const char *filename,

View File

@ -17,6 +17,7 @@ typedef enum UseDomains {
UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto);
bool link_get_use_dns(Link *link, NetworkConfigSource proto);
bool link_get_use_dnr(Link *link, NetworkConfigSource proto);
const char* use_domains_to_string(UseDomains p) _const_;
UseDomains use_domains_from_string(const char *s) _pure_;

View File

@ -503,6 +503,118 @@ static int dns_append_json(Link *link, sd_json_variant **v) {
return json_variant_set_field_non_null(v, "DNS", array);
}
static int dnr_append_json_one(Link *link, const struct sd_dns_resolver *res, NetworkConfigSource s, const union in_addr_union *p, sd_json_variant **array) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *addrs_array = NULL;
_cleanup_strv_free_ char **transports = NULL;
int r;
assert(link);
assert(res);
assert(array);
FOREACH_ARRAY(addr, res->addrs, res->n_addrs) {
r = sd_json_variant_append_arrayb(
&addrs_array,
JSON_BUILD_IN_ADDR(addr, res->family));
if (r < 0)
return r;
}
r = dns_resolver_transports_to_strv(res->transports, &transports);
if (r < 0)
return r;
//FIXME ifindex?
return sd_json_variant_append_arrayb(
array,
SD_JSON_BUILD_OBJECT(
SD_JSON_BUILD_PAIR_INTEGER("Family", res->family),
SD_JSON_BUILD_PAIR_INTEGER("Priority", res->priority),
JSON_BUILD_PAIR_VARIANT_NON_NULL("Addresses", addrs_array),
JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("Port", res->port),
JSON_BUILD_PAIR_STRING_NON_EMPTY("ServerName", res->auth_name),
JSON_BUILD_PAIR_STRING_NON_EMPTY("DoHPath", res->dohpath),
JSON_BUILD_PAIR_STRV_NON_EMPTY("Transports", transports),
SD_JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)),
JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, res->family)));
}
static int dnr_append_json(Link *link, sd_json_variant **v) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
int r;
assert(link);
assert(v);
if (!link->network)
return 0;
if (link->dhcp_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
struct sd_dns_resolver *dnr;
union in_addr_union s;
int n_dnr;
r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in);
if (r < 0)
return r;
n_dnr = sd_dhcp_lease_get_dnr(link->dhcp_lease, &dnr);
if (n_dnr <= 0)
return 0;
FOREACH_ARRAY(res, dnr, n_dnr) {
r = dnr_append_json_one(link,
res,
NETWORK_CONFIG_SOURCE_DHCP4,
&s,
&array);
if (r < 0)
return r;
}
}
if (link->dhcp6_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
struct sd_dns_resolver *dnr;
union in_addr_union s;
int n_dnr;
r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &s.in6);
if (r < 0)
return r;
n_dnr = sd_dhcp6_lease_get_dnr(link->dhcp6_lease, &dnr);
if (n_dnr <= 0)
return 0;
FOREACH_ARRAY(res, dnr, n_dnr) {
r = dnr_append_json_one(link,
res,
NETWORK_CONFIG_SOURCE_DHCP6,
&s,
&array);
if (r < 0)
return r;
}
}
if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC)) {
NDiscDNR *a;
SET_FOREACH(a, link->ndisc_dnr) {
r = dnr_append_json_one(link,
&a->resolver,
NETWORK_CONFIG_SOURCE_NDISC,
&(union in_addr_union) { .in6 = a->router },
&array);
if (r < 0)
return r;
}
}
return json_variant_set_field_non_null(v, "DNR", array);
}
static int server_append_json_one_addr(int family, const union in_addr_union *a, NetworkConfigSource s, const union in_addr_union *p, sd_json_variant **array) {
assert(IN_SET(family, AF_INET, AF_INET6));
assert(a);
@ -1268,6 +1380,10 @@ int link_build_json(Link *link, sd_json_variant **ret) {
if (r < 0)
return r;
r = dnr_append_json(link, &v);
if (r < 0)
return r;
r = ntp_append_json(link, &v);
if (r < 0)
return r;

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"
@ -1824,6 +1825,147 @@ 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;
if (!link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC))
return 0;
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;
@ -1875,6 +2017,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;
@ -1887,6 +2032,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;
@ -1985,6 +2131,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);
@ -2012,6 +2166,7 @@ static int ndisc_setup_expire(Link *link) {
NDiscDNSSL *dnssl;
NDiscRDNSS *rdnss;
NDiscPREF64 *p64;
NDiscDNR *dnr;
Address *address;
Route *route;
int r;
@ -2064,6 +2219,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;
@ -2326,6 +2484,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;
}
@ -2509,7 +2675,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);
@ -2519,6 +2685,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

@ -227,6 +227,7 @@ NextHop.Group, config_parse_nexthop_group,
DHCPv4.RequestAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_request_address)
DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier)
DHCPv4.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp_use_dns)
DHCPv4.UseDNR, config_parse_tristate, 0, offsetof(Network, dhcp_use_dnr)
DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns)
DHCPv4.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp_use_ntp)
DHCPv4.RoutesToNTP, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_ntp)
@ -276,6 +277,7 @@ DHCPv4.RapidCommit, config_parse_tristate,
DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address)
DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix)
DHCPv6.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp6_use_dns)
DHCPv6.UseDNR, config_parse_tristate, 0, offsetof(Network, dhcp6_use_dnr)
DHCPv6.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp6_use_hostname)
DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp6_use_domains)
DHCPv6.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp6_use_ntp)
@ -304,6 +306,7 @@ IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool,
IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_onlink_prefix)
IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ndisc_use_pref64)
IPv6AcceptRA.UseDNS, config_parse_tristate, 0, offsetof(Network, ndisc_use_dns)
IPv6AcceptRA.UseDNR, config_parse_tristate, 0, offsetof(Network, ndisc_use_dnr)
IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Network, ndisc_use_domains)
IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ndisc_use_mtu)
IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ndisc_use_hop_limit)

View File

@ -388,6 +388,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp_use_sip = true,
.dhcp_use_captive_portal = true,
.dhcp_use_dns = -1,
.dhcp_use_dnr = -1,
.dhcp_routes_to_dns = true,
.dhcp_use_domains = _USE_DOMAINS_INVALID,
.dhcp_use_hostname = true,
@ -406,6 +407,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.dhcp6_use_address = true,
.dhcp6_use_pd_prefix = true,
.dhcp6_use_dns = -1,
.dhcp6_use_dnr = -1,
.dhcp6_use_domains = _USE_DOMAINS_INVALID,
.dhcp6_use_hostname = true,
.dhcp6_use_ntp = -1,
@ -480,6 +482,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.ndisc = -1,
.ndisc_use_redirect = true,
.ndisc_use_dns = -1,
.ndisc_use_dnr = -1,
.ndisc_use_gateway = true,
.ndisc_use_captive_portal = true,
.ndisc_use_route_prefix = true,

View File

@ -153,6 +153,7 @@ struct Network {
int dhcp_ipv6_only_mode;
int dhcp_use_rapid_commit;
int dhcp_use_dns;
int dhcp_use_dnr;
bool dhcp_routes_to_dns;
int dhcp_use_ntp;
bool dhcp_routes_to_ntp;
@ -184,6 +185,7 @@ struct Network {
bool dhcp6_send_hostname;
bool dhcp6_send_hostname_set;
int dhcp6_use_dns;
int dhcp6_use_dnr;
bool dhcp6_use_hostname;
int dhcp6_use_ntp;
bool dhcp6_use_captive_portal;
@ -341,6 +343,7 @@ struct Network {
/* NDisc support */
int ndisc;
int ndisc_use_dnr;
bool ndisc_use_redirect;
int ndisc_use_dns;
bool ndisc_use_gateway;

View File

@ -3,6 +3,8 @@
#include <netinet/in.h>
#include <linux/if.h>
#include "dns-resolver-internal.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "escape.h"
@ -113,6 +115,24 @@ static int link_put_dns(Link *link, OrderedSet **s) {
}
}
if (link->dhcp_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4)) {
sd_dns_resolver *resolvers;
r = sd_dhcp_lease_get_dnr(link->dhcp_lease, &resolvers);
if (r >= 0) {
struct in_addr_full **dot_servers;
size_t n = 0;
CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free);
r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n);
if (r < 0)
return r;
r = ordered_set_put_dns_servers(s, link->ifindex, dot_servers, n);
if (r < 0)
return r;
}
}
if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
const struct in6_addr *addresses;
@ -124,6 +144,26 @@ static int link_put_dns(Link *link, OrderedSet **s) {
}
}
if (link->dhcp6_lease && link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6)) {
sd_dns_resolver *resolvers;
r = sd_dhcp6_lease_get_dnr(link->dhcp6_lease, &resolvers);
if (r >= 0 ) {
struct in_addr_full **dot_servers;
size_t n = 0;
CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free);
r = dns_resolvers_to_dot_addrs(resolvers, r, &dot_servers, &n);
if (r < 0)
return r;
r = ordered_set_put_dns_servers(s, link->ifindex, dot_servers, n);
if (r < 0)
return r;
}
}
if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) {
NDiscRDNSS *a;
@ -134,6 +174,24 @@ static int link_put_dns(Link *link, OrderedSet **s) {
}
}
if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC)) {
NDiscDNR *a;
SET_FOREACH(a, link->ndisc_dnr) {
struct in_addr_full **dot_servers = NULL;
size_t n = 0;
CLEANUP_ARRAY(dot_servers, n, in_addr_full_array_free);
r = dns_resolvers_to_dot_addrs(&a->resolver, 1, &dot_servers, &n);
if (r < 0)
return r;
r = ordered_set_put_dns_servers(s, link->ifindex, dot_servers, n);
if (r < 0)
return r;
}
}
return 0;
}
@ -529,6 +587,56 @@ static void serialize_addresses(
fputc('\n', f);
}
static void serialize_resolvers(
FILE *f,
const char *lvalue,
bool *space,
sd_dhcp_lease *lease,
bool conditional,
sd_dhcp6_lease *lease6,
bool conditional6) {
bool _space = false;
if (!space)
space = &_space;
if (lvalue)
fprintf(f, "%s=", lvalue);
if (lease && conditional) {
sd_dns_resolver *resolvers;
_cleanup_strv_free_ char **names = NULL;
int r;
r = sd_dhcp_lease_get_dnr(lease, &resolvers);
if (r < 0)
return;
r = dns_resolvers_to_dot_strv(resolvers, r, &names);
if (r > 0)
fputstrv(f, names, NULL, space);
}
if (lease6 && conditional6) {
sd_dns_resolver *resolvers;
_cleanup_strv_free_ char **names = NULL;
int r;
r = sd_dhcp6_lease_get_dnr(lease6, &resolvers);
if (r < 0)
return;
r = dns_resolvers_to_dot_strv(resolvers, r, &names);
if (r > 0)
fputstrv(f, names, NULL, space);
}
if (lvalue)
fputc('\n', f);
return;
}
static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, UseDomains use_domains) {
bool space = false;
const char *p;
@ -664,6 +772,21 @@ static int link_save(Link *link) {
space = false;
link_save_dns(link, f, link->network->dns, link->network->n_dns, &space);
/* DNR resolvers are not required to provide Do53 service, however resolved doesn't
* know how to handle such a server so for now Do53 service is required, and
* assumed. */
serialize_resolvers(f, NULL, &space,
link->dhcp_lease,
link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP4),
link->dhcp6_lease,
link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_DHCP6));
if (link_get_use_dnr(link, NETWORK_CONFIG_SOURCE_NDISC)) {
NDiscDNR *dnr;
SET_FOREACH(dnr, link->ndisc_dnr)
serialize_dnr(f, &dnr->resolver, 1, &space);
}
serialize_addresses(f, NULL, &space,
NULL,
link->dhcp_lease,

View File

@ -2574,8 +2574,10 @@ static int context_load_partition_table(Context *context) {
/* If we found the sector size and we're operating on a block device, use it as the file
* system sector size as well, as we know its the sector size of the actual block device and
* not just the offset at which we found the GPT header. */
if (r > 0 && S_ISBLK(st.st_mode))
if (r > 0 && S_ISBLK(st.st_mode)) {
log_debug("Probed sector size of %s is %" PRIu32 " bytes.", context->node, ssz);
fs_secsz = ssz;
}
}
r = fdisk_save_user_sector_size(c, /* phy= */ 0, ssz);
@ -2639,7 +2641,7 @@ static int context_load_partition_table(Context *context) {
* larger */
grainsz = secsz < 4096 ? 4096 : secsz;
log_debug("Sector size of device is %lu bytes. Using grain size of %" PRIu64 ".", secsz, grainsz);
log_debug("Sector size of device is %lu bytes. Using filesystem sector size of %" PRIu64 " and grain size of %" PRIu64 ".", secsz, fs_secsz, grainsz);
switch (arg_empty) {

View File

@ -2881,6 +2881,27 @@ size_t dns_packet_size_unfragmented(DnsPacket *p) {
return LESS_BY(p->fragsize, udp_header_size(p->family));
}
static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
[DNS_SVC_PARAM_KEY_MANDATORY] = "mandatory",
[DNS_SVC_PARAM_KEY_ALPN] = "alpn",
[DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
[DNS_SVC_PARAM_KEY_PORT] = "port",
[DNS_SVC_PARAM_KEY_IPV4HINT] = "ipv4hint",
[DNS_SVC_PARAM_KEY_ECH] = "ech",
[DNS_SVC_PARAM_KEY_IPV6HINT] = "ipv6hint",
[DNS_SVC_PARAM_KEY_DOHPATH] = "dohpath",
[DNS_SVC_PARAM_KEY_OHTTP] = "ohttp",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
const char *p = dns_svc_param_key_to_string(i);
if (p)
return p;
return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
}
static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_SUCCESS] = "SUCCESS",
[DNS_RCODE_FORMERR] = "FORMERR",
@ -2955,27 +2976,6 @@ const char* format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) {
return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i);
}
static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = {
[DNS_SVC_PARAM_KEY_MANDATORY] = "mandatory",
[DNS_SVC_PARAM_KEY_ALPN] = "alpn",
[DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn",
[DNS_SVC_PARAM_KEY_PORT] = "port",
[DNS_SVC_PARAM_KEY_IPV4HINT] = "ipv4hint",
[DNS_SVC_PARAM_KEY_ECH] = "ech",
[DNS_SVC_PARAM_KEY_IPV6HINT] = "ipv6hint",
[DNS_SVC_PARAM_KEY_DOHPATH] = "dohpath",
[DNS_SVC_PARAM_KEY_OHTTP] = "ohttp",
};
DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int);
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) {
const char *p = dns_svc_param_key_to_string(i);
if (p)
return p;
return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i);
}
static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = {
[DNS_PROTOCOL_DNS] = "dns",
[DNS_PROTOCOL_MDNS] = "mdns",

View File

@ -361,25 +361,6 @@ const char* format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]);
const char* dns_protocol_to_string(DnsProtocol p) _const_;
DnsProtocol dns_protocol_from_string(const char *s) _pure_;
/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
enum {
DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 section 8 */
DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 section 7.1 */
DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 Section 7.1 */
DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 section 7.2 */
DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 section 7.3 */
DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */
DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 section 7.3 */
DNS_SVC_PARAM_KEY_DOHPATH = 7, /* RFC 9461 */
DNS_SVC_PARAM_KEY_OHTTP = 8,
_DNS_SVC_PARAM_KEY_MAX_DEFINED,
DNS_SVC_PARAM_KEY_INVALID = 65535 /* RFC 9460 */
};
const char* dns_svc_param_key_to_string(int i) _const_;
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) })
#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } })
@ -388,6 +369,25 @@ const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX
extern const struct hash_ops dns_packet_hash_ops;
/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */
enum {
DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 § 8 */
DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 § 7.1 */
DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 § 7.1 */
DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 § 7.2 */
DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 § 7.3 */
DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */
DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 § 7.3 */
DNS_SVC_PARAM_KEY_DOHPATH = 7, /* RFC 9461 */
DNS_SVC_PARAM_KEY_OHTTP = 8,
_DNS_SVC_PARAM_KEY_MAX_DEFINED,
DNS_SVC_PARAM_KEY_INVALID = 65535 /* RFC 9460 */
};
const char* dns_svc_param_key_to_string(int i) _const_;
const char* format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]);
#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {})
static inline uint64_t SD_RESOLVED_FLAGS_MAKE(
DnsProtocol protocol,
int family,

View File

@ -898,6 +898,75 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo
return out - buffer;
}
/* Decode a domain name according to RFC 1035 Section 3.1, without compression */
int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret) {
_cleanup_free_ char *domain = NULL;
const uint8_t *optval;
size_t optlen, n = 0;
int r;
assert(data);
assert(len);
assert(*data || *len == 0);
assert(ret);
optval = *data;
optlen = *len;
for (;;) {
const char *label;
uint8_t c;
/* Unterminated name */
if (optlen == 0)
return -EBADMSG;
/* RFC 1035 § 3.1 total length of encoded name is limited to 255 octets */
if (*len - optlen > 255)
return -EMSGSIZE;
c = *optval;
optval++;
optlen--;
if (c == 0)
/* End label */
break;
if (c > DNS_LABEL_MAX)
return -EBADMSG;
if (c > optlen)
return -EMSGSIZE;
/* Literal label */
label = (const char*) optval;
optval += c;
optlen -= c;
if (!GREEDY_REALLOC(domain, n + (n != 0) + DNS_LABEL_ESCAPED_MAX))
return -ENOMEM;
if (n != 0)
domain[n++] = '.';
r = dns_label_escape(label, c, domain + n, DNS_LABEL_ESCAPED_MAX);
if (r < 0)
return r;
n += r;
}
if (!GREEDY_REALLOC(domain, n + 1))
return -ENOMEM;
domain[n] = '\0';
*ret = TAKE_PTR(domain);
*data = optval;
*len = optlen;
return n;
}
static bool srv_type_label_is_valid(const char *label, size_t n) {
assert(label);

View File

@ -79,6 +79,7 @@ bool dns_name_is_root(const char *name);
bool dns_name_is_single_label(const char *name);
int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical);
int dns_name_from_wire_format(const uint8_t **data, size_t *len, char **ret);
bool dns_srv_type_is_valid(const char *name);
bool dnssd_srv_type_is_valid(const char *name);

View File

@ -347,6 +347,15 @@ struct in_addr_full *in_addr_full_free(struct in_addr_full *a) {
return mfree(a);
}
void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n) {
assert(addrs || n == 0);
FOREACH_ARRAY(a, addrs, n)
in_addr_full_freep(a);
free(addrs);
}
int in_addr_full_new(
int family,
const union in_addr_union *a,

View File

@ -39,6 +39,7 @@ struct in_addr_full {
struct in_addr_full *in_addr_full_free(struct in_addr_full *a);
DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free);
void in_addr_full_array_free(struct in_addr_full *addrs[], size_t n);
int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret);
int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret);
const char* in_addr_full_to_string(struct in_addr_full *a);

View File

@ -32,6 +32,7 @@ _SD_BEGIN_DECLARATIONS;
typedef struct sd_dhcp_lease sd_dhcp_lease;
typedef struct sd_dhcp_route sd_dhcp_route;
typedef struct sd_dns_resolver sd_dns_resolver;
sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease);
sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease);
@ -75,6 +76,7 @@ int sd_dhcp_lease_get_search_domains(sd_dhcp_lease *lease, char ***domains);
int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname);
int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path);
int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **captive_portal);
int sd_dhcp_lease_get_dnr(sd_dhcp_lease *lease, sd_dns_resolver **ret_resolvers);
int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret);
int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret);
int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len);

View File

@ -171,7 +171,8 @@ enum {
SD_DHCP_OPTION_PORT_PARAMS = 159, /* [RFC7618] */
/* option code 160 is unassigned [RFC7710][RFC8910] */
SD_DHCP_OPTION_MUD_URL = 161, /* [RFC8520] */
/* option codes 162-174 are unassigned [RFC3942] */
SD_DHCP_OPTION_V4_DNR = 162, /* [RFC9463] */
/* option codes 163-174 are unassigned [RFC3942] */
/* option codes 175-177 are temporary assigned. */
/* option codes 178-207 are unassigned [RFC3942] */
SD_DHCP_OPTION_PXELINUX_MAGIC = 208, /* [RFC5071] Deprecated */

View File

@ -30,6 +30,7 @@
_SD_BEGIN_DECLARATIONS;
typedef struct sd_dhcp6_lease sd_dhcp6_lease;
typedef struct sd_dns_resolver sd_dns_resolver;
int sd_dhcp6_lease_get_timestamp(sd_dhcp6_lease *lease, clockid_t clock, uint64_t *ret);
int sd_dhcp6_lease_get_t1(sd_dhcp6_lease *lease, uint64_t *ret);
@ -74,6 +75,7 @@ int sd_dhcp6_lease_get_pd_lifetime_timestamp(
int sd_dhcp6_lease_has_pd_prefix(sd_dhcp6_lease *lease);
int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, const struct in6_addr **ret);
int sd_dhcp6_lease_get_dnr(sd_dhcp6_lease *lease, sd_dns_resolver **ret);
int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***ret);
int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, const struct in6_addr **ret);
int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ret);

View File

@ -165,8 +165,9 @@ enum {
SD_DHCP6_OPTION_SLAP_QUAD = 140, /* RFC 8948 */
SD_DHCP6_OPTION_V6_DOTS_RI = 141, /* RFC 8973 */
SD_DHCP6_OPTION_V6_DOTS_ADDRESS = 142, /* RFC 8973 */
SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF = 143 /* RFC 6153 */
/* option codes 144-65535 are unassigned */
SD_DHCP6_OPTION_IPV6_ADDRESS_ANDSF = 143, /* RFC 6153 */
SD_DHCP6_OPTION_V6_DNR = 144 /* RFC 9463 */
/* option codes 145-65535 are unassigned */
};
_SD_END_DECLARATIONS;

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

View File

@ -119,6 +119,95 @@ TEST(dns_name_to_wire_format) {
test_dns_name_to_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", out4, sizeof(out4), sizeof(out4));
}
static void test_dns_name_from_wire_format_one(const char *expect, const uint8_t *what, size_t len, int ret) {
_cleanup_free_ char *name = NULL;
int r;
log_info("%s, %s, %zu, →%d", what, strnull(expect), len, ret);
r = dns_name_from_wire_format(&what, &len, &name);
assert_se(r == ret);
if (r >= 0) {
assert(expect); /* for gcc */
assert_se(memcmp(name, expect, r) == 0);
}
}
TEST(dns_name_from_wire_format) {
static const uint8_t in0[] = { 0 };
static const uint8_t in1[] = { 3, 'f', 'o', 'o', 0 };
static const uint8_t in2[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
static const uint8_t in2_1[] = { 5, 'h', 'a', 'l', 'l', 'o', 3, 'f', 'o', 'o', 0, 'b', 'a', 'r', 0 };
static const uint8_t in3[] = { 4, ' ', 'f', 'o', 'o', 3, 'b', 'a', 'r', 0 };
static const uint8_t in4[] = { 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
3, 'a', '1', '2', 0 }; /* 255 octets */
static const uint8_t in5[] = { 9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
9, 'a', '1', '2', '3', '4', '5', '6', '7', '8',
3, 'a', '1', '2', 0 }; /* 265 octets */
test_dns_name_from_wire_format_one("", in0, sizeof(in0), strlen(""));
test_dns_name_from_wire_format_one("foo", in1, sizeof(in1), strlen("foo"));
test_dns_name_from_wire_format_one(NULL, in1, sizeof(in1) - 1, -EBADMSG);
test_dns_name_from_wire_format_one("hallo.foo.bar", in2, sizeof(in2), strlen("hallo.foo.bar"));
test_dns_name_from_wire_format_one("hallo.foo", in2_1, sizeof(in2_1), strlen("hallo.foo"));
test_dns_name_from_wire_format_one("\\032foo.bar", in3, sizeof(in3), strlen("\\032foo.bar"));
test_dns_name_from_wire_format_one(NULL, in5, sizeof(in5), -EMSGSIZE);
test_dns_name_from_wire_format_one("a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12345678.a12", in4, sizeof(in4), 253);
}
static void test_dns_label_unescape_suffix_one(const char *what, const char *expect1, const char *expect2, size_t buffer_sz, int ret1, int ret2) {
char buffer[buffer_sz];
const char *label;

View File

@ -3,7 +3,7 @@
integration_tests += [
integration_test_template + {
'name' : fs.name(meson.current_source_dir()),
'cmdline' : integration_test_template['cmdline'] + ['selinux=1', 'lsm=selinux'],
'cmdline' : integration_test_template['cmdline'] + ['systemd.wants=autorelabel.service', 'selinux=1', 'lsm=selinux'],
# FIXME; Figure out why reboot sometimes hangs with 'linux' firmware.
# Use 'auto' to automatically fallback on non-uefi architectures.
'firmware' : 'auto',

View File

@ -4,5 +4,8 @@ integration_tests += [
integration_test_template + {
'name' : fs.name(meson.current_source_dir()),
'storage' : 'persistent',
# FIXME: Figure out why reboot sometimes hangs with 'linux' firmware.
# Use 'auto' to automatically fallback on non-uefi architectures.
'firmware' : 'auto',
},
]

View File

@ -3,5 +3,8 @@
integration_tests += [
integration_test_template + {
'name' : fs.name(meson.current_source_dir()),
# FIXME: Figure out why reboot sometimes hangs with 'linux' firmware.
# Use 'auto' to automatically fallback on non-uefi architectures.
'firmware' : 'auto',
},
]

View File

@ -27,5 +27,6 @@ integration_tests += [
'-smbios', 'type=11,value=io.systemd.credential.binary:fstab.extra=aW5qZWN0ZWQgL2luamVjdGVkIHRtcGZzIFgtbW91bnQubWtkaXIgMCAwCg==',
'-smbios', 'type=11,value=io.systemd.credential:getty.ttys.container=idontexist',
],
'firmware' : 'auto',
},
]

View File

@ -23,10 +23,9 @@ config["QemuArgs"] += ["-device", "pci-bridge,id=pci_bridge0,chassis_nr=64"]
for bridge in range(1, 26):
config["QemuArgs"] += [
"-device",
f"pci-bridge,id=pci_bridge{bridge},bus=pci_bridge{bridge - 1},"
f"chassis_nr={64 + bridge}"
f"pci-bridge,id=pci_bridge{bridge},bus=pci_bridge{bridge - 1},chassis_nr={64 + bridge},addr=1",
]
config["QemuArgs"] += ["-device", f"virtio-blk-pci,drive=drive0,scsi=off,bus=pci_bridge25"]
config["QemuArgs"] += ["-device", f"virtio-blk-pci,drive=drive0,scsi=off,bus=pci_bridge25,addr=1"]
json.dump(config, sys.stdout)

View File

@ -9,8 +9,8 @@ import sys
config = json.load(sys.stdin)
qemu = f"qemu-system-{os.environ["QEMU_ARCHITECTURE"]}"
result = subprocess.run([qemu, '-device', 'help'], check=True, text=True, stdout=subprocess.PIPE)
qemu = f"qemu-system-{os.environ['QEMU_ARCHITECTURE']}"
result = subprocess.run([qemu, "-device", "help"], check=True, text=True, stdout=subprocess.PIPE)
if 'name "nvme"' not in result.stdout:
print("nvme device driver is not available, skipping test...", file=sys.stderr)
exit(77)

View File

@ -9,8 +9,8 @@ import sys
config = json.load(sys.stdin)
qemu = f"qemu-system-{os.environ["QEMU_ARCHITECTURE"]}"
result = subprocess.run([qemu, '-device', 'help'], check=True, text=True, stdout=subprocess.PIPE)
qemu = f"qemu-system-{os.environ['QEMU_ARCHITECTURE']}"
result = subprocess.run([qemu, "-device", "help"], check=True, text=True, stdout=subprocess.PIPE)
if 'name "nvme"' not in result.stdout:
print("nvme device driver is not available, skipping test...", file=sys.stderr)
exit(77)

View File

@ -9,8 +9,8 @@ import sys
config = json.load(sys.stdin)
qemu = f"qemu-system-{os.environ["QEMU_ARCHITECTURE"]}"
result = subprocess.run([qemu, '-device', 'help'], check=True, text=True, stdout=subprocess.PIPE)
qemu = f"qemu-system-{os.environ['QEMU_ARCHITECTURE']}"
result = subprocess.run([qemu, "-device", "help"], check=True, text=True, stdout=subprocess.PIPE)
if 'name "virtio-scsi-pci"' not in result.stdout:
print("virtio-scsi-pci device driver is not available, skipping test...", file=sys.stderr)
exit(77)

View File

@ -8,5 +8,6 @@ integration_tests += [
'after' : '@0@ tpm2.target'.format(integration_test_template['configuration']['after']),
},
'vm' : true,
'firmware' : 'auto',
},
]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -103,6 +103,12 @@ def main():
journal_file = (args.meson_build_dir / (f"test/journal/{name}.journal")).absolute()
journal_file.unlink(missing_ok=True)
else:
dropin += textwrap.dedent(
"""
[Unit]
Wants=multi-user.target
"""
)
journal_file = None
cmd = [

View File

@ -17,8 +17,10 @@
import argparse
import datetime
import enum
import errno
import itertools
import ipaddress
import json
import os
import pathlib
@ -27,6 +29,7 @@ import re
import shutil
import signal
import socket
import struct
import subprocess
import sys
import time
@ -742,6 +745,54 @@ def stop_by_pid_file(pid_file):
print(f"Unexpected exception when waiting for {pid} to die: {e.errno}")
rm_f(pid_file)
def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None):
b = bytes()
pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c
pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n)
ipv4 = ipaddress.IPv4Address
class SvcParam(enum.Enum):
ALPN = 1
DOHPATH = 7
data = pyton(prio)
adn = adn.rstrip('.') + '.'
data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.')))
if not addrs: # adn-only mode
return pack(data, 2)
data += pack(b.join(ipv4(addr).packed for addr in addrs))
data += pyton(SvcParam.ALPN.value) + pack(b.join(pack(alpn.encode('ascii')) for alpn in alpns), 2)
if dohpath is not None:
data += pyton(SvcParam.DOHPATH.value) + pack(dohpath.encode('utf-8'), 2)
return pack(data, 2)
def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None):
b = bytes()
pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c
pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n)
ipv6 = ipaddress.IPv6Address
class SvcParam(enum.Enum):
ALPN = 1
DOHPATH = 7
data = pyton(prio)
adn = adn.rstrip('.') + '.'
data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.')), 2)
if not addrs: # adn-only mode
return data
data += pack(b.join(ipv6(addr).packed for addr in addrs), 2)
data += pyton(SvcParam.ALPN.value) + pack(b.join(pack(alpn.encode('ascii')) for alpn in alpns), 2)
if dohpath is not None:
data += pyton(SvcParam.DOHPATH.value) + pack(dohpath.encode('utf-8'), 2)
return data
def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'):
if ra_mode:
ra_mode = f',{ra_mode}'
@ -7017,6 +7068,56 @@ class NetworkdDHCPClientTests(unittest.TestCase, Utilities):
check(self, False, False, True)
check(self, False, False, False)
def test_dhcp_client_use_dnr(self):
def check(self, ipv4, ipv6):
os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)
with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f:
f.write('[DHCPv4]\nUseDNS=')
f.write('yes' if ipv4 else 'no')
f.write('\n[DHCPv6]\nUseDNS=')
f.write('yes' if ipv6 else 'no')
f.write('\n[IPv6AcceptRA]\nUseDNS=no')
networkctl_reload()
self.wait_online('veth99:routable')
# link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses.
self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4')
self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6')
# make resolved re-read the link state file
resolvectl('revert', 'veth99')
output = resolvectl('dns', 'veth99')
print(output)
if ipv4:
self.assertIn('8.8.8.8#dns.google', output)
self.assertIn('0.7.4.2#homer.simpson', output)
else:
self.assertNotIn('8.8.8.8#dns.google', output)
if ipv6:
self.assertIn('2001:4860:4860::8888#dns.google', output)
else:
self.assertNotIn('2001:4860:4860::8888#dns.google', output)
check_json(networkctl_json())
copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False)
start_networkd()
self.wait_online('veth-peer:carrier')
dnr_v4 = dnr_v4_instance_data(adn = "dns.google", addrs = ["8.8.8.8", "8.8.4.4"])
dnr_v4 += dnr_v4_instance_data(adn = "homer.simpson", addrs = ["0.7.4.2"], alpns = ("dot","h2","h3"), dohpath = "/springfield{?dns}")
dnr_v6 = dnr_v6_instance_data(adn = "dns.google", addrs = ["2001:4860:4860::8888", "2001:4860:4860::8844"])
masq = lambda bs: ':'.join(f"{b:02x}" for b in bs)
start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}',
f'--dhcp-option=option6:144,{masq(dnr_v6)}')
check(self, True, True)
check(self, True, False)
check(self, False, True)
check(self, False, False)
def test_dhcp_client_use_captive_portal(self):
def check(self, ipv4, ipv6):
os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True)

View File

@ -218,7 +218,7 @@ assert_ntp() {
assert_timedated_signal() {
local timestamp="${1:?}"
local value="${2:?}"
local args=(-q -n 1 --since="$timestamp" -p info _SYSTEMD_UNIT="busctl-monitor.service")
local args=(-q -n 1 --since="$timestamp" -p info -t busctl)
journalctl --sync
@ -298,7 +298,7 @@ assert_timesyncd_signal() {
local timestamp="${1:?}"
local property="${2:?}"
local value="${3:?}"
local args=(-q --since="$timestamp" -p info _SYSTEMD_UNIT="busctl-monitor.service")
local args=(-q --since="$timestamp" -p info -t busctl)
journalctl --sync

View File

@ -231,8 +231,8 @@ testcase_nvme_subsystem() {
/dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_16
/dev/disk/by-id/nvme-QEMU_NVMe_Ctrl_deadbeef_17
# Shared namespaces
/dev/disk/by-path/pci-*-nvme-16
/dev/disk/by-path/pci-*-nvme-17
/dev/disk/by-path/*pci*-nvme-16
/dev/disk/by-path/*pci*-nvme-17
)
udevadm wait --settle --timeout=30 "${expected_symlinks[@]}"