Compare commits

...

38 Commits

Author SHA1 Message Date
Ronan Pigott 35b04b016d
Merge 9e3482d2df into 7a7f306b6c 2024-09-17 22:50:38 +02:00
Daan De Meyer 7a7f306b6c ukify: Remove debug log
This prints a python data structure which we shouldn't do during
normal operation.
2024-09-17 22:34:13 +02:00
Yu Watanabe 4f2975385f
Merge pull request #34040 from AdrianVovk/repart-dollar-boot
repart: Implement $BOOT support
2024-09-18 05:09:20 +09:00
Daan De Meyer 0432e28394
Merge pull request #34440 from yuwata/network-log-no-matching-network
network: log when no matching .network file found
2024-09-17 21:09:19 +02:00
Yu Watanabe fc956a3973 network/dhcp4: use device_get_property_bool() at link_needs_dhcp_broadcast()
No functional change, just refactoring.
2024-09-17 21:03:59 +02:00
Yu Watanabe b0dbb4aa3a
Merge pull request #34457 from poettering/uki-with-many-testcase
multi-profile UKIs: test case
2024-09-18 03:48:45 +09:00
Michael Ferrari 91ea3dcf35 homed: wait for user input during firstboot
This mirrors the behavior of `systemd-firstboot` and allows bootup
messages to settle down before user input is actually processed.

See: https://github.com/systemd/systemd/issues/34448
2024-09-18 03:21:11 +09:00
Yu Watanabe a95ae2d36a conf-parser: use hashmap_ensure_put() at one more place 2024-09-18 03:13:47 +09:00
Yu Watanabe be8e4b1a87 conf-parser: log errors in config_parse_many_files() and friends
Previously, if an file cannot be opened, e.g. due to its permission,
config_parse_many() or so did not log the error even if CONFIG_PARSE_WARN
flag is set. This makes all error paths in these functions are logged,
and the log level is controlled by the flag.

Prompted by #34436.
2024-09-18 03:13:25 +09:00
Adrian Vovk cf612c5fd5
repart: Add tests for supplement partitions 2024-09-17 14:06:51 -04:00
Adrian Vovk 2cb9c68c3a
repart: Add SupplementFor= logic
This was designed to deal with $BOOT, as defined by the Boot Loader
Specification, but it was made a generic mechanism because it is useful
elsewhere too. See the updated man page for usage examples, motivation,
and an explanation of how this works.
2024-09-17 14:06:50 -04:00
Adrian Vovk 78e9059208
repart: Consider existing partitions when placing
Fixes an oversight in `context_allocate_partitions` that makes it
succeed in cases where it should fail. Essentially, there was nothing
actually enforcing SizeMinBytes= and PaddingMinBytes= for partitions
that exist, only for new partitions. This behavior is inconsistent with
the docs, which state that existing partitions will be grown to at least
the specified minimum size, and that "If the backing device does not
provide enough space to fulfill the constraints placing the partition
will fail".
2024-09-17 14:06:49 -04:00
Adrian Vovk e671bdc5c3
strv: Fixup STRV_FOREACH_PAIR macro
The macro didn't properly parenthesize a caller-controlled argument.
For example: `STRV_FOREACH_PAIR(a, b, something ?: something_else)`
would expand to `typeof(*something ?: something_else)`, which would
cause compile failures
2024-09-17 14:06:26 -04:00
Yu Watanabe 572d031eca log: introduce log_oom_full() 2024-09-18 02:50:19 +09:00
Yu Watanabe 25da422bd1 network: log loaded .network and .netdev files 2024-09-18 02:35:28 +09:00
Yu Watanabe 5872ea7008 network: log when no matching .network file found
When an interface enters unmanaged state, there are two possibilities:
- no matching .network file found,
- found a matching .network with Unmanaged=yes.

When a matching .network file is found, networkd logs the filename.
Let's also log when no matching .network file is found.

This also slightly adjust the log message when a matching .network file
found.

Closes #34436.
2024-09-18 02:27:13 +09:00
Lennart Poettering a2369d0224 update TODO 2024-09-17 10:40:51 +02:00
Lennart Poettering a37640653c ci: add testcase for multi-profile UKIs
This tests the whole shebang:

1. That ukify can generate them properly
2. That systemd-boot can dissect them properly
3. That systemd-stub can accept profile selection propery
4. That the profile information ends up in /run/systemd/stub/ properly
5. That systemd-measure correctly calculates the expected PCR 11 values
   for each profile and that we can unlock a public-key bound LUKS
   volume with it
2024-09-17 10:40:51 +02: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
65 changed files with 2737 additions and 263 deletions

2
TODO
View File

@ -189,6 +189,8 @@ Features:
* go through our codebase, and convert "vertical tables" (i.e. things such as
"systemctl status") to use table_new_vertical() for output
* pcrlock: add support for multi-profile UKIs
* logind: when logging in use new tmpfs quota support to configure quota on
/tmp/ + /dev/shm/. But do so only in case of tmpfs, because otherwise quota
is persistent and any persistent settings mean we don#t have to reapply them.

View File

