Compare commits

...

31 Commits

Author SHA1 Message Date
Ronan Pigott 49d1f8ceb5
Merge 9e3482d2df into 81af8f998e 2024-09-18 11:31:16 +02:00
Daan De Meyer 81af8f998e repart: Support specifying multiple directories to ExcludeFiles= 2024-09-18 10:22:33 +02:00
chenjiayi 4fc8a63f9e systemd: rewatch pids under cgroup v1 when sigchld of processes more than main pid and control pid is captured
If `Delegate` is configured in service, cgroup agent will never send out
any datagram as .control subcgroup is generated. Thus systemd will watch
all processes on the cgroup hierarchy for SIGCHLD to deal with unreliable
cgroup notifications.

In this way, systemd should rewatch all processes when any SIGCHLD is
captured, more than the control pid or main pid.
2024-09-18 10:13:20 +02:00
Jason Yundt dfb3155419 man: document ShowStatus and SetShowStatus()
SetShowStatus() was added in order to fix #11447. Recently, I ran into
the exact same problem that OP was experiencing in #11447. I wasn’t able
to figure out how to deal with the problem until I found #11447, and it
took me a while to find #11447.

This commit takes what I learned from reading #11447 and adds it to the
documentation. Hopefully, this will make it easier for other people who
run into the same problem in the future.
2024-09-18 10:11:55 +02:00
Daan De Meyer fc5037e7d7
Merge pull request #34464 from yuwata/test-space-in-path
test: allow to run tests under directory that contains spaces
2024-09-18 08:50:38 +02:00
Yu Watanabe 13f6ec7ce7 test: quote paths to executables
Fixes #34459.
2024-09-18 09:47:04 +09:00
Yu Watanabe 6e1816ef16 kernel-install: unquote plugin paths in KERNEL_INSTALL_PLUGINS
To support the case that paths to plugins contain spaces.

Prompted by #34459
2024-09-18 09:47:00 +09:00
Yu Watanabe 7ac1ad90d0
Merge pull request #34460 from yuwata/test-86-follow-ups
test: follow-ups for TEST-86
2024-09-18 09:31:17 +09:00
Daan De Meyer 099b16c3e7 tmpfiles.d: Remove purge flag from lines that don't support it
Fixes db15657dfb
2024-09-17 23:02:01 +02:00
Yu Watanabe d265b8afb7 test: drop unused test.sh for TEST-86-MULTI-PROFILE-UKI
The test cannot run with the bash test runner, as it requires python.
Hence, test.sh is not necessary.

Follow-up for a37640653c.
2024-09-18 04:00:05 +09:00
Yu Watanabe 1aab0a5b10 test: minor coding style fixlets
Follow-up for a37640653c.
2024-09-18 03:50:46 +09:00
Ronan Pigott 9e3482d2df resolve: move sd-* api into libsystemd-network
This duplicates the svc param constants for the benefit of the
resolved-core library.
2024-09-13 22:57:51 -07:00
Ronan Pigott 62ad75c295 ndisc: implement ndisc_option_build_encrypted_dns
This is only used by the fuzzer so far.
2024-09-13 22:57:51 -07:00
Ronan Pigott 7db6e9c52c network: add dnr resolvers to networkctl status json output 2024-09-13 22:57:51 -07:00
Ronan Pigott 9b9c04c23f 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-09-13 22:57:51 -07:00
Ronan Pigott afad8b936b network: Serialize ipv6ra DNR
Serialize DNR servers acquired by ipv6ra option, same as the V4/V6 DNR
DHCP options.
2024-09-13 22:57:51 -07:00
Ronan Pigott 41d515edc4 network: Introduce IPv6RA UseDNR= option
Same as the DHCP v4/v6 options, this controls the use of DNR received
from ipv6ra.
2024-09-13 22:57:51 -07:00
Ronan Pigott 748ecb5236 ndisc: Parse RFC9463 encrypted DNS (DNR) option
This option is equivalent to the V4/V6 DNR options for DHCP.
2024-09-13 22:57:51 -07:00
Ronan Pigott 684ed0e352 test-network: add DHCPv6 DNR test
Same as the DHCPv4 test.
2024-09-13 22:57:51 -07:00
Ronan Pigott 2fab384cfe network: Serialize DHCPv6 DNR servers
This serializes DNR servers acquired by V6_DNR option, equivalent to the
V4_DNR option.
2024-09-13 22:57:51 -07:00
Ronan Pigott 257be1d320 network: Introduce UseDNR DHCPv6 option
This is equivalent to the DHCPv4 option introduced earlier.
2024-09-13 22:57:51 -07:00
Ronan Pigott 8f69c9ce0f 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-09-13 22:57:51 -07:00
Ronan Pigott 60722f1a83 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-09-13 22:57:51 -07:00
Ronan Pigott a525df4d7d 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-09-13 22:57:51 -07:00
Ronan Pigott 4f16e209d3 test-network: add test for DHCPv4 DNR
This will test that networkd/resolved can understand the V4_DNR DHCP
option.
2024-09-13 22:57:51 -07:00
Ronan Pigott 476ce3a795 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-09-13 22:57:51 -07:00
Ronan Pigott ec0e5398f3 network: Add serialization for DoT resolvers
For now only DoT is supported, so DoT resolvers are represented using
the existing configuration format.
2024-09-13 22:57:51 -07:00
Ronan Pigott 26f9f2677d 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-09-13 22:57:51 -07:00
Ronan Pigott 25c33e3500 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-09-13 22:57:51 -07:00
Ronan Pigott 1e2ead52e1 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-09-13 22:57:50 -07:00
Ronan Pigott 427166c3b0 dns: introduce dns_name_from_wire_format
This is implemented in various places, but it is better to share this
code.
2024-09-13 22:57:50 -07:00
63 changed files with 2172 additions and 220 deletions

View File

@ -593,8 +593,6 @@ node /org/freedesktop/systemd1 {
<!--method GetJobBefore is not documented!-->
<!--method SetShowStatus is not documented!-->
<!--method ListUnitsFiltered is not documented!-->
<!--method ListUnitsByPatterns is not documented!-->
@ -673,8 +671,6 @@ node /org/freedesktop/systemd1 {
<!--property ConfirmSpawn is not documented!-->
<!--property ShowStatus is not documented!-->
<!--property DefaultStandardOutput is not documented!-->
<!--property DefaultStandardError is not documented!-->
@ -1362,6 +1358,24 @@ node /org/freedesktop/systemd1 {
<para><function>ResetFailedUnit()</function> resets the "failed" state of a specific unit.</para>
<para><function>SetShowStatus()</function> configures the display of status messages during bootup and
shutdown. The <varname>mode</varname> parameter can be set to any value that's valid for the
<varname>systemd.show_status</varname> kernel parameter. For more information about
<varname>systemd.show_status</varname>, see
<citerefentry project="man-pages"><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
The <varname>mode</varname> parameter can also be set to an empty string. When <varname>mode</varname>
is set to an empty string, <function>SetShowStatus()</function> will reset
<varname>ShowStatus</varname> back to its original value. You can use
<function>SetShowStatus()</function> create a service that does something like this:
<orderedlist>
<listitem><para>Send a D-Bus message that will turn off status messages.</para></listitem>
<listitem><para>Block until a reply to that message is received.</para></listitem>
<listitem><para>Print multiples lines without being interrupted by status messages.</para></listitem>
<listitem><para>Send a D-Bus message that will reset <varname>ShowStatus</varname> back to its
original value.</para></listitem>
</orderedlist>
</para>
<para><function>ResetFailed()</function> resets the "failed" state of all units.</para>
<para><function>ListUnits()</function> returns an array of all currently loaded units. Note that
@ -1788,6 +1802,12 @@ node /org/freedesktop/systemd1 {
<para><varname>Environment</varname> encodes the environment block passed to all executed services. It
may be altered with bus calls such as <function>SetEnvironment()</function> (see above).</para>
<para><varname>ShowStatus</varname> encodes systemd's current policy for displaying status messages
during bootup and shutdown. Its value can be any valid value for the
<varname>systemd.show_status</varname> kernel parameter (see
<citerefentry project="man-pages"><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>).
It may be altered using <function>SetShowStatus()</function> (see above).</para>
<para><varname>UnitPath</varname> encodes the currently active unit file search path. It is an array of
file system paths encoded as strings.</para>

View File

@ -483,18 +483,18 @@
<term><varname>ExcludeFiles=</varname></term>
<term><varname>ExcludeFilesTarget=</varname></term>
<listitem><para>Takes an absolute file system path referring to a source file or directory on the
host. This setting may be used to exclude files or directories from the host from being copied into
the file system when <varname>CopyFiles=</varname> is used. This option may be used multiple times to
exclude multiple files or directories from host from being copied into the newly formatted file
system.</para>
<listitem><para>Takes one or more absolute paths, separated by whitespace, each referring to a
source file or directory on the host. This setting may be used to exclude files or directories from
the host from being copied into the file system when <varname>CopyFiles=</varname> is used. This
option may be used multiple times to exclude multiple files or directories from host from being
copied into the newly formatted file system.</para>
<para>If the path is a directory and ends with <literal>/</literal>, only the directory's
contents are excluded but not the directory itself. If the path is a directory and does not end with
<literal>/</literal>, both the directory and its contents are excluded.</para>
<para><varname>ExcludeFilesTarget=</varname> is like <varname>ExcludeFiles=</varname> except that
instead of excluding the path on the host from being copied into the partition, we exclude any files
instead of excluding the path on the host from being copied into the partition, it exclude any files
and directories from being copied into the given path in the partition.</para>
<para>When

View File

@ -3001,7 +3001,12 @@ SystemCallErrorNumber=EPERM</programlisting>
<para><option>tty</option> connects standard output to a tty (as configured via <varname>TTYPath=</varname>,
see below). If the TTY is used for output only, the executed process will not become the controlling process of
the terminal, and will not fail or wait for other processes to release the terminal.</para>
the terminal, and will not fail or wait for other processes to release the terminal. Note: if a unit
tries to print multiple lines to a TTY during bootup or shutdown, then there's a chance that those
lines will be broken up by status messages. <function>SetShowStatus()</function> can be used to
prevent this problem. See
<citerefentry project="man-pages"><refentrytitle>org.freedesktop.systemd1</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for details.</para>
<para><option>journal</option> connects standard output with the journal, which is accessible via
<citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>. Note

View File

@ -2605,6 +2605,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>
@ -3131,6 +3143,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>
@ -3418,6 +3431,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

@ -568,7 +568,11 @@
<listitem><para>Enables display of status messages on the
console, as controlled via
<varname>systemd.show_status=1</varname> on the kernel command
line.</para></listitem>
line.</para>
<para>You may want to use <function>SetShowStatus()</function> instead of
<constant>SIGRTMIN+20</constant> in order to prevent race conditions. See
<citerefentry project="man-pages"><refentrytitle>org.freedesktop.systemd1</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para></listitem>
</varlistentry>
<varlistentry>
@ -579,7 +583,11 @@
controlled via
<varname>systemd.show_status=0</varname>
on the kernel command
line.</para></listitem>
line.</para>
<para>You may want to use <function>SetShowStatus()</function> instead of
<constant>SIGRTMIN+21</constant> in order to prevent race conditions. See
<citerefentry project="man-pages"><refentrytitle>org.freedesktop.systemd1</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para></listitem>
</varlistentry>
<varlistentry>

View File

@ -4169,7 +4169,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
* detect when the cgroup becomes empty. Note that the control process is always
* our child so it's pointless to watch all other processes. */
if (!control_pid_good(s))
if (!s->main_pid_known || s->main_pid_alien)
if (!s->main_pid_known || s->main_pid_alien || unit_cgroup_delegate(u))
(void) unit_enqueue_rewatch_pids(u);
}

View File

@ -404,15 +404,16 @@ static int context_set_path_strv(Context *c, char* const* strv, const char *sour
static int context_set_plugins(Context *c, const char *s, const char *source) {
_cleanup_strv_free_ char **v = NULL;
int r;
assert(c);
if (c->plugins || !s)
return 0;
v = strv_split(s, NULL);
if (!v)
return log_oom();
r = strv_split_full(&v, s, NULL, EXTRACT_UNQUOTE);
if (r < 0)
return log_error_errno(r, "Failed to parse plugin paths from %s: %m", source);
return context_set_path_strv(c, v, source, "plugins", &c->plugins);
}

View File

@ -46,7 +46,13 @@ echo 'DTBDTBDTBDTB' >"$D/sources/subdir/whatever.dtb"
export KERNEL_INSTALL_CONF_ROOT="$D/sources"
# We "install" multiple plugins, but control which ones will be active via install.conf.
export KERNEL_INSTALL_PLUGINS="${ukify_install} ${loaderentry_install} ${uki_copy_install}"
KERNEL_INSTALL_PLUGINS="'${loaderentry_install}' '${uki_copy_install}'"
if [[ -n "$ukify_install" ]]; then
# shellcheck disable=SC2089
KERNEL_INSTALL_PLUGINS="'${ukify_install}' $KERNEL_INSTALL_PLUGINS"
fi
# shellcheck disable=SC2090
export KERNEL_INSTALL_PLUGINS
export BOOT_ROOT="$D/boot"
export BOOT_MNT="$D/boot"
export MACHINE_ID='3e0484f3634a418b8e6a39e8828b03e3'

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,59 @@
/* 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 sd_dns_resolver_done(sd_dns_resolver *res);
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

@ -3,6 +3,7 @@
#include <linux/ipv6.h>
#include <netinet/icmp6.h>
#include "dns-resolver-internal.h"
#include "dns-domain.h"
#include "ether-addr-util.h"
#include "hostname-util.h"
@ -89,6 +90,13 @@ static void ndisc_dnssl_done(sd_ndisc_dnssl *dnssl) {
strv_free(dnssl->domains);
}
static void ndisc_dnr_done(sd_ndisc_dnr *dnr) {
if (!dnr)
return;
sd_dns_resolver_unref(dnr->resolver);
}
sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) {
if (!option)
return NULL;
@ -109,6 +117,10 @@ sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) {
case SD_NDISC_OPTION_CAPTIVE_PORTAL:
free(option->captive_portal);
break;
case SD_NDISC_OPTION_ENCRYPTED_DNS:
ndisc_dnr_done(&option->encrypted_dns);
break;
}
return mfree(option);
@ -1270,6 +1282,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);
@ -1376,6 +1665,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);
}
@ -1487,6 +1780,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 = {.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,98 @@ 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);
if (!res.addrs)
return -ENOMEM;
for (size_t i = 0; i < n_addrs; i++) {
union in_addr_union addr = {.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 +944,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 +1006,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_alpn(sd_dns_resolver *res, sd_dns_alpn_flags *ret_alpn) {
assert_return(res, -EINVAL);
assert_return(ret_alpn, -EINVAL);
/* ADN-only mode has no transports */
if (!res->transports)
return -ENODATA;
*ret_alpn = 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]) {
assert(buf);
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(&option[poff], alen, "dot", STRLEN("dot")) == 0)
transports |= SD_DNS_ALPN_DOT;
if (memcmp_nn(&option[poff], alen, "h2", STRLEN("h2")) == 0)
transports |= SD_DNS_ALPN_HTTP_2_TLS;
if (memcmp_nn(&option[poff], alen, "h3", STRLEN("h3")) == 0)
transports |= SD_DNS_ALPN_HTTP_3;
if (memcmp_nn(&option[poff], alen, "doq", STRLEN("doq")) == 0)
transports |= SD_DNS_ALPN_DOQ;
poff += alen;
}
if (poff != pend)
return -EBADMSG;
break;
case DNS_SVC_PARAM_KEY_PORT:
if (plen != sizeof(uint16_t))
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,44 @@
#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_ENUM_FORCE_S64(SD_DNS_ALPN)
} 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_alpn(sd_dns_resolver *res, sd_dns_alpn_flags *ret_alpn);
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);
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

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

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,8 +3,10 @@
#include <netinet/in.h>
#include "conf-parser.h"
#include "dhcp-duid-internal.h"
#include "dns-resolver-internal.h"
#include "in-addr-util.h"
#include "set.h"
#include "time-util.h"

