Compare commits
38 Commits
8d4fa46e9a
...
35b04b016d
Author | SHA1 | Date |
---|---|---|
Ronan Pigott | 35b04b016d | |
Daan De Meyer | 7a7f306b6c | |
Yu Watanabe | 4f2975385f | |
Daan De Meyer | 0432e28394 | |
Yu Watanabe | fc956a3973 | |
Yu Watanabe | b0dbb4aa3a | |
Michael Ferrari | 91ea3dcf35 | |
Yu Watanabe | a95ae2d36a | |
Yu Watanabe | be8e4b1a87 | |
Adrian Vovk | cf612c5fd5 | |
Adrian Vovk | 2cb9c68c3a | |
Adrian Vovk | 78e9059208 | |
Adrian Vovk | e671bdc5c3 | |
Yu Watanabe | 572d031eca | |
Yu Watanabe | 25da422bd1 | |
Yu Watanabe | 5872ea7008 | |
Lennart Poettering | a2369d0224 | |
Lennart Poettering | a37640653c | |
Ronan Pigott | 9e3482d2df | |
Ronan Pigott | 62ad75c295 | |
Ronan Pigott | 7db6e9c52c | |
Ronan Pigott | 9b9c04c23f | |
Ronan Pigott | afad8b936b | |
Ronan Pigott | 41d515edc4 | |
Ronan Pigott | 748ecb5236 | |
Ronan Pigott | 684ed0e352 | |
Ronan Pigott | 2fab384cfe | |
Ronan Pigott | 257be1d320 | |
Ronan Pigott | 8f69c9ce0f | |
Ronan Pigott | 60722f1a83 | |
Ronan Pigott | a525df4d7d | |
Ronan Pigott | 4f16e209d3 | |
Ronan Pigott | 476ce3a795 | |
Ronan Pigott | ec0e5398f3 | |
Ronan Pigott | 26f9f2677d | |
Ronan Pigott | 25c33e3500 | |
Ronan Pigott | 1e2ead52e1 | |
Ronan Pigott | 427166c3b0 |
2
TODO
2
TODO
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2434,6 +2434,8 @@ static int create_interactively(void) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
any_key_to_proceed();
|
||||
|
||||
r = acquire_bus(&bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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*);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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',
|
||||
},
|
||||
]
|
|
@ -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.
|
@ -376,6 +376,7 @@ foreach dirname : [
|
|||
'TEST-83-BTRFS',
|
||||
'TEST-84-STORAGETM',
|
||||
'TEST-85-NETWORK',
|
||||
'TEST-86-MULTI-PROFILE-UKI',
|
||||
]
|
||||
subdir(dirname)
|
||||
endforeach
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue