1
0
mirror of https://github.com/systemd/systemd synced 2025-10-01 17:54:45 +02:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Lennart Poettering
0eaee8281d journald: when we fail to add a new entry to a journal, return the seqno
Fixes: #18005
2021-02-18 18:30:52 +01:00
Lennart Poettering
669963968c
Merge pull request #18664 from poettering/resolved-defrag
resolved: many UDP fragmentation tweaks
2021-02-18 18:20:36 +01:00
Lennart Poettering
5f94d96c47
Merge pull request #18667 from poettering/resolved-change-notification
resolved: optimize dbus change notification signals a bit
2021-02-18 18:19:09 +01:00
Lennart Poettering
cbf23f3853 resolved: optimize change notification handling away if bus calls set the same values as were already set
Prompted-by: #17577 (but doesn't fix this, since this commit only
handles D-Bus-induced changes, not the ones made via networkd)
2021-02-18 16:38:27 +01:00
Lennart Poettering
e4304fb8d4 basic: add set_equal() helper 2021-02-18 16:38:27 +01:00
Lennart Poettering
980821f3f0 resolved: take fragment size into consideration when determining EDNS0 udp packet size 2021-02-18 15:55:58 +01:00
Lennart Poettering
acbf761b5d resolved: let's track fragment sizes of servers/retry on fragmenting
Fragmenting sucks, let's avoid it. Thus let's start tracking the maximum
fragment size we receive.

Also, let's redo a transaction via TCP if we see fragmenting on UDP, as
effective mitigation against DNS fragment attacks.
2021-02-18 15:55:58 +01:00
Lennart Poettering
d79677ab44 resolved: tweak how we calculate MTU for sending packets
Let's take all MTU info we possibly have into account, i.e. the one
reported via netlink, as before and the one the socket might now (from
PMTUD and such), clamped by our own ideas.
2021-02-18 15:55:58 +01:00
Lennart Poettering
4565863fff resolved: add udp_header_size() helper 2021-02-18 15:55:58 +01:00
Lennart Poettering
20a001bdd7 resolved: collect incoming fragment size when receiving UDP datagrams
We can later use this to adapt our announced EDNS buffer size in order
to avoid fragmentation to make the best of large datagrams while still
avoiding he security weaknesses of it.
2021-02-18 15:42:18 +01:00
Lennart Poettering
eb170e75ab resolved: disable path MTU discovery for UDP traffic
This disables path MTU discovery both for our UDP upstream connections
and our UDP stub, following the suggestions of:

https://blog.apnic.net/2019/07/12/its-time-to-consider-avoiding-ip-fragmentation-in-the-dns/

This more or less follows the model of other DNS servers on this.
2021-02-18 15:42:18 +01:00
Дамјан Георгиевски
95aa3937da man: Rename duplicate Credentials section name
A "Credentials" section name in systemd.exec man page was used
both for User/Group and for actual credentials support in systemd.

Rename the first instance to "User/Group Identity"
2021-02-18 15:40:47 +01:00
Lennart Poettering
489344f24b networkd-test: reenable dnssec while testing
We need to list the synthesized domains as NTAs, otherwise the DNSSEC
validation of course cannot succeed.

Fixes: #10487 #5029
2021-02-18 14:12:42 +00:00
Zbigniew Jędrzejewski-Szmek
2a5095af0c
Merge pull request #18557 from poettering/enum-force-s64
force public enums to be 64bit wide
2021-02-18 14:16:15 +01:00
Susant Sahani
d75bf6cfe2 network: Add "route_localnet" sysctl support 2021-02-18 21:04:17 +09:00
Zbigniew Jędrzejewski-Szmek
d97a35e228
Merge pull request #18665 from poettering/resolved-fastopen
resolved: use TCP FASTOPEN on the local DNS stub
2021-02-18 12:55:10 +01:00
Luca Boccassi
905348da28
Merge pull request #18625 from bluca/sysext_refactor
dissect: parse and store extension-release metadata
2021-02-18 10:43:08 +00:00
Luca Boccassi
93547f2812 env-util: refactor parsing helper for SYSTEMD_SYSEXT_HIERARCHIES out of sysext 2021-02-17 21:45:31 +00:00
Luca Boccassi
d335f4c583 os-util: allow missing VERSION_ID on the host
Rolling releases, like ArchLinux, do not set VERSION_ID in
their os-release files, so allow matching simply on ID if the host
does not provide anything.
2021-02-17 21:45:31 +00:00
Luca Boccassi
7eda2d7fa5 os-util: split extension_release_validate out of sysext 2021-02-17 21:45:31 +00:00
Luca Boccassi
bcf94222a5 machine: parse and store extension-release
Follow the same pattern as os-release parsing, and store the key-value
pairs in a strv if found
2021-02-17 21:45:31 +00:00
Luca Boccassi
7718ac9721 dissect: parse, store and show extension-release info 2021-02-17 21:45:31 +00:00
Luca Boccassi
593fe6c04d dissect: store image name, following usual parsing rules
The name of '/foo/bar/baz.raw' name is 'baz'
2021-02-17 21:24:23 +00:00
Luca Boccassi
42e6a77bc5 env-util: add strv_env_pairs_get helper 2021-02-17 21:24:23 +00:00
Luca Boccassi
eb590035b9 os-util: add load_extension_release_pairs helper 2021-02-17 21:24:23 +00:00
Luca Boccassi
1d0796739c os-util: add path_is_extension_tree helper 2021-02-17 21:24:23 +00:00
Lennart Poettering
b850e51320 resolved: also use TCP tweaks on LLMNR (plus unify setsockopt() code) 2021-02-17 21:12:53 +01:00
Lennart Poettering
8624f1286a resolved: enable TCP_FASTOPEN + TCP_NODELAY on stub TCP socket
Latency matters. Four our local DNS stub it's not really that important,
but let's still do it, it's basically free after all.
2021-02-17 21:12:53 +01:00
Lennart Poettering
386059e119 network: make use of SD_ENUM_FORCE_S64() for all public enums 2021-02-16 20:36:52 +01:00
Lennart Poettering
9823703ae6 sd-common: add new macro for forcing 64bit size for public enums
That's the only way we can retain type-safe enums while being able to
guarantee for stable enum sizes.
2021-02-16 20:36:30 +01:00
Lennart Poettering
d385d54638 test-tables: make sure we can test tables of either int or int64_t base type
To support both types, we unfortunately need to go down the macro rabbit
hole a bit more. But it works.
2021-02-16 20:35:07 +01:00
46 changed files with 1051 additions and 325 deletions

View File

@ -437,7 +437,7 @@
</refsect1>
<refsect1>
<title>Credentials</title>
<title>User/Group Identity</title>
<xi:include href="system-only.xml" xpointer="plural"/>

View File

@ -809,6 +809,12 @@ IPv6Token=prefixstable:2002:da8:1::</programlisting></para>
the wire and have them accepted properly. When unset, the kernel's default will be used.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>IPv4RouteLocalnet=</varname></term>
<listitem><para>Takes a boolean. When true, the kernel does not consider loopback addresses as martian source or destination
while routing. This enables the use of 127.0.0.0/8 for local routing purposes. When unset, the kernel's default will be used.
</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>IPv4ProxyARP=</varname></term>
<listitem><para>Takes a boolean. Configures proxy ARP for IPv4. Proxy ARP is the technique in which one host,

View File

@ -12,6 +12,7 @@
#include "extract-word.h"
#include "macro.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "stdio-util.h"
#include "string-util.h"
@ -455,6 +456,18 @@ char *strv_env_get(char **l, const char *name) {
return strv_env_get_n(l, name, strlen(name), 0);
}
char *strv_env_pairs_get(char **l, const char *name) {
char **key, **value, *result = NULL;
assert(name);
STRV_FOREACH_PAIR(key, value, l)
if (streq(*key, name))
result = *value;
return result;
}
char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) {
char **p, **q;
int k = 0;
@ -775,3 +788,44 @@ int setenv_systemd_exec_pid(bool update_only) {
return 1;
}
int getenv_path_list(const char *name, char ***ret_paths) {
_cleanup_strv_free_ char **l = NULL;
const char *e;
char **p;
int r;
assert(name);
assert(ret_paths);
*ret_paths = NULL;
e = secure_getenv(name);
if (!e)
return 0;
r = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return log_debug_errno(r, "Failed to parse $%s: %m", name);
STRV_FOREACH(p, l) {
if (!path_is_absolute(*p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Path '%s' is not absolute, refusing.", *p);
if (!path_is_normalized(*p))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Path '%s' is not normalized, refusing.", *p);
if (path_equal(*p, "/"))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"Path '%s' is the root fs, refusing.", *p);
}
if (strv_isempty(l))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"No paths specified, refusing.");
*ret_paths = TAKE_PTR(l);
return 0;
}

View File

@ -50,6 +50,7 @@ int strv_env_assign(char ***l, const char *key, const char *value);
char *strv_env_get_n(char **l, const char *name, size_t k, unsigned flags) _pure_;
char *strv_env_get(char **x, const char *n) _pure_;
char *strv_env_pairs_get(char **l, const char *name) _pure_;
int getenv_bool(const char *p);
int getenv_bool_secure(const char *p);
@ -58,3 +59,7 @@ int getenv_bool_secure(const char *p);
int set_unset_env(const char *name, const char *value, bool overwrite);
int setenv_systemd_exec_pid(bool update_only);
/* Parses and does sanity checks on an environment variable containing
* PATH-like colon-separated absolute paths */
int getenv_path_list(const char *name, char ***ret_paths);

View File

@ -2036,3 +2036,35 @@ int set_strjoin(Set *s, const char *separator, bool wrap_with_separator, char **
*ret = TAKE_PTR(str);
return 0;
}
bool set_equal(Set *a, Set *b) {
void *p;
/* Checks whether each entry of 'a' is also in 'b' and vice versa, i.e. the two sets contain the same
* entries */
if (a == b)
return true;
if (set_isempty(a) && set_isempty(b))
return true;
if (set_size(a) != set_size(b)) /* Cheap check that hopefully catches a lot of inequality cases
* already */
return false;
SET_FOREACH(p, a)
if (!set_contains(b, p))
return false;
/* If we have the same hashops, then we don't need to check things backwards given we compared the
* size and that all of a is in b. */
if (a->b.hash_ops == b->b.hash_ops)
return true;
SET_FOREACH(p, b)
if (!set_contains(a, p))
return false;
return true;
}

View File

@ -152,3 +152,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free);
#define _cleanup_set_free_free_ _cleanup_(set_free_freep)
int set_strjoin(Set *s, const char *separator, bool wrap_with_separator, char **ret);
bool set_equal(Set *a, Set *b);

View File