View File

@ -1564,6 +1564,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,117 @@ 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 +1379,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"
@ -1828,6 +1829,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;
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 = { .resolver = *res };
if (lifetime_usec == 0) {
dnr = set_remove(link->ndisc_dnr, &d);
if (dnr) {
ndisc_dnr_free(dnr);
link_dirty(link);
}
return 0;
}
dnr = set_get(link->ndisc_dnr, &d);
if (dnr) {
dnr->router = router;
dnr->lifetime_usec = lifetime_usec;
return 0;
}
new_entry = new(NDiscDNR, 1);
if (!new_entry)
return log_oom();
*new_entry = (NDiscDNR) {
.router = router,
/* .resolver, */
.lifetime_usec = lifetime_usec,
};
r = sd_dns_resolver_copy(res, &new_entry->resolver);
if (r < 0)
return log_oom();
/* Not sorted by priority */
r = set_ensure_put(&link->ndisc_dnr, &ndisc_dnr_hash_ops, new_entry);
if (r < 0)
return log_oom();
assert(r > 0);
TAKE_PTR(new_entry);
link_dirty(link);
return 0;
}
static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
size_t n_captive_portal = 0;
int r;
@ -1879,6 +2021,9 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) {
case SD_NDISC_OPTION_PREF64:
r = ndisc_router_process_pref64(link, rt);
break;
case SD_NDISC_OPTION_ENCRYPTED_DNS:
r = ndisc_router_process_encrypted_dns(link, rt);
break;
}
if (r < 0 && r != -EBADMSG)
return r;
@ -1891,6 +2036,7 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t
NDiscRDNSS *rdnss;
NDiscCaptivePortal *cp;
NDiscPREF64 *p64;
NDiscDNR *dnr;
Address *address;
Route *route;
int r, ret = 0;
@ -1989,6 +2135,14 @@ static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t
* the 'updated' flag. */
}
SET_FOREACH(dnr, link->ndisc_dnr) {
if (dnr->lifetime_usec > timestamp_usec)
continue; /* The resolver is still valid */
ndisc_dnr_free(set_remove(link->ndisc_dnr, dnr));
updated = true;
}
if (updated)
link_dirty(link);
@ -2016,6 +2170,7 @@ static int ndisc_setup_expire(Link *link) {
NDiscDNSSL *dnssl;
NDiscRDNSS *rdnss;
NDiscPREF64 *p64;
NDiscDNR *dnr;
Address *address;
Route *route;
int r;
@ -2068,6 +2223,9 @@ static int ndisc_setup_expire(Link *link) {
SET_FOREACH(p64, link->ndisc_pref64)
lifetime_usec = MIN(lifetime_usec, p64->lifetime_usec);
SET_FOREACH(dnr, link->ndisc_dnr)
lifetime_usec = MIN(lifetime_usec, dnr->lifetime_usec);
if (lifetime_usec == USEC_INFINITY)
return 0;
@ -2324,6 +2482,14 @@ static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *n
p64->router = current_address;
}
NDiscDNR *dnr;
SET_FOREACH(dnr, link->ndisc_dnr) {
if (!in6_addr_equal(&dnr->router, &original_address))
continue;
dnr->router = current_address;
}
return 0;
}
@ -2507,7 +2673,7 @@ int ndisc_stop(Link *link) {
void ndisc_flush(Link *link) {
assert(link);
/* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */
/* Remove all addresses, routes, RDNSS, DNSSL, DNR, and Captive Portal entries, without exception. */
(void) ndisc_drop_outdated(link, /* router = */ NULL, /* timestamp_usec = */ USEC_INFINITY);
(void) ndisc_drop_redirect(link, /* router = */ NULL);
@ -2517,6 +2683,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

@ -228,6 +228,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)
@ -278,6 +279,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)
@ -307,6 +309,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,
@ -407,6 +408,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,
@ -482,6 +484,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;
@ -185,6 +186,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;
@ -343,6 +345,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

@ -5,6 +5,7 @@
#include "alloc-util.h"
#include "dns-domain.h"
#include "dns-resolver-internal.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
@ -113,6 +114,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 +143,25 @@ 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 +172,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;
}
@ -522,6 +578,60 @@ 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 (void) log_warning_errno(r, "Failed to get DNR from DHCP lease, ignoring.");
r = dns_resolvers_to_dot_strv(resolvers, r, &names);
if (r < 0)
return (void) log_warning_errno(r, "Failed to get DoT servers from DHCP DNR, ignoring.");
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 (void) log_warning_errno(r, "Failed to get DNR from DHCPv6 lease, ignoring.");
r = dns_resolvers_to_dot_strv(resolvers, r, &names);
if (r < 0)
return (void) log_warning_errno(r, "Failed to get DoT servers from DHCPv6 DNR, ignoring.");
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;
@ -665,6 +775,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

@ -1742,8 +1742,9 @@ static int config_parse_exclude_files(
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *resolved = NULL;
char ***exclude_files = ASSERT_PTR(data);
const char *p = ASSERT_PTR(rvalue);
int r;
if (isempty(rvalue)) {
@ -1751,20 +1752,34 @@ static int config_parse_exclude_files(
return 0;
}
r = specifier_printf(rvalue, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to expand specifiers in ExcludeFiles= path, ignoring: %s", rvalue);
return 0;
for (;;) {
_cleanup_free_ char *word = NULL, *resolved = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", p);
return 0;
}
if (r == 0)
return 0;
r = specifier_printf(word, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to expand specifiers in %s path, ignoring: %s", lvalue, word);
return 0;
}
r = path_simplify_and_warn(resolved, PATH_CHECK_ABSOLUTE|PATH_KEEP_TRAILING_SLASH, unit, filename, line, lvalue);
if (r < 0)
return 0;
if (strv_consume(exclude_files, TAKE_PTR(resolved)) < 0)
return log_oom();
}
r = path_simplify_and_warn(resolved, PATH_CHECK_ABSOLUTE|PATH_KEEP_TRAILING_SLASH, unit, filename, line, lvalue);
if (r < 0)
return 0;
if (strv_consume(exclude_files, TAKE_PTR(resolved)) < 0)
return log_oom();
return 0;
}

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

@ -362,25 +362,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 } })
@ -389,6 +370,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