@ -76,16 +76,7 @@
<term><varname>Type=</varname></term>
<listitem><para>The GPT partition type UUID to match. This may be a GPT partition type UUID such as
<constant>4f68bce3-e8cd-4db1-96e7-fbcaf984b709</constant>, or an identifier.
Architecture specific partition types can use one of these architecture identifiers:
<constant>alpha</constant>, <constant>arc</constant>, <constant>arm</constant> (32-bit),
<constant>arm64</constant> (64-bit, aka aarch64), <constant>ia64</constant>,
<constant>loongarch64</constant>, <constant>mips-le</constant>, <constant>mips64-le</constant>,
<constant>parisc</constant>, <constant>ppc</constant>, <constant>ppc64</constant>,
<constant>ppc64-le</constant>, <constant>riscv32</constant>, <constant>riscv64</constant>,
<constant>s390</constant>, <constant>s390x</constant>, <constant>tilegx</constant>,
<constant>x86</constant> (32-bit, aka i386) and <constant>x86-64</constant> (64-bit, aka amd64).
</para>
<constant>4f68bce3-e8cd-4db1-96e7-fbcaf984b709</constant>, or an identifier.</para>
<para>The supported identifiers are:</para>
@ -237,7 +228,14 @@
</tgroup>
</table>
<para>This setting defaults to <constant>linux-generic</constant>.</para>
<para>Architecture specific partition types can use one of these architecture identifiers:
<constant>alpha</constant>, <constant>arc</constant>, <constant>arm</constant> (32-bit),
<constant>arm64</constant> (64-bit, aka aarch64), <constant>ia64</constant>,
<constant>loongarch64</constant>, <constant>mips-le</constant>, <constant>mips64-le</constant>,
<constant>parisc</constant>, <constant>ppc</constant>, <constant>ppc64</constant>,
<constant>ppc64-le</constant>, <constant>riscv32</constant>, <constant>riscv64</constant>,
<constant>s390</constant>, <constant>s390x</constant>, <constant>tilegx</constant>,
<constant>x86</constant> (32-bit, aka i386) and <constant>x86-64</constant> (64-bit, aka amd64).</para>
<para>Most of the partition type UUIDs listed above are defined in the <ulink
url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable Partitions
@ -897,6 +895,59 @@
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>SupplementFor=</varname></term>
<listitem><para>Takes a partition definition name, such as <literal>10-esp</literal>. If specified,
<command>systemd-repart</command> will avoid creating this partition and instead prefer to partially
merge the two definitions. However, depending on the existing layout of partitions on disk,
<command>systemd-repart</command> may be forced to fall back onto un-merging the definitions and
using them as originally written, potentially creating this partition. Specifically,
<command>systemd-repart</command> will fall back if this partition is found to already exist on disk,
or if the target partition already exists on disk but is too small, or if it cannot allocate space
for the merged partition for some other reason.</para>
<para>The following fields are merged into the target definition in the specified ways:
<varname>Weight=</varname> and <varname>PaddingWeight=</varname> are simply overwritten;
<varname>SizeMinBytes=</varname> and <varname>PaddingMinBytes=</varname> use the larger of the two
values; <varname>SizeMaxBytes=</varname> and <varname>PaddingMaxBytes=</varname> use the smaller
value; and <varname>CopyFiles=</varname>, <varname>ExcludeFiles=</varname>,
<varname>ExcludeFilesTarget=</varname>, <varname>MakeDirectories=</varname>, and
<varname>Subvolumes=</varname> are concatenated.</para>
<para>Usage of this option in combination with <varname>CopyBlocks=</varname>,
<varname>Encrypt=</varname>, or <varname>Verity=</varname> is not supported. The target definition
cannot set these settings either. A definition cannot simultaneously be a supplement and act as a
target for some other supplement definition. A target cannot have more than one supplement partition
associated with it.</para>
<para>For example, distributions can use this to implement <variable>$BOOT</variable> as defined in
the <ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification/">Boot Loader
Specification</ulink>. Distributions may prefer to use the ESP as <variable>$BOOT</variable> whenever
possible, but to adhere to the spec XBOOTLDR must sometimes be used instead. So, they should create
two definitions: the first defining an ESP big enough to hold just the bootloader, and a second for
the XBOOTLDR that's sufficiently large to hold kernels and configured as a supplement for the ESP.
Whenever possible, <command>systemd-repart</command> will try to merge the two definitions to create
one large ESP, but if that's not allowable due to the existing conditions on disk a small ESP and a
large XBOOTLDR will be created instead.</para>
<para>As another example, distributions can also use this to seamlessly share a single
<filename>/home</filename> partition in a multi-boot scenario, while preferring to keep
<filename>/home</filename> on the root partition by default. Having a <filename>/home</filename>
partition separated from the root partition entails some extra complexity: someone has to decide how
to split the space between the two partitions. On the other hand, it allows a user to share their
home area between multiple installed OSs (i.e. via <citerefentry><refentrytitle>systemd-homed.service
</refentrytitle><manvolnum>8</manvolnum></citerefentry>). Distributions should create two definitions:
the first for a root partition that takes up some relatively small percentage of the disk, and the
second as a supplement for the first to create a <filename>/home</filename> partition that takes up
all the remaining free space. On first boot, if <command>systemd-repart</command> finds an existing
<filename>/home</filename> partition on disk, it'll un-merge the definitions and create just a small
root partition. Otherwise, the definitions will be merged and a single large root partition will be
created.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
</variablelist>
</refsect1>

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

@ -300,9 +300,10 @@ int log_emergency_level(void);
#define log_dump(level, buffer) \
log_dump_internal(level, 0, PROJECT_FILE, __LINE__, __func__, buffer)
#define log_oom() log_oom_internal(LOG_ERR, PROJECT_FILE, __LINE__, __func__)
#define log_oom_debug() log_oom_internal(LOG_DEBUG, PROJECT_FILE, __LINE__, __func__)
#define log_oom_warning() log_oom_internal(LOG_WARNING, PROJECT_FILE, __LINE__, __func__)
#define log_oom_full(level) log_oom_internal(level, PROJECT_FILE, __LINE__, __func__)
#define log_oom() log_oom_full(LOG_ERR)
#define log_oom_debug() log_oom_full(LOG_DEBUG)
#define log_oom_warning() log_oom_full(LOG_WARNING)
bool log_on_console(void) _pure_;

View File

@ -153,7 +153,7 @@ bool strv_overlap(char * const *a, char * const *b) _pure_;
_STRV_FOREACH_BACKWARDS(s, l, UNIQ_T(h, UNIQ), UNIQ_T(i, UNIQ))
#define _STRV_FOREACH_PAIR(x, y, l, i) \
for (typeof(*l) *x, *y, *i = (l); \
for (typeof(*(l)) *x, *y, *i = (l); \
i && *(x = i) && *(y = i + 1); \
i += 2)

View File

@ -255,6 +255,25 @@ int ask_string(char **ret, const char *text, ...) {
return 0;
}
bool any_key_to_proceed(void) {
char key = 0;
bool need_nl = true;
/*
* Insert a new line here as well as to when the user inputs, as this is also used during the
* boot up sequence when status messages may be interleaved with the current program output.
* This ensures that the status messages aren't appended on the same line as this message.
*/
puts("-- Press any key to proceed --");
(void) read_one_char(stdin, &key, USEC_INFINITY, &need_nl);
if (need_nl)
putchar('\n');
return key != 'q';
}
int open_terminal(const char *name, int mode) {
_cleanup_close_ int fd = -EBADF;
unsigned c = 0;

View File

@ -78,6 +78,7 @@ int chvt(int vt);
int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl);
int ask_char(char *ret, const char *replies, const char *text, ...) _printf_(3, 4);
int ask_string(char **ret, const char *text, ...) _printf_(2, 3);
bool any_key_to_proceed(void);
int vt_disallocate(const char *name);

View File

@ -93,20 +93,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_root_shell, freep);
STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
static bool press_any_key(void) {
char k = 0;
bool need_nl = true;
puts("-- Press any key to proceed --");
(void) read_one_char(stdin, &k, USEC_INFINITY, &need_nl);
if (need_nl)
putchar('\n');
return k != 'q';
}
static void print_welcome(int rfd) {
_cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL;
static bool done = false;
@ -141,7 +127,7 @@ static void print_welcome(int rfd) {
printf("\nPlease configure your system!\n\n");
press_any_key();
any_key_to_proceed();
done = true;
}
@ -184,7 +170,7 @@ static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned perc
/* on the first screen we reserve 2 extra lines for the title */
if (i % break_lines == break_modulo) {
if (!press_any_key())
if (!any_key_to_proceed())
return 0;
}
}

View File