@ -412,13 +412,23 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
*p, *q);
}
if (!strv_isempty(m->extension_release)) {
char **p, **q;
STRV_FOREACH_PAIR(p, q, m->extension_release)
printf("%s %s=%s\n",
p == m->extension_release ? "Extension Release:" : " ",
*p, *q);
}
if (m->hostname ||
!sd_id128_is_null(m->machine_id) ||
!strv_isempty(m->machine_info) ||
!strv_isempty(m->extension_release) ||
!strv_isempty(m->os_release))
putc('\n', stdout);
} else {
_cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *mi = NULL, *osr = NULL, *exr = NULL;
if (!strv_isempty(m->machine_info)) {
r = strv_pair_to_json(m->machine_info, &mi);
@ -432,13 +442,20 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) {
return log_oom();
}
if (!strv_isempty(m->extension_release)) {
r = strv_pair_to_json(m->extension_release, &exr);
if (r < 0)
return log_oom();
}
r = json_build(&v, JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("name", JSON_BUILD_STRING(basename(arg_image))),
JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(size)),
JSON_BUILD_PAIR_CONDITION(m->hostname, "hostname", JSON_BUILD_STRING(m->hostname)),
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(m->machine_id), "machineId", JSON_BUILD_ID128(m->machine_id)),
JSON_BUILD_PAIR_CONDITION(mi, "machineInfo", JSON_BUILD_VARIANT(mi)),
JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr))));
JSON_BUILD_PAIR_CONDITION(osr, "osRelease", JSON_BUILD_VARIANT(osr)),
JSON_BUILD_PAIR_CONDITION(exr, "extensionRelease", JSON_BUILD_VARIANT(exr))));
if (r < 0)
return log_oom();
}

View File

@ -990,31 +990,59 @@ int journal_file_move_to_object(JournalFile *f, ObjectType type, uint64_t offset
return 0;
}
static uint64_t journal_file_entry_seqnum(JournalFile *f, uint64_t *seqnum) {
uint64_t r;
static uint64_t journal_file_entry_seqnum(
JournalFile *f,
uint64_t *seqnum) {
uint64_t ret;
assert(f);
assert(f->header);
r = le64toh(f->header->tail_entry_seqnum) + 1;
/* Picks a new sequence number for the entry we are about to add and returns it. */
ret = le64toh(f->header->tail_entry_seqnum) + 1;
if (seqnum) {
/* If an external seqnum counter was passed, we update
* both the local and the external one, and set it to
* the maximum of both */
/* If an external seqnum counter was passed, we update both the local and the external one,
* and set it to the maximum of both */
if (*seqnum + 1 > r)
r = *seqnum + 1;
if (*seqnum + 1 > ret)
ret = *seqnum + 1;
*seqnum = r;
*seqnum = ret;
}
f->header->tail_entry_seqnum = htole64(r);
f->header->tail_entry_seqnum = htole64(ret);
if (f->header->head_entry_seqnum == 0)
f->header->head_entry_seqnum = htole64(r);
f->header->head_entry_seqnum = htole64(ret);
return r;
return ret;
}
static void journal_file_revert_entry_seqnum(
JournalFile *f,
uint64_t *seqnum,
uint64_t revert_seqnum) {
assert(f);
assert(f->header);
if (revert_seqnum == 0) /* sequence number 0? can't go back */
return;
/* Undoes the effect of journal_file_entry_seqnum() above: if we fail to append an entry to a file,
* let's revert the seqnum we were about to use, so that we can use it on the next entry. */
if (le64toh(f->header->tail_entry_seqnum) == revert_seqnum)
f->header->tail_entry_seqnum = htole64(revert_seqnum - 1);
if (le64toh(f->header->head_entry_seqnum) == revert_seqnum)
f->header->head_entry_seqnum = 0;
if (seqnum && *seqnum == revert_seqnum)
*seqnum = revert_seqnum - 1;
}
int journal_file_append_object(
@ -1976,12 +2004,12 @@ static int journal_file_append_entry_internal(
#if HAVE_GCRYPT
r = journal_file_hmac_put_object(f, OBJECT_ENTRY, o, np);
if (r < 0)
return r;
goto fail;
#endif
r = journal_file_link_entry(f, o, np);
if (r < 0)
return r;
goto fail;
if (ret)
*ret = o;
@ -1990,6 +2018,10 @@ static int journal_file_append_entry_internal(
*ret_offset = np;
return 0;
fail:
journal_file_revert_entry_seqnum(f, seqnum, le64toh(o->entry.seqnum));
return r;
}
void journal_file_post_change(JournalFile *f) {

View File

@ -120,6 +120,7 @@ Network.IPv6HopLimit, config_parse_int,
Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp)
Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu)
Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local)
Network.IPv4RouteLocalnet, config_parse_tristate, 0, offsetof(Network, ipv4_route_localnet)
Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave)
Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave)
Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp)

View File

@ -422,6 +422,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi
.ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID,
.ipv4_accept_local = -1,
.ipv4_route_localnet = -1,
.ipv6_privacy_extensions = IPV6_PRIVACY_EXTENSIONS_NO,
.ipv6_accept_ra = -1,
.ipv6_dad_transmits = -1,

View File

@ -253,6 +253,7 @@ struct Network {
/* sysctl settings */
AddressFamily ip_forward;
int ipv4_accept_local;
int ipv4_route_localnet;
int ipv6_dad_transmits;
int ipv6_hop_limit;
int proxy_arp;

View File

@ -173,6 +173,18 @@ static int link_set_ipv4_accept_local(Link *link) {
return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "accept_local", link->network->ipv4_accept_local > 0);
}
static int link_set_ipv4_route_localnet(Link *link) {
assert(link);
if (link->flags & IFF_LOOPBACK)
return 0;
if (link->network->ipv4_route_localnet < 0)
return 0;
return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "route_localnet", link->network->ipv4_route_localnet > 0);
}
int link_set_sysctl(Link *link) {
int r;
@ -216,6 +228,10 @@ int link_set_sysctl(Link *link) {
if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv4 accept_local flag for interface, ignoring: %m");
r = link_set_ipv4_route_localnet(link);
if (r < 0)
log_link_warning_errno(link, r, "Cannot set IPv4 route_localnet flag for interface, ignoring: %m");
/* If promote_secondaries is not set, DHCP will work only as long as the IP address does not
* changes between leases. The kernel will remove all secondary IP addresses of an interface
* otherwise. The way systemd-networkd works is that the new IP of a lease is added as a

View File

@ -40,10 +40,16 @@ int main(int argc, char **argv) {
test_table(wol, WOL);
test_table(lldp_event, SD_LLDP_EVENT);
test_table(ndisc_event, SD_NDISC_EVENT);
test_table(dhcp_lease_server_type, SD_DHCP_LEASE_SERVER_TYPE);
test_table_sparse(ipvlan_mode, NETDEV_IPVLAN_MODE);
test_table_sparse(macvlan_mode, NETDEV_MACVLAN_MODE);
test_table_sparse(address_family, ADDRESS_FAMILY);
assert_cc(sizeof(sd_lldp_event) == sizeof(int64_t));
assert_cc(sizeof(sd_ndisc_event) == sizeof(int64_t));
assert_cc(sizeof(sd_dhcp_lease_server_type) == sizeof(int64_t));
assert_cc(sizeof(sd_genl_family) == sizeof(int64_t));
return EXIT_SUCCESS;
}

View File

@ -2626,6 +2626,20 @@ int dns_packet_has_nsid_request(DnsPacket *p) {
return has_nsid;
}
size_t dns_packet_size_unfragmented(DnsPacket *p) {
assert(p);
if (p->fragsize == 0) /* Wasn't fragmented */
return p->size;
/* The fragment size (p->fragsize) covers the whole (fragmented) IP packet, while the regular packet
* size (p->size) only covers the DNS part. Thus, subtract the UDP header from the largest fragment
* size, in order to determine which size of DNS packet would have gone through without
* fragmenting. */
return LESS_BY(p->fragsize, udp_header_size(p->family));
}
static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_SUCCESS] = "SUCCESS",
[DNS_RCODE_FORMERR] = "FORMERR",

View File

@ -2,6 +2,7 @@
#pragma once
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/udp.h>
#include "hashmap.h"
@ -32,14 +33,19 @@ struct DnsPacketHeader {
be16_t ancount;
be16_t nscount;
be16_t arcount;
};
} _packed_;
#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader)
#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr))
#define UDP4_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr))
#define UDP6_PACKET_HEADER_SIZE (sizeof(struct ip6_hdr) + sizeof(struct udphdr))
/* The various DNS protocols deviate in how large a packet can grow,
* but the TCP transport has a 16bit size field, hence that appears to
* be the absolute maximum. */
assert_cc(sizeof(struct ip6_hdr) == 40);
assert_cc(sizeof(struct iphdr) == 20);
assert_cc(sizeof(struct udphdr) == 8);
assert_cc(sizeof(DnsPacketHeader) == 12);
/* The various DNS protocols deviate in how large a packet can grow, but the TCP transport has a 16bit size
* field, hence that appears to be the absolute maximum. */
#define DNS_PACKET_SIZE_MAX 0xFFFFu
/* The default size to use for allocation when we don't know how large
@ -55,7 +61,7 @@ struct DnsPacketHeader {
struct DnsPacket {
unsigned n_ref;
DnsProtocol protocol;
size_t size, allocated, rindex, max_size;
size_t size, allocated, rindex, max_size, fragsize;
void *_data; /* don't access directly, use DNS_PACKET_DATA()! */
Hashmap *names; /* For name compression */
size_t opt_start, opt_size;
@ -146,6 +152,14 @@ static inline bool DNS_PACKET_VERSION_SUPPORTED(DnsPacket *p) {
return DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(p->opt);
}
static inline bool DNS_PACKET_IS_FRAGMENTED(DnsPacket *p) {
assert(p);
/* For ingress packets: was this packet fragmented according to our knowledge? */
return p->fragsize != 0;
}
/* LLMNR defines some bits differently */
#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p)
#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p)
@ -307,3 +321,17 @@ static inline size_t dns_packet_size_max(DnsPacket *p) {
return p->max_size != 0 ? p->max_size : DNS_PACKET_SIZE_MAX;
}
static inline size_t udp_header_size(int af) {
switch (af) {
case AF_INET:
return UDP4_PACKET_HEADER_SIZE;
case AF_INET6:
return UDP6_PACKET_HEADER_SIZE;
default:
assert_not_reached("Unexpected address family");
}
}
size_t dns_packet_size_unfragmented(DnsPacket *p);

View File

@ -5,6 +5,7 @@
#include "af-list.h"
#include "alloc-util.h"
#include "dns-domain.h"
#include "errno-util.h"
#include "fd-util.h"
#include "hostname-util.h"
#include "missing_network.h"
@ -185,43 +186,73 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) {
s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC);
}
static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
union in_addr_union addr;
int ifindex = 0, r;
int family;
uint32_t mtu;
static int dns_scope_emit_one(DnsScope *s, int fd, int family, DnsPacket *p) {
int r;
assert(s);
assert(p);
assert(p->protocol == s->protocol);
if (s->link) {
mtu = s->link->mtu;
ifindex = s->link->ifindex;
} else
mtu = manager_find_mtu(s->manager);
if (family == AF_UNSPEC) {
if (s->family == AF_UNSPEC)
return -EAFNOSUPPORT;
family = s->family;
}
switch (s->protocol) {
case DNS_PROTOCOL_DNS:
case DNS_PROTOCOL_DNS: {
size_t mtu, udp_size, min_mtu, socket_mtu = 0;
assert(fd >= 0);
if (DNS_PACKET_QDCOUNT(p) > 1)
if (DNS_PACKET_QDCOUNT(p) > 1) /* Classic DNS only allows one question per packet */
return -EOPNOTSUPP;
if (p->size > DNS_PACKET_UNICAST_SIZE_MAX)
return -EMSGSIZE;
if (p->size + UDP_PACKET_HEADER_SIZE > mtu)
return -EMSGSIZE;
/* Determine the local most accurate MTU */
if (s->link)
mtu = s->link->mtu;
else
mtu = manager_find_mtu(s->manager);
/* Acquire the socket's PMDU MTU */
r = socket_get_mtu(fd, family, &socket_mtu);
if (r < 0 && !ERRNO_IS_DISCONNECT(r)) /* Will return ENOTCONN if no information is available yet */
return log_debug_errno(r, "Failed to read socket MTU: %m");
/* Determine the appropriate UDP header size */
udp_size = udp_header_size(family);
min_mtu = udp_size + DNS_PACKET_HEADER_SIZE;
log_debug("Emitting UDP, link MTU is %zu, socket MTU is %zu, minimal MTU is %zu",
mtu, socket_mtu, min_mtu);
/* Clamp by the kernel's idea of the (path) MTU */
if (socket_mtu != 0 && socket_mtu < mtu)
mtu = socket_mtu;
/* Put a lower limit, in case all MTU data we acquired was rubbish */
if (mtu < min_mtu)
mtu = min_mtu;
/* Now check our packet size against the MTU we determined */
if (udp_size + p->size > mtu)
return -EMSGSIZE; /* This means: try TCP instead */
r = manager_write(s->manager, fd, p);
if (r < 0)
return r;
break;
}
case DNS_PROTOCOL_LLMNR: {
union in_addr_union addr;
case DNS_PROTOCOL_LLMNR:
assert(fd < 0);
if (DNS_PACKET_QDCOUNT(p) > 1)
@ -230,8 +261,6 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
if (!ratelimit_below(&s->ratelimit))
return -EBUSY;
family = s->family;
if (family == AF_INET) {
addr.in = LLMNR_MULTICAST_IPV4_ADDRESS;
fd = manager_llmnr_ipv4_udp_fd(s->manager);
@ -243,20 +272,20 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
if (fd < 0)
return fd;
r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, NULL, p);
r = manager_send(s->manager, fd, s->link->ifindex, family, &addr, LLMNR_PORT, NULL, p);
if (r < 0)
return r;
break;
}
case DNS_PROTOCOL_MDNS:
case DNS_PROTOCOL_MDNS: {
union in_addr_union addr;
assert(fd < 0);
if (!ratelimit_below(&s->ratelimit))
return -EBUSY;
family = s->family;
if (family == AF_INET) {
addr.in = MDNS_MULTICAST_IPV4_ADDRESS;
fd = manager_mdns_ipv4_fd(s->manager);
@ -268,11 +297,12 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
if (fd < 0)
return fd;
r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, NULL, p);
r = manager_send(s->manager, fd, s->link->ifindex, family, &addr, MDNS_PORT, NULL, p);
if (r < 0)
return r;
break;
}
default:
return -EAFNOSUPPORT;
@ -281,7 +311,7 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
return 1;
}
int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) {
int dns_scope_emit_udp(DnsScope *s, int fd, int af, DnsPacket *p) {
int r;
assert(s);
@ -296,7 +326,7 @@ int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) {
dns_packet_set_flags(p, true, true);
}
r = dns_scope_emit_one(s, fd, p);
r = dns_scope_emit_one(s, fd, af, p);
if (r < 0)
return r;
@ -410,6 +440,16 @@ static int dns_scope_socket(
r = socket_set_recvpktinfo(fd, sa.sa.sa_family, true);
if (r < 0)
return r;
/* Turn of path MTU discovery for security reasons */
r = socket_disable_pmtud(fd, sa.sa.sa_family);
if (r < 0)
log_debug_errno(r, "Failed to disable UDP PMTUD, ignoring: %m");
/* Learn about fragmentation taking place */
r = socket_set_recvfragsize(fd, sa.sa.sa_family, true);
if (r < 0)
log_debug_errno(r, "Failed to enable fragment size reception, ignoring: %m");
}
if (ret_socket_address)
@ -1123,7 +1163,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata
return 0;
}
r = dns_scope_emit_udp(scope, -1, p);
r = dns_scope_emit_udp(scope, -1, AF_UNSPEC, p);
if (r < 0)
log_debug_errno(r, "Failed to send conflict packet: %m");
}
@ -1420,7 +1460,7 @@ int dns_scope_announce(DnsScope *scope, bool goodbye) {
if (r < 0)
return log_debug_errno(r, "Failed to build reply packet: %m");
r = dns_scope_emit_udp(scope, -1, p);
r = dns_scope_emit_udp(scope, -1, AF_UNSPEC, p);
if (r < 0)
return log_debug_errno(r, "Failed to send reply packet: %m");

View File

@ -71,7 +71,7 @@ DnsScope* dns_scope_free(DnsScope *s);
void dns_scope_packet_received(DnsScope *s, usec_t rtt);
void dns_scope_packet_lost(DnsScope *s, usec_t usec);
int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p);
int dns_scope_emit_udp(DnsScope *s, int fd, int af, DnsPacket *p);
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port, union sockaddr_union *ret_socket_address);
int dns_scope_socket_udp(DnsScope *s, DnsServer *server);

View File

@ -151,18 +151,22 @@ void dns_search_domain_unlink_all(DnsSearchDomain *first) {
dns_search_domain_unlink_all(next);
}
void dns_search_domain_unlink_marked(DnsSearchDomain *first) {
bool dns_search_domain_unlink_marked(DnsSearchDomain *first) {
DnsSearchDomain *next;
bool changed;
if (!first)
return;
return false;
next = first->domains_next;
if (first->marked)
if (first->marked) {
dns_search_domain_unlink(first);
changed = true;
} else
changed = false;
dns_search_domain_unlink_marked(next);
return changed || dns_search_domain_unlink_marked(next);
}
void dns_search_domain_mark_all(DnsSearchDomain *first) {

View File

@ -44,7 +44,7 @@ void dns_search_domain_unlink(DnsSearchDomain *d);
void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d);
void dns_search_domain_unlink_all(DnsSearchDomain *first);
void dns_search_domain_unlink_marked(DnsSearchDomain *first);
bool dns_search_domain_unlink_marked(DnsSearchDomain *first);
void dns_search_domain_mark_all(DnsSearchDomain *first);
int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret);

View File