@ -905,6 +905,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

@ -197,7 +197,7 @@ _unused_ static void test_compress_stream(const char *compression,
ASSERT_OK(compress(src, dst, -1, &uncompressed_size));
if (cat) {
assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0);
assert_se(asprintf(&cmd, "%s %s | diff '%s' -", cat, pattern, srcfile) > 0);
assert_se(system(cmd) == 0);
}
@ -212,7 +212,7 @@ _unused_ static void test_compress_stream(const char *compression,
r = decompress(dst, dst2, st.st_size);
assert_se(r == 0);
assert_se(asprintf(&cmd2, "diff %s %s", srcfile, pattern2) > 0);
assert_se(asprintf(&cmd2, "diff '%s' %s", srcfile, pattern2) > 0);
assert_se(system(cmd2) == 0);
log_debug("/* test faulty decompression */");

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

@ -52,7 +52,8 @@ static void test_event_spawn_self(const char *self, const char *arg, bool with_p
log_debug("/* %s(%s, %s) */", __func__, arg, yes_no(with_pidfd));
assert_se(cmd = strjoin(self, " ", arg));
/* 'self' may contain spaces, hence needs to be quoted. */
assert_se(cmd = strjoin("'", self, "' ", arg));
test_event_spawn_core(with_pidfd, cmd, result_buf, BUF_SIZE);

View File

@ -1,10 +0,0 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
TEST_DESCRIPTION="Test Multi-Profile UKI Boots"
# shellcheck source=test/test-functions
. "${TEST_BASE_DIR:?}/test-functions"
do_test "$@"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,32 +4,32 @@ set -e
ANALYZE="${1:-systemd-analyze}"
$ANALYZE compare-versions 1 lt 2
$ANALYZE compare-versions 1 '<' 2
$ANALYZE compare-versions 1 le 2
$ANALYZE compare-versions 1 '<=' 2
$ANALYZE compare-versions 1 ne 2
$ANALYZE compare-versions 1 '!=' 2
( ! $ANALYZE compare-versions 1 ge 2 )
( ! $ANALYZE compare-versions 1 '>=' 2 )
( ! $ANALYZE compare-versions 1 eq 2 )
( ! $ANALYZE compare-versions 1 '==' 2 )
( ! $ANALYZE compare-versions 1 gt 2 )
( ! $ANALYZE compare-versions 1 '>' 2 )
"$ANALYZE" compare-versions 1 lt 2
"$ANALYZE" compare-versions 1 '<' 2
"$ANALYZE" compare-versions 1 le 2
"$ANALYZE" compare-versions 1 '<=' 2
"$ANALYZE" compare-versions 1 ne 2
"$ANALYZE" compare-versions 1 '!=' 2
( ! "$ANALYZE" compare-versions 1 ge 2 )
( ! "$ANALYZE" compare-versions 1 '>=' 2 )
( ! "$ANALYZE" compare-versions 1 eq 2 )
( ! "$ANALYZE" compare-versions 1 '==' 2 )
( ! "$ANALYZE" compare-versions 1 gt 2 )
( ! "$ANALYZE" compare-versions 1 '>' 2 )
test "$($ANALYZE compare-versions 1 2)" = '1 < 2'
test "$($ANALYZE compare-versions 2 2)" = '2 == 2'
test "$($ANALYZE compare-versions 2 1)" = '2 > 1'
test "$($ANALYZE compare-versions '' '')" = "'' == ''"
test "$("$ANALYZE" compare-versions 1 2)" = '1 < 2'
test "$("$ANALYZE" compare-versions 2 2)" = '2 == 2'
test "$("$ANALYZE" compare-versions 2 1)" = '2 > 1'
test "$("$ANALYZE" compare-versions '' '')" = "'' == ''"
set +e
$ANALYZE compare-versions 1 2; ret1=$?
$ANALYZE compare-versions 2 2; ret2=$?
$ANALYZE compare-versions 2 1; ret3=$?
"$ANALYZE" compare-versions 1 2; ret1=$?
"$ANALYZE" compare-versions 2 2; ret2=$?
"$ANALYZE" compare-versions 2 1; ret3=$?
set -e
test $ret1 == 12
test $ret2 == 0
test $ret3 == 11
test "$ret1" == 12
test "$ret2" == 0
test "$ret3" == 11

View File

@ -44,9 +44,9 @@ test_one() (
fi
if [[ "${input##*/}" =~ \.fstab\.input ]]; then
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD="$initrd" SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=yes root=fstab" SYSTEMD_FSTAB="$input" SYSTEMD_SYSROOT_FSTAB="/dev/null" $generator "$out" "$out" "$out"
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD="$initrd" SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=yes root=fstab" SYSTEMD_FSTAB="$input" SYSTEMD_SYSROOT_FSTAB="/dev/null" "$generator" "$out" "$out" "$out"
else
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD="$initrd" SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=no $(cat "$input")" $generator "$out" "$out" "$out"
SYSTEMD_LOG_LEVEL=debug SYSTEMD_IN_INITRD="$initrd" SYSTEMD_SYSFS_CHECK=no SYSTEMD_PROC_CMDLINE="fstab=no $(cat "$input")" "$generator" "$out" "$out" "$out"
fi
# The option x-systemd.growfs creates symlink to system's systemd-growfs@.service in .mount.wants directory.

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}'
@ -7253,6 +7304,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

@ -53,7 +53,7 @@ for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f"
prepare_testdir "${f%.input}"
cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
$SYSUSERS --root="$TESTDIR"
"$SYSUSERS" --root="$TESTDIR"
compare "${f%.*}" ""
done
@ -62,7 +62,7 @@ for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f on stdin"
prepare_testdir "${f%.input}"
touch "$TESTDIR/etc/sysusers.d/test.conf"
$SYSUSERS --root="$TESTDIR" - <"$f"
"$SYSUSERS" --root="$TESTDIR" - <"$f"
compare "${f%.*}" "on stdin"
done
@ -72,9 +72,9 @@ for f in $(find "$SOURCE"/test-*.input | sort -V); do
prepare_testdir "${f%.input}"
touch "$TESTDIR/etc/sysusers.d/test.conf"
# this overrides test.conf which is masked on disk
$SYSUSERS --root="$TESTDIR" --replace=/etc/sysusers.d/test.conf - <"$f"
"$SYSUSERS" --root="$TESTDIR" --replace=/etc/sysusers.d/test.conf - <"$f"
# this should be ignored
$SYSUSERS --root="$TESTDIR" --replace=/usr/lib/sysusers.d/test.conf - <"$SOURCE/test-1.input"
"$SYSUSERS" --root="$TESTDIR" --replace=/usr/lib/sysusers.d/test.conf - <"$SOURCE/test-1.input"
compare "${f%.*}" "on stdin with --replace"
done
@ -84,9 +84,9 @@ echo "*** Testing --inline"
prepare_testdir "$SOURCE/inline"
# copy a random file to make sure it is ignored
cp "$f" "$TESTDIR/etc/sysusers.d/confuse.conf"
$SYSUSERS --root="$TESTDIR" --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
"$SYSUSERS" --root="$TESTDIR" --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare "$SOURCE/inline" "(--inline)"
@ -95,19 +95,19 @@ echo "*** Testing --inline with --replace"
prepare_testdir "$SOURCE/inline"
# copy a random file to make sure it is ignored
cp "$f" "$TESTDIR/etc/sysusers.d/confuse.conf"
$SYSUSERS --root="$TESTDIR" \
--inline \
--replace=/etc/sysusers.d/confuse.conf \
"u u1 222 - - /bin/zsh" \
"g g1 111"
"$SYSUSERS" --root="$TESTDIR" \
--inline \
--replace=/etc/sysusers.d/confuse.conf \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare "$SOURCE/inline" "(--inline --replace=…)"
echo "*** Testing --inline with no /etc"
rm -rf "${TESTDIR:?}/etc"
$SYSUSERS --root="$TESTDIR" --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
"$SYSUSERS" --root="$TESTDIR" --inline \
"u u1 222 - - /bin/zsh" \
"g g1 111"
compare "$SOURCE/inline" "(--inline)"
@ -136,7 +136,7 @@ for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f (with login.defs)"
prepare_testdir "${f%.input}"
cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
$SYSUSERS --root="$TESTDIR"
"$SYSUSERS" --root="$TESTDIR"
# shellcheck disable=SC2050
[ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
@ -152,7 +152,7 @@ for f in $(find "$SOURCE"/test-*.input | sort -V); do
echo "*** Running $f (with login.defs symlinked)"
prepare_testdir "${f%.input}"
cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
$SYSUSERS --root="$TESTDIR"
"$SYSUSERS" --root="$TESTDIR"
# shellcheck disable=SC2050
[ @ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES@ = 1 ] && bound=555 || bound=$system_guid_max
@ -166,7 +166,7 @@ for f in $(find "$SOURCE"/unhappy-*.input | sort -V); do
echo "*** Running test $f"
prepare_testdir "${f%.input}"
cp "$f" "$TESTDIR/usr/lib/sysusers.d/test.conf"
SYSTEMD_LOG_LEVEL=info $SYSUSERS --root="$TESTDIR" 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >"$TESTDIR/err"
SYSTEMD_LOG_LEVEL=info "$SYSUSERS" --root="$TESTDIR" 2>&1 | tail -n1 | sed -r 's/^[^:]+:[^:]+://' >"$TESTDIR/err"
if ! diff -u "$TESTDIR/err" "${f%.*}.expected-err"; then
echo >&2 "**** Unexpected error output for $f"
cat >&2 "$TESTDIR/err"

View File

@ -17,9 +17,9 @@ if test -f /run/systemd/stub/profile; then
fi
echo "CURRENT MEASUREMENT:"
/usr/lib/systemd/systemd-measure --current
if test -f /run/systemd/tpm2-pcr-signature.json ; then
if test -f /run/systemd/tpm2-pcr-signature.json; then
echo "CURRENT SIGNATURE:"
jq < /run/systemd/tpm2-pcr-signature.json
jq </run/systemd/tpm2-pcr-signature.json
fi
echo "CURRENT EVENT LOG + PCRS:"
@ -45,7 +45,7 @@ TITLE="Profile Two"' --measure-base=/tmp/extended1.efi --cmdline="testprofile2=1
# Prepare a disk image, locked to the PCR measurements of the UKI we just generated
truncate -s 32M /root/encrypted.raw
echo -n "geheim" > /root/encrypted.secret
echo -n "geheim" >/root/encrypted.secret
cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom /root/encrypted.raw --key-file=/root/encrypted.secret
systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs= --tpm2-public-key=/root/pcrsign.public.pem --unlock-key-file=/root/encrypted.secret /root/encrypted.raw
rm -f /root/encrypted.secret
@ -62,12 +62,12 @@ else
if [ "$ID" = "profile0" ]; then
grep -v testprofile /proc/cmdline
echo "default $(basename "$CURRENT_UKI")@profile1" > "$(bootctl -p)/loader/loader.conf"
echo "default $(basename "$CURRENT_UKI")@profile1" >"$(bootctl -p)/loader/loader.conf"
reboot
exit 0
elif [ "$ID" = "profile1" ]; then
grep testprofile1=1 /proc/cmdline
echo "default $(basename "$CURRENT_UKI")@profile2" > "$(bootctl -p)/loader/loader.conf"
echo "default $(basename "$CURRENT_UKI")@profile2" >"$(bootctl -p)/loader/loader.conf"
reboot
exit 0
elif [ "$ID" = "profile2" ]; then

View File

@ -19,5 +19,5 @@ Q /var/lib/machines 0700 - - -
# systemd-nspawn --ephemeral places snapshots) we are more strict, to
# avoid removing unrelated temporary files.
R!$ /var/lib/machines/.#*
R!$ /.#machine.*
R! /var/lib/machines/.#*
R! /.#machine.*

View File

@ -14,10 +14,10 @@ x /var/tmp/systemd-private-%b-*
X /var/tmp/systemd-private-%b-*/tmp
# Remove top-level private temporary directories on each boot
R!$ /tmp/systemd-private-*
R!$ /var/tmp/systemd-private-*
R! /tmp/systemd-private-*
R! /var/tmp/systemd-private-*
# Handle lost systemd-coredump temp files. They could be lost on old filesystems,
# for example, after hard reboot.
x /var/lib/systemd/coredump/.#core*.%b*
r!$ /var/lib/systemd/coredump/.#*
r! /var/lib/systemd/coredump/.#*