diff options
63 files changed, 2631 insertions, 1235 deletions
diff --git a/Makefile.am b/Makefile.am index 132e592976..296f2c7e5f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5140,6 +5140,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-manager.h \ src/resolve/resolved-conf.c \ src/resolve/resolved-conf.h \ + src/resolve/resolved-resolv-conf.c \ + src/resolve/resolved-resolv-conf.h \ src/resolve/resolved-bus.c \ src/resolve/resolved-bus.h \ src/resolve/resolved-link.h \ @@ -5163,6 +5165,8 @@ systemd_resolved_SOURCES = \ src/resolve/resolved-dns-scope.c \ src/resolve/resolved-dns-server.h \ src/resolve/resolved-dns-server.c \ + src/resolve/resolved-dns-search-domain.h \ + src/resolve/resolved-dns-search-domain.c \ src/resolve/resolved-dns-cache.h \ src/resolve/resolved-dns-cache.c \ src/resolve/resolved-dns-zone.h \ @@ -183,16 +183,12 @@ Features: we always process them before we process client requests - DNSSEC - add display of private key types (http://tools.ietf.org/html/rfc4034#appendix-A.1.1)? - - DNS - - search paths - mDNS/DNS-SD - mDNS RR resolving - service registration - service/domain/types browsing - avahi compat - DNS-SD service registration from socket units - - edns0 - - cname on PTR (?) - resolved should optionally register additional per-interface LLMNR names, so that for the container case we can establish the same name (maybe "host") for referencing the server, everywhere. diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 811e33f4fa..4680b6a4e5 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -1,4 +1,4 @@ -<?xml version='1.0'?> <!--*-nxml-*--> +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> @@ -77,10 +77,11 @@ sent to one of the listed DNS servers in parallel to any per-interface DNS servers acquired from <citerefentry><refentrytitle>systemd-networkd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. - For compatibility reasons, if set to the empty list, the DNS - servers listed in <filename>/etc/resolv.conf</filename> are - used, if any are configured there. This setting defaults to - the empty list.</para></listitem> + For compatibility reasons, if this setting is not specified, + the DNS servers listed in + <filename>/etc/resolv.conf</filename> are used instead, if + that file exists and any servers are configured in it. This + setting defaults to the empty list.</para></listitem> </varlistentry> <varlistentry> @@ -98,6 +99,16 @@ </varlistentry> <varlistentry> + <term><varname>Domains=</varname></term> + <listitem><para>A space-separated list of search domains. For + compatibility reasons, if this setting is not specified, the + search domains listed in <filename>/etc/resolv.conf</filename> + are used instead, if that file exists and any domains are + configured in it. This setting defaults to the empty + list.</para></listitem> + </varlistentry> + + <varlistentry> <term><varname>LLMNR=</varname></term> <listitem><para>Takes a boolean argument or <literal>resolve</literal>. Controls Link-Local Multicast Name diff --git a/man/sd_event_add_io.xml b/man/sd_event_add_io.xml index e3ac407cdf..4cc0428e29 100644 --- a/man/sd_event_add_io.xml +++ b/man/sd_event_add_io.xml @@ -196,7 +196,7 @@ with <function>sd_event_add_io()</function>. It takes the event source object and the new event mask to set.</para> - <para><function>sd_event_source_get_io_events()</function> + <para><function>sd_event_source_get_io_revents()</function> retrieves the I/O event mask of currently seen but undispatched events from an I/O event source created previously with <function>sd_event_add_io()</function>. It takes the event source diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index ead52951da..edc6df914a 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -354,13 +354,26 @@ <listitem><para>These settings control various default resource limits for units. See <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry> - for details. Use the string <varname>infinity</varname> to - configure no limit on a specific resource. The multiplicative suffixes - K (=1024), M (=1024*1024) and so on for G, T, P and E may be used for - resource limits measured in bytes (e.g. DefaultLimitAS=16G). These - settings may be overridden in individual units using the corresponding - LimitXXX= directives. Note that these resource limits are only - defaults for units, they are not applied to PID 1 + for details. The resource limit is possible to specify in two formats, + <option>value</option> to set soft and hard limits to the same value, + or <option>soft:hard</option> to set both limits individually (e.g. DefaultLimitAS=4G:16G). + Use the string <varname>infinity</varname> to + configure no limit on a specific resource. The multiplicative + suffixes K (=1024), M (=1024*1024) and so on for G, T, P and E + may be used for resource limits measured in bytes + (e.g. DefaultLimitAS=16G). For the limits referring to time values, + the usual time units ms, s, min, h and so on may be used (see + <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> + for details). Note that if no time unit is specified for + <varname>DefaultLimitCPU=</varname> the default unit of seconds is + implied, while for <varname>DefaultLimitRTTIME=</varname> the default + unit of microseconds is implied. Also, note that the effective + granularity of the limits might influence their + enforcement. For example, time limits specified for + <varname>DefaultLimitCPU=</varname> will be rounded up implicitly to + multiples of 1s. These settings may be overridden in individual units + using the corresponding LimitXXX= directives. Note that these resource + limits are only defaults for units, they are not applied to PID 1 itself.</para></listitem> </varlistentry> </variablelist> diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index ecc3a8973c..5f98ef163c 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -630,7 +630,10 @@ <listitem><para>These settings set both soft and hard limits of various resources for executed processes. See <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry> - for details. Use the string <varname>infinity</varname> to + for details. The resource limit is possible to specify in two formats, + <option>value</option> to set soft and hard limits to the same value, + or <option>soft:hard</option> to set both limits individually (e.g. LimitAS=4G:16G). + Use the string <varname>infinity</varname> to configure no limit on a specific resource. The multiplicative suffixes K (=1024), M (=1024*1024) and so on for G, T, P and E may be used for resource limits measured in bytes diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml index a724d88584..4a8d265fed 100644 --- a/man/systemd.mount.xml +++ b/man/systemd.mount.xml @@ -88,7 +88,8 @@ configured in a unit file <filename>home-lennart.mount</filename>. For details about the escaping logic used to convert a file system path to a unit name, see - <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para> + <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. + Note that mount units cannot be templated.</para> <para>Optionally, a mount unit may be accompanied by an automount unit, to allow on-demand or parallelized mounting. See diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index f4e24121e7..b75c39aac7 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -44,7 +44,7 @@ int in_addr_is_link_local(int family, const union in_addr_union *u) { assert(u); if (family == AF_INET) - return (be32toh(u->in.s_addr) & 0xFFFF0000) == (169U << 24 | 254U << 16); + return (be32toh(u->in.s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); if (family == AF_INET6) return IN6_IS_ADDR_LINKLOCAL(&u->in6); @@ -52,6 +52,19 @@ int in_addr_is_link_local(int family, const union in_addr_union *u) { return -EAFNOSUPPORT; } +int in_addr_is_localhost(int family, const union in_addr_union *u) { + assert(u); + + if (family == AF_INET) + /* All of 127.x.x.x is localhost. */ + return (be32toh(u->in.s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; + + if (family == AF_INET6) + return IN6_IS_ADDR_LOOPBACK(&u->in6); + + return -EAFNOSUPPORT; +} + int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) { assert(a); assert(b); diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index 51af08868c..58f55b3418 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -33,6 +33,7 @@ union in_addr_union { int in_addr_is_null(int family, const union in_addr_union *u); int in_addr_is_link_local(int family, const union in_addr_union *u); +int in_addr_is_localhost(int family, const union in_addr_union *u); int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); diff --git a/src/basic/ordered-set.h b/src/basic/ordered-set.h index 6c617ab305..da10e90ff2 100644 --- a/src/basic/ordered-set.h +++ b/src/basic/ordered-set.h @@ -29,6 +29,17 @@ static inline OrderedSet* ordered_set_new(const struct hash_ops *ops) { return (OrderedSet*) ordered_hashmap_new(ops); } +static inline int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { + if (*s) + return 0; + + *s = ordered_set_new(ops); + if (!*s) + return -ENOMEM; + + return 0; +} + static inline OrderedSet* ordered_set_free(OrderedSet *s) { ordered_hashmap_free((OrderedHashmap*) s); return NULL; diff --git a/src/basic/set.h b/src/basic/set.h index 4554ef2d49..5fd7de08f9 100644 --- a/src/basic/set.h +++ b/src/basic/set.h @@ -27,7 +27,6 @@ Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) - static inline Set *set_free(Set *s) { internal_hashmap_free(HASHMAP_BASE(s)); return NULL; diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 3b55781d18..8847578bd7 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1089,46 +1089,107 @@ int config_parse_bounding_set( return 0; } -int config_parse_limit( - const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - struct rlimit **rl = data; - rlim_t v; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); +static int rlim_parse_u64(const char *val, rlim_t *res) { + int r = 0; - rl += ltype; - - if (streq(rvalue, "infinity")) - v = RLIM_INFINITY; + if (streq(val, "infinity")) + *res = RLIM_INFINITY; else { uint64_t u; /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); - r = safe_atou64(rvalue, &u); + r = safe_atou64(val, &u); if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) r = -ERANGE; - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } + if (r == 0) + *res = (rlim_t) u; + } + return r; +} + +static int rlim_parse_size(const char *val, rlim_t *res) { + int r = 0; + + if (streq(val, "infinity")) + *res = RLIM_INFINITY; + else { + uint64_t u; + + r = parse_size(val, 1024, &u); + if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) + r = -ERANGE; + if (r == 0) + *res = (rlim_t) u; + } + return r; +} + +static int rlim_parse_sec(const char *val, rlim_t *res) { + int r = 0; + + if (streq(val, "infinity")) + *res = RLIM_INFINITY; + else { + usec_t t; + + r = parse_sec(val, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + *res = RLIM_INFINITY; + else + *res = (rlim_t) (DIV_ROUND_UP(t, USEC_PER_SEC)); + + } + return r; +} + +static int rlim_parse_usec(const char *val, rlim_t *res) { + int r = 0; + + if (streq(val, "infinity")) + *res = RLIM_INFINITY; + else { + usec_t t; + + r = parse_time(val, &t, 1); + if (r < 0) + return r; + if (t == USEC_INFINITY) + *res = RLIM_INFINITY; + else + *res = (rlim_t) t; + } + return r; +} - v = (rlim_t) u; +static int parse_rlimit_range( + const char *unit, + const char *filename, + unsigned line, + const char *value, + struct rlimit **rl, + int (*rlim_parser)(const char *, rlim_t *)) { + + rlim_t soft, hard; + _cleanup_free_ char *sword = NULL, *hword = NULL; + int nwords, r; + + assert(value); + + /* <value> or <soft:hard> */ + nwords = extract_many_words(&value, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &sword, &hword, NULL); + r = nwords < 0 ? nwords : nwords == 0 ? -EINVAL : 0; + + if (r == 0) + r = rlim_parser(sword, &soft); + if (r == 0 && nwords == 2) + r = rlim_parser(hword, &hard); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", value); + return 0; } if (!*rl) { @@ -1136,12 +1197,12 @@ int config_parse_limit( if (!*rl) return log_oom(); } - - (*rl)->rlim_cur = (*rl)->rlim_max = v; + (*rl)->rlim_cur = soft; + (*rl)->rlim_max = nwords == 2 ? hard : soft; return 0; } -int config_parse_bytes_limit( +int config_parse_limit( const char *unit, const char *filename, unsigned line, @@ -1154,8 +1215,6 @@ int config_parse_bytes_limit( void *userdata) { struct rlimit **rl = data; - rlim_t bytes; - int r; assert(filename); assert(lvalue); @@ -1163,31 +1222,30 @@ int config_parse_bytes_limit( assert(data); rl += ltype; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_u64); +} - if (streq(rvalue, "infinity")) - bytes = RLIM_INFINITY; - else { - uint64_t u; - - r = parse_size(rvalue, 1024, &u); - if (r >= 0 && u >= (uint64_t) RLIM_INFINITY) - r = -ERANGE; - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } +int config_parse_bytes_limit( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { - bytes = (rlim_t) u; - } + struct rlimit **rl = data; - if (!*rl) { - *rl = new(struct rlimit, 1); - if (!*rl) - return log_oom(); - } + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); - (*rl)->rlim_cur = (*rl)->rlim_max = bytes; - return 0; + rl += ltype; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_size); } int config_parse_sec_limit( @@ -1203,8 +1261,6 @@ int config_parse_sec_limit( void *userdata) { struct rlimit **rl = data; - rlim_t seconds; - int r; assert(filename); assert(lvalue); @@ -1212,35 +1268,9 @@ int config_parse_sec_limit( assert(data); rl += ltype; - - if (streq(rvalue, "infinity")) - seconds = RLIM_INFINITY; - else { - usec_t t; - - r = parse_sec(rvalue, &t); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } - - if (t == USEC_INFINITY) - seconds = RLIM_INFINITY; - else - seconds = (rlim_t) (DIV_ROUND_UP(t, USEC_PER_SEC)); - } - - if (!*rl) { - *rl = new(struct rlimit, 1); - if (!*rl) - return log_oom(); - } - - (*rl)->rlim_cur = (*rl)->rlim_max = seconds; - return 0; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_sec); } - int config_parse_usec_limit( const char *unit, const char *filename, @@ -1254,8 +1284,6 @@ int config_parse_usec_limit( void *userdata) { struct rlimit **rl = data; - rlim_t useconds; - int r; assert(filename); assert(lvalue); @@ -1263,33 +1291,10 @@ int config_parse_usec_limit( assert(data); rl += ltype; + return parse_rlimit_range(unit, filename, line, rvalue, rl, rlim_parse_usec); +} - if (streq(rvalue, "infinity")) - useconds = RLIM_INFINITY; - else { - usec_t t; - - r = parse_time(rvalue, &t, 1); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } - - if (t == USEC_INFINITY) - useconds = RLIM_INFINITY; - else - useconds = (rlim_t) t; - } - - if (!*rl) { - *rl = new(struct rlimit, 1); - if (!*rl) - return log_oom(); - } - (*rl)->rlim_cur = (*rl)->rlim_max = useconds; - return 0; -} #ifdef HAVE_SYSV_COMPAT int config_parse_sysv_priority(const char *unit, diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index f7c8d11ace..87b8b77f22 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -248,6 +248,7 @@ static int add_mount( assert(what); assert(where); assert(opts); + assert(post); assert(source); if (streq_ptr(fstype, "autofs")) @@ -297,7 +298,7 @@ static int add_mount( "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", source); - if (post && !noauto && !nofail && !automount) + if (!noauto && !nofail && !automount) fprintf(f, "Before=%s\n", post); if (!automount && opts) { @@ -337,7 +338,7 @@ static int add_mount( if (r < 0) return log_error_errno(r, "Failed to write unit file %s: %m", unit); - if (!noauto && post) { + if (!noauto) { lnk = strjoin(arg_dest, "/", post, nofail || automount ? ".wants/" : ".requires/", name, NULL); if (!lnk) return log_oom(); @@ -368,10 +369,7 @@ static int add_mount( "Documentation=man:fstab(5) man:systemd-fstab-generator(8)\n", source); - if (post) - fprintf(f, - "Before=%s\n", - post); + fprintf(f, "Before=%s\n", post); if (opts) { r = write_requires_after(f, opts); diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h index a5daaa543a..7038212bcf 100644 --- a/src/libsystemd-network/dhcp-internal.h +++ b/src/libsystemd-network/dhcp-internal.h @@ -47,8 +47,7 @@ int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, uint8_ typedef int (*dhcp_option_cb_t)(uint8_t code, uint8_t len, const void *option, void *userdata); -int dhcp_option_parse(DHCPMessage *message, size_t len, - dhcp_option_cb_t cb, void *userdata); +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_cb_t cb, void *userdata, char **error_message); int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, uint8_t type, uint16_t arp_type, size_t optlen, diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index a6c410ba91..1de7f3639c 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -24,6 +24,9 @@ #include <stdio.h> #include <string.h> +#include "alloc-util.h" +#include "utf8.h" + #include "dhcp-internal.h" static int option_append(uint8_t options[], size_t size, size_t *offset, @@ -139,72 +142,84 @@ int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, } static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, - uint8_t *message_type, dhcp_option_cb_t cb, + uint8_t *message_type, char **error_message, dhcp_option_cb_t cb, void *userdata) { uint8_t code, len; + const uint8_t *option; size_t offset = 0; while (offset < buflen) { - switch (options[offset]) { - case DHCP_OPTION_PAD: - offset++; + code = options[offset ++]; - break; + switch (code) { + case DHCP_OPTION_PAD: + continue; case DHCP_OPTION_END: return 0; + } - case DHCP_OPTION_MESSAGE_TYPE: - if (buflen < offset + 3) - return -ENOBUFS; + if (buflen < offset + 1) + return -ENOBUFS; + + len = options[offset ++]; - len = options[++offset]; + if (buflen < offset + len) + return -EINVAL; + + option = &options[offset]; + + switch (code) { + case DHCP_OPTION_MESSAGE_TYPE: if (len != 1) return -EINVAL; if (message_type) - *message_type = options[++offset]; - else - offset++; - - offset++; + *message_type = *option; break; - case DHCP_OPTION_OVERLOAD: - if (buflen < offset + 3) - return -ENOBUFS; - - len = options[++offset]; - if (len != 1) + case DHCP_OPTION_ERROR_MESSAGE: + if (len == 0) return -EINVAL; - if (overload) - *overload = options[++offset]; - else - offset++; + if (error_message) { + _cleanup_free_ char *string = NULL; - offset++; + /* Accept a trailing NUL byte */ + if (memchr(option, 0, len - 1)) + return -EINVAL; - break; + string = strndup((const char *) option, len); + if (!string) + return -ENOMEM; - default: - if (buflen < offset + 3) - return -ENOBUFS; + if (!ascii_is_valid(string)) + return -EINVAL; - code = options[offset]; - len = options[++offset]; + free(*error_message); + *error_message = string; + string = NULL; + } - if (buflen < ++offset + len) + break; + case DHCP_OPTION_OVERLOAD: + if (len != 1) return -EINVAL; - if (cb) - cb(code, len, &options[offset], userdata); + if (overload) + *overload = *option; - offset += len; + break; + + default: + if (cb) + cb(code, len, option, userdata); break; } + + offset += len; } if (offset < buflen) @@ -213,8 +228,8 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo return 0; } -int dhcp_option_parse(DHCPMessage *message, size_t len, - dhcp_option_cb_t cb, void *userdata) { +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_cb_t cb, void *userdata, char **_error_message) { + _cleanup_free_ char *error_message = NULL; uint8_t overload = 0; uint8_t message_type = 0; int r; @@ -227,27 +242,29 @@ int dhcp_option_parse(DHCPMessage *message, size_t len, len -= sizeof(DHCPMessage); - r = parse_options(message->options, len, &overload, &message_type, - cb, userdata); + r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); if (r < 0) return r; if (overload & DHCP_OVERLOAD_FILE) { - r = parse_options(message->file, sizeof(message->file), - NULL, &message_type, cb, userdata); + r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); if (r < 0) return r; } if (overload & DHCP_OVERLOAD_SNAME) { - r = parse_options(message->sname, sizeof(message->sname), - NULL, &message_type, cb, userdata); + r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); if (r < 0) return r; } - if (message_type) - return message_type; + if (message_type == 0) + return -ENOMSG; + + if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) { + *_error_message = error_message; + error_message = NULL; + } - return -ENOMSG; + return message_type; } diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index 05bb5ae493..f65529a00e 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -132,6 +132,7 @@ enum { DHCP_OPTION_MESSAGE_TYPE = 53, DHCP_OPTION_SERVER_IDENTIFIER = 54, DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, + DHCP_OPTION_ERROR_MESSAGE = 56, DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, DHCP_OPTION_RENEWAL_T1_TIME = 58, DHCP_OPTION_REBINDING_T2_TIME = 59, diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index 62023a9e49..850212aea1 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -360,7 +360,6 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char * /* End of name */ break; else if (c <= 63) { - _cleanup_free_ char *t = NULL; const char *label; /* Literal label */ @@ -369,21 +368,20 @@ int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char * if (pos > optlen) return -EMSGSIZE; - r = dns_label_escape(label, c, &t); - if (r < 0) - goto fail; - - if (!GREEDY_REALLOC0(ret, allocated, n + !first + strlen(t) + 1)) { + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) { r = -ENOMEM; goto fail; } - if (!first) - ret[n++] = '.'; - else + if (first) first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + goto fail; - memcpy(ret + n, t, r); n += r; continue; } else { diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index 91308bf6c3..acad9d7d6a 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -47,17 +47,15 @@ int icmp6_bind_router_solicitation(int index) { .ipv6mr_interface = index, }; _cleanup_close_ int s = -1; - int r, zero = 0, hops = 255; + int r, zero = 0, one = 1, hops = 255; - s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, - IPPROTO_ICMPV6); + s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); if (s < 0) return -errno; ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); - r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, - sizeof(filter)); + r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); if (r < 0) return -errno; @@ -65,23 +63,23 @@ int icmp6_bind_router_solicitation(int index) { IPV6_PKTINFO socket option also applies for ICMPv6 multicast. Empirical experiments indicates otherwise and therefore an IPV6_MULTICAST_IF socket option is used here instead */ - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, - sizeof(index)); + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)); if (r < 0) return -errno; - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, - sizeof(zero)); + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)); if (r < 0) return -errno; - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, - sizeof(hops)); + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops)); if (r < 0) return -errno; - r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, - sizeof(mreq)); + r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); if (r < 0) return -errno; @@ -101,25 +99,25 @@ int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { struct ether_addr rs_opt_mac; } _packed_ rs = { .rs.nd_rs_type = ND_ROUTER_SOLICIT, + .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR, + .rs_opt.nd_opt_len = 1, }; - struct iovec iov[1] = { - { &rs, }, + struct iovec iov = { + .iov_base = &rs, + .iov_len = sizeof(rs), }; struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), - .msg_iov = iov, + .msg_iov = &iov, .msg_iovlen = 1, }; int r; - if (ether_addr) { - memcpy(&rs.rs_opt_mac, ether_addr, ETH_ALEN); - rs.rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR; - rs.rs_opt.nd_opt_len = 1; - iov[0].iov_len = sizeof(rs); - } else - iov[0].iov_len = sizeof(rs.rs); + assert(s >= 0); + assert(ether_addr); + + rs.rs_opt_mac = *ether_addr; r = sendmsg(s, &msg, 0); if (r < 0) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 5ec0e661f7..7deb00af9c 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -604,7 +604,7 @@ static int client_send_discover(sd_dhcp_client *client) { their messages MUST NOT also send the Host Name option". Just send one of the two depending on the hostname type. */ - if (dns_name_single_label(client->hostname)) { + if (dns_name_is_single_label(client->hostname)) { /* it is unclear from RFC 2131 if client should send hostname in DHCPDISCOVER but dhclient does and so we do as well */ @@ -719,7 +719,7 @@ static int client_send_request(sd_dhcp_client *client) { } if (client->hostname) { - if (dns_name_single_label(client->hostname)) + if (dns_name_is_single_label(client->hostname)) r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, DHCP_OPTION_HOST_NAME, strlen(client->hostname), client->hostname); @@ -1082,7 +1082,7 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, return r; } - r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease); + r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL); if (r != DHCP_OFFER) { log_dhcp_client(client, "received message was not an OFFER, ignoring"); return -ENOMSG; @@ -1121,7 +1121,7 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { int r; - r = dhcp_option_parse(force, len, NULL, NULL); + r = dhcp_option_parse(force, len, NULL, NULL, NULL); if (r != DHCP_FORCERENEW) return -ENOMSG; @@ -1133,6 +1133,7 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) { _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; + _cleanup_free_ char *error_message = NULL; int r; r = dhcp_lease_new(&lease); @@ -1147,9 +1148,9 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, return r; } - r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease); + r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message); if (r == DHCP_NAK) { - log_dhcp_client(client, "NAK"); + log_dhcp_client(client, "NAK: %s", strna(error_message)); return -EADDRNOTAVAIL; } @@ -1513,9 +1514,8 @@ static int client_receive_message_udp(sd_event_source *s, int fd, r = ioctl(fd, FIONREAD, &buflen); if (r < 0) - return r; - - if (buflen < 0) + return -errno; + else if (buflen < 0) /* this can't be right */ return -EIO; @@ -1525,26 +1525,28 @@ static int client_receive_message_udp(sd_event_source *s, int fd, len = read(fd, message, buflen); if (len < 0) { - log_dhcp_client(client, "could not receive message from UDP " - "socket: %m"); - return 0; + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_dhcp_client(client, "Could not receive message from UDP socket: %m"); + return -errno; } else if ((size_t)len < sizeof(DHCPMessage)) { - log_dhcp_client(client, "too small to be a DHCP message: ignoring"); + log_dhcp_client(client, "Too small to be a DHCP message: ignoring"); return 0; } if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { - log_dhcp_client(client, "not a DHCP message: ignoring"); + log_dhcp_client(client, "Not a DHCP message: ignoring"); return 0; } if (message->op != BOOTREPLY) { - log_dhcp_client(client, "not a BOOTREPLY message: ignoring"); + log_dhcp_client(client, "Not a BOOTREPLY message: ignoring"); return 0; } if (message->htype != client->arp_type) { - log_dhcp_client(client, "packet type does not match client type"); + log_dhcp_client(client, "Packet type does not match client type"); return 0; } @@ -1558,13 +1560,12 @@ static int client_receive_message_udp(sd_event_source *s, int fd, } if (message->hlen != expected_hlen) { - log_dhcp_client(client, "unexpected packet hlen %d", message->hlen); + log_dhcp_client(client, "Unexpected packet hlen %d", message->hlen); return 0; } if (memcmp(&message->chaddr[0], expected_chaddr, ETH_ALEN)) { - log_dhcp_client(client, "received chaddr does not match " - "expected: ignoring"); + log_dhcp_client(client, "Received chaddr does not match expected: ignoring"); return 0; } @@ -1572,8 +1573,7 @@ static int client_receive_message_udp(sd_event_source *s, int fd, be32toh(message->xid) != client->xid) { /* in BOUND state, we may receive FORCERENEW with xid set by server, so ignore the xid in this case */ - log_dhcp_client(client, "received xid (%u) does not match " - "expected (%u): ignoring", + log_dhcp_client(client, "Received xid (%u) does not match expected (%u): ignoring", be32toh(message->xid), client->xid); return 0; } @@ -1602,9 +1602,8 @@ static int client_receive_message_raw(sd_event_source *s, int fd, r = ioctl(fd, FIONREAD, &buflen); if (r < 0) - return r; - - if (buflen < 0) + return -errno; + else if (buflen < 0) /* this can't be right */ return -EIO; @@ -1617,9 +1616,12 @@ static int client_receive_message_raw(sd_event_source *s, int fd, len = recvmsg(fd, &msg, 0); if (len < 0) { - log_dhcp_client(client, "could not receive message from raw " - "socket: %m"); - return 0; + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_dhcp_client(client, "Could not receive message from raw socket: %m"); + + return -errno; } else if ((size_t)len < sizeof(DHCPPacket)) return 0; diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 8befedc500..fccdc01bc3 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -661,7 +661,7 @@ int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void break; default: - log_debug("Ignoring option DHCP option %i while parsing.", code); + log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code); break; } diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 277c88e2b9..587ff936ba 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -699,6 +699,7 @@ static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) { int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length) { _cleanup_dhcp_request_free_ DHCPRequest *req = NULL; + _cleanup_free_ char *error_message = NULL; DHCPLease *existing_lease; int type, r; @@ -714,7 +715,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, if (!req) return -ENOMEM; - type = dhcp_option_parse(message, length, parse_request, req); + type = dhcp_option_parse(message, length, parse_request, req, &error_message); if (type < 0) return 0; @@ -784,8 +785,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, break; } case DHCP_DECLINE: - log_dhcp_server(server, "DECLINE (0x%x)", - be32toh(req->message->xid)); + log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); /* TODO: make sure we don't offer this address again */ @@ -963,10 +963,10 @@ static int server_receive_message(sd_event_source *s, int fd, if (ioctl(fd, FIONREAD, &buflen) < 0) return -errno; - if (buflen < 0) + else if (buflen < 0) return -EIO; - message = malloc0(buflen); + message = malloc(buflen); if (!message) return -ENOMEM; @@ -974,9 +974,12 @@ static int server_receive_message(sd_event_source *s, int fd, iov.iov_len = buflen; len = recvmsg(fd, &msg, 0); - if (len < buflen) - return 0; - else if ((size_t)len < sizeof(DHCPMessage)) + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } else if ((size_t)len < sizeof(DHCPMessage)) return 0; CMSG_FOREACH(cmsg, &msg) { diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 801331d270..36d909a4c5 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -895,7 +895,7 @@ static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *adver static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { sd_dhcp6_client *client = userdata; DHCP6_CLIENT_DONT_DESTROY(client); - _cleanup_free_ DHCP6Message *message; + _cleanup_free_ DHCP6Message *message = NULL; int r, buflen, len; assert(s); @@ -903,18 +903,26 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, assert(client->event); r = ioctl(fd, FIONREAD, &buflen); - if (r < 0 || buflen <= 0) - buflen = DHCP6_MIN_OPTIONS_SIZE; + if (r < 0) + return -errno; + else if (buflen < 0) + /* This really should not happen */ + return -EIO; - message = malloc0(buflen); + message = malloc(buflen); if (!message) return -ENOMEM; len = read(fd, message, buflen); - if ((size_t)len < sizeof(DHCP6Message)) { - log_dhcp6_client(client, "could not receive message from UDP socket: %m"); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_dhcp6_client(client, "Could not receive message from UDP socket: %m"); + + return -errno; + } else if ((size_t)len < sizeof(DHCP6Message)) return 0; - } switch(message->type) { case DHCP6_SOLICIT: diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 713438f212..f2bce3b99f 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -418,8 +418,7 @@ static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len, return 0; } -static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, - ssize_t len) { +static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, ssize_t len) { void *opt; struct nd_opt_hdr *opt_hdr; @@ -482,36 +481,79 @@ static int ndisc_ra_parse(sd_ndisc *nd, struct nd_router_advert *ra, static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_free_ struct nd_router_advert *ra = NULL; sd_ndisc *nd = userdata; - int r, buflen = 0, pref, stateful; - union sockaddr_union router = {}; - socklen_t router_len = sizeof(router); + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_LEN(sizeof(int))]; + } control = {}; + struct iovec iov = {}; + union sockaddr_union sa = {}; + struct msghdr msg = { + .msg_name = &sa.sa, + .msg_namelen = sizeof(sa), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; struct in6_addr *gw; unsigned lifetime; ssize_t len; + int r, pref, stateful, buflen = 0; assert(s); assert(nd); assert(nd->event); r = ioctl(fd, FIONREAD, &buflen); - if (r < 0 || buflen <= 0) - buflen = ICMP6_RECV_SIZE; + if (r < 0) + return -errno; + else if (buflen < 0) + /* This really should not happen */ + return -EIO; + + iov.iov_len = buflen; - ra = malloc(buflen); + ra = malloc(iov.iov_len); if (!ra) return -ENOMEM; - len = recvfrom(fd, ra, buflen, 0, &router.sa, &router_len); + iov.iov_base = ra; + + len = recvmsg(fd, &msg, 0); if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + log_ndisc(nd, "Could not receive message from ICMPv6 socket: %m"); + return -errno; + } else if ((size_t)len < sizeof(struct nd_router_advert)) { return 0; - } else if (router_len == 0) + } else if (msg.msg_namelen == 0) gw = NULL; /* only happens when running the test-suite over a socketpair */ - else if (router_len != sizeof(router.in6)) { - log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)router_len); + else if (msg.msg_namelen != sizeof(sa.in6)) { + log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen); return 0; } else - gw = &router.in6.sin6_addr; + gw = &sa.in6.sin6_addr; + + assert(!(msg.msg_flags & MSG_CTRUNC)); + assert(!(msg.msg_flags & MSG_TRUNC)); + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_IPV6 && + cmsg->cmsg_type == IPV6_HOPLIMIT && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + int hops = *(int*)CMSG_DATA(cmsg); + + if (hops != 255) { + log_ndisc(nd, "Received RA with invalid hop limit %d. Ignoring.", hops); + return 0; + } + + break; + } + } if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) { _cleanup_free_ char *addr = NULL; @@ -566,8 +608,6 @@ static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t r static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) { sd_ndisc *nd = userdata; uint64_t time_now, next_timeout; - struct ether_addr unset = { }; - struct ether_addr *addr = NULL; int r; assert(s); @@ -581,10 +621,7 @@ static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata); nd->state = NDISC_STATE_ADVERTISMENT_LISTEN; } else { - if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr))) - addr = &nd->mac_addr; - - r = icmp6_send_router_solicitation(nd->fd, addr); + r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); if (r < 0) log_ndisc(nd, "Error sending Router Solicitation"); else { diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index 1200a7c251..4478147a83 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -223,7 +223,7 @@ int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const voi static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) { int res; - res = dhcp_option_parse(dhcp, size, check_options, NULL); + res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL); assert_se(res == DHCP_DISCOVER); if (verbose) @@ -390,7 +390,7 @@ static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { uint8_t *msg_bytes = (uint8_t *)request; int res; - res = dhcp_option_parse(request, size, check_options, NULL); + res = dhcp_option_parse(request, size, check_options, NULL, NULL); assert_se(res == DHCP_REQUEST); assert_se(xid == request->xid); @@ -420,7 +420,7 @@ static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { uint8_t *msg_bytes = (uint8_t *)discover; int res; - res = dhcp_option_parse(discover, size, check_options, NULL); + res = dhcp_option_parse(discover, size, check_options, NULL, NULL); assert_se(res == DHCP_DISCOVER); assert_se(msg_bytes[size - 1] == DHCP_OPTION_END); diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index 3607df63af..75d22c4df3 100644 --- a/src/libsystemd-network/test-dhcp-option.c +++ b/src/libsystemd-network/test-dhcp-option.c @@ -75,9 +75,8 @@ static const char *dhcp_type(int type) { static void test_invalid_buffer_length(void) { DHCPMessage message; - assert_se(dhcp_option_parse(&message, 0, NULL, NULL) == -EINVAL); - assert_se(dhcp_option_parse(&message, sizeof(DHCPMessage) - 1, NULL, NULL) - == -EINVAL); + assert_se(dhcp_option_parse(&message, 0, NULL, NULL, NULL) == -EINVAL); + assert_se(dhcp_option_parse(&message, sizeof(DHCPMessage) - 1, NULL, NULL, NULL) == -EINVAL); } static void test_message_init(void) { @@ -101,7 +100,7 @@ static void test_message_init(void) { assert_se(magic[2] == 83); assert_se(magic[3] == 99); - assert_se(dhcp_option_parse(message, len, NULL, NULL) >= 0); + assert_se(dhcp_option_parse(message, len, NULL, NULL, NULL) >= 0); } static DHCPMessage *create_message(uint8_t *options, uint16_t optlen, @@ -264,19 +263,12 @@ static void test_options(struct option_desc *desc) { buflen = sizeof(DHCPMessage) + optlen; if (!desc) { - assert_se((res = dhcp_option_parse(message, buflen, - test_options_cb, - NULL)) == -ENOMSG); + assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, NULL, NULL)) == -ENOMSG); } else if (desc->success) { - assert_se((res = dhcp_option_parse(message, buflen, - test_options_cb, - desc)) >= 0); - assert_se(desc->pos == -1 && desc->filepos == -1 && - desc->snamepos == -1); + assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) >= 0); + assert_se(desc->pos == -1 && desc->filepos == -1 && desc->snamepos == -1); } else - assert_se((res = dhcp_option_parse(message, buflen, - test_options_cb, - desc)) < 0); + assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) < 0); if (verbose) printf("DHCP type %s\n", dhcp_type(res)); diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 64a4b74e15..a9d91b07f6 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2040,9 +2040,13 @@ static int link_configure(Link *link) { assert(link->network); assert(link->state == LINK_STATE_PENDING); - r = link_drop_foreign_config(link); - if (r < 0) - return r; + /* Drop foreign config, but ignore loopback device. + * We do not want to remove loopback address. */ + if (!(link->flags & IFF_LOOPBACK)) { + r = link_drop_foreign_config(link); + if (r < 0) + return r; + } r = link_set_bridge_fdb(link); if (r < 0) diff --git a/src/resolve-host/resolve-host.c b/src/resolve-host/resolve-host.c index 2c17ad6ede..f68751a2e5 100644 --- a/src/resolve-host/resolve-host.c +++ b/src/resolve-host/resolve-host.c @@ -680,6 +680,7 @@ static void help(void) { " --service-address=BOOL Do [not] resolve address for services\n" " --service-txt=BOOL Do [not] resolve TXT records for services\n" " --cname=BOOL Do [not] follow CNAME redirects\n" + " --search=BOOL Do [not] use search domains\n" " --legend=BOOL Do [not] print column headers\n" , program_invocation_short_name, program_invocation_short_name); } @@ -692,6 +693,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CNAME, ARG_SERVICE_ADDRESS, ARG_SERVICE_TXT, + ARG_SEARCH, }; static const struct option options[] = { @@ -705,6 +707,7 @@ static int parse_argv(int argc, char *argv[]) { { "service", no_argument, NULL, ARG_SERVICE }, { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, + { "search", required_argument, NULL, ARG_SEARCH }, {} }; @@ -834,6 +837,16 @@ static int parse_argv(int argc, char *argv[]) { arg_flags &= ~SD_RESOLVED_NO_TXT; break; + case ARG_SEARCH: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --search argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_SEARCH; + else + arg_flags &= ~SD_RESOLVED_NO_SEARCH; + break; + case '?': return -EINVAL; diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index a43aa59cc0..62bb08a2e8 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -38,7 +38,7 @@ static int reply_query_state(DnsQuery *q) { name = ip; } else - name = dns_question_name(q->question); + name = dns_question_first_name(q->question); switch (q->state) { @@ -145,7 +145,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); goto finish; } if (r < 0) @@ -166,7 +166,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { int ifindex; DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) goto finish; if (r == 0) @@ -184,7 +184,7 @@ static void bus_method_resolve_hostname_complete(DnsQuery *q) { } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question)); goto finish; } @@ -254,7 +254,7 @@ static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, if (r == 0) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); - r = check_ifindex_flags(ifindex, &flags, 0, error); + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); if (r < 0) return r; @@ -298,7 +298,15 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { goto finish; } - /* We don't process CNAME for PTR lookups. */ + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); + goto finish; + } + if (r < 0) + goto finish; + if (r > 0) /* This was a cname, and the query was restarted. */ + return; r = sd_bus_message_new_method_return(q->request, &reply); if (r < 0) @@ -310,7 +318,7 @@ static void bus_method_resolve_address_complete(DnsQuery *q) { if (q->answer) { DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, NULL); if (r < 0) goto finish; if (r == 0) @@ -392,7 +400,7 @@ static int bus_method_resolve_address(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags); + r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -466,7 +474,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); goto finish; } if (r < 0) @@ -487,7 +495,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { int ifindex; DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, NULL); if (r < 0) goto finish; if (r == 0) @@ -502,7 +510,7 @@ static void bus_method_resolve_record_complete(DnsQuery *q) { } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_question_first_name(q->question)); goto finish; } @@ -566,7 +574,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags); + r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -612,7 +620,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) if (aux->auxiliary_result != 0) continue; - r = dns_name_equal(dns_question_name(aux->question), rr->srv.name); + r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name); if (r < 0) return r; if (r == 0) @@ -620,7 +628,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) DNS_ANSWER_FOREACH(zz, aux->answer) { - r = dns_question_matches_rr(aux->question, zz); + r = dns_question_matches_rr(aux->question, zz, NULL); if (r < 0) return r; if (r == 0) @@ -664,7 +672,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) if (aux->auxiliary_result != 0) continue; - r = dns_name_equal(dns_question_name(aux->question), rr->srv.name); + r = dns_name_equal(dns_question_first_name(aux->question), rr->srv.name); if (r < 0) return r; if (r == 0) @@ -672,7 +680,7 @@ static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { - r = dns_question_matches_rr(aux->question, zz); + r = dns_question_matches_rr(aux->question, zz, NULL); if (r < 0) return r; if (r == 0) @@ -773,7 +781,7 @@ static void resolve_service_all_complete(DnsQuery *q) { assert(bad->auxiliary_result != 0); if (bad->auxiliary_result == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(bad->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(bad->question)); goto finish; } @@ -798,7 +806,7 @@ static void resolve_service_all_complete(DnsQuery *q) { DnsResourceRecord *rr; DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, NULL); if (r < 0) goto finish; if (r == 0) @@ -818,7 +826,7 @@ static void resolve_service_all_complete(DnsQuery *q) { } if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question)); goto finish; } @@ -834,7 +842,7 @@ static void resolve_service_all_complete(DnsQuery *q) { DnsResourceRecord *rr; DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, NULL); if (r < 0) goto finish; if (r == 0) @@ -911,7 +919,7 @@ static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifin if (r < 0) return r; - r = dns_query_new(q->manager, &aux, question, ifindex, q->flags); + r = dns_query_new(q->manager, &aux, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -957,7 +965,7 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { r = dns_query_process_cname(q); if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_question_first_name(q->question)); goto finish; } if (r < 0) @@ -970,7 +978,7 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { int ifindex; DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, NULL); if (r < 0) goto finish; if (r == 0) @@ -993,7 +1001,7 @@ static void bus_method_resolve_service_complete(DnsQuery *q) { } if (found <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_name(q->question)); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_question_first_name(q->question)); goto finish; } @@ -1041,13 +1049,8 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (isempty(type)) type = NULL; - else { - r = dns_srv_type_verify(type); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); - } + else if (!dns_srv_type_is_valid(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); r = dns_name_is_valid(domain); if (r < 0) @@ -1078,7 +1081,7 @@ static int bus_method_resolve_service(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = dns_query_new(m, &q, question, ifindex, flags); + r = dns_query_new(m, &q, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); if (r < 0) return r; @@ -1101,8 +1104,123 @@ fail: return r; } +static int append_dns_server(sd_bus_message *reply, DnsServer *s) { + int r; + + assert(reply); + assert(s); + + r = sd_bus_message_open_container(reply, 'r', "iiay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "ii", s->link ? s->link->ifindex : 0, s->family); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_dns_servers( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + unsigned c = 0; + DnsServer *s; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + LIST_FOREACH(servers, s, m->dns_servers) { + r = append_dns_server(reply, s); + if (r < 0) + return r; + + c++; + } + + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = append_dns_server(reply, s); + if (r < 0) + return r; + c++; + } + } + + if (c == 0) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = append_dns_server(reply, s); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_search_domains( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = userdata; + DnsSearchDomain *d; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(is)"); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, m->search_domains) { + r = sd_bus_message_append(reply, "(is)", 0, d->name); + if (r < 0) + return r; + } + + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(domains, d, l->search_domains) { + r = sd_bus_message_append(reply, "is", l->ifindex, d->name); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + static const sd_bus_vtable resolve_vtable[] = { SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), + SD_BUS_PROPERTY("DNSServers", "a(iiay)", bus_property_get_dns_servers, 0, 0), + SD_BUS_PROPERTY("SearchDomains", "a(is)", bus_property_get_search_domains, 0, 0), + SD_BUS_METHOD("ResolveHostname", "isit", "a(iiay)st", bus_method_resolve_hostname, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveAddress", "iiayt", "a(is)t", bus_method_resolve_address, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("ResolveRecord", "isqqt", "a(iqqay)t", bus_method_resolve_record, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 9207719551..3fc7d9ae3d 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -27,53 +27,99 @@ #include "resolved-conf.h" #include "string-util.h" -int manager_parse_dns_server(Manager *m, DnsServerType type, const char *string) { - DnsServer *first; +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { + union in_addr_union address; + int family, r; + DnsServer *s; + + assert(m); + assert(word); + + r = in_addr_from_string_auto(word, &family, &address); + if (r < 0) + return r; + + /* Filter out duplicates */ + s = dns_server_find(manager_get_first_dns_server(m, type), family, &address); + if (s) { + /* + * Drop the marker. This is used to find the servers + * that ceased to exist, see + * manager_mark_dns_servers() and + * manager_flush_marked_dns_servers(). + */ + dns_server_move_back_and_unmark(s); + return 0; + } + + return dns_server_new(m, NULL, type, NULL, family, &address); +} + +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { int r; assert(m); assert(string); - first = type == DNS_SERVER_FALLBACK ? m->fallback_dns_servers : m->dns_servers; - for(;;) { _cleanup_free_ char *word = NULL; - union in_addr_union addr; - bool found = false; - DnsServer *s; - int family; r = extract_first_word(&string, &word, NULL, 0); if (r < 0) - return log_error_errno(r, "Failed to parse resolved dns server syntax \"%s\": %m", string); + return r; if (r == 0) break; - r = in_addr_from_string_auto(word, &family, &addr); - if (r < 0) { - log_warning("Ignoring invalid DNS address '%s'", word); - continue; - } + r = manager_add_dns_server_by_string(m, type, word); + if (r < 0) + log_warning_errno(r, "Failed to add DNS server address '%s', ignoring.", word); + } + + return 0; +} - /* Filter out duplicates */ - LIST_FOREACH(servers, s, first) - if (s->family == family && in_addr_equal(family, &s->address, &addr)) { - found = true; - break; - } +int manager_add_search_domain_by_string(Manager *m, const char *domain) { + DnsSearchDomain *d; + int r; - if (found) - continue; + assert(m); + assert(domain); - r = dns_server_new(m, NULL, type, NULL, family, &addr); + r = dns_search_domain_find(m->search_domains, domain, &d); + if (r < 0) + return r; + if (r > 0) { + dns_search_domain_move_back_and_unmark(d); + return 0; + } + + return dns_search_domain_new(m, NULL, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); +} + +int manager_parse_search_domains_and_warn(Manager *m, const char *string) { + int r; + + assert(m); + assert(string); + + for(;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); if (r < 0) return r; + if (r == 0) + break; + + r = manager_add_search_domain_by_string(m, word); + if (r < 0) + log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); } return 0; } -int config_parse_dnsv( +int config_parse_dns_servers( const char *unit, const char *filename, unsigned line, @@ -95,10 +141,10 @@ int config_parse_dnsv( if (isempty(rvalue)) /* Empty assignment means clear the list */ - manager_flush_dns_servers(m, ltype); + dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); else { /* Otherwise, add to the list */ - r = manager_parse_dns_server(m, ltype, rvalue); + r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); return 0; @@ -109,6 +155,47 @@ int config_parse_dnsv( * /etc/resolv.conf */ if (ltype == DNS_SERVER_SYSTEM) m->read_resolv_conf = false; + if (ltype == DNS_SERVER_FALLBACK) + m->need_builtin_fallbacks = false; + + return 0; +} + +int config_parse_search_domains( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_search_domain_unlink_all(m->search_domains); + else { + /* Otherwise, add to the list */ + r = manager_parse_search_domains_and_warn(m, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + m->read_resolv_conf = false; return 0; } @@ -148,11 +235,24 @@ int config_parse_support( } int manager_parse_config_file(Manager *m) { + int r; + assert(m); - return config_parse_many(PKGSYSCONFDIR "/resolved.conf", - CONF_PATHS_NULSTR("systemd/resolved.conf.d"), - "Resolve\0", - config_item_perf_lookup, resolved_gperf_lookup, - false, m); + r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", + CONF_PATHS_NULSTR("systemd/resolved.conf.d"), + "Resolve\0", + config_item_perf_lookup, resolved_gperf_lookup, + false, m); + if (r < 0) + return r; + + if (m->need_builtin_fallbacks) { + r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); + if (r < 0) + return r; + } + + return 0; + } diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index b3dbea7b6b..28d2549d35 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -23,10 +23,16 @@ #include "resolved-manager.h" -int manager_parse_dns_server(Manager *m, DnsServerType type, const char *string); int manager_parse_config_file(Manager *m); +int manager_add_search_domain_by_string(Manager *m, const char *domain); +int manager_parse_search_domains_and_warn(Manager *m, const char *string); + +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); + const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); -int config_parse_dnsv(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_dns_servers(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_search_domains(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_support(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index 85af1923ff..be29f51663 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -21,12 +21,13 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#define SD_RESOLVED_DNS ((uint64_t) 1) -#define SD_RESOLVED_LLMNR_IPV4 ((uint64_t) 2) -#define SD_RESOLVED_LLMNR_IPV6 ((uint64_t) 4) -#define SD_RESOLVED_NO_CNAME ((uint64_t) 8) -#define SD_RESOLVED_NO_TXT ((uint64_t) 16) -#define SD_RESOLVED_NO_ADDRESS ((uint64_t) 32) +#define SD_RESOLVED_DNS (UINT64_C(1) << 0) +#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) +#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) +#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) +#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) +#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) +#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) #define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) #define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index 3cf9c68074..4db67f7278 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -141,7 +141,7 @@ int dns_answer_contains(DnsAnswer *a, DnsResourceKey *key) { return 0; for (i = 0; i < a->n_rrs; i++) { - r = dns_resource_key_match_rr(key, a->items[i].rr); + r = dns_resource_key_match_rr(key, a->items[i].rr, NULL); if (r < 0) return r; if (r > 0) diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h index b5b1ad56ba..8814919deb 100644 --- a/src/resolve/resolved-dns-answer.h +++ b/src/resolve/resolved-dns-answer.h @@ -61,7 +61,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); #define DNS_ANSWER_FOREACH(kk, a) \ for (unsigned _i = ({ \ - (kk) = ((a) && (a)->n_rrs > 0 ? (a)->items[0].rr : NULL); \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ 0; \ }); \ (a) && ((_i) < (a)->n_rrs); \ @@ -69,9 +69,9 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); #define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) \ for (unsigned _i = ({ \ - (kk) = ((a) && (a)->n_rrs > 0 ? (a)->items[0].rr : NULL); \ - (ifindex) = ((a) && (a)->n_rrs > 0 ? (a)->items[0].ifindex : 0); \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (ifindex) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ 0; \ }); \ (a) && ((_i) < (a)->n_rrs); \ - _i++, (kk) = (_i < (a)->n_rrs ? (a)->items[_i].rr : NULL), (ifindex) = (_i < (a)->n_rrs ? (a)->items[_i].ifindex : 0)) + _i++, (kk) = ((_i < (a)->n_rrs) ? (a)->items[_i].rr : NULL), (ifindex) = ((_i < (a)->n_rrs) ? (a)->items[_i].ifindex : 0)) diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 472486777c..40b662246f 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -1145,7 +1145,6 @@ int dns_packet_read_name( /* End of name */ break; else if (c <= 63) { - _cleanup_free_ char *t = NULL; const char *label; /* Literal label */ @@ -1153,21 +1152,20 @@ int dns_packet_read_name( if (r < 0) goto fail; - r = dns_label_escape(label, c, &t); - if (r < 0) - goto fail; - - if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) { + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) { r = -ENOMEM; goto fail; } - if (!first) - ret[n++] = '.'; - else + if (first) first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + goto fail; - memcpy(ret + n, t, r); n += r; continue; } else if (allow_compression && (c & 0xc0) == 0xc0) { @@ -1746,12 +1744,9 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) { if (r < 0) goto fail; - /* The types bitmap must contain at least the NSEC record itself, so an empty bitmap means - something went wrong */ - if (bitmap_isclear(rr->nsec.types)) { - r = -EBADMSG; - goto fail; - } + /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself + * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records + * without the NSEC bit set. */ break; diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 48df5dfc53..90b5a7c8bd 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -177,6 +177,14 @@ void dns_packet_rewind(DnsPacket *p, size_t idx); int dns_packet_skip_question(DnsPacket *p); int dns_packet_extract(DnsPacket *p); +static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { + /* Never cache data originating from localhost, under the + * assumption, that it's coming from a locally DNS forwarder + * or server, that is caching on its own. */ + + return in_addr_is_localhost(p->family, &p->sender) == 0; +} + enum { DNS_RCODE_SUCCESS = 0, DNS_RCODE_FORMERR = 1, diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index c1cd650651..a96cf439ad 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -32,19 +32,265 @@ #define QUERIES_MAX 2048 #define AUXILIARY_QUERIES_MAX 64 -static void dns_query_stop(DnsQuery *q) { - DnsTransaction *t; +static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; + assert(ret); assert(q); + assert(s); - q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + c = new0(DnsQueryCandidate, 1); + if (!c) + return -ENOMEM; - while ((t = set_steal_first(q->transactions))) { - set_remove(t->queries, q); + c->query = q; + c->scope = s; + + LIST_PREPEND(candidates_by_query, q->candidates, c); + LIST_PREPEND(candidates_by_scope, s->query_candidates, c); + + *ret = c; + return 0; +} + +static void dns_query_candidate_stop(DnsQueryCandidate *c) { + DnsTransaction *t; + + assert(c); + + while ((t = set_steal_first(c->transactions))) { + set_remove(t->query_candidates, c); dns_transaction_gc(t); } } +DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { + + if (!c) + return NULL; + + dns_query_candidate_stop(c); + + set_free(c->transactions); + dns_search_domain_unref(c->search_domain); + + if (c->query) + LIST_REMOVE(candidates_by_query, c->query->candidates, c); + + if (c->scope) + LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); + + free(c); + + return NULL; +} + +static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { + _cleanup_(dns_search_domain_unrefp) DnsSearchDomain *previous = NULL; + DnsSearchDomain *next = NULL; + + assert(c); + + if (c->search_domain && c->search_domain->linked) { + next = c->search_domain->domains_next; + + if (!next) /* We hit the end of the list */ + return 0; + + } else { + next = dns_scope_get_search_domains(c->scope); + + if (!next) /* OK, there's nothing. */ + return 0; + } + + dns_search_domain_unref(c->search_domain); + c->search_domain = dns_search_domain_ref(next); + + return 1; +} + +static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { + DnsTransaction *t; + int r; + + assert(c); + assert(key); + + r = set_ensure_allocated(&c->transactions, NULL); + if (r < 0) + return r; + + t = dns_scope_find_transaction(c->scope, key, true); + if (!t) { + r = dns_transaction_new(&t, c->scope, key); + if (r < 0) + return r; + } + + r = set_ensure_allocated(&t->query_candidates, NULL); + if (r < 0) + goto gc; + + r = set_put(t->query_candidates, c); + if (r < 0) + goto gc; + + r = set_put(c->transactions, t); + if (r < 0) { + set_remove(t->query_candidates, c); + goto gc; + } + + return 0; + +gc: + dns_transaction_gc(t); + return r; +} + +static int dns_query_candidate_go(DnsQueryCandidate *c) { + DnsTransaction *t; + Iterator i; + int r; + + assert(c); + + /* Start the transactions that are not started yet */ + SET_FOREACH(t, c->transactions, i) { + if (t->state != DNS_TRANSACTION_NULL) + continue; + + r = dns_transaction_go(t); + if (r < 0) + return r; + } + + return 0; +} + +static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + DnsTransaction *t; + Iterator i; + + assert(c); + + if (c->error_code != 0) + return DNS_TRANSACTION_RESOURCES; + + SET_FOREACH(t, c->transactions, i) { + + switch (t->state) { + + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_NULL: + return t->state; + + case DNS_TRANSACTION_SUCCESS: + state = t->state; + break; + + default: + if (state != DNS_TRANSACTION_SUCCESS) + state = t->state; + + break; + } + } + + return state; +} + +static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { + DnsResourceKey *key; + int n = 0, r; + + assert(c); + + dns_query_candidate_stop(c); + + /* Create one transaction per question key */ + DNS_QUESTION_FOREACH(key, c->query->question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; + + if (c->search_domain) { + r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); + if (r < 0) + goto fail; + } + + r = dns_query_candidate_add_transaction(c, new_key ?: key); + if (r < 0) + goto fail; + + n++; + } + + return n; + +fail: + dns_query_candidate_stop(c); + return r; +} + +void dns_query_candidate_ready(DnsQueryCandidate *c) { + DnsTransactionState state; + int r; + + assert(c); + + state = dns_query_candidate_state(c); + + if (IN_SET(state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) + return; + + if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { + + r = dns_query_candidate_next_search_domain(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* OK, there's another search domain to try, let's do so. */ + + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* New transactions where queued. Start them and wait */ + + r = dns_query_candidate_go(c); + if (r < 0) + goto fail; + + return; + } + } + + } + + dns_query_ready(c->query); + return; + +fail: + log_warning_errno(r, "Failed to follow search domains: %m"); + c->error_code = r; + dns_query_ready(c->query); +} + +static void dns_query_stop(DnsQuery *q) { + DnsQueryCandidate *c; + + assert(q); + + q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + + LIST_FOREACH(candidates_by_query, c, q->candidates) + dns_query_candidate_stop(c); +} + DnsQuery *dns_query_free(DnsQuery *q) { if (!q) return NULL; @@ -58,11 +304,12 @@ DnsQuery *dns_query_free(DnsQuery *q) { LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); } - dns_query_stop(q); - set_free(q->transactions); + while (q->candidates) + dns_query_candidate_free(q->candidates); dns_question_unref(q->question); dns_answer_unref(q->answer); + dns_search_domain_unref(q->answer_search_domain); sd_bus_message_unref(q->request); sd_bus_track_unref(q->bus_track); @@ -85,7 +332,7 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex assert(m); assert(question); - r = dns_question_is_valid(question); + r = dns_question_is_valid_for_query(question); if (r < 0) return r; @@ -99,6 +346,8 @@ int dns_query_new(Manager *m, DnsQuery **ret, DnsQuestion *question, int ifindex q->question = dns_question_ref(question); q->ifindex = ifindex; q->flags = flags; + q->answer_family = AF_UNSPEC; + q->answer_protocol = _DNS_PROTOCOL_INVALID; for (i = 0; i < question->n_keys; i++) { _cleanup_free_ char *p; @@ -170,64 +419,40 @@ static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { return 0; } -static int dns_query_add_transaction(DnsQuery *q, DnsScope *s, DnsResourceKey *key) { - DnsTransaction *t; +static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; int r; assert(q); assert(s); - assert(key); - r = set_ensure_allocated(&q->transactions, NULL); + r = dns_query_candidate_new(&c, q, s); if (r < 0) return r; - t = dns_scope_find_transaction(s, key, true); - if (!t) { - r = dns_transaction_new(&t, s, key); - if (r < 0) - return r; - } - - r = set_ensure_allocated(&t->queries, NULL); - if (r < 0) - goto gc; - - r = set_put(t->queries, q); + /* If this a single-label domain on DNS, we might append a suitable search domain first. */ + r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question)); if (r < 0) - goto gc; + goto fail; + if (r > 0) { + /* OK, we need a search domain now. Let's find one for this scope */ - r = set_put(q->transactions, t); - if (r < 0) { - set_remove(t->queries, q); - goto gc; + r = dns_query_candidate_next_search_domain(c); + if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ + goto fail; } + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + return 0; -gc: - dns_transaction_gc(t); +fail: + dns_query_candidate_free(c); return r; } -static int dns_query_add_transaction_split(DnsQuery *q, DnsScope *s) { - unsigned i; - int r; - - assert(q); - assert(s); - - /* Create one transaction per question key */ - - for (i = 0; i < q->question->n_keys; i++) { - r = dns_query_add_transaction(q, s, q->question->keys[i]); - if (r < 0) - return r; - } - - return 0; -} - static int SYNTHESIZE_IFINDEX(int ifindex) { /* When the caller asked for resolving on a specific @@ -630,9 +855,9 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { q->answer = answer; answer = NULL; - q->answer_family = SYNTHESIZE_FAMILY(q->flags); - q->answer_protocol = SYNTHESIZE_PROTOCOL(q->flags); q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = SYNTHESIZE_PROTOCOL(q->flags); + q->answer_family = SYNTHESIZE_FAMILY(q->flags); *state = DNS_TRANSACTION_SUCCESS; @@ -642,9 +867,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { int dns_query_go(DnsQuery *q) { DnsScopeMatch found = DNS_SCOPE_NO; DnsScope *s, *first = NULL; - DnsTransaction *t; + DnsQueryCandidate *c; const char *name; - Iterator i; int r; assert(q); @@ -655,7 +879,7 @@ int dns_query_go(DnsQuery *q) { assert(q->question); assert(q->question->n_keys > 0); - name = dns_question_name(q->question); + name = dns_question_first_name(q->question); LIST_FOREACH(scopes, s, q->manager->dns_scopes) { DnsScopeMatch match; @@ -688,7 +912,7 @@ int dns_query_go(DnsQuery *q) { return 1; } - r = dns_query_add_transaction_split(q, first); + r = dns_query_add_candidate(q, first); if (r < 0) goto fail; @@ -702,7 +926,7 @@ int dns_query_go(DnsQuery *q) { if (match != found) continue; - r = dns_query_add_transaction_split(q, s); + r = dns_query_add_candidate(q, s); if (r < 0) goto fail; } @@ -724,14 +948,13 @@ int dns_query_go(DnsQuery *q) { q->state = DNS_TRANSACTION_PENDING; q->block_ready++; - /* Start the transactions that are not started yet */ - SET_FOREACH(t, q->transactions, i) { - if (t->state != DNS_TRANSACTION_NULL) - continue; - - r = dns_transaction_go(t); - if (r < 0) + /* Start the transactions */ + LIST_FOREACH(candidates_by_query, c, q->candidates) { + r = dns_query_candidate_go(c); + if (r < 0) { + q->block_ready--; goto fail; + } } q->block_ready--; @@ -744,124 +967,119 @@ fail: return r; } -void dns_query_ready(DnsQuery *q) { - DnsTransaction *t; +static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int rcode = 0; - DnsScope *scope = NULL; - bool pending = false; + DnsTransaction *t; Iterator i; assert(q); - assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function, unless the block_ready - * counter was explicitly bumped before doing so. */ - - if (q->block_ready > 0) + if (!c) { + dns_query_synthesize_reply(q, &state); + dns_query_complete(q, state); return; + } - SET_FOREACH(t, q->transactions, i) { + SET_FOREACH(t, c->transactions, i) { - /* If we found a successful answer, ignore all answers from other scopes */ - if (state == DNS_TRANSACTION_SUCCESS && t->scope != scope) - continue; + switch (t->state) { - /* One of the transactions is still going on, let's maybe wait for it */ - if (IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) { - pending = true; - continue; - } + case DNS_TRANSACTION_SUCCESS: { + /* We found a successfuly reply, merge it into the answer */ + DnsAnswer *merged; - /* One of the transactions is successful, let's use - * it, and copy its data out */ - if (t->state == DNS_TRANSACTION_SUCCESS) { - DnsAnswer *a; - - if (t->received) { - rcode = DNS_PACKET_RCODE(t->received); - a = t->received->answer; - } else { - rcode = t->cached_rcode; - a = t->cached; + merged = dns_answer_merge(q->answer, t->answer); + if (!merged) { + dns_query_complete(q, DNS_TRANSACTION_RESOURCES); + return; } - if (state == DNS_TRANSACTION_SUCCESS) { - DnsAnswer *merged; + dns_answer_unref(q->answer); + q->answer = merged; + q->answer_rcode = t->answer_rcode; - merged = dns_answer_merge(answer, a); - if (!merged) { - dns_query_complete(q, DNS_TRANSACTION_RESOURCES); - return; - } + state = DNS_TRANSACTION_SUCCESS; + break; + } - dns_answer_unref(answer); - answer = merged; - } else { - dns_answer_unref(answer); - answer = dns_answer_ref(a); + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_ABORTED: + /* Ignore transactions that didn't complete */ + continue; + + default: + /* Any kind of failure? Store the data away, + * if there's nothing stored yet. */ + + if (state != DNS_TRANSACTION_SUCCESS) { + + dns_answer_unref(q->answer); + q->answer = dns_answer_ref(t->answer); + q->answer_rcode = t->answer_rcode; + + state = t->state; } - scope = t->scope; - state = DNS_TRANSACTION_SUCCESS; - continue; + break; } + } - /* One of the transactions has failed, let's see - * whether we find anything better, but if not, return - * its response data */ - if (state != DNS_TRANSACTION_SUCCESS && t->state == DNS_TRANSACTION_FAILURE) { - DnsAnswer *a; - - if (t->received) { - rcode = DNS_PACKET_RCODE(t->received); - a = t->received->answer; - } else { - rcode = t->cached_rcode; - a = t->cached; - } + q->answer_protocol = c->scope->protocol; + q->answer_family = c->scope->family; - dns_answer_unref(answer); - answer = dns_answer_ref(a); + dns_search_domain_unref(q->answer_search_domain); + q->answer_search_domain = dns_search_domain_ref(c->search_domain); - scope = t->scope; - state = DNS_TRANSACTION_FAILURE; - continue; - } + dns_query_synthesize_reply(q, &state); + dns_query_complete(q, state); +} - if (state == DNS_TRANSACTION_NO_SERVERS && t->state != DNS_TRANSACTION_NO_SERVERS) - state = t->state; - } +void dns_query_ready(DnsQuery *q) { - if (pending) { + DnsQueryCandidate *bad = NULL, *c; + bool pending = false; + + assert(q); + assert(IN_SET(q->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING)); - /* If so far we weren't successful, and there's - * something still pending, then wait for it */ - if (state != DNS_TRANSACTION_SUCCESS) + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function, unless the block_ready + * counter was explicitly bumped before doing so. */ + + if (q->block_ready > 0) + return; + + LIST_FOREACH(candidates_by_query, c, q->candidates) { + DnsTransactionState state; + + state = dns_query_candidate_state(c); + switch (state) { + + case DNS_TRANSACTION_SUCCESS: + /* One of the transactions is successful, + * let's use it, and copy its data out */ + dns_query_accept(q, c); return; - /* If we already were successful, then only wait for - * other transactions on the same scope to finish. */ - SET_FOREACH(t, q->transactions, i) { - if (t->scope == scope && IN_SET(t->state, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_NULL)) - return; - } - } + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_NULL: + /* One of the transactions is still going on, let's maybe wait for it */ + pending = true; + break; - if (IN_SET(state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE)) { - q->answer = dns_answer_ref(answer); - q->answer_rcode = rcode; - q->answer_protocol = scope ? scope->protocol : _DNS_PROTOCOL_INVALID; - q->answer_family = scope ? scope->family : AF_UNSPEC; + default: + /* Any kind of failure */ + bad = c; + break; + } } - /* Try to synthesize a reply if we couldn't resolve something. */ - dns_query_synthesize_reply(q, &state); + if (pending) + return; - dns_query_complete(q, state); + dns_query_accept(q, bad); } static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { @@ -900,13 +1118,13 @@ int dns_query_process_cname(DnsQuery *q) { DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) return r; if (r > 0) return 0; /* The answer matches directly, no need to follow cnames */ - r = dns_question_matches_cname(q->question, rr); + r = dns_question_matches_cname(q->question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); if (r < 0) return r; if (r > 0 && !cname) @@ -927,7 +1145,7 @@ int dns_query_process_cname(DnsQuery *q) { /* Let's see if the answer can already answer the new * redirected question */ DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(q->question, rr); + r = dns_question_matches_rr(q->question, rr, NULL); if (r < 0) return r; if (r > 0) diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 256dddc00b..a9d7904a8d 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -26,11 +26,26 @@ #include "set.h" +typedef struct DnsQueryCandidate DnsQueryCandidate; typedef struct DnsQuery DnsQuery; #include "resolved-dns-answer.h" #include "resolved-dns-question.h" #include "resolved-dns-stream.h" +#include "resolved-dns-search-domain.h" + +struct DnsQueryCandidate { + DnsQuery *query; + DnsScope *scope; + + DnsSearchDomain *search_domain; + + int error_code; + Set *transactions; + + LIST_FIELDS(DnsQueryCandidate, candidates_by_query); + LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); +}; struct DnsQuery { Manager *manager; @@ -45,20 +60,21 @@ struct DnsQuery { int auxiliary_result; DnsQuestion *question; - uint64_t flags; int ifindex; DnsTransactionState state; unsigned n_cname_redirects; + LIST_HEAD(DnsQueryCandidate, candidates); sd_event_source *timeout_event_source; /* Discovered data */ DnsAnswer *answer; - int answer_family; - DnsProtocol answer_protocol; int answer_rcode; + DnsProtocol answer_protocol; + int answer_family; + DnsSearchDomain *answer_search_domain; /* Bus client information */ sd_bus_message *request; @@ -71,14 +87,15 @@ struct DnsQuery { void (*complete)(DnsQuery* q); unsigned block_ready; - Set *transactions; - sd_bus_track *bus_track; LIST_FIELDS(DnsQuery, queries); LIST_FIELDS(DnsQuery, auxiliary_queries); }; +DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); +void dns_query_candidate_ready(DnsQueryCandidate *c); + int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question, int family, uint64_t flags); DnsQuery *dns_query_free(DnsQuery *q); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 9fb3038381..3249448d3b 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -89,7 +89,7 @@ int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { return 0; } -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr) { +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { unsigned i; int r; @@ -99,7 +99,7 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr) { return 0; for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_match_rr(q->keys[i], rr); + r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); if (r != 0) return r; } @@ -107,7 +107,7 @@ int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr) { return 0; } -int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr) { +int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { unsigned i; int r; @@ -117,7 +117,7 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr) { return 0; for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_match_cname(q->keys[i], rr); + r = dns_resource_key_match_cname(q->keys[i], rr, search_domain); if (r != 0) return r; } @@ -125,7 +125,7 @@ int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr) { return 0; } -int dns_question_is_valid(DnsQuestion *q) { +int dns_question_is_valid_for_query(DnsQuestion *q) { const char *name; unsigned i; int r; @@ -274,8 +274,10 @@ int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, return 1; } -const char *dns_question_name(DnsQuestion *q) { - assert(q); +const char *dns_question_first_name(DnsQuestion *q) { + + if (!q) + return NULL; if (q->n_keys < 1) return NULL; diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h index 9894ef6ec5..e77116c03a 100644 --- a/src/resolve/resolved-dns-question.h +++ b/src/resolve/resolved-dns-question.h @@ -25,7 +25,7 @@ typedef struct DnsQuestion DnsQuestion; #include "resolved-dns-rr.h" -/* A simple array of resources keys, all sharing the same domain */ +/* A simple array of resources keys */ struct DnsQuestion { unsigned n_ref; @@ -43,14 +43,22 @@ int dns_question_new_service(DnsQuestion **ret, const char *name, bool with_txt) int dns_question_add(DnsQuestion *q, DnsResourceKey *key); -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr); -int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr); -int dns_question_is_valid(DnsQuestion *q); +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain); +int dns_question_matches_cname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); +int dns_question_is_valid_for_query(DnsQuestion *q); int dns_question_contains(DnsQuestion *a, DnsResourceKey *k); int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); -const char *dns_question_name(DnsQuestion *q); +const char *dns_question_first_name(DnsQuestion *q); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); + +#define DNS_QUESTION_FOREACH(key, q) \ + for (unsigned _i = ({ \ + (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ + 0; \ + }); \ + (q) && ((_i) < (q)->n_keys); \ + _i++, (key) = (_i < (q)->n_keys ? (q)->keys[_i] : NULL)) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index 9b264900fc..4a1abb0cdc 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -86,6 +86,34 @@ DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const D } } +int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { + DnsResourceKey *new_key; + char *joined; + int r; + + assert(ret); + assert(key); + assert(name); + + if (dns_name_is_root(name)) { + *ret = dns_resource_key_ref(key); + return 0; + } + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), name, &joined); + if (r < 0) + return r; + + new_key = dns_resource_key_new_consume(key->class, key->type, joined); + if (!new_key) { + free(joined); + return -ENOMEM; + } + + *ret = new_key; + return 0; +} + DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { DnsResourceKey *k; @@ -145,20 +173,42 @@ int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { return 1; } -int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr) { +int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) { + int r; + assert(key); assert(rr); + /* Checks if an rr matches the specified key. If a search + * domain is specified, it will also be checked if the key + * with the search domain suffixed might match the RR. */ + if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) return 0; if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) return 0; - return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); + if (r < 0) + return r; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), joined); + } + + return 0; } -int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr) { +int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain) { + int r; + assert(key); assert(rr); @@ -166,11 +216,30 @@ int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRec return 0; if (rr->key->type == DNS_TYPE_CNAME) - return dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); else if (rr->key->type == DNS_TYPE_DNAME) - return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(rr->key)); else return 0; + + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); + if (r < 0) + return r; + + if (rr->key->type == DNS_TYPE_CNAME) + return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(rr->key)); + else if (rr->key->type == DNS_TYPE_DNAME) + return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(rr->key)); + } + + return 0; + } static void dns_resource_key_hash_func(const void *i, struct siphash *state) { diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index c1601fb694..f8066c06a6 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -188,12 +188,13 @@ DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char * DnsResourceKey* dns_resource_key_new_cname(const DnsResourceKey *key); DnsResourceKey* dns_resource_key_new_dname(const DnsResourceKey *key); DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname); +int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name); DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); -int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr); -int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr); +int dns_resource_key_match_rr(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain); +int dns_resource_key_match_cname(const DnsResourceKey *key, const DnsResourceRecord *rr, const char *search_domain); int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 873d76e40c..fc4ae57ce0 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -69,18 +69,12 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int return 0; } -DnsScope* dns_scope_free(DnsScope *s) { - DnsTransaction *t; - DnsResourceRecord *rr; - - if (!s) - return NULL; - - log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); +static void dns_scope_abort_transactions(DnsScope *s) { + assert(s); - dns_scope_llmnr_membership(s, false); + while (s->transactions) { + DnsTransaction *t = s->transactions; - while ((t = hashmap_steal_first(s->transactions))) { /* Abort the transaction, but make sure it is not * freed while we still look at it */ @@ -90,8 +84,23 @@ DnsScope* dns_scope_free(DnsScope *s) { dns_transaction_free(t); } +} - hashmap_free(s->transactions); +DnsScope* dns_scope_free(DnsScope *s) { + DnsResourceRecord *rr; + + if (!s) + return NULL; + + log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); + + dns_scope_llmnr_membership(s, false); + dns_scope_abort_transactions(s); + + while (s->query_candidates) + dns_query_candidate_free(s->query_candidates); + + hashmap_free(s->transactions_by_key); while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) dns_resource_record_unref(rr); @@ -103,7 +112,6 @@ DnsScope* dns_scope_free(DnsScope *s) { dns_zone_flush(&s->zone); LIST_REMOVE(scopes, s->manager->dns_scopes, s); - strv_free(s->domains); free(s); return NULL; @@ -136,11 +144,11 @@ void dns_scope_next_dns_server(DnsScope *s) { void dns_scope_packet_received(DnsScope *s, usec_t rtt) { assert(s); - if (rtt > s->max_rtt) { - s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), - MULTICAST_RESEND_TIMEOUT_MAX_USEC); - } + if (rtt <= s->max_rtt) + return; + + s->max_rtt = rtt; + s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); } void dns_scope_packet_lost(DnsScope *s, usec_t usec) { @@ -323,7 +331,7 @@ int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *add } DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { - char **i; + DnsSearchDomain *d; assert(s); assert(domain); @@ -334,7 +342,7 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family) & flags) == 0) return DNS_SCOPE_NO; - if (dns_name_root(domain) != 0) + if (dns_name_is_root(domain)) return DNS_SCOPE_NO; /* Never resolve any loopback hostname or IP address via DNS, @@ -345,15 +353,22 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) return DNS_SCOPE_NO; - STRV_FOREACH(i, s->domains) - if (dns_name_endswith(domain, *i) > 0) + /* Always honour search domains for routing queries. Note that + * we return DNS_SCOPE_YES here, rather than just + * DNS_SCOPE_MAYBE, which means wildcard scopes won't be + * considered anymore. */ + LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) + if (dns_name_endswith(domain, d->name) > 0) return DNS_SCOPE_YES; switch (s->protocol) { + case DNS_PROTOCOL_DNS: - if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && - dns_name_endswith(domain, "0.8.e.f.ip6.arpa") == 0 && - dns_name_single_label(domain) == 0) + + if ((!dns_name_is_single_label(domain) || + (!(flags & SD_RESOLVED_NO_SEARCH) && dns_scope_has_search_domains(s))) && + dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && + dns_name_endswith(domain, "0.8.e.f.ip6.arpa") == 0) return DNS_SCOPE_MAYBE; return DNS_SCOPE_NO; @@ -371,7 +386,7 @@ DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, co case DNS_PROTOCOL_LLMNR: if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_single_label(domain) > 0 && /* only resolve single label names via LLMNR */ + (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ return DNS_SCOPE_MAYBE; @@ -638,7 +653,7 @@ DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, /* Try to find an ongoing transaction that is a equal to the * specified question */ - t = hashmap_get(scope->transactions, key); + t = hashmap_get(scope->transactions_by_key, key); if (!t) return NULL; @@ -646,7 +661,7 @@ DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, * data instead of a real packet, if that's requested. */ if (!cache_ok && IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_FAILURE) && - !t->received) + t->answer_source != DNS_TRANSACTION_NETWORK) return NULL; return t; @@ -850,3 +865,45 @@ void dns_scope_dump(DnsScope *s, FILE *f) { dns_cache_dump(&s->cache, f); } } + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { + assert(s); + + /* Returns the list of *local* search domains -- not the + * global ones. */ + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return s->link->search_domains; + + return NULL; +} + +bool dns_scope_has_search_domains(DnsScope *s) { + assert(s); + + /* Tests if there are *any* search domains suitable for this + * scope. This means either local or global ones */ + + if (s->protocol != DNS_PROTOCOL_DNS) + return false; + + if (s->manager->search_domains) + return true; + + if (s->link && s->link->search_domains) + return true; + + return false; +} + +bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return false; + + return dns_name_is_single_label(name); +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index f9b2bfda9d..7876410b7d 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -47,8 +47,6 @@ struct DnsScope { Link *link; - char **domains; - DnsCache cache; DnsZone zone; @@ -60,7 +58,18 @@ struct DnsScope { usec_t resend_timeout; usec_t max_rtt; - Hashmap *transactions; + LIST_HEAD(DnsQueryCandidate, query_candidates); + + /* Note that we keep track of ongoing transactions in two + * ways: once in a hashmap, indexed by the rr key, and once in + * a linked list. We use the hashmap to quickly find + * transactions we can reuse for a key. But note that there + * might be multiple transactions for the same key (because + * the zone probing can't reuse a transaction answered from + * the zone or the cache), and the hashmap only tracks the + * most recent entry. */ + Hashmap *transactions_by_key; + LIST_HEAD(DnsTransaction, transactions); LIST_FIELDS(DnsScope, scopes); }; @@ -91,3 +100,8 @@ int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); void dns_scope_dump(DnsScope *s, FILE *f); + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s); +bool dns_scope_has_search_domains(DnsScope *s); + +bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name); diff --git a/src/resolve/resolved-dns-search-domain.c b/src/resolve/resolved-dns-search-domain.c new file mode 100644 index 0000000000..f9d966abb1 --- /dev/null +++ b/src/resolve/resolved-dns-search-domain.c @@ -0,0 +1,232 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-search-domain.h" + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *l, + const char *name) { + + _cleanup_free_ char *normalized = NULL; + DnsSearchDomain *d; + int r; + + assert(m); + assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); + assert(name); + + r = dns_name_normalize(name, &normalized); + if (r < 0) + return r; + + if (dns_name_is_root(normalized)) + return -EINVAL; + + if (l) { + if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) + return -E2BIG; + } else { + if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) + return -E2BIG; + } + + d = new0(DnsSearchDomain, 1); + if (!d) + return -ENOMEM; + + d->n_ref = 1; + d->manager = m; + d->type = type; + d->name = normalized; + normalized = NULL; + + switch (type) { + + case DNS_SEARCH_DOMAIN_LINK: + d->link = l; + LIST_APPEND(domains, l->search_domains, d); + l->n_search_domains++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(domains, m->search_domains, d); + m->n_search_domains++; + break; + + default: + assert_not_reached("Unknown search domain type"); + } + + d->linked = true; + + if (ret) + *ret = d; + + return 0; +} + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref++; + + return d; +} + +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref--; + + if (d->n_ref > 0) + return NULL; + + free(d->name); + free(d); + + return NULL; +} + +void dns_search_domain_unlink(DnsSearchDomain *d) { + assert(d); + assert(d->manager); + + if (!d->linked) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + assert(d->link->n_search_domains > 0); + LIST_REMOVE(domains, d->link->search_domains, d); + d->link->n_search_domains--; + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + assert(d->manager->n_search_domains > 0); + LIST_REMOVE(domains, d->manager->search_domains, d); + d->manager->n_search_domains--; + break; + } + + d->linked = false; + + dns_search_domain_unref(d); +} + +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { + DnsSearchDomain *tail; + + assert(d); + + if (!d->marked) + return; + + d->marked = false; + + if (!d->linked || !d->domains_next) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->link->search_domains, d); + LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->manager->search_domains, d); + LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); + break; + + default: + assert_not_reached("Unknown search domain type"); + } +} + +void dns_search_domain_unlink_all(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + dns_search_domain_unlink(first); + + dns_search_domain_unlink_all(next); +} + +void dns_search_domain_unlink_marked(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + + if (first->marked) + dns_search_domain_unlink(first); + + dns_search_domain_unlink_marked(next); +} + +void dns_search_domain_mark_all(DnsSearchDomain *first) { + if (!first) + return; + + first->marked = true; + dns_search_domain_mark_all(first->domains_next); +} + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { + DnsSearchDomain *d; + int r; + + assert(name); + assert(ret); + + LIST_FOREACH(domains, d, first) { + + r = dns_name_equal(name, d->name); + if (r < 0) + return r; + if (r > 0) { + *ret = d; + return 1; + } + } + + *ret = NULL; + return 0; +} diff --git a/src/resolve/resolved-dns-search-domain.h b/src/resolve/resolved-dns-search-domain.h new file mode 100644 index 0000000000..2e0af31dda --- /dev/null +++ b/src/resolve/resolved-dns-search-domain.h @@ -0,0 +1,75 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "macro.h" + +typedef struct DnsSearchDomain DnsSearchDomain; + +typedef enum DnsSearchDomainType { + DNS_SEARCH_DOMAIN_SYSTEM, + DNS_SEARCH_DOMAIN_LINK, +} DnsSearchDomainType; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsSearchDomain { + Manager *manager; + + unsigned n_ref; + + DnsSearchDomainType type; + Link *link; + + char *name; + + bool marked:1; + + bool linked:1; + LIST_FIELDS(DnsSearchDomain, domains); +}; + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *link, + const char *name); + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); + +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); +void dns_search_domain_mark_all(DnsSearchDomain *first); + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); + +static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { + return d ? d->name : NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index e803f635ab..0ebd22fe22 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -21,7 +21,9 @@ #include "alloc-util.h" #include "resolved-dns-server.h" +#include "resolved-resolv-conf.h" #include "siphash24.h" +#include "string-util.h" /* After how much time to repeat classic DNS requests */ #define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) @@ -35,36 +37,57 @@ int dns_server_new( int family, const union in_addr_union *in_addr) { - DnsServer *s, *tail; + DnsServer *s; assert(m); assert((type == DNS_SERVER_LINK) == !!l); assert(in_addr); + if (!IN_SET(family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + if (l) { + if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) + return -E2BIG; + } else { + if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) + return -E2BIG; + } + s = new0(DnsServer, 1); if (!s) return -ENOMEM; s->n_ref = 1; + s->manager = m; s->type = type; s->family = family; s->address = *in_addr; s->resend_timeout = DNS_TIMEOUT_MIN_USEC; - if (type == DNS_SERVER_LINK) { - LIST_FIND_TAIL(servers, l->dns_servers, tail); - LIST_INSERT_AFTER(servers, l->dns_servers, tail, s); + switch (type) { + + case DNS_SERVER_LINK: s->link = l; - } else if (type == DNS_SERVER_SYSTEM) { - LIST_FIND_TAIL(servers, m->dns_servers, tail); - LIST_INSERT_AFTER(servers, m->dns_servers, tail, s); - } else if (type == DNS_SERVER_FALLBACK) { - LIST_FIND_TAIL(servers, m->fallback_dns_servers, tail); - LIST_INSERT_AFTER(servers, m->fallback_dns_servers, tail, s); - } else + LIST_APPEND(servers, l->dns_servers, s); + l->n_dns_servers++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(servers, m->dns_servers, s); + m->n_dns_servers++; + break; + + case DNS_SERVER_FALLBACK: + LIST_APPEND(servers, m->fallback_dns_servers, s); + m->n_dns_servers++; + break; + + default: assert_not_reached("Unknown server type"); + } - s->manager = m; + s->linked = true; /* A new DNS server that isn't fallback is added and the one * we used so far was a fallback one? Then let's try to pick @@ -85,56 +108,127 @@ DnsServer* dns_server_ref(DnsServer *s) { return NULL; assert(s->n_ref > 0); - s->n_ref ++; return s; } -static DnsServer* dns_server_free(DnsServer *s) { +DnsServer* dns_server_unref(DnsServer *s) { if (!s) return NULL; + assert(s->n_ref > 0); + s->n_ref --; + + if (s->n_ref > 0) + return NULL; + + free(s); + return NULL; +} + +void dns_server_unlink(DnsServer *s) { + assert(s); + assert(s->manager); + + /* This removes the specified server from the linked list of + * servers, but any server might still stay around if it has + * refs, for example from an ongoing transaction. */ + + if (!s->linked) + return; + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + assert(s->link->n_dns_servers > 0); + LIST_REMOVE(servers, s->link->dns_servers, s); + break; + + case DNS_SERVER_SYSTEM: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->dns_servers, s); + s->manager->n_dns_servers--; + break; + + case DNS_SERVER_FALLBACK: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + s->manager->n_dns_servers--; + break; + } + + s->linked = false; + if (s->link && s->link->current_dns_server == s) link_set_dns_server(s->link, NULL); - if (s->manager && s->manager->current_dns_server == s) + if (s->manager->current_dns_server == s) manager_set_dns_server(s->manager, NULL); - free(s); - - return NULL; + dns_server_unref(s); } -DnsServer* dns_server_unref(DnsServer *s) { - if (!s) - return NULL; +void dns_server_move_back_and_unmark(DnsServer *s) { + DnsServer *tail; - assert(s->n_ref > 0); + assert(s); - if (s->n_ref == 1) - dns_server_free(s); - else - s->n_ref --; + if (!s->marked) + return; - return NULL; + s->marked = false; + + if (!s->linked || !s->servers_next) + return; + + /* Move us to the end of the list, so that the order is + * strictly kept, if we are not at the end anyway. */ + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->link->dns_servers, s); + LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); + break; + + case DNS_SERVER_SYSTEM: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); + break; + + case DNS_SERVER_FALLBACK: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); + break; + + default: + assert_not_reached("Unknown server type"); + } } void dns_server_packet_received(DnsServer *s, usec_t rtt) { assert(s); - if (rtt > s->max_rtt) { - s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), - DNS_TIMEOUT_MAX_USEC); - } + if (rtt <= s->max_rtt) + return; + + s->max_rtt = rtt; + s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC); } void dns_server_packet_lost(DnsServer *s, usec_t usec) { assert(s); - if (s->resend_timeout <= usec) - s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); + if (s->resend_timeout > usec) + return; + + s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); } static void dns_server_hash_func(const void *p, struct siphash *state) { @@ -161,3 +255,140 @@ const struct hash_ops dns_server_hash_ops = { .hash = dns_server_hash_func, .compare = dns_server_compare_func }; + +void dns_server_unlink_all(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + dns_server_unlink(first); + + dns_server_unlink_all(next); +} + +void dns_server_unlink_marked(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + + if (first->marked) + dns_server_unlink(first); + + dns_server_unlink_marked(next); +} + +void dns_server_mark_all(DnsServer *first) { + if (!first) + return; + + first->marked = true; + dns_server_mark_all(first->servers_next); +} + +DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { + DnsServer *s; + + LIST_FOREACH(servers, s, first) + if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) + return s; + + return NULL; +} + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { + assert(m); + + switch (t) { + + case DNS_SERVER_SYSTEM: + return m->dns_servers; + + case DNS_SERVER_FALLBACK: + return m->fallback_dns_servers; + + default: + return NULL; + } +} + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { + assert(m); + + if (m->current_dns_server == s) + return s; + + if (s) { + _cleanup_free_ char *ip = NULL; + + in_addr_to_string(s->family, &s->address, &ip); + log_info("Switching to system DNS server %s.", strna(ip)); + } + + dns_server_unref(m->current_dns_server); + m->current_dns_server = dns_server_ref(s); + + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return s; +} + +DnsServer *manager_get_dns_server(Manager *m) { + Link *l; + assert(m); + + /* Try to read updates resolv.conf */ + manager_read_resolv_conf(m); + + /* If no DNS server was chose so far, pick the first one */ + if (!m->current_dns_server) + manager_set_dns_server(m, m->dns_servers); + + if (!m->current_dns_server) { + bool found = false; + Iterator i; + + /* No DNS servers configured, let's see if there are + * any on any links. If not, we use the fallback + * servers */ + + HASHMAP_FOREACH(l, m->links, i) + if (l->dns_servers) { + found = true; + break; + } + + if (!found) + manager_set_dns_server(m, m->fallback_dns_servers); + } + + return m->current_dns_server; +} + +void manager_next_dns_server(Manager *m) { + assert(m); + + /* If there's currently no DNS server set, then the next + * manager_get_dns_server() will find one */ + if (!m->current_dns_server) + return; + + /* Change to the next one, but make sure to follow the linked + * list only if the server is still linked. */ + if (m->current_dns_server->linked && m->current_dns_server->servers_next) { + manager_set_dns_server(m, m->current_dns_server->servers_next); + return; + } + + /* If there was no next one, then start from the beginning of + * the list */ + if (m->current_dns_server->type == DNS_SERVER_FALLBACK) + manager_set_dns_server(m, m->fallback_dns_servers); + else + manager_set_dns_server(m, m->dns_servers); +} diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 10111fd6bd..3a78d4a3b5 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -24,7 +24,6 @@ #include "in-addr-util.h" typedef struct DnsServer DnsServer; -typedef enum DnsServerSource DnsServerSource; typedef enum DnsServerType { DNS_SERVER_SYSTEM, @@ -32,6 +31,7 @@ typedef enum DnsServerType { DNS_SERVER_LINK, } DnsServerType; +#include "resolved-manager.h" #include "resolved-link.h" struct DnsServer { @@ -40,7 +40,6 @@ struct DnsServer { unsigned n_ref; DnsServerType type; - Link *link; int family; @@ -51,23 +50,40 @@ struct DnsServer { bool marked:1; + /* If linked is set, then this server appears in the servers linked list */ + bool linked:1; LIST_FIELDS(DnsServer, servers); }; int dns_server_new( Manager *m, - DnsServer **s, + DnsServer **ret, DnsServerType type, - Link *l, + Link *link, int family, const union in_addr_union *address); DnsServer* dns_server_ref(DnsServer *s); 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, usec_t rtt); void dns_server_packet_lost(DnsServer *s, usec_t usec); +DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); + +void dns_server_unlink_all(DnsServer *first); +void dns_server_unlink_marked(DnsServer *first); +void dns_server_mark_all(DnsServer *first); + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); +DnsServer *manager_get_dns_server(Manager *m); +void manager_next_dns_server(Manager *m); + DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 37f47c47c0..8c4f23a4da 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -29,7 +29,7 @@ #include "string-table.h" DnsTransaction* dns_transaction_free(DnsTransaction *t) { - DnsQuery *q; + DnsQueryCandidate *c; DnsZoneItem *i; if (!t) @@ -39,7 +39,8 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_packet_unref(t->sent); dns_packet_unref(t->received); - dns_answer_unref(t->cached); + + dns_answer_unref(t->answer); sd_event_source_unref(t->dns_udp_event_source); safe_close(t->dns_udp_fd); @@ -48,7 +49,8 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_stream_free(t->stream); if (t->scope) { - hashmap_remove(t->scope->transactions, t->key); + hashmap_remove_value(t->scope->transactions_by_key, t->key, t); + LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); if (t->id != 0) hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); @@ -56,9 +58,10 @@ DnsTransaction* dns_transaction_free(DnsTransaction *t) { dns_resource_key_unref(t->key); - while ((q = set_steal_first(t->queries))) - set_remove(q->transactions, t); - set_free(t->queries); + while ((c = set_steal_first(t->query_candidates))) + set_remove(c->transactions, t); + + set_free(t->query_candidates); while ((i = set_steal_first(t->zone_items))) i->probe_transaction = NULL; @@ -76,7 +79,7 @@ void dns_transaction_gc(DnsTransaction *t) { if (t->block_gc > 0) return; - if (set_isempty(t->queries) && set_isempty(t->zone_items)) + if (set_isempty(t->query_candidates) && set_isempty(t->zone_items)) dns_transaction_free(t); } @@ -92,7 +95,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) if (r < 0) return r; - r = hashmap_ensure_allocated(&s->transactions, &dns_resource_key_hash_ops); + r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); if (r < 0) return r; @@ -101,6 +104,7 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) return -ENOMEM; t->dns_udp_fd = -1; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; t->key = dns_resource_key_ref(key); /* Find a fresh, unused transaction id */ @@ -115,12 +119,13 @@ int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) return r; } - r = hashmap_put(s->transactions, t->key, t); + r = hashmap_replace(s->transactions_by_key, t->key, t); if (r < 0) { hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); return r; } + LIST_PREPEND(transactions_by_scope, s->transactions, t); t->scope = s; if (ret) @@ -136,6 +141,9 @@ static void dns_transaction_stop(DnsTransaction *t) { t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); t->stream = dns_stream_free(t->stream); + + /* Note that we do not drop the UDP socket here, as we want to + * reuse it to repeat the interaction. */ } static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { @@ -181,7 +189,7 @@ static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { } void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { - DnsQuery *q; + DnsQueryCandidate *c; DnsZoneItem *z; Iterator i; @@ -192,11 +200,12 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { * should hence not attempt to access the query or transaction * after calling this function. */ - log_debug("Transaction on scope %s on %s/%s now complete with <%s>", + log_debug("Transaction on scope %s on %s/%s now complete with <%s> from %s", dns_protocol_to_string(t->scope->protocol), t->scope->link ? t->scope->link->name : "*", t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), - dns_transaction_state_to_string(state)); + dns_transaction_state_to_string(state), + t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source)); t->state = state; @@ -205,8 +214,8 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { /* Notify all queries that are interested, but make sure the * transaction isn't freed while we are still looking at it */ t->block_gc++; - SET_FOREACH(q, t->queries, i) - dns_query_ready(q); + SET_FOREACH(c, t->query_candidates, i) + dns_query_candidate_ready(c); SET_FOREACH(z, t->zone_items, i) dns_zone_item_ready(z); t->block_gc--; @@ -314,6 +323,8 @@ static int dns_transaction_open_tcp(DnsTransaction *t) { dns_server_unref(t->server); t->server = dns_server_ref(server); t->received = dns_packet_unref(t->received); + t->answer = dns_answer_unref(t->answer); + t->answer_rcode = 0; t->stream->complete = on_stream_complete; t->stream->transaction = t; @@ -385,6 +396,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { t->received = dns_packet_ref(p); } + t->answer_source = DNS_TRANSACTION_NETWORK; + if (p->ipproto == IPPROTO_TCP) { if (DNS_PACKET_TC(p)) { /* Truncated via TCP? Somebody must be fucking with us */ @@ -453,6 +466,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } + /* Install the answer as answer to the transaction */ + dns_answer_unref(t->answer); + t->answer = dns_answer_ref(p->answer); + t->answer_rcode = DNS_PACKET_RCODE(p); + /* Only consider responses with equivalent query section to the request */ if (p->question->n_keys != 1 || dns_resource_key_equal(p->question->keys[0], t->key) <= 0) { dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); @@ -460,7 +478,8 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { } /* According to RFC 4795, section 2.9. only the RRs from the answer section shall be cached */ - dns_cache_put(&t->scope->cache, t->key, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender); + if (DNS_PACKET_SHALL_CACHE(p)) + dns_cache_put(&t->scope->cache, t->key, DNS_PACKET_RCODE(p), p->answer, DNS_PACKET_ANCOUNT(p), 0, p->family, &p->sender); if (DNS_PACKET_RCODE(p) == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); @@ -623,18 +642,20 @@ int dns_transaction_go(DnsTransaction *t) { t->n_attempts++; t->start_usec = ts; t->received = dns_packet_unref(t->received); - t->cached = dns_answer_unref(t->cached); - t->cached_rcode = 0; + t->answer = dns_answer_unref(t->answer); + t->answer_rcode = 0; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; /* Check the zone, but obly if this transaction is not used * for probing or verifying a zone item. */ if (set_isempty(t->zone_items)) { - r = dns_zone_lookup(&t->scope->zone, t->key, &t->cached, NULL, NULL); + r = dns_zone_lookup(&t->scope->zone, t->key, &t->answer, NULL, NULL); if (r < 0) return r; if (r > 0) { - t->cached_rcode = DNS_RCODE_SUCCESS; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_ZONE; dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); return 0; } @@ -652,11 +673,12 @@ int dns_transaction_go(DnsTransaction *t) { /* Let's then prune all outdated entries */ dns_cache_prune(&t->scope->cache); - r = dns_cache_lookup(&t->scope->cache, t->key, &t->cached_rcode, &t->cached); + r = dns_cache_lookup(&t->scope->cache, t->key, &t->answer_rcode, &t->answer); if (r < 0) return r; if (r > 0) { - if (t->cached_rcode == DNS_RCODE_SUCCESS) + t->answer_source = DNS_TRANSACTION_CACHE; + if (t->answer_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); else dns_transaction_complete(t, DNS_TRANSACTION_FAILURE); @@ -759,3 +781,10 @@ static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] [DNS_TRANSACTION_ABORTED] = "aborted", }; DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); + +static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { + [DNS_TRANSACTION_NETWORK] = "network", + [DNS_TRANSACTION_CACHE] = "cache", + [DNS_TRANSACTION_ZONE] = "zone", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index d0970bd695..ee80dcf5a9 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -23,6 +23,7 @@ typedef struct DnsTransaction DnsTransaction; typedef enum DnsTransactionState DnsTransactionState; +typedef enum DnsTransactionSource DnsTransactionSource; enum DnsTransactionState { DNS_TRANSACTION_NULL, @@ -39,6 +40,14 @@ enum DnsTransactionState { _DNS_TRANSACTION_STATE_INVALID = -1 }; +enum DnsTransactionSource { + DNS_TRANSACTION_NETWORK, + DNS_TRANSACTION_CACHE, + DNS_TRANSACTION_ZONE, + _DNS_TRANSACTION_SOURCE_MAX, + _DNS_TRANSACTION_SOURCE_INVALID = -1 +}; + #include "resolved-dns-answer.h" #include "resolved-dns-packet.h" #include "resolved-dns-question.h" @@ -55,8 +64,10 @@ struct DnsTransaction { bool initial_jitter; DnsPacket *sent, *received; - DnsAnswer *cached; - int cached_rcode; + + DnsAnswer *answer; + int answer_rcode; + DnsTransactionSource answer_source; usec_t start_usec; sd_event_source *timeout_event_source; @@ -71,9 +82,10 @@ struct DnsTransaction { /* TCP connection logic, if we need it */ DnsStream *stream; - /* Queries this transaction is referenced by and that shall be - * notified about this specific transaction completing. */ - Set *queries; + /* Query candidates this transaction is referenced by and that + * shall be notified about this specific transaction + * completing. */ + Set *query_candidates; /* Zone items this transaction is referenced by and that shall * be notified about completion. */ @@ -96,6 +108,9 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; +const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; +DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; + /* LLMNR Jitter interval, see RFC 4795 Section 7 */ #define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) @@ -105,4 +120,4 @@ DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; /* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ #define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 -#define TRANSACTION_ATTEMPTS_MAX(p) (p == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) +#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index a021ecb93d..493d11dd14 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -311,7 +311,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns found = true; - k = dns_resource_key_match_rr(key, j->rr); + k = dns_resource_key_match_rr(key, j->rr, NULL); if (k < 0) return k; if (k > 0) { @@ -381,7 +381,7 @@ int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, DnsAnswer **ret_answer, Dns if (j->state != DNS_ZONE_ITEM_PROBING) tentative = false; - k = dns_resource_key_match_rr(key, j->rr); + k = dns_resource_key_match_rr(key, j->rr, NULL); if (k < 0) return k; if (k > 0) { diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index 8e78fbf06a..50662656d5 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -14,6 +14,7 @@ struct ConfigPerfItem; %struct-type %includes %% -Resolve.DNS, config_parse_dnsv, DNS_SERVER_SYSTEM, 0 -Resolve.FallbackDNS, config_parse_dnsv, DNS_SERVER_FALLBACK, 0 -Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support) +Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 +Resolve.FallbackDNS, config_parse_dns_servers, DNS_SERVER_FALLBACK, 0 +Resolve.Domains, config_parse_search_domains, 0, 0 +Resolve.LLMNR, config_parse_support, 0, offsetof(Manager, llmnr_support) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 2892641075..ddd9427dab 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -65,19 +65,15 @@ Link *link_free(Link *l) { if (!l) return NULL; + dns_server_unlink_marked(l->dns_servers); + dns_search_domain_unlink_all(l->search_domains); + while (l->addresses) link_address_free(l->addresses); if (l->manager) hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); - while (l->dns_servers) { - DnsServer *s = l->dns_servers; - - LIST_REMOVE(servers, l->dns_servers, s); - dns_server_unref(s); - } - dns_scope_free(l->unicast_scope); dns_scope_free(l->llmnr_ipv4_scope); dns_scope_free(l->llmnr_ipv6_scope); @@ -158,7 +154,6 @@ int link_update_rtnl(Link *l, sd_netlink_message *m) { static int link_update_dns_servers(Link *l) { _cleanup_strv_free_ char **nameservers = NULL; char **nameserver; - DnsServer *s, *nx; int r; assert(l); @@ -167,20 +162,20 @@ static int link_update_dns_servers(Link *l) { if (r < 0) goto clear; - LIST_FOREACH(servers, s, l->dns_servers) - s->marked = true; + dns_server_mark_all(l->dns_servers); STRV_FOREACH(nameserver, nameservers) { union in_addr_union a; + DnsServer *s; int family; r = in_addr_from_string_auto(*nameserver, &family, &a); if (r < 0) goto clear; - s = link_find_dns_server(l, family, &a); + s = dns_server_find(l->dns_servers, family, &a); if (s) - s->marked = false; + dns_server_move_back_and_unmark(s); else { r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a); if (r < 0) @@ -188,22 +183,11 @@ static int link_update_dns_servers(Link *l) { } } - LIST_FOREACH_SAFE(servers, s, nx, l->dns_servers) - if (s->marked) { - LIST_REMOVE(servers, l->dns_servers, s); - dns_server_unref(s); - } - + dns_server_unlink_marked(l->dns_servers); return 0; clear: - while (l->dns_servers) { - s = l->dns_servers; - - LIST_REMOVE(servers, l->dns_servers, s); - dns_server_unref(s); - } - + dns_server_unlink_all(l->dns_servers); return r; } @@ -236,29 +220,56 @@ clear: return r; } -static int link_update_domains(Link *l) { +static int link_update_search_domains(Link *l) { + _cleanup_strv_free_ char **domains = NULL; + char **i; int r; - if (!l->unicast_scope) - return 0; - - l->unicast_scope->domains = strv_free(l->unicast_scope->domains); + assert(l); - r = sd_network_link_get_domains(l->ifindex, - &l->unicast_scope->domains); + r = sd_network_link_get_domains(l->ifindex, &domains); if (r < 0) - return r; + goto clear; + + dns_search_domain_mark_all(l->search_domains); + + STRV_FOREACH(i, domains) { + DnsSearchDomain *d; + + r = dns_search_domain_find(l->search_domains, *i, &d); + if (r < 0) + goto clear; + + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, NULL, DNS_SEARCH_DOMAIN_LINK, l, *i); + if (r < 0) + goto clear; + } + } + dns_search_domain_unlink_marked(l->search_domains); return 0; + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; } int link_update_monitor(Link *l) { + int r; + assert(l); link_update_dns_servers(l); link_update_llmnr_support(l); link_allocate_scopes(l); - link_update_domains(l); + + r = link_update_search_domains(l); + if (r < 0) + log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); + link_add_rrs(l, false); return 0; @@ -303,17 +314,6 @@ LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *i return NULL; } -DnsServer* link_find_dns_server(Link *l, int family, const union in_addr_union *in_addr) { - DnsServer *s; - - assert(l); - - LIST_FOREACH(servers, s, l->dns_servers) - if (s->family == family && in_addr_equal(family, &s->address, in_addr)) - return s; - return NULL; -} - DnsServer* link_set_dns_server(Link *l, DnsServer *s) { assert(l); @@ -327,7 +327,8 @@ DnsServer* link_set_dns_server(Link *l, DnsServer *s) { log_info("Switching to DNS server %s for interface %s.", strna(ip), l->name); } - l->current_dns_server = s; + dns_server_unref(l->current_dns_server); + l->current_dns_server = dns_server_ref(s); if (l->unicast_scope) dns_cache_flush(&l->unicast_scope->cache); @@ -350,7 +351,9 @@ void link_next_dns_server(Link *l) { if (!l->current_dns_server) return; - if (l->current_dns_server->servers_next) { + /* Change to the next one, but make sure to follow the linked + * list only if this server is actually still linked. */ + if (l->current_dns_server->linked && l->current_dns_server->servers_next) { link_set_dns_server(l, l->current_dns_server->servers_next); return; } diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h index e3ab27c249..eb00015bd5 100644 --- a/src/resolve/resolved-link.h +++ b/src/resolve/resolved-link.h @@ -30,8 +30,13 @@ typedef struct Link Link; typedef struct LinkAddress LinkAddress; #include "resolved-dns-rr.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" #include "resolved-manager.h" +#define LINK_SEARCH_DOMAINS_MAX 32 +#define LINK_DNS_SERVERS_MAX 32 + struct LinkAddress { Link *link; @@ -56,6 +61,10 @@ struct Link { LIST_HEAD(DnsServer, dns_servers); DnsServer *current_dns_server; + unsigned n_dns_servers; + + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; Support llmnr_support; @@ -76,7 +85,6 @@ LinkAddress* link_find_address(Link *l, int family, const union in_addr_union *i void link_add_rrs(Link *l, bool force_remove); DnsServer* link_set_dns_server(Link *l, DnsServer *s); -DnsServer* link_find_dns_server(Link *l, int family, const union in_addr_union *in_addr); DnsServer* link_get_dns_server(Link *l); void link_next_dns_server(Link *l); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index a4ca7c89d3..f1f454c786 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -21,7 +21,6 @@ #include <netinet/in.h> #include <poll.h> -#include <resolv.h> #include <sys/ioctl.h> #include "af-list.h" @@ -40,6 +39,7 @@ #include "resolved-conf.h" #include "resolved-llmnr.h" #include "resolved-manager.h" +#include "resolved-resolv-conf.h" #include "socket-util.h" #include "string-table.h" #include "string-util.h" @@ -351,7 +351,7 @@ static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { return -EINVAL; } - r = dns_label_escape(label, r, &n); + r = dns_label_escape_new(label, r, &n); if (r < 0) return log_error_errno(r, "Failed to escape host name: %m"); @@ -476,10 +476,7 @@ int manager_new(Manager **ret) { m->llmnr_support = SUPPORT_YES; m->read_resolv_conf = true; - - r = manager_parse_dns_server(m, DNS_SERVER_FALLBACK, DNS_SERVERS); - if (r < 0) - return r; + m->need_builtin_fallbacks = true; r = sd_event_default(&m->event); if (r < 0) @@ -536,15 +533,16 @@ Manager *manager_free(Manager *m) { if (!m) return NULL; + dns_server_unlink_all(m->dns_servers); + dns_server_unlink_all(m->fallback_dns_servers); + dns_search_domain_unlink_all(m->search_domains); + while ((l = hashmap_first(m->links))) link_free(l); while (m->dns_queries) dns_query_free(m->dns_queries); - manager_flush_dns_servers(m, DNS_SERVER_SYSTEM); - manager_flush_dns_servers(m, DNS_SERVER_FALLBACK); - dns_scope_free(m->unicast_scope); hashmap_free(m->links); @@ -579,294 +577,6 @@ Manager *manager_free(Manager *m) { return NULL; } -int manager_read_resolv_conf(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - struct stat st, own; - char line[LINE_MAX]; - DnsServer *s, *nx; - usec_t t; - int r; - - assert(m); - - /* Reads the system /etc/resolv.conf, if it exists and is not - * symlinked to our own resolv.conf instance */ - - if (!m->read_resolv_conf) - return 0; - - r = stat("/etc/resolv.conf", &st); - if (r < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); - r = -errno; - goto clear; - } - - /* Have we already seen the file? */ - t = timespec_load(&st.st_mtim); - if (t == m->resolv_conf_mtime) - return 0; - - m->resolv_conf_mtime = t; - - /* Is it symlinked to our own file? */ - if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && - st.st_dev == own.st_dev && - st.st_ino == own.st_ino) { - r = 0; - goto clear; - } - - f = fopen("/etc/resolv.conf", "re"); - if (!f) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); - r = -errno; - goto clear; - } - - if (fstat(fileno(f), &st) < 0) { - r = log_error_errno(errno, "Failed to stat open file: %m"); - goto clear; - } - - LIST_FOREACH(servers, s, m->dns_servers) - s->marked = true; - - FOREACH_LINE(line, f, r = -errno; goto clear) { - union in_addr_union address; - int family; - char *l; - const char *a; - - truncate_nl(line); - - l = strstrip(line); - if (*l == '#' || *l == ';') - continue; - - a = first_word(l, "nameserver"); - if (!a) - continue; - - r = in_addr_from_string_auto(a, &family, &address); - if (r < 0) { - log_warning("Failed to parse name server %s.", a); - continue; - } - - LIST_FOREACH(servers, s, m->dns_servers) - if (s->family == family && in_addr_equal(family, &s->address, &address) > 0) - break; - - if (s) - s->marked = false; - else { - r = dns_server_new(m, NULL, DNS_SERVER_SYSTEM, NULL, family, &address); - if (r < 0) - goto clear; - } - } - - LIST_FOREACH_SAFE(servers, s, nx, m->dns_servers) - if (s->marked) { - LIST_REMOVE(servers, m->dns_servers, s); - dns_server_unref(s); - } - - /* Whenever /etc/resolv.conf changes, start using the first - * DNS server of it. This is useful to deal with broken - * network managing implementations (like NetworkManager), - * that when connecting to a VPN place both the VPN DNS - * servers and the local ones in /etc/resolv.conf. Without - * resetting the DNS server to use back to the first entry we - * will continue to use the local one thus being unable to - * resolve VPN domains. */ - manager_set_dns_server(m, m->dns_servers); - - return 0; - -clear: - while (m->dns_servers) { - s = m->dns_servers; - - LIST_REMOVE(servers, m->dns_servers, s); - dns_server_unref(s); - } - - return r; -} - -static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { - _cleanup_free_ char *t = NULL; - int r; - - assert(s); - assert(f); - assert(count); - - r = in_addr_to_string(s->family, &s->address, &t); - if (r < 0) { - log_warning_errno(r, "Invalid DNS address. Ignoring: %m"); - return; - } - - if (*count == MAXNS) - fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); - - fprintf(f, "nameserver %s\n", t); - (*count) ++; -} - -static void write_resolv_conf_search( - const char *domain, FILE *f, - unsigned *count, - unsigned *length) { - - assert(domain); - assert(f); - assert(length); - - if (*count >= MAXDNSRCH || - *length + strlen(domain) > 256) { - if (*count == MAXDNSRCH) - fputs(" # Too many search domains configured, remaining ones ignored.", f); - if (*length <= 256) - fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); - - return; - } - - fprintf(f, " %s", domain); - - (*length) += strlen(domain); - (*count) ++; -} - -static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { - Iterator i; - - fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" - "# Third party programs must not access this file directly, but\n" - "# only through the symlink at /etc/resolv.conf. To manage\n" - "# resolv.conf(5) in a different way, replace the symlink by a\n" - "# static file or a different symlink.\n\n", f); - - if (ordered_set_isempty(dns)) - fputs("# No DNS servers known.\n", f); - else { - DnsServer *s; - unsigned count = 0; - - ORDERED_SET_FOREACH(s, dns, i) - write_resolv_conf_server(s, f, &count); - } - - if (!ordered_set_isempty(domains)) { - unsigned length = 0, count = 0; - char *domain; - - fputs("search", f); - ORDERED_SET_FOREACH(domain, domains, i) - write_resolv_conf_search(domain, f, &count, &length); - fputs("\n", f); - } - - return fflush_and_check(f); -} - -int manager_write_resolv_conf(Manager *m) { - static const char path[] = "/run/systemd/resolve/resolv.conf"; - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(m); - - /* Read the system /etc/resolv.conf first */ - manager_read_resolv_conf(m); - - /* Add the full list to a set, to filter out duplicates */ - dns = ordered_set_new(&dns_server_hash_ops); - if (!dns) - return -ENOMEM; - - domains = ordered_set_new(&dns_name_hash_ops); - if (!domains) - return -ENOMEM; - - /* First add the system-wide servers */ - LIST_FOREACH(servers, s, m->dns_servers) { - r = ordered_set_put(dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - /* Then, add the per-link servers and domains */ - HASHMAP_FOREACH(l, m->links, i) { - char **domain; - - LIST_FOREACH(servers, s, l->dns_servers) { - r = ordered_set_put(dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - if (!l->unicast_scope) - continue; - - STRV_FOREACH(domain, l->unicast_scope->domains) { - r = ordered_set_put(domains, *domain); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - /* If we found nothing, add the fallback servers */ - if (ordered_set_isempty(dns)) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = ordered_set_put(dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - r = fopen_temporary_label(path, path, &f, &temp_path); - if (r < 0) - return r; - - fchmod(fileno(f), 0644); - - r = write_resolv_conf_contents(f, dns, domains); - if (r < 0) - goto fail; - - if (rename(temp_path, path) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - (void) unlink(path); - (void) unlink(temp_path); - return r; -} - int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; union { @@ -1174,97 +884,6 @@ int manager_send(Manager *m, int fd, int ifindex, int family, const union in_add return -EAFNOSUPPORT; } -DnsServer* manager_find_dns_server(Manager *m, int family, const union in_addr_union *in_addr) { - DnsServer *s; - - assert(m); - assert(in_addr); - - LIST_FOREACH(servers, s, m->dns_servers) - if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) - return s; - - LIST_FOREACH(servers, s, m->fallback_dns_servers) - if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) - return s; - - return NULL; -} - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { - assert(m); - - if (m->current_dns_server == s) - return s; - - if (s) { - _cleanup_free_ char *ip = NULL; - - in_addr_to_string(s->family, &s->address, &ip); - log_info("Switching to system DNS server %s.", strna(ip)); - } - - m->current_dns_server = s; - - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return s; -} - -DnsServer *manager_get_dns_server(Manager *m) { - Link *l; - assert(m); - - /* Try to read updates resolv.conf */ - manager_read_resolv_conf(m); - - if (!m->current_dns_server) - manager_set_dns_server(m, m->dns_servers); - - if (!m->current_dns_server) { - bool found = false; - Iterator i; - - /* No DNS servers configured, let's see if there are - * any on any links. If not, we use the fallback - * servers */ - - HASHMAP_FOREACH(l, m->links, i) - if (l->dns_servers) { - found = true; - break; - } - - if (!found) - manager_set_dns_server(m, m->fallback_dns_servers); - } - - return m->current_dns_server; -} - -void manager_next_dns_server(Manager *m) { - assert(m); - - /* If there's currently no DNS server set, then the next - * manager_get_dns_server() will find one */ - if (!m->current_dns_server) - return; - - /* Change to the next one */ - if (m->current_dns_server->servers_next) { - manager_set_dns_server(m, m->current_dns_server->servers_next); - return; - } - - /* If there was no next one, then start from the beginning of - * the list */ - if (m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, m->fallback_dns_servers); - else - manager_set_dns_server(m, m->dns_servers); -} - uint32_t manager_find_mtu(Manager *m) { uint32_t mtu = 0; Link *l; @@ -1418,42 +1037,102 @@ void manager_verify_all(Manager *m) { dns_zone_verify_all(&s->zone); } -void manager_flush_dns_servers(Manager *m, DnsServerType t) { +int manager_is_own_hostname(Manager *m, const char *name) { + int r; + + assert(m); + assert(name); + + if (m->llmnr_hostname) { + r = dns_name_equal(name, m->llmnr_hostname); + if (r != 0) + return r; + } + + if (m->mdns_hostname) + return dns_name_equal(name, m->mdns_hostname); + + return 0; +} + +int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { DnsServer *s; + Iterator i; + Link *l; + int r; assert(m); + assert(dns); + + r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); + if (r < 0) + return r; - if (t == DNS_SERVER_SYSTEM) - while (m->dns_servers) { - s = m->dns_servers; + /* First add the system-wide servers and domains */ + LIST_FOREACH(servers, s, m->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } - LIST_REMOVE(servers, m->dns_servers, s); - dns_server_unref(s); + /* Then, add the per-link servers */ + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; } + } - if (t == DNS_SERVER_FALLBACK) - while (m->fallback_dns_servers) { - s = m->fallback_dns_servers; - - LIST_REMOVE(servers, m->fallback_dns_servers, s); - dns_server_unref(s); + /* If we found nothing, add the fallback servers */ + if (ordered_set_isempty(*dns)) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; } + } + + return 0; } -int manager_is_own_hostname(Manager *m, const char *name) { +int manager_compile_search_domains(Manager *m, OrderedSet **domains) { + DnsSearchDomain *d; + Iterator i; + Link *l; int r; assert(m); - assert(name); + assert(domains); - if (m->llmnr_hostname) { - r = dns_name_equal(name, m->llmnr_hostname); - if (r != 0) + r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, m->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) return r; } - if (m->mdns_hostname) - return dns_name_equal(name, m->mdns_hostname); + HASHMAP_FOREACH(l, m->links, i) { + + LIST_FOREACH(domains, d, l->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } return 0; } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 074ce6c63d..d00c444583 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -27,6 +27,7 @@ #include "hashmap.h" #include "list.h" +#include "ordered-set.h" typedef struct Manager Manager; typedef enum Support Support; @@ -40,9 +41,14 @@ enum Support { }; #include "resolved-dns-query.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" #include "resolved-dns-stream.h" #include "resolved-link.h" +#define MANAGER_SEARCH_DOMAINS_MAX 32 +#define MANAGER_DNS_SERVERS_MAX 32 + struct Manager { sd_event *event; @@ -68,9 +74,15 @@ struct Manager { /* Unicast dns */ LIST_HEAD(DnsServer, dns_servers); LIST_HEAD(DnsServer, fallback_dns_servers); + unsigned n_dns_servers; /* counts both main and fallback */ DnsServer *current_dns_server; - bool read_resolv_conf; + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; + + bool need_builtin_fallbacks:1; + + bool read_resolv_conf:1; usec_t resolv_conf_mtime; LIST_HEAD(DnsScope, dns_scopes); @@ -113,13 +125,6 @@ int manager_new(Manager **ret); Manager* manager_free(Manager *m); int manager_start(Manager *m); -int manager_read_resolv_conf(Manager *m); -int manager_write_resolv_conf(Manager *m); - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); -DnsServer *manager_find_dns_server(Manager *m, int family, const union in_addr_union *in_addr); -DnsServer *manager_get_dns_server(Manager *m); -void manager_next_dns_server(Manager *m); uint32_t manager_find_mtu(Manager *m); @@ -138,13 +143,14 @@ DnsScope* manager_find_scope(Manager *m, DnsPacket *p); void manager_verify_all(Manager *m); -void manager_flush_dns_servers(Manager *m, DnsServerType t); - DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); #define EXTRA_CMSG_SPACE 1024 int manager_is_own_hostname(Manager *m, const char *name); +int manager_compile_dns_servers(Manager *m, OrderedSet **servers); +int manager_compile_search_domains(Manager *m, OrderedSet **domains); + const char* support_to_string(Support p) _const_; int support_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c new file mode 100644 index 0000000000..956f380f3c --- /dev/null +++ b/src/resolve/resolved-resolv-conf.c @@ -0,0 +1,273 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen <teg@jklm.no> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. + ***/ + +#include <resolv.h> + +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "fileio.h" +#include "ordered-set.h" +#include "resolved-conf.h" +#include "resolved-resolv-conf.h" +#include "string-util.h" +#include "strv.h" + +int manager_read_resolv_conf(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + struct stat st, own; + char line[LINE_MAX]; + usec_t t; + int r; + + assert(m); + + /* Reads the system /etc/resolv.conf, if it exists and is not + * symlinked to our own resolv.conf instance */ + + if (!m->read_resolv_conf) + return 0; + + r = stat("/etc/resolv.conf", &st); + if (r < 0) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); + goto clear; + } + + /* Have we already seen the file? */ + t = timespec_load(&st.st_mtim); + if (t == m->resolv_conf_mtime) + return 0; + + /* Is it symlinked to our own file? */ + if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && + st.st_dev == own.st_dev && + st.st_ino == own.st_ino) + return 0; + + f = fopen("/etc/resolv.conf", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); + goto clear; + } + + if (fstat(fileno(f), &st) < 0) { + r = log_error_errno(errno, "Failed to stat open file: %m"); + goto clear; + } + + dns_server_mark_all(m->dns_servers); + dns_search_domain_mark_all(m->search_domains); + + FOREACH_LINE(line, f, r = -errno; goto clear) { + const char *a; + char *l; + + l = strstrip(line); + if (*l == '#' || *l == ';') + continue; + + a = first_word(l, "nameserver"); + if (a) { + r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a); + if (r < 0) + log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); + + continue; + } + + a = first_word(l, "domain"); + if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ + a = first_word(l, "search"); + if (a) { + r = manager_parse_search_domains_and_warn(m, a); + if (r < 0) + log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); + } + } + + m->resolv_conf_mtime = t; + + /* Flush out all servers and search domains that are still + * marked. Those are then ones that didn't appear in the new + * /etc/resolv.conf */ + dns_server_unlink_marked(m->dns_servers); + dns_search_domain_unlink_marked(m->search_domains); + + /* Whenever /etc/resolv.conf changes, start using the first + * DNS server of it. This is useful to deal with broken + * network managing implementations (like NetworkManager), + * that when connecting to a VPN place both the VPN DNS + * servers and the local ones in /etc/resolv.conf. Without + * resetting the DNS server to use back to the first entry we + * will continue to use the local one thus being unable to + * resolve VPN domains. */ + manager_set_dns_server(m, m->dns_servers); + + /* Unconditionally flush the cache when /etc/resolv.conf is + * modified, even if the data it contained was completely + * identical to the previous version we used. We do this + * because altering /etc/resolv.conf is typically done when + * the network configuration changes, and that should be + * enough to flush the global unicast DNS cache. */ + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return 0; + +clear: + dns_server_unlink_all(m->dns_servers); + dns_search_domain_unlink_all(m->search_domains); + return r; +} + +static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { + _cleanup_free_ char *t = NULL; + int r; + + assert(s); + assert(f); + assert(count); + + r = in_addr_to_string(s->family, &s->address, &t); + if (r < 0) { + log_warning_errno(r, "Invalid DNS address. Ignoring: %m"); + return; + } + + if (*count == MAXNS) + fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); + (*count) ++; + + fprintf(f, "nameserver %s\n", t); +} + +static void write_resolv_conf_search( + const char *domain, + FILE *f, + unsigned *count, + unsigned *length) { + + assert(domain); + assert(f); + assert(length); + + if (*count >= MAXDNSRCH || + *length + strlen(domain) > 256) { + if (*count == MAXDNSRCH) + fputs(" # Too many search domains configured, remaining ones ignored.", f); + if (*length <= 256) + fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); + + return; + } + + (*length) += strlen(domain); + (*count) ++; + + fputc(' ', f); + fputs(domain, f); +} + +static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { + Iterator i; + + fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n" + "# Third party programs must not access this file directly, but\n" + "# only through the symlink at /etc/resolv.conf. To manage\n" + "# resolv.conf(5) in a different way, replace the symlink by a\n" + "# static file or a different symlink.\n\n", f); + + if (ordered_set_isempty(dns)) + fputs("# No DNS servers known.\n", f); + else { + unsigned count = 0; + DnsServer *s; + + ORDERED_SET_FOREACH(s, dns, i) + write_resolv_conf_server(s, f, &count); + } + + if (!ordered_set_isempty(domains)) { + unsigned length = 0, count = 0; + char *domain; + + fputs("search", f); + ORDERED_SET_FOREACH(domain, domains, i) + write_resolv_conf_search(domain, f, &count, &length); + fputs("\n", f); + } + + return fflush_and_check(f); +} + +int manager_write_resolv_conf(Manager *m) { + + #define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" + + _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + /* Read the system /etc/resolv.conf first */ + manager_read_resolv_conf(m); + + /* Add the full list to a set, to filter out duplicates */ + r = manager_compile_dns_servers(m, &dns); + if (r < 0) + return r; + + r = manager_compile_search_domains(m, &domains); + if (r < 0) + return r; + + r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + r = write_resolv_conf_contents(f, dns, domains); + if (r < 0) + goto fail; + + if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + (void) unlink(PRIVATE_RESOLV_CONF); + (void) unlink(temp_path); + return r; +} diff --git a/src/resolve/resolved-resolv-conf.h b/src/resolve/resolved-resolv-conf.h new file mode 100644 index 0000000000..a3355e994b --- /dev/null +++ b/src/resolve/resolved-resolv-conf.h @@ -0,0 +1,27 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen <teg@jklm.no> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "resolved-manager.h" + +int manager_read_resolv_conf(Manager *m); +int manager_write_resolv_conf(Manager *m); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index 7ba0546f4a..be406b71fe 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -26,6 +26,7 @@ #include "mkdir.h" #include "resolved-conf.h" #include "resolved-manager.h" +#include "resolved-resolv-conf.h" #include "selinux-util.h" #include "signal-util.h" #include "user-util.h" @@ -81,8 +82,10 @@ int main(int argc, char *argv[]) { } r = manager_parse_config_file(m); - if (r < 0) - log_warning_errno(r, "Failed to parse configuration file: %m"); + if (r < 0) { + log_error_errno(r, "Failed to parse configuration file: %m"); + goto finish; + } r = manager_start(m); if (r < 0) { diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in index 3eb19e42b7..39ecf83217 100644 --- a/src/resolve/resolved.conf.in +++ b/src/resolve/resolved.conf.in @@ -14,4 +14,5 @@ [Resolve] #DNS= #FallbackDNS=@DNS_SERVERS@ +#Domains= #LLMNR=yes diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index e6aad39c74..4cf6355b71 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -29,6 +29,7 @@ #include "hexdecoct.h" #include "parse-util.h" #include "string-util.h" +#include "strv.h" #include "utf8.h" int dns_label_unescape(const char **name, char *dest, size_t sz) { @@ -181,30 +182,31 @@ int dns_label_unescape_suffix(const char *name, const char **label_terminal, cha return r; } -int dns_label_escape(const char *p, size_t l, char **ret) { - _cleanup_free_ char *s = NULL; +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { char *q; - int r; - - assert(p); - assert(ret); if (l > DNS_LABEL_MAX) return -EINVAL; + if (sz < 1) + return -ENOSPC; - s = malloc(l * 4 + 1); - if (!s) - return -ENOMEM; + assert(p); + assert(dest); - q = s; + q = dest; while (l > 0) { if (*p == '.' || *p == '\\') { + if (sz < 3) + return -ENOSPC; + /* Dot or backslash */ *(q++) = '\\'; *(q++) = *p; + sz -= 2; + } else if (*p == '_' || *p == '-' || (*p >= '0' && *p <= '9') || @@ -212,15 +214,27 @@ int dns_label_escape(const char *p, size_t l, char **ret) { (*p >= 'A' && *p <= 'Z')) { /* Proper character */ + + if (sz < 2) + return -ENOSPC; + *(q++) = *p; + sz -= 1; + } else if ((uint8_t) *p >= (uint8_t) ' ' && *p != 127) { /* Everything else */ + + if (sz < 5) + return -ENOSPC; + *(q++) = '\\'; *(q++) = '0' + (char) ((uint8_t) *p / 100); *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); *(q++) = '0' + (char) ((uint8_t) *p % 10); + sz -= 4; + } else return -EINVAL; @@ -229,8 +243,28 @@ int dns_label_escape(const char *p, size_t l, char **ret) { } *q = 0; + return (int) (q - dest); +} + +int dns_label_escape_new(const char *p, size_t l, char **ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(p); + assert(ret); + + if (l > DNS_LABEL_MAX) + return -EINVAL; + + s = new(char, DNS_LABEL_ESCAPED_MAX); + if (!s) + return -ENOMEM; + + r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + *ret = s; - r = q - s; s = NULL; return r; @@ -350,28 +384,32 @@ int dns_name_concat(const char *a, const char *b, char **_ret) { if (k > 0) r = k; - r = dns_label_escape(label, r, &t); - if (r < 0) - return r; - if (_ret) { - if (!GREEDY_REALLOC(ret, allocated, n + !first + strlen(t) + 1)) + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) return -ENOMEM; + r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + if (!first) - ret[n++] = '.'; - else - first = false; + ret[n] = '.'; + } else { + char escaped[DNS_LABEL_ESCAPED_MAX]; - memcpy(ret + n, t, r); + r = dns_label_escape(label, r, escaped, sizeof(escaped)); + if (r < 0) + return r; } + if (!first) + n++; + else + first = false; + n += r; } - if (n > DNS_NAME_MAX) - return -EINVAL; - if (_ret) { if (!GREEDY_REALLOC(ret, allocated, n + 1)) return -ENOMEM; @@ -752,36 +790,27 @@ int dns_name_address(const char *p, int *family, union in_addr_union *address) { return 0; } -int dns_name_root(const char *name) { - char label[DNS_LABEL_MAX+1]; - int r; +bool dns_name_is_root(const char *name) { assert(name); - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - return r; + /* There are exactly two ways to encode the root domain name: + * as empty string, or with a single dot. */ - return r == 0 && *name == 0; + return STR_IN_SET(name, "", "."); } -int dns_name_single_label(const char *name) { +bool dns_name_is_single_label(const char *name) { char label[DNS_LABEL_MAX+1]; int r; assert(name); r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - return r; - if (r == 0) - return 0; - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - return r; + if (r <= 0) + return false; - return r == 0 && *name == 0; + return dns_name_is_root(name); } /* Encode a domain name according to RFC 1035 Section 3.1 */ @@ -846,12 +875,12 @@ static bool srv_type_label_is_valid(const char *label, size_t n) { return true; } -int dns_srv_type_verify(const char *name) { +bool dns_srv_type_is_valid(const char *name) { unsigned c = 0; int r; if (!name) - return 0; + return false; for (;;) { char label[DNS_LABEL_MAX]; @@ -859,18 +888,16 @@ int dns_srv_type_verify(const char *name) { /* This more or less implements RFC 6335, Section 5.1 */ r = dns_label_unescape(&name, label, sizeof(label)); - if (r == -EINVAL) - return 0; if (r < 0) - return r; + return false; if (r == 0) break; if (c >= 2) - return 0; + return false; if (!srv_type_label_is_valid(label, r)) - return 0; + return false; c++; } @@ -902,14 +929,15 @@ bool dns_service_name_is_valid(const char *name) { } int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { - _cleanup_free_ char *escaped = NULL, *n = NULL; + char escaped[DNS_LABEL_ESCAPED_MAX]; + _cleanup_free_ char *n = NULL; int r; assert(type); assert(domain); assert(ret); - if (!dns_srv_type_verify(type)) + if (!dns_srv_type_is_valid(type)) return -EINVAL; if (!name) @@ -918,7 +946,7 @@ int dns_service_join(const char *name, const char *type, const char *domain, cha if (!dns_service_name_is_valid(name)) return -EINVAL; - r = dns_label_escape(name, strlen(name), &escaped); + r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped)); if (r < 0) return r; diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index a68df330e6..99c72574db 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -26,11 +26,12 @@ #include "in-addr-util.h" #define DNS_LABEL_MAX 63 -#define DNS_NAME_MAX 255 +#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1) int dns_label_unescape(const char **name, char *dest, size_t sz); int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); -int dns_label_escape(const char *p, size_t l, char **ret); +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); +int dns_label_escape_new(const char *p, size_t l, char **ret); int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); @@ -67,16 +68,13 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char int dns_name_reverse(int family, const union in_addr_union *a, char **ret); int dns_name_address(const char *p, int *family, union in_addr_union *a); -int dns_name_root(const char *name); -int dns_name_single_label(const char *name); +bool dns_name_is_root(const char *name); +bool dns_name_is_single_label(const char *name); int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len); -int dns_srv_type_verify(const char *name); - +bool dns_srv_type_is_valid(const char *name); bool dns_service_name_is_valid(const char *name); int dns_service_join(const char *name, const char *type, const char *domain, char **ret); int dns_service_split(const char *joined, char **name, char **type, char **domain); - -int dns_name_replace_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index b76a31a549..f010e4e19a 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -126,7 +126,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex _cleanup_free_ char *t = NULL; int r; - r = dns_label_escape(what, l, &t); + r = dns_label_escape_new(what, l, &t); assert_se(r == ret); if (r < 0) @@ -246,21 +246,21 @@ static void test_dns_name_endswith(void) { test_dns_name_endswith_one("x.y\001.z", "waldo", -EINVAL); } -static void test_dns_name_root(void) { - assert_se(dns_name_root("") == true); - assert_se(dns_name_root(".") == true); - assert_se(dns_name_root("xxx") == false); - assert_se(dns_name_root("xxx.") == false); - assert_se(dns_name_root("..") == -EINVAL); +static void test_dns_name_is_root(void) { + assert_se(dns_name_is_root("")); + assert_se(dns_name_is_root(".")); + assert_se(!dns_name_is_root("xxx")); + assert_se(!dns_name_is_root("xxx.")); + assert_se(!dns_name_is_root("..")); } -static void test_dns_name_single_label(void) { - assert_se(dns_name_single_label("") == false); - assert_se(dns_name_single_label(".") == false); - assert_se(dns_name_single_label("..") == -EINVAL); - assert_se(dns_name_single_label("x") == true); - assert_se(dns_name_single_label("x.") == true); - assert_se(dns_name_single_label("xx.yy") == false); +static void test_dns_name_is_single_label(void) { + assert_se(!dns_name_is_single_label("")); + assert_se(!dns_name_is_single_label(".")); + assert_se(!dns_name_is_single_label("..")); + assert_se(dns_name_is_single_label("x")); + assert_se(dns_name_is_single_label("x.")); + assert_se(!dns_name_is_single_label("xx.yy")); } static void test_dns_name_reverse_one(const char *address, const char *name) { @@ -327,27 +327,27 @@ static void test_dns_service_name_is_valid(void) { assert_se(!dns_service_name_is_valid("this is an overly long string that is certainly longer than 63 characters")); } -static void test_dns_srv_type_verify(void) { +static void test_dns_srv_type_is_valid(void) { - assert_se(dns_srv_type_verify("_http._tcp") > 0); - assert_se(dns_srv_type_verify("_foo-bar._tcp") > 0); - assert_se(dns_srv_type_verify("_w._udp") > 0); - assert_se(dns_srv_type_verify("_a800._tcp") > 0); - assert_se(dns_srv_type_verify("_a-800._tcp") > 0); + assert_se(dns_srv_type_is_valid("_http._tcp")); + assert_se(dns_srv_type_is_valid("_foo-bar._tcp")); + assert_se(dns_srv_type_is_valid("_w._udp")); + assert_se(dns_srv_type_is_valid("_a800._tcp")); + assert_se(dns_srv_type_is_valid("_a-800._tcp")); - assert_se(dns_srv_type_verify(NULL) == 0); - assert_se(dns_srv_type_verify("") == 0); - assert_se(dns_srv_type_verify("x") == 0); - assert_se(dns_srv_type_verify("_foo") == 0); - assert_se(dns_srv_type_verify("_tcp") == 0); - assert_se(dns_srv_type_verify("_") == 0); - assert_se(dns_srv_type_verify("_foo.") == 0); - assert_se(dns_srv_type_verify("_föo._tcp") == 0); - assert_se(dns_srv_type_verify("_f\no._tcp") == 0); - assert_se(dns_srv_type_verify("_800._tcp") == 0); - assert_se(dns_srv_type_verify("_-800._tcp") == 0); - assert_se(dns_srv_type_verify("_-foo._tcp") == 0); - assert_se(dns_srv_type_verify("_piep._foo._udp") == 0); + assert_se(!dns_srv_type_is_valid(NULL)); + assert_se(!dns_srv_type_is_valid("")); + assert_se(!dns_srv_type_is_valid("x")); + assert_se(!dns_srv_type_is_valid("_foo")); + assert_se(!dns_srv_type_is_valid("_tcp")); + assert_se(!dns_srv_type_is_valid("_")); + assert_se(!dns_srv_type_is_valid("_foo.")); + assert_se(!dns_srv_type_is_valid("_föo._tcp")); + assert_se(!dns_srv_type_is_valid("_f\no._tcp")); + assert_se(!dns_srv_type_is_valid("_800._tcp")); + assert_se(!dns_srv_type_is_valid("_-800._tcp")); + assert_se(!dns_srv_type_is_valid("_-foo._tcp")); + assert_se(!dns_srv_type_is_valid("_piep._foo._udp")); } static void test_dns_service_join_one(const char *a, const char *b, const char *c, int r, const char *d) { @@ -436,14 +436,14 @@ int main(int argc, char *argv[]) { test_dns_name_equal(); test_dns_name_endswith(); test_dns_name_between(); - test_dns_name_root(); - test_dns_name_single_label(); + test_dns_name_is_root(); + test_dns_name_is_single_label(); test_dns_name_reverse(); test_dns_name_concat(); test_dns_name_is_valid(); test_dns_name_to_wire_format(); test_dns_service_name_is_valid(); - test_dns_srv_type_verify(); + test_dns_srv_type_is_valid(); test_dns_service_join(); test_dns_service_split(); test_dns_name_change_suffix(); diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index c3973a316e..854f60be80 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -680,11 +680,21 @@ static void test_config_parse_rlimit(void) { assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "55:66", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == 55); + assert_se(rl[RLIMIT_NOFILE]->rlim_max == 66); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_NOFILE]); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + assert_se(config_parse_limit(NULL, "fake", 1, "section", 1, "LimitNOFILE", RLIMIT_NOFILE, "infinity:infinity", rl, NULL) >= 0); + assert_se(rl[RLIMIT_NOFILE]); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_NOFILE]->rlim_cur == rl[RLIMIT_NOFILE]->rlim_max); + rl[RLIMIT_NOFILE] = mfree(rl[RLIMIT_NOFILE]); assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "56", rl, NULL) >= 0); @@ -697,6 +707,11 @@ static void test_config_parse_rlimit(void) { assert_se(rl[RLIMIT_CPU]->rlim_cur == 57); assert_se(rl[RLIMIT_CPU]->rlim_cur == rl[RLIMIT_CPU]->rlim_max); + assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "40s:1m", rl, NULL) >= 0); + assert_se(rl[RLIMIT_CPU]); + assert_se(rl[RLIMIT_CPU]->rlim_cur == 40); + assert_se(rl[RLIMIT_CPU]->rlim_max == 60); + assert_se(config_parse_sec_limit(NULL, "fake", 1, "section", 1, "LimitCPU", RLIMIT_CPU, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_CPU]); assert_se(rl[RLIMIT_CPU]->rlim_cur == RLIM_INFINITY); @@ -714,16 +729,31 @@ static void test_config_parse_rlimit(void) { assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "58:60", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 58); + assert_se(rl[RLIMIT_RTTIME]->rlim_max == 60); + assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "59s:123s", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 59 * USEC_PER_SEC); + assert_se(rl[RLIMIT_RTTIME]->rlim_max == 123 * USEC_PER_SEC); + assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "infinity:infinity", rl, NULL) >= 0); + assert_se(rl[RLIMIT_RTTIME]); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == RLIM_INFINITY); + assert_se(rl[RLIMIT_RTTIME]->rlim_cur == rl[RLIMIT_RTTIME]->rlim_max); + assert_se(config_parse_usec_limit(NULL, "fake", 1, "section", 1, "LimitRTTIME", RLIMIT_RTTIME, "2345ms", rl, NULL) >= 0); assert_se(rl[RLIMIT_RTTIME]); assert_se(rl[RLIMIT_RTTIME]->rlim_cur == 2345 * USEC_PER_MSEC); |