@ -255,7 +255,7 @@ static void dns_server_reset_counters(DnsServer *s) {
* incomplete. */
}
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t size) {
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t fragsize) {
assert(s);
if (protocol == IPPROTO_UDP) {
@ -289,10 +289,10 @@ void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLeve
dns_server_verified(s, level);
/* Remember the size of the largest UDP packet we received from a server, we know that we can always
* announce support for packets with at least this size. */
if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size)
s->received_udp_packet_max = size;
/* Remember the size of the largest UDP packet fragment we received from a server, we know that we
* can always announce support for packets with at least this size. */
if (protocol == IPPROTO_UDP && s->received_udp_fragment_max < fragsize)
s->received_udp_fragment_max = fragsize;
}
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level) {
@ -389,6 +389,19 @@ void dns_server_packet_do_off(DnsServer *s, DnsServerFeatureLevel level) {
s->packet_do_off = true;
}
void dns_server_packet_udp_fragmented(DnsServer *s, size_t fragsize) {
assert(s);
/* Invoked whenever we got a fragmented UDP packet. Let's do two things: keep track of the largest
* fragment we ever received from the server, and remember this, so that we can use it to lower the
* advertised packet size in EDNS0 */
if (s->received_udp_fragment_max < fragsize)
s->received_udp_fragment_max = fragsize;
s->packet_fragmented = true;
}
static bool dns_server_grace_period_expired(DnsServer *s) {
usec_t ts;
@ -604,10 +617,47 @@ int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeature
edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO;
if (level == DNS_SERVER_FEATURE_LEVEL_LARGE)
packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
else
packet_size = server->received_udp_packet_max;
if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) {
size_t udp_size;
/* In large mode, advertise the local MTU, in order to avoid fragmentation (for security
* reasons) except if we are talking to localhost (where the security considerations don't
* matter). If we see fragmentation, lower the reported size to the largest fragment, to
* avoid it. */
udp_size = udp_header_size(server->family);
if (in_addr_is_localhost(server->family, &server->address) > 0)
packet_size = 65536 - udp_size; /* force linux loopback MTU if localhost address */
else {
/* Use the MTU pointing to the server, subtract the IP/UDP header size */
packet_size = LESS_BY(dns_server_get_mtu(server), udp_size);
/* On the Internet we want to avoid fragmentation for security reasons. If we saw
* fragmented packets, the above was too large, let's clamp it to the largest
* fragment we saw */
if (server->packet_fragmented)
packet_size = MIN(server->received_udp_fragment_max, packet_size);
/* Let's not pick ridiculously large sizes, i.e. not more than 4K. Noone appears to
* ever use such large sized on the Internet IRL, hence let's not either. */
packet_size = MIN(packet_size, 4096U);
}
/* Strictly speaking we quite possibly can receive larger datagrams than the MTU (since the
* MTU is for egress, not for ingress), but more often than not the value is symmetric, and
* we want something that does the right thing in the majority of cases, and not just in the
* theoretical edge case. */
} else
/* In non-large mode, let's advertise the size of the largest fragment we ever managed to accept. */
packet_size = server->received_udp_fragment_max;
/* Safety clamp, never advertise less than 512 or more than 65535 */
packet_size = CLAMP(packet_size,
DNS_PACKET_UNICAST_SIZE_MAX,
DNS_PACKET_SIZE_MAX);
log_debug("Announcing packet size %zu in egress EDNS(0) packet.", packet_size);
return dns_packet_append_opt(packet, packet_size, edns_do, /* include_rfc6975 = */ true, NULL, 0, NULL);
}
@ -700,6 +750,15 @@ void dns_server_warn_downgrade(DnsServer *server) {
server->warned_downgrade = true;
}
size_t dns_server_get_mtu(DnsServer *s) {
assert(s);
if (s->link && s->link->mtu != 0)
return s->link->mtu;
return manager_find_mtu(s->manager);
}
static void dns_server_hash_func(const DnsServer *s, struct siphash *state) {
assert(s);
@ -746,18 +805,22 @@ void dns_server_unlink_all(DnsServer *first) {
dns_server_unlink_all(next);
}
void dns_server_unlink_marked(DnsServer *first) {
bool dns_server_unlink_marked(DnsServer *first) {
DnsServer *next;
bool changed;
if (!first)
return;
return false;
next = first->servers_next;
if (first->marked)
if (first->marked) {
changed = true;
dns_server_unlink(first);
} else
changed = false;
dns_server_unlink_marked(next);
return changed || dns_server_unlink_marked(next);
}
void dns_server_mark_all(DnsServer *first) {
@ -919,7 +982,7 @@ void dns_server_reset_features(DnsServer *s) {
s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->received_udp_fragment_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->packet_bad_opt = false;
s->packet_rrsig_missing = false;
@ -979,7 +1042,7 @@ void dns_server_dump(DnsServer *s, FILE *f) {
fputc('\n', f);
fprintf(f,
"\tMaximum UDP packet size received: %zu\n"
"\tMaximum UDP fragment size received: %zu\n"
"\tFailed UDP attempts: %u\n"
"\tFailed TCP attempts: %u\n"
"\tSeen truncated packet: %s\n"
@ -987,7 +1050,7 @@ void dns_server_dump(DnsServer *s, FILE *f) {
"\tSeen RRSIG RR missing: %s\n"
"\tSeen invalid packet: %s\n"
"\tServer dropped DO flag: %s\n",
s->received_udp_packet_max,
s->received_udp_fragment_max,
s->n_failed_udp,
s->n_failed_tcp,
yes_no(s->packet_truncated),

View File

@ -75,7 +75,7 @@ struct DnsServer {
DnsServerFeatureLevel verified_feature_level;
DnsServerFeatureLevel possible_feature_level;
size_t received_udp_packet_max;
size_t received_udp_fragment_max; /* largest packet or fragment (without IP/UDP header) we saw so far */
unsigned n_failed_udp;
unsigned n_failed_tcp;
@ -86,6 +86,7 @@ struct DnsServer {
bool packet_rrsig_missing:1; /* Set when RRSIG was missing */
bool packet_invalid:1; /* Set when we failed to parse a reply */
bool packet_do_off:1; /* Set when the server didn't copy DNSSEC DO flag from request to response */
bool packet_fragmented:1; /* Set when we ever saw a fragmented packet */
usec_t verified_usec;
usec_t features_grace_period_usec;
@ -118,7 +119,7 @@ DnsServer* dns_server_unref(DnsServer *s);
void dns_server_unlink(DnsServer *s);
void dns_server_move_back_and_unmark(DnsServer *s);
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t size);
void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t fragsize);
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level);
void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level);
@ -126,6 +127,7 @@ void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rcode_downgrade(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_invalid(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_do_off(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_udp_fragmented(DnsServer *s, size_t fragsize);
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
@ -143,7 +145,7 @@ void dns_server_warn_downgrade(DnsServer *server);
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, uint16_t port, int ifindex, const char *name);
void dns_server_unlink_all(DnsServer *first);
void dns_server_unlink_marked(DnsServer *first);
bool dns_server_unlink_marked(DnsServer *first);
void dns_server_mark_all(DnsServer *first);
DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t);
@ -155,6 +157,8 @@ void manager_next_dns_server(Manager *m, DnsServer *if_current);
DnssecMode dns_server_get_dnssec_mode(DnsServer *s);
DnsOverTlsMode dns_server_get_dns_over_tls_mode(DnsServer *s);
size_t dns_server_get_mtu(DnsServer *s);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
extern const struct hash_ops dns_server_hash_ops;

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <net/if_arp.h>
#include <netinet/tcp.h>
#include "errno-util.h"
#include "fd-util.h"
@ -987,6 +988,22 @@ static int set_dns_stub_common_socket_options(int fd, int family) {
return 0;
}
static int set_dns_stub_common_tcp_socket_options(int fd) {
int r;
assert(fd >= 0);
r = setsockopt_int(fd, IPPROTO_TCP, TCP_FASTOPEN, 5); /* Everybody appears to pick qlen=5, let's do the same here. */
if (r < 0)
log_debug_errno(r, "Failed to enable TCP_FASTOPEN on TCP listening socket, ignoring: %m");
r = setsockopt_int(fd, IPPROTO_TCP, TCP_NODELAY, true);
if (r < 0)
log_debug_errno(r, "Failed to enable TCP_NODELAY mode, ignoring: %m");
return 0;
}
static int manager_dns_stub_fd(Manager *m, int type) {
union sockaddr_union sa = {
.in.sin_family = AF_INET,
@ -1010,6 +1027,12 @@ static int manager_dns_stub_fd(Manager *m, int type) {
if (r < 0)
return r;
if (type == SOCK_STREAM) {
r = set_dns_stub_common_tcp_socket_options(fd);
if (r < 0)
return r;
}
/* Make sure no traffic from outside the local host can leak to onto this socket */
r = socket_bind_to_ifindex(fd, LOOPBACK_IFINDEX);
if (r < 0)
@ -1081,6 +1104,12 @@ static int manager_dns_stub_fd_extra(Manager *m, DnsStubListenerExtra *l, int ty
if (r < 0)
goto fail;
if (type == SOCK_STREAM) {
r = set_dns_stub_common_tcp_socket_options(fd);
if (r < 0)
goto fail;
}
/* Do not set IP_TTL for extra DNS stub listeners, as the address may not be local and in that case
* people may want ttl > 1. */
@ -1088,6 +1117,16 @@ static int manager_dns_stub_fd_extra(Manager *m, DnsStubListenerExtra *l, int ty
if (r < 0)
goto fail;
if (type == SOCK_DGRAM) {
r = socket_disable_pmtud(fd, l->family);
if (r < 0)
log_debug_errno(r, "Failed to disable UDP PMTUD, ignoring: %m");
r = socket_set_recvfragsize(fd, l->family, true);
if (r < 0)
log_debug_errno(r, "Failed to enable fragment size reception, ignoring: %m");
}
if (bind(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0) {
r = -errno;
goto fail;

View File

@ -1031,6 +1031,7 @@ static int dns_transaction_fix_rcode(DnsTransaction *t) {
}
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypted) {
bool retry_with_tcp = false;
int r;
assert(t);
@ -1193,9 +1194,29 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
return;
}
log_debug("Reply truncated, retrying via TCP.");
/* Response was truncated, let's try again with good old TCP */
log_debug("Reply truncated, retrying via TCP.");
retry_with_tcp = true;
} else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
DNS_PACKET_IS_FRAGMENTED(p)) {
/* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
if (t->server)
dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
/* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
* issues. (We don't do that on the lowest feature level however, since crappy DNS
* servers often do not implement TCP, hence falling back to TCP on fragmentation is
* counter-productive there.) */
log_debug("Reply fragmented, retrying via TCP.");
retry_with_tcp = true;
}
}
if (retry_with_tcp) {
r = dns_transaction_emit_tcp(t);
if (r == -ESRCH) {
/* No servers found? Damn! */
@ -1296,8 +1317,10 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
if (DNS_PACKET_DO(t->sent) && !DNS_PACKET_DO(t->received))
dns_server_packet_do_off(t->server, t->current_feature_level);
/* Report that we successfully received a packet */
dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, p->size);
/* Report that we successfully received a packet. We keep track of the largest packet
* size/fragment size we got. Which is useful for announcing the EDNS(0) packet size we can
* receive to our server. */
dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, dns_packet_size_unfragmented(p));
}
/* See if we know things we didn't know before that indicate we better restart the lookup immediately. */
@ -1470,7 +1493,7 @@ static int dns_transaction_emit_udp(DnsTransaction *t) {
} else
dns_transaction_close_connection(t, true);
r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent);
r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->server ? t->server->family : AF_UNSPEC, t->sent);
if (r < 0)
return r;

View File

@ -255,6 +255,7 @@ static int verify_unmanaged_link(Link *l, sd_bus_error *error) {
static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, void *userdata, sd_bus_error *error, bool extended) {
_cleanup_free_ char *j = NULL;
struct in_addr_full **dns;
bool changed = false;
Link *l = userdata;
size_t n;
int r;
@ -312,21 +313,26 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi
dns_server_unlink_all(l->dns_servers);
goto finalize;
}
changed = true;
}
}
dns_server_unlink_marked(l->dns_servers);
link_allocate_scopes(l);
changed = dns_server_unlink_marked(l->dns_servers) || changed;
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
(void) manager_send_changed(l->manager, "DNS");
if (changed) {
link_allocate_scopes(l);
if (j)
log_link_info(l, "Bus client set DNS server list to: %s", j);
else
log_link_info(l, "Bus client reset DNS server list.");
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
(void) manager_send_changed(l->manager, "DNS");
if (j)
log_link_info(l, "Bus client set DNS server list to: %s", j);
else
log_link_info(l, "Bus client reset DNS server list.");
}
r = sd_bus_reply_method_return(message, NULL);
@ -349,6 +355,7 @@ int bus_link_method_set_dns_servers_ex(sd_bus_message *message, void *userdata,
int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_free_ char *j = NULL;
Link *l = userdata;
bool changed = false;
int r;
assert(message);
@ -431,6 +438,8 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_
r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name);
if (r < 0)
goto clear;
changed = true;
}
d->route_only = route_only;
@ -440,15 +449,17 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_
if (r < 0)
goto clear;
dns_search_domain_unlink_marked(l->search_domains);
changed = dns_search_domain_unlink_marked(l->search_domains) || changed;
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
if (changed) {
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
if (j)
log_link_info(l, "Bus client set search domain list to: %s", j);
else
log_link_info(l, "Bus client reset search domain list.");
if (j)
log_link_info(l, "Bus client set search domain list to: %s", j);
else
log_link_info(l, "Bus client reset search domain list.");
}
return sd_bus_reply_method_return(message, NULL);
@ -488,9 +499,9 @@ int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, s
(void) link_save_user(l);
(void) manager_write_resolv_conf(l->manager);
}
log_link_info(l, "Bus client set default route setting: %s", yes_no(b));
log_link_info(l, "Bus client set default route setting: %s", yes_no(b));
}
return sd_bus_reply_method_return(message, NULL);
}
@ -531,13 +542,15 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er
bus_client_log(message, "LLMNR change");
l->llmnr_support = mode;
link_allocate_scopes(l);
link_add_rrs(l, false);
if (l->llmnr_support != mode) {
l->llmnr_support = mode;
link_allocate_scopes(l);
link_add_rrs(l, false);
(void) link_save_user(l);
(void) link_save_user(l);
log_link_info(l, "Bus client set LLMNR setting: %s", resolve_support_to_string(mode));
log_link_info(l, "Bus client set LLMNR setting: %s", resolve_support_to_string(mode));
}
return sd_bus_reply_method_return(message, NULL);
}
@ -578,13 +591,15 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err
bus_client_log(message, "mDNS change");
l->mdns_support = mode;
link_allocate_scopes(l);
link_add_rrs(l, false);
if (l->mdns_support != mode) {
l->mdns_support = mode;
link_allocate_scopes(l);
link_add_rrs(l, false);
(void) link_save_user(l);
(void) link_save_user(l);
log_link_info(l, "Bus client set MulticastDNS setting: %s", resolve_support_to_string(mode));
log_link_info(l, "Bus client set MulticastDNS setting: %s", resolve_support_to_string(mode));
}
return sd_bus_reply_method_return(message, NULL);
}
@ -625,12 +640,14 @@ int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd
bus_client_log(message, "D-o-T change");
link_set_dns_over_tls_mode(l, mode);
if (l->dns_over_tls_mode != mode) {
link_set_dns_over_tls_mode(l, mode);
(void) link_save_user(l);
(void) link_save_user(l);
log_link_info(l, "Bus client set DNSOverTLS setting: %s",
mode < 0 ? "default" : dns_over_tls_mode_to_string(mode));
log_link_info(l, "Bus client set DNSOverTLS setting: %s",
mode < 0 ? "default" : dns_over_tls_mode_to_string(mode));
}
return sd_bus_reply_method_return(message, NULL);
}
@ -671,12 +688,14 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e
bus_client_log(message, "DNSSEC change");
link_set_dnssec_mode(l, mode);
if (l->dnssec_mode != mode) {
link_set_dnssec_mode(l, mode);
(void) link_save_user(l);
(void) link_save_user(l);
log_link_info(l, "Bus client set DNSSEC setting: %s",
mode < 0 ? "default" : dnssec_mode_to_string(mode));
log_link_info(l, "Bus client set DNSSEC setting: %s",
mode < 0 ? "default" : dnssec_mode_to_string(mode));
}
return sd_bus_reply_method_return(message, NULL);
}
@ -731,15 +750,17 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v
bus_client_log(message, "DNSSEC NTA change");
set_free_free(l->dnssec_negative_trust_anchors);
l->dnssec_negative_trust_anchors = TAKE_PTR(ns);
if (!set_equal(ns, l->dnssec_negative_trust_anchors)) {
set_free_free(l->dnssec_negative_trust_anchors);
l->dnssec_negative_trust_anchors = TAKE_PTR(ns);
(void) link_save_user(l);
(void) link_save_user(l);
if (j)
log_link_info(l, "Bus client set NTA list to: %s", j);
else
log_link_info(l, "Bus client reset NTA list.");
if (j)
log_link_info(l, "Bus client set NTA list to: %s", j);
else
log_link_info(l, "Bus client reset NTA list.");
}
return sd_bus_reply_method_return(message, NULL);
}

View File

@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <resolv.h>
#include "errno-util.h"
@ -111,6 +112,31 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
return 0;
}
static int set_llmnr_common_socket_options(int fd, int family) {
int r;
r = socket_set_recvpktinfo(fd, family, true);
if (r < 0)
return r;
r = socket_set_recvttl(fd, family, true);
if (r < 0)
return r;
return 0;
}
static int set_llmnr_common_udp_socket_options(int fd, int family) {
int r;
/* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
r = socket_set_ttl(fd, family, 255);
if (r < 0)
return r;
return 0;
}
int manager_llmnr_ipv4_udp_fd(Manager *m) {
union sockaddr_union sa = {
.in.sin_family = AF_INET,
@ -128,10 +154,13 @@ int manager_llmnr_ipv4_udp_fd(Manager *m) {
if (s < 0)
return log_error_errno(errno, "LLMNR-IPv4(UDP): Failed to create socket: %m");
/* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
r = setsockopt_int(s, IPPROTO_IP, IP_TTL, 255);
r = set_llmnr_common_socket_options(s, AF_INET);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(UDP): Failed to set IP_TTL: %m");
return log_error_errno(r, "LLMNR-IPv4(UDP): Failed to set common socket options: %m");
r = set_llmnr_common_udp_socket_options(s, AF_INET);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(UDP): Failed to set common UDP socket options: %m");
r = setsockopt_int(s, IPPROTO_IP, IP_MULTICAST_TTL, 255);
if (r < 0)
@ -141,14 +170,6 @@ int manager_llmnr_ipv4_udp_fd(Manager *m) {
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(UDP): Failed to set IP_MULTICAST_LOOP: %m");
r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(UDP): Failed to set IP_PKTINFO: %m");
r = setsockopt_int(s, IPPROTO_IP, IP_RECVTTL, true);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(UDP): Failed to set IP_RECVTTL: %m");
/* Disable Don't-Fragment bit in the IP header */
r = setsockopt_int(s, IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DONT);
if (r < 0)
@ -203,9 +224,13 @@ int manager_llmnr_ipv6_udp_fd(Manager *m) {
if (s < 0)
return log_error_errno(errno, "LLMNR-IPv6(UDP): Failed to create socket: %m");
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 255);
r = set_llmnr_common_socket_options(s, AF_INET6);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(UDP): Failed to set IPV6_UNICAST_HOPS: %m");
return log_error_errno(r, "LLMNR-IPv6(UDP): Failed to set common socket options: %m");
r = set_llmnr_common_udp_socket_options(s, AF_INET6);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(UDP): Failed to set common UDP socket options: %m");
/* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, 255);
@ -220,14 +245,6 @@ int manager_llmnr_ipv6_udp_fd(Manager *m) {
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(UDP): Failed to set IPV6_V6ONLY: %m");
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, true);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(UDP): Failed to set IPV6_RECVPKTINFO: %m");
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, true);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(UDP): Failed to set IPV6_RECVHOPLIMIT: %m");
/* first try to bind without SO_REUSEADDR to detect another LLMNR responder */
r = bind(s, &sa.sa, sizeof(sa.in6));
if (r < 0) {
@ -308,6 +325,25 @@ static int on_llmnr_stream(sd_event_source *s, int fd, uint32_t revents, void *u
return 0;
}
static int set_llmnr_common_tcp_socket_options(int fd, int family) {
int r;
/* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */
r = socket_set_ttl(fd, family, 1);
if (r < 0)
return r;
r = setsockopt_int(fd, IPPROTO_TCP, TCP_FASTOPEN, 5); /* Everybody appears to pick qlen=5, let's do the same here. */
if (r < 0)
log_debug_errno(r, "Failed to enable TCP_FASTOPEN on TCP listening socket, ignoring: %m");
r = setsockopt_int(fd, IPPROTO_TCP, TCP_NODELAY, true);
if (r < 0)
log_debug_errno(r, "Failed to enable TCP_NODELAY mode, ignoring: %m");
return 0;
}
int manager_llmnr_ipv4_tcp_fd(Manager *m) {
union sockaddr_union sa = {
.in.sin_family = AF_INET,
@ -325,18 +361,13 @@ int manager_llmnr_ipv4_tcp_fd(Manager *m) {
if (s < 0)
return log_error_errno(errno, "LLMNR-IPv4(TCP): Failed to create socket: %m");
/* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */
r = setsockopt_int(s, IPPROTO_IP, IP_TTL, 1);
r = set_llmnr_common_socket_options(s, AF_INET);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(TCP): Failed to set IP_TTL: %m");
return log_error_errno(r, "LLMNR-IPv4(TCP): Failed to set common socket options: %m");
r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true);
r = set_llmnr_common_tcp_socket_options(s, AF_INET);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(TCP): Failed to set IP_PKTINFO: %m");
r = setsockopt_int(s, IPPROTO_IP, IP_RECVTTL, true);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv4(TCP): Failed to set IP_RECVTTL: %m");
return log_error_errno(r, "LLMNR-IPv4(TCP): Failed to set common TCP socket options: %m");
/* Disable Don't-Fragment bit in the IP header */
r = setsockopt_int(s, IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_DONT);
@ -396,22 +427,17 @@ int manager_llmnr_ipv6_tcp_fd(Manager *m) {
if (s < 0)
return log_error_errno(errno, "LLMNR-IPv6(TCP): Failed to create socket: %m");
/* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, 1);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(TCP): Failed to set IPV6_UNICAST_HOPS: %m");
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_V6ONLY, true);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(TCP): Failed to set IPV6_V6ONLY: %m");
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, true);
r = set_llmnr_common_socket_options(s, AF_INET6);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(TCP): Failed to set IPV6_RECVPKTINFO: %m");
return log_error_errno(r, "LLMNR-IPv6(TCP): Failed to set common socket options: %m");
r = setsockopt_int(s, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, true);
r = set_llmnr_common_tcp_socket_options(s, AF_INET6);
if (r < 0)
return log_error_errno(r, "LLMNR-IPv6(TCP): Failed to set IPV6_RECVHOPLIMIT: %m");
return log_error_errno(r, "LLMNR-IPv6(TCP): Failed to set common TCP socket options: %m");
/* first try to bind without SO_REUSEADDR to detect another LLMNR responder */
r = bind(s, &sa.sa, sizeof(sa.in6));

View File

@ -19,6 +19,7 @@
#include "idn-util.h"
#include "io-util.h"
#include "missing_network.h"
#include "missing_socket.h"
#include "netlink-util.h"
#include "ordered-set.h"
#include "parse-util.h"
@ -881,6 +882,9 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
p->ttl = *(int *) CMSG_DATA(cmsg);
break;
case IPV6_RECVFRAGSIZE:
p->fragsize = *(int *) CMSG_DATA(cmsg);
break;
}
} else if (cmsg->cmsg_level == IPPROTO_IP) {
assert(p->family == AF_INET);
@ -900,6 +904,10 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) {
case IP_TTL:
p->ttl = *(int *) CMSG_DATA(cmsg);
break;
case IP_RECVFRAGSIZE:
p->fragsize = *(int *) CMSG_DATA(cmsg);
break;
}
}
}
@ -1658,3 +1666,63 @@ bool manager_server_is_stub(Manager *m, DnsServer *s) {
return false;
}
int socket_disable_pmtud(int fd, int af) {
int r;
assert(fd >= 0);
if (af == AF_UNSPEC) {
r = socket_get_family(fd, &af);
if (r < 0)
return r;
}
switch (af) {
case AF_INET: {
/* Turn off path MTU discovery, let's rather fragment on the way than to open us up against
* PMTU forgery vulnerabilities.
*
* There appears to be no documentation about IP_PMTUDISC_OMIT, but it has the effect that
* the "Don't Fragment" bit in the IPv4 header is turned off, thus enforcing fragmentation if
* our datagram size exceeds the MTU of a router in the path, and turning off path MTU
* discovery.
*
* This helps mitigating the PMTUD vulnerability described here:
*
* https://blog.apnic.net/2019/07/12/its-time-to-consider-avoiding-ip-fragmentation-in-the-dns/
*
* Similar logic is in place in most DNS servers.
*
* There are multiple conflicting goals: we want to allow the largest datagrams possible (for
* efficiency reasons), but not have fragmentation (for security reasons), nor use PMTUD (for
* security reasons, too). Our strategy to deal with this is: use large packets, turn off
* PMTUD, but watch fragmentation taking place, and then size our packets to the max of the
* fragments seen and if we need larger packets always go to TCP.
*/
r = setsockopt_int(fd, IPPROTO_IP, IP_MTU_DISCOVER, IP_PMTUDISC_OMIT);
if (r < 0)
return r;
return 0;
}
case AF_INET6: {
/* On IPv6 fragmentation only is done by the sender — never by routers on the path. PMTUD is
* mandatory. If we want to turn off PMTUD, the only way is by sending with minimal MTU only,
* so that we apply maximum fragmentation locally already, and thus PMTUD doesn't happen
* because there's nothing that could be fragmented further anymore. */
r = setsockopt_int(fd, IPPROTO_IPV6, IPV6_MTU, IPV6_MIN_MTU);
if (r < 0)
return r;
return 0;
}
default:
return -EAFNOSUPPORT;
}
}

View File

@ -204,3 +204,5 @@ void manager_cleanup_saved_user(Manager *m);
bool manager_next_dnssd_names(Manager *m);
bool manager_server_is_stub(Manager *m, DnsServer *s);
int socket_disable_pmtud(int fd, int af);

View File

@ -237,7 +237,7 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) {
if (!ratelimit_below(&s->ratelimit))
return 0;
r = dns_scope_emit_udp(s, -1, reply);
r = dns_scope_emit_udp(s, -1, AF_UNSPEC, reply);
if (r < 0)
return log_debug_errno(r, "Failed to send reply packet: %m");

View File

@ -71,6 +71,7 @@ static Image *image_free(Image *i) {
free(i->hostname);
strv_free(i->machine_info);
strv_free(i->os_release);
strv_free(i->extension_release);
return mfree(i);
}
@ -1129,7 +1130,7 @@ int image_read_metadata(Image *i) {
case IMAGE_SUBVOLUME:
case IMAGE_DIRECTORY: {
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL;
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL;
sd_id128_t machine_id = SD_ID128_NULL;
_cleanup_free_ char *hostname = NULL;
_cleanup_free_ char *path = NULL;
@ -1176,10 +1177,15 @@ int image_read_metadata(Image *i) {
if (r < 0)
log_debug_errno(r, "Failed to read os-release in image, ignoring: %m");
r = load_extension_release_pairs(i->path, i->name, &extension_release);
if (r < 0)
log_debug_errno(r, "Failed to read extension-release in image, ignoring: %m");
free_and_replace(i->hostname, hostname);
i->machine_id = machine_id;
strv_free_and_replace(i->machine_info, machine_info);
strv_free_and_replace(i->os_release, os_release);
strv_free_and_replace(i->extension_release, extension_release);
break;
}
@ -1205,6 +1211,7 @@ int image_read_metadata(Image *i) {
i->machine_id = m->machine_id;
strv_free_and_replace(i->machine_info, m->machine_info);
strv_free_and_replace(i->os_release, m->os_release);
strv_free_and_replace(i->extension_release, m->extension_release);
break;
}

View File

@ -50,6 +50,7 @@ typedef struct Image {
sd_id128_t machine_id;
char **machine_info;
char **os_release;
char **extension_release;
bool metadata_valid:1;
bool discoverable:1; /* true if we know for sure that image_find() would find the image given just the short name */

View File

@ -23,6 +23,7 @@
#include "def.h"
#include "device-nodes.h"
#include "device-util.h"
#include "discover-image.h"
#include "dissect-image.h"
#include "dm-util.h"
#include "env-file.h"
@ -34,6 +35,7 @@
#include "hexdecoct.h"
#include "hostname-setup.h"
#include "id128-util.h"
#include "import-util.h"
#include "mkdir.h"
#include "mount-util.h"
#include "mountpoint-util.h"
@ -472,7 +474,7 @@ int dissect_image(
_cleanup_(blkid_free_probep) blkid_probe b = NULL;
_cleanup_free_ char *generic_node = NULL;
sd_id128_t generic_uuid = SD_ID128_NULL;
const char *pttype = NULL;
const char *pttype = NULL, *sysname = NULL;
blkid_partlist pl;
int r, generic_nr, n_partitions;
struct stat st;
@ -579,6 +581,34 @@ int dissect_image(
if (!m)
return -ENOMEM;
r = sd_device_get_sysname(d, &sysname);
if (r < 0)
return log_debug_errno(r, "Failed to get device sysname: %m");
if (startswith(sysname, "loop")) {
_cleanup_free_ char *name_stripped = NULL;
const char *full_path;
r = sd_device_get_sysattr_value(d, "loop/backing_file", &full_path);
if (r < 0)
log_debug_errno(r, "Failed to lookup image name via loop device backing file sysattr, ignoring: %m");
else {
r = raw_strip_suffixes(basename(full_path), &name_stripped);
if (r < 0)
return r;
}
free_and_replace(m->image_name, name_stripped);
} else {
r = free_and_strdup(&m->image_name, sysname);
if (r < 0)
return r;
}
if (!image_name_is_valid(m->image_name)) {
log_debug("Image name %s is not valid, ignoring", strempty(m->image_name));
m->image_name = mfree(m->image_name);
}
if ((!(flags & DISSECT_IMAGE_GPT_ONLY) &&
(flags & DISSECT_IMAGE_REQUIRE_ROOT)) ||
(flags & DISSECT_IMAGE_NO_PARTITION_TABLE)) {
@ -1197,9 +1227,11 @@ DissectedImage* dissected_image_unref(DissectedImage *m) {
free(m->partitions[i].mount_options);
}
free(m->image_name);
free(m->hostname);
strv_free(m->machine_info);
strv_free(m->os_release);
strv_free(m->extension_release);
return mfree(m);
}
@ -1370,7 +1402,7 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
/* Returns:
*
* -ENXIO No root partition found
* -EMEDIUMTYPE DISSECT_IMAGE_VALIDATE_OS set but no os-release file found
* -EMEDIUMTYPE DISSECT_IMAGE_VALIDATE_OS set but no os-release/extension-release file found
* -EUNATCH Encrypted partition found for which no dm-crypt was set up yet
* -EUCLEAN fsck for file system failed
* -EBUSY File system already mounted/used elsewhere (kernel)
@ -1400,8 +1432,13 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift,
r = path_is_os_tree(where);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
if (r == 0) {
r = path_is_extension_tree(where, m->image_name);
if (r < 0)
return r;
if (r == 0)
return -EMEDIUMTYPE;
}
}
}
@ -1481,7 +1518,7 @@ int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t u
if (r == -ENXIO)
return log_error_errno(r, "Not root file system found in image.");
if (r == -EMEDIUMTYPE)
return log_error_errno(r, "No suitable os-release file in image found.");
return log_error_errno(r, "No suitable os-release/extension-release file in image found.");
if (r == -EUNATCH)
return log_error_errno(r, "Encrypted file system discovered, but decryption not requested.");
if (r == -EUCLEAN)
@ -2207,18 +2244,20 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
META_MACHINE_ID,
META_MACHINE_INFO,
META_OS_RELEASE,
META_EXTENSION_RELEASE,
_META_MAX,
};
static const char *const paths[_META_MAX] = {
[META_HOSTNAME] = "/etc/hostname\0",
[META_MACHINE_ID] = "/etc/machine-id\0",
[META_MACHINE_INFO] = "/etc/machine-info\0",
[META_OS_RELEASE] = ("/etc/os-release\0"
"/usr/lib/os-release\0"),
static const char *paths[_META_MAX] = {
[META_HOSTNAME] = "/etc/hostname\0",
[META_MACHINE_ID] = "/etc/machine-id\0",
[META_MACHINE_INFO] = "/etc/machine-info\0",
[META_OS_RELEASE] = ("/etc/os-release\0"
"/usr/lib/os-release\0"),
[META_EXTENSION_RELEASE] = NULL,
};
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL;
_cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **extension_release = NULL;
_cleanup_close_pair_ int error_pipe[2] = { -1, -1 };
_cleanup_(rmdir_and_freep) char *t = NULL;
_cleanup_(sigkill_waitp) pid_t child = 0;
@ -2232,11 +2271,21 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
assert(m);
for (; n_meta_initialized < _META_MAX; n_meta_initialized ++)
/* As per the os-release spec, if the image is an extension it will have a file
* named after the image name in extension-release.d/ */
if (m->image_name)
paths[META_EXTENSION_RELEASE] = strjoina("/usr/lib/extension-release.d/extension-release.", m->image_name);
else
log_debug("No image name available, will skip extension-release metadata");
for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) {
if (!paths[n_meta_initialized])
continue;
if (pipe2(fds + 2*n_meta_initialized, O_CLOEXEC) < 0) {
r = -errno;
goto finish;
}
}
r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t);
if (r < 0)
@ -2266,6 +2315,9 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
_cleanup_close_ int fd = -ENOENT;
const char *p;
if (!paths[k])
continue;
fds[2*k] = safe_close(fds[2*k]);
NULSTR_FOREACH(p, paths[k]) {
@ -2296,6 +2348,9 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
for (k = 0; k < _META_MAX; k++) {
_cleanup_fclose_ FILE *f = NULL;
if (!paths[k])
continue;
fds[2*k+1] = safe_close(fds[2*k+1]);
f = take_fdopen(&fds[2*k], "r");
@ -2346,6 +2401,13 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
log_debug_errno(r, "Failed to read OS release file: %m");
break;
case META_EXTENSION_RELEASE:
r = load_env_file_pairs(f, "extension-release", &extension_release);
if (r < 0)
log_debug_errno(r, "Failed to read extension release file: %m");
break;
}
}
@ -2369,10 +2431,14 @@ int dissected_image_acquire_metadata(DissectedImage *m) {
m->machine_id = machine_id;
strv_free_and_replace(m->machine_info, machine_info);
strv_free_and_replace(m->os_release, os_release);
strv_free_and_replace(m->extension_release, extension_release);
finish:
for (k = 0; k < n_meta_initialized; k++)
for (k = 0; k < n_meta_initialized; k++) {
if (!paths[k])
continue;
safe_close_pair(fds + 2*k);
}
return r;
}

View File

@ -97,10 +97,12 @@ struct DissectedImage {
DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX];
char *image_name;
char *hostname;
sd_id128_t machine_id;
char **machine_info;
char **os_release;
char **extension_release;
};
struct MountOptions {

View File

@ -3,6 +3,7 @@
#include "alloc-util.h"
#include "discover-image.h"
#include "env-file.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@ -11,7 +12,7 @@
#include "string-util.h"
#include "strv.h"
int path_is_os_tree(const char *path) {
int path_is_extension_tree(const char *path, const char *extension) {
int r;
assert(path);
@ -22,8 +23,9 @@ int path_is_os_tree(const char *path) {
if (laccess(path, F_OK) < 0)
return -errno;
/* We use {/etc|/usr/lib}/os-release as flag file if something is an OS */
r = open_os_release(path, NULL, NULL);
/* We use /usr/lib/extension-release.d/extension-release.NAME as flag file if something is a system extension,
* and {/etc|/usr/lib}/os-release as flag file if something is an OS (in case extension == NULL) */
r = open_extension_release(path, extension, NULL, NULL);
if (r == -ENOENT) /* We got nothing */
return 0;
if (r < 0)
@ -179,3 +181,86 @@ int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char
return 0;
}
int load_extension_release_pairs(const char *root, const char *extension, char ***ret) {
_cleanup_fclose_ FILE *f = NULL;
_cleanup_free_ char *p = NULL;
int r;
r = fopen_extension_release(root, extension, &p, &f);
if (r < 0)
return r;
return load_env_file_pairs(f, p, ret);
}
int extension_release_validate(
const char *name,
const char *host_os_release_id,
const char *host_os_release_version_id,
const char *host_os_release_sysext_level,
char **extension_release) {
const char *extension_release_id = NULL, *extension_release_sysext_level = NULL;
assert(name);
assert(!isempty(host_os_release_id));
/* Now that we can look into the extension image, let's see if the OS version is compatible */
if (strv_isempty(extension_release)) {
log_debug("Extension '%s' carries no extension-release data, ignoring extension.", name);
return 0;
}
extension_release_id = strv_env_pairs_get(extension_release, "ID");
if (isempty(extension_release_id)) {
log_debug("Extension '%s' does not contain ID in extension-release but requested to match '%s'",
name, strna(host_os_release_id));
return 0;
}
if (!streq_ptr(host_os_release_id, extension_release_id)) {
log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
name, strna(extension_release_id), strna(host_os_release_id));
return 0;
}
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
return 1;
}
/* If the extension has a sysext API level declared, then it must match the host API
* level. Otherwise, compare OS version as a whole */
extension_release_sysext_level = strv_env_pairs_get(extension_release, "SYSEXT_LEVEL");
if (!isempty(host_os_release_sysext_level) && !isempty(extension_release_sysext_level)) {
if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
log_debug("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s'",
name, strna(extension_release_sysext_level), strna(host_os_release_sysext_level));
return 0;
}
} else if (!isempty(host_os_release_version_id)) {
const char *extension_release_version_id;
extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID");
if (isempty(extension_release_version_id)) {
log_debug("Extension '%s' does not contain VERSION_ID in extension-release but requested to match '%s'",
name, strna(host_os_release_version_id));
return 0;
}
if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) {
log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.",
name, strna(extension_release_version_id), strna(host_os_release_version_id));
return 0;
}
} else if (isempty(host_os_release_version_id) && isempty(host_os_release_sysext_level)) {
/* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */
log_debug("No version info on the host (rolling release?), but ID in %s matched.", name);
return 1;
}
log_debug("Version info of extension '%s' matches host.", name);
return 1;
}

View File

@ -3,10 +3,14 @@
#include <stdio.h>
int path_is_os_tree(const char *path);
/* The *_extension_release flavours will look for /usr/lib/extension-release/extension-release.NAME
* in accordance with the OS extension specification, rather than for /usr/lib/ or /etc/os-release. */
int path_is_extension_tree(const char *path, const char *extension);
static inline int path_is_os_tree(const char *path) {
return path_is_extension_tree(path, NULL);
}
int open_extension_release(const char *root, const char *extension, char **ret_path, int *ret_fd);
static inline int open_os_release(const char *root, char **ret_path, int *ret_fd) {
return open_extension_release(root, NULL, ret_path, ret_fd);
@ -19,5 +23,11 @@ static inline int fopen_os_release(const char *root, char **ret_path, FILE **ret
int parse_extension_release(const char *root, const char *extension, ...) _sentinel_;
int parse_os_release(const char *root, ...) _sentinel_;
int load_extension_release_pairs(const char *root, const char *extension, char ***ret);
int load_os_release_pairs(const char *root, char ***ret);
int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret);
/* Given an image name (for logging purposes), a set of os-release values from the host
* and a key-value pair vector of extension-release variables, check that the distro and
* (system extension level or distro version) match and return 1, and 0 otherwise. */
int extension_release_validate(const char *name, const char *host_os_release_id, const char *host_os_release_version_id, const char *host_os_release_sysext_level, char **extension_release);

View File

@ -1,48 +1,43 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <errno.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef const char* (*lookup_t)(int);
typedef int (*reverse_t)(const char*);
#include "string-util.h"
static inline void _test_table(const char *name,
lookup_t lookup,
reverse_t reverse,
int size,
bool sparse) {
int i, boring = 0;
for (i = -1; i < size + 1; i++) {
const char* val = lookup(i);
int rev;
if (val) {
rev = reverse(val);
boring = 0;
} else {
rev = reverse("--no-such--value----");
boring += i >= 0;
}
if (boring < 1 || i == size)
printf("%s: %d → %s → %d\n", name, i, strnull(val), rev);
else if (boring == 1)
printf("%*s ...\n", (int) strlen(name), "");
if (i >= 0 && i < size) {
if (sparse)
assert_se(rev == i || rev == -EINVAL);
else
assert_se(val != NULL && rev == i);
} else
assert_se(val == NULL && rev == -EINVAL);
#define _test_table(name, lookup, reverse, size, sparse) \
for (int64_t _i = -EINVAL, _boring = 0; _i < size + 1; _i++) { \
const char* _val; \
int64_t _rev; \
\
_val = lookup(_i); \
if (_val) { \
_rev = reverse(_val); \
_boring = 0; \
} else { \
_rev = reverse("--no-such--value----"); \
_boring += _i >= 0; \
} \
if (_boring == 0 || _i == size) \
printf("%s: %" PRIi64 " → %s → %" PRIi64 "\n", name, _i, strnull(_val), _rev); \
else if (_boring == 1) \
printf("%*s ...\n", (int) strlen(name), ""); \
\
if (_i >= 0 && _i < size) { \
if (sparse) \
assert_se(_rev == _i || _rev == -EINVAL); \
else \
assert_se(_val && _rev == _i); \
} else \
assert_se(!_val && _rev == -EINVAL); \
}
}
#define test_table(lower, upper) \
#define test_table(lower, upper) \
_test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, false)
#define test_table_sparse(lower, upper) \
#define test_table_sparse(lower, upper) \
_test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, true)

View File

@ -42,7 +42,7 @@ int wifi_get_interface(sd_netlink *genl, int ifindex, enum nl80211_iftype *iftyp
if (r < 0)
return log_debug_errno(r, "Failed to determine genl family: %m");
if (family != SD_GENL_NL80211) {
log_debug("Received message of unexpected genl family %u, ignoring.", family);
log_debug("Received message of unexpected genl family %" PRIi64 ", ignoring.", family);
goto nodata;
}
@ -110,7 +110,7 @@ int wifi_get_station(sd_netlink *genl, int ifindex, struct ether_addr *bssid) {
if (r < 0)
return log_debug_errno(r, "Failed to determine genl family: %m");
if (family != SD_GENL_NL80211) {
log_debug("Received message of unexpected genl family %u, ignoring.", family);
log_debug("Received message of unexpected genl family %" PRIi64 ", ignoring.", family);
goto nodata;
}

View File

@ -8,6 +8,7 @@
#include "capability-util.h"
#include "discover-image.h"
#include "dissect-image.h"
#include "env-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
@ -403,16 +404,15 @@ static int strverscmp_improvedp(char *const* a, char *const* b) {
static int validate_version(
const char *root,
const char *name,
const Image *img,
const char *host_os_release_id,
const char *host_os_release_version_id,
const char *host_os_release_sysext_level) {
_cleanup_free_ char *extension_release_id = NULL, *extension_release_version_id = NULL, *extension_release_sysext_level = NULL;
int r;
assert(root);
assert(name);
assert(img);
if (arg_force) {
log_debug("Force mode enabled, skipping version validation.");
@ -430,45 +430,12 @@ static int validate_version(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Extension image contains /usr/lib/os-release file, which is not allowed (it may carry /etc/os-release), refusing.");
/* Now that we can look into the extension image, let's see if the OS version is compatible */
r = parse_extension_release(
root,
name,
"ID", &extension_release_id,
"VERSION_ID", &extension_release_version_id,
"SYSEXT_LEVEL", &extension_release_sysext_level,
NULL);
if (r == -ENOENT) {
log_notice_errno(r, "Extension '%s' carries no extension-release data, ignoring extension.", name);
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to acquire 'os-release' data of extension '%s': %m", name);
if (!streq_ptr(host_os_release_id, extension_release_id)) {
log_notice("Extension '%s' is for OS '%s', but running on '%s', ignoring extension.",
name, strna(extension_release_id), strna(host_os_release_id));
return 0;
}
/* If the extension has a sysext API level declared, then it must match the host API
* level. Otherwise, compare OS version as a whole */
if (extension_release_sysext_level) {
if (!streq_ptr(host_os_release_sysext_level, extension_release_sysext_level)) {
log_notice("Extension '%s' is for sysext API level '%s', but running on sysext API level '%s', ignoring extension.",
name, extension_release_sysext_level, strna(host_os_release_sysext_level));
return 0;
}
} else {
if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) {
log_notice("Extension '%s' is for OS version '%s', but running on OS version '%s', ignoring extension.",
name, extension_release_version_id, strna(host_os_release_version_id));
return 0;
}
}
log_debug("Version info of extension '%s' matches host.", name);
return 1;
return extension_release_validate(
img->name,
host_os_release_id,
host_os_release_version_id,
host_os_release_sysext_level,
img->extension_release);
}
static int merge_subprocess(Hashmap *images, const char *workspace) {
@ -596,7 +563,7 @@ static int merge_subprocess(Hashmap *images, const char *workspace) {
r = validate_version(
p,
img->name,
img,
host_os_release_id,
host_os_release_version_id,
host_os_release_sysext_level);
@ -742,13 +709,12 @@ static int merge(Hashmap *images) {
return r != 123; /* exit code 123 means: didn't do anything */
}
static int verb_merge(int argc, char **argv, void *userdata) {
static int image_discover_and_read_metadata(Hashmap **ret_images) {
_cleanup_(hashmap_freep) Hashmap *images = NULL;
char **p;
Image *img;
int r;
if (!have_effective_cap(CAP_SYS_ADMIN))
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
assert(ret_images);
images = hashmap_new(&image_hash_ops);
if (!images)
@ -758,6 +724,29 @@ static int verb_merge(int argc, char **argv, void *userdata) {
if (r < 0)
return log_error_errno(r, "Failed to discover extension images: %m");
HASHMAP_FOREACH(img, images) {
r = image_read_metadata(img);
if (r < 0)
return log_error_errno(r, "Failed to read metadata for image %s: %m", img->name);
}
*ret_images = TAKE_PTR(images);
return 0;
}
static int verb_merge(int argc, char **argv, void *userdata) {
_cleanup_(hashmap_freep) Hashmap *images = NULL;
char **p;
int r;
if (!have_effective_cap(CAP_SYS_ADMIN))
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
r = image_discover_and_read_metadata(&images);
if (r < 0)
return r;
/* In merge mode fail if things are already merged. (In --refresh mode below we'll unmerge if we find
* things are already merged...) */
STRV_FOREACH(p, arg_hierarchies) {
@ -789,13 +778,9 @@ static int verb_refresh(int argc, char **argv, void *userdata) {
if (!have_effective_cap(CAP_SYS_ADMIN))
return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be privileged.");
images = hashmap_new(&image_hash_ops);
if (!images)
return log_oom();
r = image_discover(IMAGE_EXTENSION, arg_root, images);
r = image_discover_and_read_metadata(&images);
if (r < 0)
return log_error_errno(r, "Failed to discover extension images: %m");
return r;
r = merge(images); /* Returns > 0 if it did something, i.e. a new overlayfs is mounted now. When it
* does so it implicitly unmounts any overlayfs placed there before. Returns == 0
@ -970,46 +955,6 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
static int parse_env(void) {
_cleanup_strv_free_ char **l = NULL;
const char *e;
char **p;
int r;
e = secure_getenv("SYSTEMD_SYSEXT_HIERARCHIES");
if (!e)
return 0;
/* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
* /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
* switch. */
r = strv_split_full(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return log_error_errno(r, "Failed to parse $SYSTEMD_SYSEXT_HIERARCHIES: %m");
STRV_FOREACH(p, l) {
if (!path_is_absolute(*p))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Hierarchy path '%s' is not absolute, refusing.", *p);
if (!path_is_normalized(*p))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Hierarchy path '%s' is not normalized, refusing.", *p);
if (path_equal(*p, "/"))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Hierarchy path '%s' is the root fs, refusing.", *p);
}
if (strv_isempty(l))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No hierarchies specified, refusing.");
strv_free_and_replace(arg_hierarchies, l);
return 0;
}
static int sysext_main(int argc, char *argv[]) {
static const Verb verbs[] = {
@ -1034,9 +979,12 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
r = parse_env();
/* For debugging purposes it might make sense to do this for other hierarchies than /usr/ and
* /opt/, but let's make that a hacker/debugging feature, i.e. env var instead of cmdline
* switch. */
r = getenv_path_list("SYSTEMD_SYSEXT_HIERARCHIES", &arg_hierarchies);
if (r < 0)
return r;
return log_error_errno(r, "Failed to parse $SYSTEMD_SYSEXT_HIERARCHIES environment variable: %m");
if (!arg_hierarchies) {
arg_hierarchies = strv_new("/usr", "/opt");

View File

@ -99,4 +99,10 @@ typedef void (*_sd_destroy_t)(void *userdata);
} \
struct _sd_useless_struct_to_allow_trailing_semicolon_
/* The following macro should be used in all public enums, to force 64bit wideness on them, so that we can
* freely extend them later on, without breaking compatibility. */
#define _SD_ENUM_FORCE_S64(id) \
_SD_##id##_INT64_MIN = INT64_MIN, \
_SD_##id##_INT64_MAX = INT64_MAX
#endif

View File

@ -43,6 +43,7 @@ typedef enum sd_dhcp_lease_server_type {
SD_DHCP_LEASE_LPR,
_SD_DHCP_LEASE_SERVER_TYPE_MAX,
_SD_DHCP_LEASE_SERVER_TYPE_INVALID = -EINVAL,
_SD_ENUM_FORCE_S64(DHCP_LEASE_SERVER_TYPE),
} sd_dhcp_lease_server_type;
int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr);

View File

@ -129,6 +129,7 @@ typedef enum sd_lldp_event {
SD_LLDP_EVENT_REFRESHED,
_SD_LLDP_EVENT_MAX,
_SD_LLDP_EVENT_INVALID = -EINVAL,
_SD_ENUM_FORCE_S64(LLDP_EVENT),
} sd_lldp_event;
typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata);

View File

@ -60,6 +60,7 @@ typedef enum sd_ndisc_event {
SD_NDISC_EVENT_ROUTER,
_SD_NDISC_EVENT_MAX,
_SD_NDISC_EVENT_INVALID = -EINVAL,
_SD_ENUM_FORCE_S64(NDISC_EVENT),
} sd_ndisc_event;
typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata);

View File

@ -17,6 +17,7 @@
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <errno.h>
#include <inttypes.h>
#include <net/ethernet.h>
#include <netinet/in.h>
@ -34,7 +35,7 @@ typedef struct sd_genl_socket sd_genl_socket;
typedef struct sd_netlink_message sd_netlink_message;
typedef struct sd_netlink_slot sd_netlink_slot;
typedef enum sd_gen_family {
typedef enum sd_genl_family {
SD_GENL_ERROR,
SD_GENL_DONE,
SD_GENL_ID_CTRL,
@ -43,6 +44,9 @@ typedef enum sd_gen_family {
SD_GENL_L2TP,
SD_GENL_MACSEC,
SD_GENL_NL80211,
_SD_GENL_FAMILY_MAX,
_SD_GENL_FAMILY_INVALID = -EINVAL,
_SD_ENUM_FORCE_S64(GENL_FAMILY)
} sd_genl_family;
/* callback */

View File

@ -44,6 +44,17 @@ static void test_strv_env_get(void) {
assert_se(streq(strv_env_get(l, "FOUR"), "4"));
}
static void test_strv_env_pairs_get(void) {
log_info("/* %s */", __func__);
char **l = STRV_MAKE("ONE_OR_TWO", "1", "THREE", "3", "ONE_OR_TWO", "2", "FOUR", "4", "FIVE", "5", "SIX", "FIVE", "SEVEN", "7");
assert_se(streq(strv_env_pairs_get(l, "ONE_OR_TWO"), "2"));
assert_se(streq(strv_env_pairs_get(l, "THREE"), "3"));
assert_se(streq(strv_env_pairs_get(l, "FOUR"), "4"));
assert_se(streq(strv_env_pairs_get(l, "FIVE"), "5"));
}
static void test_strv_env_unset(void) {
log_info("/* %s */", __func__);
@ -390,6 +401,7 @@ int main(int argc, char *argv[]) {
test_strv_env_delete();
test_strv_env_get();
test_strv_env_pairs_get();
test_strv_env_unset();
test_strv_env_merge();
test_strv_env_replace_strdup();

View File

@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "random-util.h"
#include "set.h"
#include "strv.h"
@ -227,6 +228,77 @@ static void test_set_strjoin(void) {
assert_se(STR_IN_SET(joined, "xxxaaaxxxbbbxxx", "xxxbbbxxxaaaxxx"));
}
static void test_set_equal(void) {
_cleanup_set_free_ Set *a = NULL, *b = NULL;
void *p;
int r;
assert_se(a = set_new(NULL));
assert_se(b = set_new(NULL));
assert_se(set_equal(a, a));
assert_se(set_equal(b, b));
assert_se(set_equal(a, b));
assert_se(set_equal(b, a));
assert_se(set_equal(NULL, a));
assert_se(set_equal(NULL, b));
assert_se(set_equal(a, NULL));
assert_se(set_equal(b, NULL));
assert_se(set_equal(NULL, NULL));
for (unsigned i = 0; i < 333; i++) {
p = INT32_TO_PTR(1 + (random_u32() & 0xFFFU));
r = set_put(a, p);
assert_se(r >= 0 || r == -EEXIST);
}
assert_se(set_put(a, INT32_TO_PTR(0x1000U)) >= 0);
assert_se(set_size(a) >= 2);
assert_se(set_size(a) <= 334);
assert_se(!set_equal(a, b));
assert_se(!set_equal(b, a));
assert_se(!set_equal(a, NULL));
SET_FOREACH(p, a)
assert_se(set_put(b, p) >= 0);
assert_se(set_equal(a, b));
assert_se(set_equal(b, a));
assert_se(set_remove(a, INT32_TO_PTR(0x1000U)) == INT32_TO_PTR(0x1000U));
assert_se(!set_equal(a, b));
assert_se(!set_equal(b, a));
assert_se(set_remove(b, INT32_TO_PTR(0x1000U)) == INT32_TO_PTR(0x1000U));
assert_se(set_equal(a, b));
assert_se(set_equal(b, a));
assert_se(set_put(b, INT32_TO_PTR(0x1001U)) >= 0);
assert_se(!set_equal(a, b));
assert_se(!set_equal(b, a));
assert_se(set_put(a, INT32_TO_PTR(0x1001U)) >= 0);
assert_se(set_equal(a, b));
assert_se(set_equal(b, a));
set_clear(a);
assert_se(!set_equal(a, b));
assert_se(!set_equal(b, a));
set_clear(b);
assert_se(set_equal(a, b));
assert_se(set_equal(b, a));
}
int main(int argc, const char *argv[]) {
test_set_steal_first();
test_set_free_with_destructor();
@ -238,6 +310,7 @@ int main(int argc, const char *argv[]) {
test_set_ensure_put();
test_set_ensure_consume();
test_set_strjoin();
test_set_equal();
return 0;
}

View File

@ -178,6 +178,7 @@ IPv6ProxyNDPAddress=
IPv6AcceptRA=
IPv6AcceptRouterAdvertisements=
IPv4AcceptLocal=
IPv4RouteLocalnet=
DNSSECNegativeTrustAnchors=
MACVTAP=
IPv6PrivacyExtensions=

View File

@ -609,11 +609,11 @@ class DnsmasqClientTest(ClientTestBase, unittest.TestCase):
def test_resolved_domain_restricted_dns(self):
'''resolved: domain-restricted DNS servers'''
# FIXME: resolvectl query fails with enabled DNSSEC against our dnsmasq
conf = '/run/systemd/resolved.conf.d/test-disable-dnssec.conf'
# enable DNSSEC in allow downgrade mode, and turn off stuff we don't want to test to make looking at logs easier
conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf'
os.makedirs(os.path.dirname(conf), exist_ok=True)
with open(conf, 'w') as f:
f.write('[Resolve]\nDNSSEC=no\n')
f.write('[Resolve]\nDNSSEC=allow-downgrade\nLLMNR=no\nMulticastDNS=no\nDNSOverTLS=no\n')
self.addCleanup(os.remove, conf)
# create interface for generic connections; this will map all DNS names
@ -625,6 +625,7 @@ Name={}
[Network]
DHCP=ipv4
IPv6AcceptRA=False
DNSSECNegativeTrustAnchors=megasearch.net
'''.format(self.iface))
# create second device/dnsmasq for a .company/.lab VPN interface
@ -651,7 +652,8 @@ Name=testvpnclient
IPv6AcceptRA=False
Address=10.241.3.2/24
DNS=10.241.3.1
Domains= ~company ~lab
Domains=~company ~lab
DNSSECNegativeTrustAnchors=company lab
''')
self.start_unit('systemd-networkd')
@ -690,21 +692,27 @@ Domains= ~company ~lab
def test_resolved_etc_hosts(self):
'''resolved queries to /etc/hosts'''
# FIXME: -t MX query fails with enabled DNSSEC (even when using
# the known negative trust anchor .internal instead of .example.com)
conf = '/run/systemd/resolved.conf.d/test-disable-dnssec.conf'
# enabled DNSSEC in allow-downgrade mode
conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf'
os.makedirs(os.path.dirname(conf), exist_ok=True)
with open(conf, 'w') as f:
f.write('[Resolve]\nDNSSEC=no\nLLMNR=no\nMulticastDNS=no\n')
f.write('[Resolve]\nDNSSEC=allow-downgrade\nLLMNR=no\nMulticastDNS=no\nDNSOverTLS=no\n')
self.addCleanup(os.remove, conf)
# Add example.com to NTA list for this test
negative = '/run/dnssec-trust-anchors.d/example.com.negative'
os.makedirs(os.path.dirname(negative), exist_ok=True)
with open(negative, 'w') as f:
f.write('example.com\n16.172.in-addr.arpa\n')
self.addCleanup(os.remove, negative)
# create /etc/hosts bind mount which resolves my.example.com for IPv4
hosts = os.path.join(self.workdir, 'hosts')
with open(hosts, 'w') as f:
f.write('172.16.99.99 my.example.com\n')
subprocess.check_call(['mount', '--bind', hosts, '/etc/hosts'])
self.addCleanup(subprocess.call, ['umount', '/etc/hosts'])
subprocess.check_call(['systemctl', 'stop', 'systemd-resolved.service'])
subprocess.check_call(['systemctl', 'restart', 'systemd-resolved.service'])
# note: different IPv4 address here, so that it's easy to tell apart
# what resolved the query