@ -2434,6 +2434,8 @@ static int create_interactively(void) {
return 0;
}
any_key_to_proceed();
r = acquire_bus(&bus);
if (r < 0)
return r;

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

@ -973,7 +973,7 @@ int netdev_load_one(Manager *manager, const char *filename) {
if (r < 0)
return r;
log_netdev_debug(netdev, "loaded \"%s\"", netdev_kind_to_string(netdev->kind));
log_syntax(/* unit = */ NULL, LOG_DEBUG, filename, /* config_line = */ 0, /* error = */ 0, "Successfully loaded.");
r = netdev_request_to_create(netdev);
if (r < 0)

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

@ -6,6 +6,7 @@
#include <linux/if_arp.h>
#include "alloc-util.h"
#include "device-private.h"
#include "dhcp-client-internal.h"
#include "hostname-setup.h"
#include "hostname-util.h"
@ -1428,27 +1429,33 @@ static int dhcp4_set_request_address(Link *link) {
}
static bool link_needs_dhcp_broadcast(Link *link) {
const char *val;
int r;
assert(link);
assert(link->network);
/* Return the setting in DHCP[4].RequestBroadcast if specified. Otherwise return the device property
* ID_NET_DHCP_BROADCAST setting, which may be set for interfaces requiring that the DHCPOFFER message
* is being broadcast because they can't handle unicast messages while not fully configured.
* If neither is set or a failure occurs, return false, which is the default for this flag.
*/
r = link->network->dhcp_broadcast;
if (r < 0 && link->dev && sd_device_get_property_value(link->dev, "ID_NET_DHCP_BROADCAST", &val) >= 0) {
r = parse_boolean(val);
if (r < 0)
log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to parse ID_NET_DHCP_BROADCAST, ignoring: %m");
else
log_link_debug(link, "DHCPv4 CLIENT: Detected ID_NET_DHCP_BROADCAST='%d'.", r);
* ID_NET_DHCP_BROADCAST setting, which may be set for interfaces requiring that the DHCPOFFER
* message is being broadcast because they can't handle unicast messages while not fully configured.
* If neither is set or a failure occurs, return false, which is the default for this flag. */
r = link->network->dhcp_broadcast;
if (r >= 0)
return r;
if (!link->dev)
return false;
r = device_get_property_bool(link->dev, "ID_NET_DHCP_BROADCAST");
if (r < 0) {
if (r != -ENOENT)
log_link_warning_errno(link, r, "DHCPv4 CLIENT: Failed to get or parse ID_NET_DHCP_BROADCAST, ignoring: %m");
return false;
}
return r == true;
log_link_debug(link, "DHCPv4 CLIENT: Detected ID_NET_DHCP_BROADCAST='%d'.", r);
return r;
}
static bool link_dhcp4_ipv6_only_mode(Link *link) {
@ -1557,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

@ -1293,9 +1293,9 @@ static int link_get_network(Link *link, Network **ret) {
}
log_link_full(link, warn ? LOG_WARNING : LOG_DEBUG,
"found matching network '%s'%s.",
network->filename,
warn ? ", based on potentially unpredictable interface name" : "");
"Found matching .network file%s: %s",
warn ? ", based on potentially unpredictable interface name" : "",
network->filename);
if (network->unmanaged)
return -ENOENT;
@ -1304,7 +1304,7 @@ static int link_get_network(Link *link, Network **ret) {
return 0;
}
return -ENOENT;
return log_link_debug_errno(link, SYNTHETIC_ERRNO(ENOENT), "No matching .network found.");
}
int link_reconfigure_impl(Link *link, bool force) {

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,
@ -590,6 +593,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
return log_warning_errno(r, "%s: Failed to store configuration into hashmap: %m", filename);
TAKE_PTR(network);
log_syntax(/* unit = */ NULL, LOG_DEBUG, filename, /* config_line = */ 0, /* error = */ 0, "Successfully loaded.");
return 0;
}

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

@ -404,6 +404,10 @@ typedef struct Partition {
PartitionEncryptedVolume *encrypted_volume;
char *supplement_for_name;
struct Partition *supplement_for, *supplement_target_for;
struct Partition *suppressing;
struct Partition *siblings[_VERITY_MODE_MAX];
LIST_FIELDS(struct Partition, partitions);
@ -411,6 +415,7 @@ typedef struct Partition {
#define PARTITION_IS_FOREIGN(p) (!(p)->definition_path)
#define PARTITION_EXISTS(p) (!!(p)->current_partition)
#define PARTITION_SUPPRESSED(p) ((p)->supplement_for && (p)->supplement_for->suppressing == (p))
struct FreeArea {
Partition *after;
@ -520,6 +525,28 @@ static Partition *partition_new(void) {
return p;
}
static void partition_unlink_supplement(Partition *p) {
assert(p);
assert(!p->supplement_for || !p->supplement_target_for); /* Can't be both */
if (p->supplement_target_for) {
assert(p->supplement_target_for->supplement_for == p);
p->supplement_target_for->supplement_for = NULL;
}
if (p->supplement_for) {
assert(p->supplement_for->supplement_target_for == p);
assert(!p->supplement_for->suppressing || p->supplement_for->suppressing == p);
p->supplement_for->supplement_target_for = p->supplement_for->suppressing = NULL;
}
p->supplement_for_name = mfree(p->supplement_for_name);
p->supplement_target_for = p->supplement_for = p->suppressing = NULL;
}
static Partition* partition_free(Partition *p) {
if (!p)
return NULL;
@ -563,6 +590,8 @@ static Partition* partition_free(Partition *p) {
partition_encrypted_volume_free(p->encrypted_volume);
partition_unlink_supplement(p);
return mfree(p);
}
@ -608,6 +637,8 @@ static void partition_foreignize(Partition *p) {
p->n_mountpoints = 0;
p->encrypted_volume = partition_encrypted_volume_free(p->encrypted_volume);
partition_unlink_supplement(p);
}
static bool partition_type_exclude(const GptPartitionType *type) {
@ -740,6 +771,10 @@ static void partition_drop_or_foreignize(Partition *p) {
p->dropped = true;
p->allocated_to_area = NULL;
/* If a supplement partition is dropped, we don't want to merge in its settings. */
if (PARTITION_SUPPRESSED(p))
p->supplement_for->suppressing = NULL;
}
}
@ -775,7 +810,7 @@ static bool context_drop_or_foreignize_one_priority(Context *context) {
}
static uint64_t partition_min_size(const Context *context, const Partition *p) {
uint64_t sz;
uint64_t sz, override_min;
assert(context);
assert(p);
@ -817,11 +852,13 @@ static uint64_t partition_min_size(const Context *context, const Partition *p) {
sz = d;
}
return MAX(round_up_size(p->size_min != UINT64_MAX ? p->size_min : DEFAULT_MIN_SIZE, context->grain_size), sz);
override_min = p->suppressing ? MAX(p->size_min, p->suppressing->size_min) : p->size_min;
return MAX(round_up_size(override_min != UINT64_MAX ? override_min : DEFAULT_MIN_SIZE, context->grain_size), sz);
}
static uint64_t partition_max_size(const Context *context, const Partition *p) {
uint64_t sm;
uint64_t sm, override_max;
/* Calculate how large the partition may become at max. This is generally the configured maximum
* size, except when it already exists and is larger than that. In that case it's the existing size,
@ -839,10 +876,11 @@ static uint64_t partition_max_size(const Context *context, const Partition *p) {
if (p->verity == VERITY_SIG)
return VERITY_SIG_SIZE;
if (p->size_max == UINT64_MAX)
override_max = p->suppressing ? MIN(p->size_max, p->suppressing->size_max) : p->size_max;
if (override_max == UINT64_MAX)
return UINT64_MAX;
sm = round_down_size(p->size_max, context->grain_size);
sm = round_down_size(override_max, context->grain_size);
if (p->current_size != UINT64_MAX)
sm = MAX(p->current_size, sm);
@ -851,13 +889,17 @@ static uint64_t partition_max_size(const Context *context, const Partition *p) {
}
static uint64_t partition_min_padding(const Partition *p) {
uint64_t override_min;
assert(p);
return p->padding_min != UINT64_MAX ? p->padding_min : 0;
override_min = p->suppressing ? MAX(p->padding_min, p->suppressing->padding_min) : p->padding_min;
return override_min != UINT64_MAX ? override_min : 0;
}
static uint64_t partition_max_padding(const Partition *p) {
assert(p);
return p->padding_max;
return p->suppressing ? MIN(p->padding_max, p->suppressing->padding_max) : p->padding_max;
}
static uint64_t partition_min_size_with_padding(Context *context, const Partition *p) {
@ -977,14 +1019,22 @@ static bool context_allocate_partitions(Context *context, uint64_t *ret_largest_
uint64_t required;
FreeArea *a = NULL;
/* Skip partitions we already dropped or that already exist */
if (p->dropped || PARTITION_EXISTS(p))
if (p->dropped || PARTITION_IS_FOREIGN(p) || PARTITION_SUPPRESSED(p))
continue;
/* How much do we need to fit? */
required = partition_min_size_with_padding(context, p);
assert(required % context->grain_size == 0);
/* For existing partitions, we should verify that they'll actually fit */
if (PARTITION_EXISTS(p)) {
if (p->current_size + p->current_padding < required)
return false; /* 😢 We won't be able to grow to the required min size! */
continue;
}
/* For new partitions, see if there's a free area big enough */
for (size_t i = 0; i < context->n_free_areas; i++) {
a = context->free_areas[i];
@ -1007,6 +1057,57 @@ static bool context_allocate_partitions(Context *context, uint64_t *ret_largest_
return true;
}
static bool context_unmerge_and_allocate_partitions(Context *context) {
assert(context);
/* This should only be called after plain context_allocate_partitions fails. This algorithm will
* try, in the order that minimizes the number of created supplement partitions, all combinations of
* un-suppressing supplement partitions until it finds one that works. */
/* First, let's try to un-suppress just one supplement partition and see if that gets us anywhere */
LIST_FOREACH(partitions, p, context->partitions) {
Partition *unsuppressed;
if (!p->suppressing)
continue;
unsuppressed = TAKE_PTR(p->suppressing);
if (context_allocate_partitions(context, NULL))
return true;
p->suppressing = unsuppressed;
}
/* Looks like not. So we have to un-suppress at least two partitions. We can do this recursively */
LIST_FOREACH(partitions, p, context->partitions) {
Partition *unsuppressed;
if (!p->suppressing)
continue;
unsuppressed = TAKE_PTR(p->suppressing);
if (context_unmerge_and_allocate_partitions(context))
return true;
p->suppressing = unsuppressed;
}
/* No combination of un-suppressed supplements made it possible to fit the partitions */
return false;
}
static uint32_t partition_weight(const Partition *p) {
assert(p);
return p->suppressing ? p->suppressing->weight : p->weight;
}
static uint32_t partition_padding_weight(const Partition *p) {
assert(p);
return p->suppressing ? p->suppressing->padding_weight : p->padding_weight;
}
static int context_sum_weights(Context *context, FreeArea *a, uint64_t *ret) {
uint64_t weight_sum = 0;
@ -1020,13 +1121,11 @@ static int context_sum_weights(Context *context, FreeArea *a, uint64_t *ret) {
if (p->padding_area != a && p->allocated_to_area != a)
continue;
if (p->weight > UINT64_MAX - weight_sum)
if (!INC_SAFE(&weight_sum, partition_weight(p)))
goto overflow_sum;
weight_sum += p->weight;
if (p->padding_weight > UINT64_MAX - weight_sum)
if (!INC_SAFE(&weight_sum, partition_padding_weight(p)))
goto overflow_sum;
weight_sum += p->padding_weight;
}
*ret = weight_sum;
@ -1091,7 +1190,6 @@ static bool context_grow_partitions_phase(
* get any additional room from the left-overs. Similar, if two partitions have the same weight they
* should get the same space if possible, even if one has a smaller minimum size than the other. */
LIST_FOREACH(partitions, p, context->partitions) {
/* Look only at partitions associated with this free area, i.e. immediately
* preceding it, or allocated into it */
if (p->allocated_to_area != a && p->padding_area != a)
@ -1099,11 +1197,14 @@ static bool context_grow_partitions_phase(
if (p->new_size == UINT64_MAX) {
uint64_t share, rsz, xsz;
uint32_t weight;
bool charge = false;
weight = partition_weight(p);
/* Calculate how much this space this partition needs if everyone would get
* the weight based share */
share = scale_by_weight(*span, p->weight, *weight_sum);
share = scale_by_weight(*span, weight, *weight_sum);
rsz = partition_min_size(context, p);
xsz = partition_max_size(context, p);
@ -1143,15 +1244,18 @@ static bool context_grow_partitions_phase(
if (charge) {
*span = charge_size(context, *span, p->new_size);
*weight_sum = charge_weight(*weight_sum, p->weight);
*weight_sum = charge_weight(*weight_sum, weight);
}
}
if (p->new_padding == UINT64_MAX) {
uint64_t share, rsz, xsz;
uint32_t padding_weight;
bool charge = false;
share = scale_by_weight(*span, p->padding_weight, *weight_sum);
padding_weight = partition_padding_weight(p);
share = scale_by_weight(*span, padding_weight, *weight_sum);
rsz = partition_min_padding(p);
xsz = partition_max_padding(p);
@ -1170,7 +1274,7 @@ static bool context_grow_partitions_phase(
if (charge) {
*span = charge_size(context, *span, p->new_padding);
*weight_sum = charge_weight(*weight_sum, p->padding_weight);
*weight_sum = charge_weight(*weight_sum, padding_weight);
}
}
}
@ -2155,7 +2259,9 @@ static int partition_finalize_fstype(Partition *p, const char *path) {
static bool partition_needs_populate(const Partition *p) {
assert(p);
return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || !strv_isempty(p->make_symlinks);
assert(!p->supplement_for || !p->suppressing); /* Avoid infinite recursion */
return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || !strv_isempty(p->make_symlinks) ||
(p->suppressing && partition_needs_populate(p->suppressing));
}
static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
@ -2196,6 +2302,7 @@ static int partition_read_definition(Partition *p, const char *path, const char
{ "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p },
{ "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression },
{ "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level },
{ "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name },
{}
};
_cleanup_free_ char *filename = NULL;
@ -2320,6 +2427,18 @@ static int partition_read_definition(Partition *p, const char *path, const char
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"DefaultSubvolume= must be one of the paths in Subvolumes=.");
if (p->supplement_for_name) {
if (!filename_part_is_valid(p->supplement_for_name))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"SupplementFor= is an invalid filename: %s",
p->supplement_for_name);
if (p->copy_blocks_path || p->copy_blocks_auto || p->encrypt != ENCRYPT_OFF ||
p->verity != VERITY_OFF)
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"SupplementFor= cannot be combined with CopyBlocks=/Encrypt=/Verity=");
}
/* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */
if ((IN_SET(p->type.designator,
PARTITION_ROOT_VERITY,
@ -2626,6 +2745,58 @@ static int context_copy_from(Context *context) {
return 0;
}
static bool check_cross_def_ranges_valid(uint64_t a_min, uint64_t a_max, uint64_t b_min, uint64_t b_max) {
if (a_min == UINT64_MAX && b_min == UINT64_MAX)
return true;
if (a_max == UINT64_MAX && b_max == UINT64_MAX)
return true;
return MAX(a_min != UINT64_MAX ? a_min : 0, b_min != UINT64_MAX ? b_min : 0) <= MIN(a_max, b_max);
}
static int supplement_find_target(const Context *context, const Partition *supplement, Partition **ret) {
int r;
assert(context);
assert(supplement);
assert(ret);
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_free_ char *filename = NULL;
if (p == supplement)
continue;
r = path_extract_filename(p->definition_path, &filename);
if (r < 0)
return log_error_errno(r,
"Failed to extract filename from path '%s': %m",
p->definition_path);
*ASSERT_PTR(endswith(filename, ".conf")) = 0; /* Remove the file extension */
if (!streq(supplement->supplement_for_name, filename))
continue;
if (p->supplement_for_name)
return log_syntax(NULL, LOG_ERR, supplement->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"SupplementFor= target is itself configured as a supplement.");
if (p->suppressing)
return log_syntax(NULL, LOG_ERR, supplement->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"SupplementFor= target already has a supplement defined: %s",
p->suppressing->definition_path);
*ret = p;
return 0;
}
return log_syntax(NULL, LOG_ERR, supplement->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"Couldn't find target partition for SupplementFor=%s",
supplement->supplement_for_name);
}
static int context_read_definitions(Context *context) {
_cleanup_strv_free_ char **files = NULL;
Partition *last = LIST_FIND_TAIL(partitions, context->partitions);
@ -2717,7 +2888,33 @@ static int context_read_definitions(Context *context) {
if (dp->minimize == MINIMIZE_OFF && !(dp->copy_blocks_path || dp->copy_blocks_auto))
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"Minimize= set for verity hash partition but data partition does not set CopyBlocks= or Minimize=.");
}
LIST_FOREACH(partitions, p, context->partitions) {
Partition *tgt = NULL;
if (!p->supplement_for_name)
continue;
r = supplement_find_target(context, p, &tgt);
if (r < 0)
return r;
if (tgt->copy_blocks_path || tgt->copy_blocks_auto || tgt->encrypt != ENCRYPT_OFF ||
tgt->verity != VERITY_OFF)
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"SupplementFor= target uses CopyBlocks=/Encrypt=/Verity=");
if (!check_cross_def_ranges_valid(p->size_min, p->size_max, tgt->size_min, tgt->size_max))
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"SizeMinBytes= larger than SizeMaxBytes= when merged with SupplementFor= target.");
if (!check_cross_def_ranges_valid(p->padding_min, p->padding_max, tgt->padding_min, tgt->padding_max))
return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL),
"PaddingMinBytes= larger than PaddingMaxBytes= when merged with SupplementFor= target.");
p->supplement_for = tgt;
tgt->suppressing = tgt->supplement_target_for = p;
}
return 0;
@ -3101,6 +3298,10 @@ static int context_load_partition_table(Context *context) {
}
}
LIST_FOREACH(partitions, p, context->partitions)
if (PARTITION_SUPPRESSED(p) && PARTITION_EXISTS(p))
p->supplement_for->suppressing = NULL;
add_initial_free_area:
nsectors = fdisk_get_nsectors(c);
assert(nsectors <= UINT64_MAX/secsz);
@ -3192,6 +3393,11 @@ static void context_unload_partition_table(Context *context) {
p->current_uuid = SD_ID128_NULL;
p->current_label = mfree(p->current_label);
/* A supplement partition is only ever un-suppressed if the existing partition table prevented
* us from suppressing it. So when unloading the partition table, we must re-suppress. */
if (p->supplement_for)
p->supplement_for->suppressing = p;
}
context->start = UINT64_MAX;
@ -4969,6 +5175,31 @@ static int add_exclude_path(const char *path, Hashmap **denylist, DenyType type)
return 0;
}
static int shallow_join_strv(char ***ret, char **a, char **b) {
_cleanup_free_ char **joined = NULL;
char **iter;
assert(ret);
joined = new(char*, strv_length(a) + strv_length(b) + 1);
if (!joined)
return log_oom();
iter = joined;
STRV_FOREACH(i, a)
*(iter++) = *i;
STRV_FOREACH(i, b)
if (!strv_contains(joined, *i))
*(iter++) = *i;
*iter = NULL;
*ret = TAKE_PTR(joined);
return 0;
}
static int make_copy_files_denylist(
Context *context,
const Partition *p,
@ -4977,6 +5208,7 @@ static int make_copy_files_denylist(
Hashmap **ret) {
_cleanup_hashmap_free_ Hashmap *denylist = NULL;
_cleanup_free_ char **override_exclude_src = NULL, **override_exclude_tgt = NULL;
int r;
assert(context);
@ -4996,13 +5228,26 @@ static int make_copy_files_denylist(
/* Add the user configured excludes. */
STRV_FOREACH(e, p->exclude_files_source) {
if (p->suppressing) {
r = shallow_join_strv(&override_exclude_src,
p->exclude_files_source,
p->suppressing->exclude_files_source);
if (r < 0)
return r;
r = shallow_join_strv(&override_exclude_tgt,
p->exclude_files_target,
p->suppressing->exclude_files_target);
if (r < 0)
return r;
}
STRV_FOREACH(e, override_exclude_src ?: p->exclude_files_source) {
r = add_exclude_path(*e, &denylist, endswith(*e, "/") ? DENY_CONTENTS : DENY_INODE);
if (r < 0)
return r;
}
STRV_FOREACH(e, p->exclude_files_target) {
STRV_FOREACH(e, override_exclude_tgt ?: p->exclude_files_target) {
_cleanup_free_ char *path = NULL;
const char *s = path_startswith(*e, target);
@ -5096,6 +5341,7 @@ static int add_subvolume_path(const char *path, Set **subvolumes) {
static int make_subvolumes_strv(const Partition *p, char ***ret) {
_cleanup_strv_free_ char **subvolumes = NULL;
Subvolume *subvolume;
int r;
assert(p);
assert(ret);
@ -5104,6 +5350,18 @@ static int make_subvolumes_strv(const Partition *p, char ***ret) {
if (strv_extend(&subvolumes, subvolume->path) < 0)
return log_oom();
if (p->suppressing) {
_cleanup_strv_free_ char **suppressing = NULL;
r = make_subvolumes_strv(p->suppressing, &suppressing);
if (r < 0)
return r;
r = strv_extend_strv(&subvolumes, suppressing, /* filter_duplicates= */ true);
if (r < 0)
return log_oom();
}
*ret = TAKE_PTR(subvolumes);
return 0;
}
@ -5114,18 +5372,22 @@ static int make_subvolumes_set(
const char *target,
Set **ret) {
_cleanup_strv_free_ char **paths = NULL;
_cleanup_set_free_ Set *subvolumes = NULL;
Subvolume *subvolume;
int r;
assert(p);
assert(target);
assert(ret);
ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) {
r = make_subvolumes_strv(p, &paths);
if (r < 0)
return r;
STRV_FOREACH(subvolume, paths) {
_cleanup_free_ char *path = NULL;
const char *s = path_startswith(subvolume->path, target);
const char *s = path_startswith(*subvolume, target);
if (!s)
continue;
@ -5168,6 +5430,7 @@ static usec_t epoch_or_infinity(void) {
static int do_copy_files(Context *context, Partition *p, const char *root) {
_cleanup_strv_free_ char **subvolumes = NULL;
_cleanup_free_ char **override_copy_files = NULL;
int r;
assert(p);
@ -5177,11 +5440,17 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
if (r < 0)
return r;
if (p->suppressing) {
r = shallow_join_strv(&override_copy_files, p->copy_files, p->suppressing->copy_files);
if (r < 0)
return r;
}
/* copy_tree_at() automatically copies the permissions of source directories to target directories if
* it created them. However, the root directory is created by us, so we have to manually take care
* that it is initialized. We use the first source directory targeting "/" as the metadata source for
* the root directory. */
STRV_FOREACH_PAIR(source, target, p->copy_files) {
STRV_FOREACH_PAIR(source, target, override_copy_files ?: p->copy_files) {
_cleanup_close_ int rfd = -EBADF, sfd = -EBADF;
if (!path_equal(*target, "/"))
@ -5202,7 +5471,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
break;
}
STRV_FOREACH_PAIR(source, target, p->copy_files) {
STRV_FOREACH_PAIR(source, target, override_copy_files ?: p->copy_files) {
_cleanup_hashmap_free_ Hashmap *denylist = NULL;
_cleanup_set_free_ Set *subvolumes_by_source_inode = NULL;
_cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF;
@ -5320,6 +5589,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
static int do_make_directories(Partition *p, const char *root) {
_cleanup_strv_free_ char **subvolumes = NULL;
_cleanup_free_ char **override_dirs = NULL;
int r;
assert(p);
@ -5329,7 +5599,13 @@ static int do_make_directories(Partition *p, const char *root) {
if (r < 0)
return r;
STRV_FOREACH(d, p->make_directories) {
if (p->suppressing) {
r = shallow_join_strv(&override_dirs, p->make_directories, p->suppressing->make_directories);
if (r < 0)
return r;
}
STRV_FOREACH(d, override_dirs ?: p->make_directories) {
r = mkdir_p_root_full(root, *d, UID_INVALID, GID_INVALID, 0755, epoch_or_infinity(), subvolumes);
if (r < 0)
return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d);
@ -5377,6 +5653,12 @@ static int make_subvolumes_read_only(Partition *p, const char *root) {
return log_error_errno(r, "Failed to make subvolume '%s' read-only: %m", subvolume->path);
}
if (p->suppressing) {
r = make_subvolumes_read_only(p->suppressing, root);
if (r < 0)
return r;
}
return 0;
}
@ -5496,6 +5778,38 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c
return 0;
}
static int append_btrfs_subvols(char ***l, OrderedHashmap *subvolumes, const char *default_subvolume) {
Subvolume *subvolume;
int r;
assert(l);
ORDERED_HASHMAP_FOREACH(subvolume, subvolumes) {
_cleanup_free_ char *s = NULL, *f = NULL;
s = strdup(subvolume->path);
if (!s)
return log_oom();
f = subvolume_flags_to_string(subvolume->flags);
if (!f)
return log_oom();
if (streq_ptr(subvolume->path, default_subvolume) &&
!strextend_with_separator(&f, ",", "default"))
return log_oom();
if (!isempty(f) && !strextend_with_separator(&s, ":", f))
return log_oom();
r = strv_extend_many(l, "--subvol", s);
if (r < 0)
return log_oom();
}
return 0;
}
static int finalize_extra_mkfs_options(const Partition *p, const char *root, char ***ret) {
_cleanup_strv_free_ char **sv = NULL;
int r;
@ -5510,28 +5824,14 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha
p->format);
if (partition_needs_populate(p) && root && streq(p->format, "btrfs")) {
Subvolume *subvolume;
r = append_btrfs_subvols(&sv, p->subvolumes, p->default_subvolume);
if (r < 0)
return r;
ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) {
_cleanup_free_ char *s = NULL, *f = NULL;
s = strdup(subvolume->path);
if (!s)
return log_oom();
f = subvolume_flags_to_string(subvolume->flags);
if (!f)
return log_oom();
if (streq_ptr(subvolume->path, p->default_subvolume) && !strextend_with_separator(&f, ",", "default"))
return log_oom();
if (!isempty(f) && !strextend_with_separator(&s, ":", f))
return log_oom();
r = strv_extend_many(&sv, "--subvol", s);
if (p->suppressing) {
r = append_btrfs_subvols(&sv, p->suppressing->subvolumes, NULL);
if (r < 0)
return log_oom();
return r;
}
}
@ -8524,7 +8824,7 @@ static int determine_auto_size(Context *c) {
LIST_FOREACH(partitions, p, c->partitions) {
uint64_t m;
if (p->dropped)
if (p->dropped || PARTITION_SUPPRESSED(p))
continue;
m = partition_min_size_with_padding(c, p);
@ -8756,13 +9056,36 @@ static int run(int argc, char *argv[]) {
if (context_allocate_partitions(context, &largest_free_area))
break; /* Success! */
if (!context_drop_or_foreignize_one_priority(context)) {
r = log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
"Can't fit requested partitions into available free space (%s), refusing.",
FORMAT_BYTES(largest_free_area));
determine_auto_size(context);
return r;
}
if (context_unmerge_and_allocate_partitions(context))
break; /* We had to un-suppress a supplement or few, but still success! */
if (context_drop_or_foreignize_one_priority(context))
continue; /* Still no luck. Let's drop a priority and try again. */
/* No more priorities left to drop. This configuration just doesn't fit on this disk... */
r = log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
"Can't fit requested partitions into available free space (%s), refusing.",
FORMAT_BYTES(largest_free_area));
determine_auto_size(context);
return r;
}
LIST_FOREACH(partitions, p, context->partitions) {
if (!p->supplement_for)
continue;
if (PARTITION_SUPPRESSED(p)) {
assert(!p->allocated_to_area);
p->dropped = true;
log_debug("Partition %s can be merged into %s, suppressing supplement.",
p->definition_path, p->supplement_for->definition_path);
} else if (PARTITION_EXISTS(p))
log_info("Partition %s already exists on disk, using supplement verbatim.",
p->definition_path);
else
log_info("Couldn't allocate partitions with %s merged into %s, using supplement verbatim.",
p->definition_path, p->supplement_for->definition_path);
}
/* Now assign free space according to the weight logic */

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

@ -465,10 +465,6 @@ int hashmap_put_stats_by_path(Hashmap **stats_by_path, const char *path, const s
assert(path);
assert(st);
r = hashmap_ensure_allocated(stats_by_path, &path_hash_ops_free_free);
if (r < 0)
return r;
st_copy = newdup(struct stat, st, 1);
if (!st_copy)
return -ENOMEM;
@ -477,7 +473,7 @@ int hashmap_put_stats_by_path(Hashmap **stats_by_path, const char *path, const s
if (!path_copy)
return -ENOMEM;
r = hashmap_put(*stats_by_path, path_copy, st_copy);
r = hashmap_ensure_put(stats_by_path, &path_hash_ops_free_free, path_copy, st_copy);
if (r < 0)
return r;
@ -502,12 +498,12 @@ static int config_parse_many_files(
_cleanup_ordered_hashmap_free_ OrderedHashmap *dropins = NULL;
_cleanup_set_free_ Set *inodes = NULL;
struct stat st;
int r;
int r, level = FLAGS_SET(flags, CONFIG_PARSE_WARN) ? LOG_WARNING : LOG_DEBUG;
if (ret_stats_by_path) {
stats_by_path = hashmap_new(&path_hash_ops_free_free);
if (!stats_by_path)
return -ENOMEM;
return log_oom_full(level);
}
STRV_FOREACH(fn, files) {
@ -518,14 +514,14 @@ static int config_parse_many_files(
if (r == -ENOENT)
continue;
if (r < 0)
return r;
return log_full_errno(level, r, "Failed to open %s: %m", *fn);
int fd = fileno(f);
r = ordered_hashmap_ensure_put(&dropins, &config_file_hash_ops_fclose, *fn, f);
if (r < 0) {
assert(r != -EEXIST);
return r;
assert(r == -ENOMEM);
return log_oom_full(level);
}
assert(r > 0);
TAKE_PTR(f);
@ -535,14 +531,14 @@ static int config_parse_many_files(
_cleanup_free_ struct stat *st_dropin = new(struct stat, 1);
if (!st_dropin)
return -ENOMEM;
return log_oom_full(level);
if (fstat(fd, st_dropin) < 0)
return -errno;
return log_full_errno(level, errno, "Failed to stat %s: %m", *fn);
r = set_ensure_consume(&inodes, &inode_hash_ops, TAKE_PTR(st_dropin));
if (r < 0)
return r;
return log_oom_full(level);
}
/* First read the first found main config file. */
@ -553,11 +549,11 @@ static int config_parse_many_files(
if (r == -ENOENT)
continue;
if (r < 0)
return r;
return log_full_errno(level, r, "Failed to open %s: %m", *fn);
if (inodes) {
if (fstat(fileno(f), &st) < 0)
return -errno;
return log_full_errno(level, errno, "Failed to stat %s: %m", *fn);
if (set_contains(inodes, &st)) {
log_debug("%s: symlink to/symlinked as drop-in, will be read later.", *fn);
@ -567,13 +563,13 @@ static int config_parse_many_files(
r = config_parse(/* unit= */ NULL, *fn, f, sections, lookup, table, flags, userdata, &st);
if (r < 0)
return r;
return r; /* config_parse() logs internally. */
assert(r > 0);
if (ret_stats_by_path) {
r = hashmap_put_stats_by_path(&stats_by_path, *fn, &st);
if (r < 0)
return r;
return log_full_errno(level, r, "Failed to save stats of %s: %m", *fn);
}
break;
@ -586,13 +582,13 @@ static int config_parse_many_files(
ORDERED_HASHMAP_FOREACH_KEY(f_dropin, path_dropin, dropins) {
r = config_parse(/* unit= */ NULL, path_dropin, f_dropin, sections, lookup, table, flags, userdata, &st);
if (r < 0)
return r;
return r; /* config_parse() logs internally. */
assert(r > 0);
if (ret_stats_by_path) {
r = hashmap_put_stats_by_path(&stats_by_path, path_dropin, &st);
if (r < 0)
return r;
return log_full_errno(level, r, "Failed to save stats of %s: %m", path_dropin);
}
}
@ -625,11 +621,12 @@ int config_parse_many(
r = conf_files_list_dropins(&files, dropin_dirname, root, conf_file_dirs);
if (r < 0)
return r;
return log_full_errno(FLAGS_SET(flags, CONFIG_PARSE_WARN) ? LOG_WARNING : LOG_DEBUG, r,
"Failed to list up drop-in configs in %s: %m", dropin_dirname);
r = config_parse_many_files(root, conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path);
if (r < 0)
return r;
return r; /* config_parse_many_files() logs internally. */
if (ret_dropin_files)
*ret_dropin_files = TAKE_PTR(files);
@ -650,22 +647,16 @@ int config_parse_standard_file_with_dropins_full(
const char* const *conf_paths = (const char* const*) CONF_PATHS_STRV("");
_cleanup_strv_free_ char **configs = NULL;
int r;
int r, level = FLAGS_SET(flags, CONFIG_PARSE_WARN) ? LOG_WARNING : LOG_DEBUG;
/* Build the list of main config files */
r = strv_extend_strv_biconcat(&configs, root, conf_paths, main_file);
if (r < 0) {
if (flags & CONFIG_PARSE_WARN)
log_oom();
return r;
}
if (r < 0)
return log_oom_full(level);
_cleanup_free_ char *dropin_dirname = strjoin(main_file, ".d");
if (!dropin_dirname) {
if (flags & CONFIG_PARSE_WARN)
log_oom();
return -ENOMEM;
}
if (!dropin_dirname)
return log_oom_full(level);
return config_parse_many(
(const char* const*) configs,

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

@ -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

@ -526,9 +526,6 @@ def call_systemd_measure(uki, linux, opts):
# First, pick up the sections we shall measure now */
for s in uki.sections:
print(s)
if not s.measure:
continue

View File

@ -0,0 +1,6 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
all setup run clean clean-again:
true
.PHONY: all setup run clean clean-again

View File

@ -0,0 +1,10 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
integration_tests += [
integration_test_template + {
'name' : fs.name(meson.current_source_dir()),
'storage' : 'persistent',
'vm' : true,
'firmware' : 'auto',
},
]

View File

@ -0,0 +1,10 @@
#!/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

@ -376,6 +376,7 @@ foreach dirname : [
'TEST-83-BTRFS',
'TEST-84-STORAGETM',
'TEST-85-NETWORK',
'TEST-86-MULTI-PROFILE-UKI',
]
subdir(dirname)
endforeach

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

@ -29,6 +29,9 @@ if ! systemd-detect-virt --quiet --container; then
udevadm control --log-level debug
fi
esp_guid=C12A7328-F81F-11D2-BA4B-00A0C93EC93B
xbootldr_guid=BC13C2FF-59E6-4262-A352-B275FD6F7172
machine="$(uname -m)"
if [ "${machine}" = "x86_64" ]; then
root_guid=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
@ -1432,6 +1435,82 @@ EOF
systemd-dissect -U "$imgs/mnt"
}
testcase_fallback_partitions() {
local workdir image defs
workdir="$(mktemp --directory "/tmp/test-repart.fallback.XXXXXXXXXX")"
# shellcheck disable=SC2064
trap "rm -rf '${workdir:?}'" RETURN
image="$workdir/image.img"
defs="$workdir/defs"
mkdir "$defs"
tee "$defs/10-esp.conf" <<EOF
[Partition]
Type=esp
Format=vfat
SizeMinBytes=10M
EOF
tee "$defs/20-xbootldr.conf" <<EOF
[Partition]
Type=xbootldr
Format=vfat
SizeMinBytes=100M
SupplementFor=10-esp
EOF
# Blank disk => big ESP should be created
systemd-repart --empty=create --size=auto --dry-run=no --definitions="$defs" "$image"
output=$(sfdisk -d "$image")
assert_in "${image}1 : start= 2048, size= 204800, type=${esp_guid}" "$output"
assert_not_in "${image}2" "$output"
# Disk with small ESP => ESP grows
sfdisk "$image" <<EOF
label: gpt
size=10M, type=${esp_guid}
EOF
systemd-repart --dry-run=no --definitions="$defs" "$image"
output=$(sfdisk -d "$image")
assert_in "${image}1 : start= 2048, size= 204800, type=${esp_guid}" "$output"
assert_not_in "${image}2" "$output"
# Disk with small ESP that can't grow => XBOOTLDR created
truncate -s 150M "$image"
sfdisk "$image" <<EOF
label: gpt
size=10M, type=${esp_guid},
size=10M, type=${root_guid},
EOF
systemd-repart --dry-run=no --definitions="$defs" "$image"
output=$(sfdisk -d "$image")
assert_in "${image}1 : start= 2048, size= 20480, type=${esp_guid}" "$output"
assert_in "${image}3 : start= 43008, size= 264152, type=${xbootldr_guid}" "$output"
# Disk with existing XBOOTLDR partition => XBOOTLDR grows, small ESP created
sfdisk "$image" <<EOF
label: gpt
size=10M, type=${xbootldr_guid},
EOF
systemd-repart --dry-run=no --definitions="$defs" "$image"
output=$(sfdisk -d "$image")
assert_in "${image}1 : start= 2048, size= 204800, type=${xbootldr_guid}" "$output"
assert_in "${image}2 : start= 206848, size= 100312, type=${esp_guid}" "$output"
}
OFFLINE="yes"
run_testcases

View File

@ -0,0 +1,81 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -eux
set -o pipefail
export SYSTEMD_LOG_LEVEL=debug
bootctl
CURRENT_UKI=$(bootctl --print-stub-path)
echo "CURRENT UKI ($CURRENT_UKI):"
ukify inspect "$CURRENT_UKI"
if test -f /run/systemd/stub/profile; then
echo "CURRENT PROFILE:"
cat /run/systemd/stub/profile
fi
echo "CURRENT MEASUREMENT:"
/usr/lib/systemd/systemd-measure --current
if test -f /run/systemd/tpm2-pcr-signature.json ; then
echo "CURRENT SIGNATURE:"
jq < /run/systemd/tpm2-pcr-signature.json
fi
echo "CURRENT EVENT LOG + PCRS:"
/usr/lib/systemd/systemd-pcrlock
if test ! -f /run/systemd/stub/profile; then
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out /root/pcrsign.private.pem
openssl rsa -pubout -in /root/pcrsign.private.pem -out /root/pcrsign.public.pem
ukify build --extend="$CURRENT_UKI" --output=/tmp/extended0.efi --profile='ID=profile0
TITLE="Profile Zero"' --measure-base="$CURRENT_UKI" --pcr-private-key=/root/pcrsign.private.pem --pcr-public-key=/root/pcrsign.public.pem --pcr-banks=sha256,sha384,sha512
ukify build --extend=/tmp/extended0.efi --output=/tmp/extended1.efi --profile='ID=profile1
TITLE="Profile One"' --measure-base=/tmp/extended0.efi --cmdline="testprofile1=1 $(cat /proc/cmdline)" --pcr-private-key=/root/pcrsign.private.pem --pcr-public-key=/root/pcrsign.public.pem --pcr-banks=sha256,sha384,sha512
ukify build --extend=/tmp/extended1.efi --output=/tmp/extended2.efi --profile='ID=profile2
TITLE="Profile Two"' --measure-base=/tmp/extended1.efi --cmdline="testprofile2=1 $(cat /proc/cmdline)" --pcr-private-key=/root/pcrsign.private.pem --pcr-public-key=/root/pcrsign.public.pem --pcr-banks=sha256,sha384,sha512
echo "EXTENDED UKI:"
ukify inspect /tmp/extended2.efi
rm /tmp/extended0.efi /tmp/extended1.efi
mv /tmp/extended2.efi "$CURRENT_UKI"
# 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
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
reboot
exit 0
else
# shellcheck source=/dev/null
. /run/systemd/stub/profile
# Validate that with the current profile we can fulfill the PCR 11 policy
systemd-cryptsetup attach multiprof /root/encrypted.raw - tpm2-device=auto,headless=1
systemd-cryptsetup detach multiprof
if [ "$ID" = "profile0" ]; then
grep -v testprofile /proc/cmdline
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"
reboot
exit 0
elif [ "$ID" = "profile2" ]; then
grep testprofile2=1 /proc/cmdline
rm /root/encrypted.raw
else
exit 1
fi
fi
touch /testok