mirror of
https://github.com/systemd/systemd
synced 2025-10-01 17:54:45 +02:00
Compare commits
31 Commits
f542f3b2ed
...
0eaee8281d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0eaee8281d | ||
![]() |
669963968c | ||
![]() |
5f94d96c47 | ||
![]() |
cbf23f3853 | ||
![]() |
e4304fb8d4 | ||
![]() |
980821f3f0 | ||
![]() |
acbf761b5d | ||
![]() |
d79677ab44 | ||
![]() |
4565863fff | ||
![]() |
20a001bdd7 | ||
![]() |
eb170e75ab | ||
![]() |
95aa3937da | ||
![]() |
489344f24b | ||
![]() |
2a5095af0c | ||
![]() |
d75bf6cfe2 | ||
![]() |
d97a35e228 | ||
![]() |
905348da28 | ||
![]() |
93547f2812 | ||
![]() |
d335f4c583 | ||
![]() |
7eda2d7fa5 | ||
![]() |
bcf94222a5 | ||
![]() |
7718ac9721 | ||
![]() |
593fe6c04d | ||
![]() |
42e6a77bc5 | ||
![]() |
eb590035b9 | ||
![]() |
1d0796739c | ||
![]() |
b850e51320 | ||
![]() |
8624f1286a | ||
![]() |
386059e119 | ||
![]() |
9823703ae6 | ||
![]() |
d385d54638 |
@ -437,7 +437,7 @@
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Credentials</title>
|
||||
<title>User/Group Identity</title>
|
||||
|
||||
<xi:include href="system-only.xml" xpointer="plural"/>
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -178,6 +178,7 @@ IPv6ProxyNDPAddress=
|
||||
IPv6AcceptRA=
|
||||
IPv6AcceptRouterAdvertisements=
|
||||
IPv4AcceptLocal=
|
||||
IPv4RouteLocalnet=
|
||||
DNSSECNegativeTrustAnchors=
|
||||
MACVTAP=
|
||||
IPv6PrivacyExtensions=
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user