From 17f6ed4d5956f4e74056ae976b638dbba33c2844 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 12:33:14 +0100 Subject: [PATCH 01/14] sd-dhcp: permit unsetting the hostname again Let's handle NULL hostnames (for unsetting it) before we validate the name. --- src/libsystemd-network/sd-dhcp-client.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 6475da2c2a..9716168c1e 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -390,17 +390,21 @@ int sd_dhcp_client_set_hostname( assert_return(client, -EINVAL); + if (!hostname) { + client->hostname = mfree(client->hostname); + return 0; + } + + /* Refuse hostnames that neither qualify as DNS nor as Linux hosntames */ if (!hostname_is_valid(hostname, false) && !dns_name_is_valid(hostname)) return -EINVAL; if (streq_ptr(client->hostname, hostname)) return 0; - if (hostname) { - new_hostname = strdup(hostname); - if (!new_hostname) - return -ENOMEM; - } + new_hostname = strdup(hostname); + if (!new_hostname) + return -ENOMEM; free(client->hostname); client->hostname = new_hostname; From 7192bb81bd99108287db82d6140d5329086b0da2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 13:02:57 +0100 Subject: [PATCH 02/14] networkd: rework how we set the hostname on the dhcp4 client Let's split out the code into its own little function and let's use gethostname_strict() which already filters out localhost for us. --- src/network/networkd-dhcp4.c | 44 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 614bceefab..ca23c1c2a7 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -536,6 +536,28 @@ static void dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) { return; } +static int dhcp4_set_hostname(Link *link) { + _cleanup_free_ char *hostname = NULL; + const char *hn; + int r; + + assert(link); + + if (!link->network->dhcp_send_hostname) + hn = NULL; + else if (link->network->dhcp_hostname) + hn = link->network->dhcp_hostname; + else { + r = gethostname_strict(&hostname); + if (r < 0 && r != -ENXIO) /* ENXIO: no hostname set or hostname is "localhost" */ + return r; + + hn = hostname; + } + + return sd_dhcp_client_set_hostname(link->dhcp_client, hn); +} + int dhcp4_configure(Link *link) { int r; @@ -605,25 +627,9 @@ int dhcp4_configure(Link *link) { if (r < 0) return r; - if (link->network->dhcp_send_hostname) { - _cleanup_free_ char *hostname = NULL; - const char *hn = NULL; - - if (!link->network->dhcp_hostname) { - hostname = gethostname_malloc(); - if (!hostname) - return -ENOMEM; - - hn = hostname; - } else - hn = link->network->dhcp_hostname; - - if (!is_localhost(hn)) { - r = sd_dhcp_client_set_hostname(link->dhcp_client, hn); - if (r < 0) - return r; - } - } + r = dhcp4_set_hostname(link); + if (r < 0) + return r; if (link->network->dhcp_vendor_class_identifier) { r = sd_dhcp_client_set_vendor_class_identifier(link->dhcp_client, From fdedbe2676b4f9142246c6ac1f42181174ab22f0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 13:13:28 +0100 Subject: [PATCH 03/14] basic: add explicit ipv4-specific in_addr classification calls This adds in4_addr_is_localhost() and in4_addr_is_link_local() that only take an IPv4 "struct in_addr", to match in_addr_is_localhost() and in_addr_is_link_local() that that a "union in_addr_union". This matches the existing in4_addr_is_null() call that already exists. For IPv6 glibc already exports a set of macros, hence we don't add similar functions in6_addr_is_localhost(). We also drop in6_addr_is_null() as IN6_IS_ADDR_UNSPECIFIED() already provides that. --- src/basic/in-addr-util.c | 30 ++++++++++++++++----------- src/basic/in-addr-util.h | 7 +++++-- src/libsystemd-network/ndisc-router.c | 2 +- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index aa7ccd1afd..d6c979acef 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -31,15 +31,9 @@ #include "util.h" bool in4_addr_is_null(const struct in_addr *a) { - return a->s_addr == 0; -} + assert(a); -bool in6_addr_is_null(const struct in6_addr *a) { - return - a->s6_addr32[0] == 0 && - a->s6_addr32[1] == 0 && - a->s6_addr32[2] == 0 && - a->s6_addr32[3] == 0; + return a->s_addr == 0; } int in_addr_is_null(int family, const union in_addr_union *u) { @@ -49,16 +43,22 @@ int in_addr_is_null(int family, const union in_addr_union *u) { return in4_addr_is_null(&u->in); if (family == AF_INET6) - return in6_addr_is_null(&u->in6); + return IN6_IS_ADDR_UNSPECIFIED(&u->in6); return -EAFNOSUPPORT; } +bool in4_addr_is_link_local(const struct in_addr *a) { + assert(a); + + return (be32toh(a->s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); +} + 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) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); + return in4_addr_is_link_local(&u->in); if (family == AF_INET6) return IN6_IS_ADDR_LINKLOCAL(&u->in6); @@ -66,12 +66,18 @@ int in_addr_is_link_local(int family, const union in_addr_union *u) { return -EAFNOSUPPORT; } +bool in4_addr_is_localhost(const struct in_addr *a) { + assert(a); + + /* All of 127.x.x.x is localhost. */ + return (be32toh(a->s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; +} + 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; + return in4_addr_is_localhost(&u->in); if (family == AF_INET6) return IN6_IS_ADDR_LOOPBACK(&u->in6); diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index d60064aef8..64a812c322 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -37,11 +37,14 @@ struct in_addr_data { }; bool in4_addr_is_null(const struct in_addr *a); -bool in6_addr_is_null(const struct in6_addr *a); - int in_addr_is_null(int family, const union in_addr_union *u); + +bool in4_addr_is_link_local(const struct in_addr *a); int in_addr_is_link_local(int family, const union in_addr_union *u); + +bool in4_addr_is_localhost(const struct in_addr *a); 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/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c index 41ff2b353a..cf56c89d76 100644 --- a/src/libsystemd-network/ndisc-router.c +++ b/src/libsystemd-network/ndisc-router.c @@ -91,7 +91,7 @@ _public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *r assert_return(rt, -EINVAL); assert_return(ret_addr, -EINVAL); - if (in6_addr_is_null(&rt->address)) + if (IN6_IS_ADDR_UNSPECIFIED(&rt->address)) return -ENODATA; *ret_addr = rt->address; From fd18634de4d84eba2422908948c27ec6c5bf4cf5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 16:48:19 +0100 Subject: [PATCH 04/14] basic: make sure in_addr_from_string() can take NULL arguments This way, we can use it for validating IP address strings. --- src/basic/in-addr-util.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index d6c979acef..3b06cb00ad 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -283,15 +283,14 @@ fallback: } int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { - + union in_addr_union buffer; assert(s); - assert(ret); if (!IN_SET(family, AF_INET, AF_INET6)) return -EAFNOSUPPORT; errno = 0; - if (inet_pton(family, s, ret) <= 0) + if (inet_pton(family, s, ret ?: &buffer) <= 0) return errno > 0 ? -errno : -EINVAL; return 0; @@ -301,18 +300,18 @@ int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *re int r; assert(s); - assert(family); - assert(ret); r = in_addr_from_string(AF_INET, s, ret); if (r >= 0) { - *family = AF_INET; + if (family) + *family = AF_INET; return 0; } r = in_addr_from_string(AF_INET6, s, ret); if (r >= 0) { - *family = AF_INET6; + if (family) + *family = AF_INET6; return 0; } From 08a4849ec94fd06c3284994b91835b31a3a2f3aa Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 16:55:01 +0100 Subject: [PATCH 05/14] shared: add new API to validate a string as hostname or IP address --- src/shared/dns-domain.c | 12 ++++++++++++ src/shared/dns-domain.h | 2 ++ src/test/test-dns-domain.c | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index f9a6fd5f03..33debadb15 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -1324,3 +1324,15 @@ int dns_name_apply_idna(const char *name, char **ret) { return (int) n; } + +int dns_name_is_valid_or_address(const char *name) { + /* Returns > 0 if the specified name is either a valid IP address formatted as string or a valid DNS name */ + + if (isempty(name)) + return 0; + + if (in_addr_from_string_auto(name, NULL, NULL) >= 0) + return 1; + + return dns_name_is_valid(name); +} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index af780f0b8b..03f160369c 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -107,3 +107,5 @@ int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); int dns_name_common_suffix(const char *a, const char *b, const char **ret); int dns_name_apply_idna(const char *name, char **ret); + +int dns_name_is_valid_or_address(const char *name); diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index e2f097c95e..b4db4a6702 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -627,6 +627,18 @@ static void test_dns_name_apply_idna(void) { test_dns_name_apply_idna_one("föö.bär.", "xn--f-1gaa.xn--br-via"); } +static void test_dns_name_is_valid_or_address(void) { + assert_se(dns_name_is_valid_or_address(NULL) == 0); + assert_se(dns_name_is_valid_or_address("") == 0); + assert_se(dns_name_is_valid_or_address("foobar") > 0); + assert_se(dns_name_is_valid_or_address("foobar.com") > 0); + assert_se(dns_name_is_valid_or_address("foobar..com") == 0); + assert_se(dns_name_is_valid_or_address("foobar.com.") > 0); + assert_se(dns_name_is_valid_or_address("127.0.0.1") > 0); + assert_se(dns_name_is_valid_or_address("::") > 0); + assert_se(dns_name_is_valid_or_address("::1") > 0); +} + int main(int argc, char *argv[]) { test_dns_label_unescape(); @@ -654,6 +666,7 @@ int main(int argc, char *argv[]) { test_dns_name_compare_func(); test_dns_name_common_suffix(); test_dns_name_apply_idna(); + test_dns_name_is_valid_or_address(); return 0; } From 265759908d674b059987453c80ae984da0642717 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:00:25 +0100 Subject: [PATCH 06/14] networkd: validate NTP server strings configured in .network files Let's be a bit stricter, and make sure users only configure proper DNS names or IP addresses, but not any other strings. --- src/network/networkd-network-gperf.gperf | 2 +- src/network/networkd-network.c | 53 ++++++++++++++++++++++++ src/network/networkd-network.h | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index efd3176ac3..463f4595c1 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -54,7 +54,7 @@ Network.LLMNR, config_parse_resolve_support, Network.MulticastDNS, config_parse_resolve_support, 0, offsetof(Network, mdns) Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode) Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, 0 -Network.NTP, config_parse_strv, 0, offsetof(Network, ntp) +Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp) Network.IPForward, config_parse_address_family_boolean_with_kernel,0, offsetof(Network, ip_forward) Network.IPMasquerade, config_parse_bool, 0, offsetof(Network, ip_masquerade) Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 31e899eecd..1b269d15ce 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -1084,6 +1084,59 @@ int config_parse_dnssec_negative_trust_anchors( return 0; } +int config_parse_ntp( + 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) { + + char ***l = data; + int r; + + assert(l); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&rvalue, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract NTP server name, ignoring: %s", rvalue); + break; + } + if (r == 0) + break; + + r = dns_name_is_valid_or_address(w); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "%s is not a valid domain name or IP address, ignoring.", w); + continue; + } + + r = strv_push(l, w); + if (r < 0) + return log_oom(); + + w = NULL; + } + + return 0; +} + int config_parse_dhcp_route_table(const char *unit, const char *filename, unsigned line, diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index e956a59fe3..ef32d1b39a 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -233,6 +233,7 @@ int config_parse_dnssec_negative_trust_anchors(const char *unit, const char *fil int config_parse_dhcp_use_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_lldp_mode(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_dhcp_route_table(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_ntp(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); /* Legacy IPv4LL support */ int config_parse_ipv4ll(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); From 94e9bd571ede5dee3e91672ef5a4af0e31fb447b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:01:32 +0100 Subject: [PATCH 07/14] networkd: reorder fields in Network struct a bit Let's reorder them a bit, so that stuff that belongs together semantically is placed together (in particular, move the various DHCP "use" booleans together). --- src/network/networkd-network.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index ef32d1b39a..735d7f7a77 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -112,19 +112,19 @@ struct Network { DCHPClientIdentifier dhcp_client_identifier; char *dhcp_vendor_class_identifier; char *dhcp_hostname; - bool dhcp_use_dns; - bool dhcp_use_ntp; - bool dhcp_use_mtu; - bool dhcp_use_hostname; - DHCPUseDomains dhcp_use_domains; - bool dhcp_send_hostname; - bool dhcp_broadcast; - bool dhcp_critical; - bool dhcp_use_routes; - bool dhcp_use_timezone; unsigned dhcp_route_metric; uint32_t dhcp_route_table; uint32_t dhcp_client_port; + bool dhcp_send_hostname; + bool dhcp_broadcast; + bool dhcp_critical; + bool dhcp_use_dns; + bool dhcp_use_ntp; + bool dhcp_use_mtu; + bool dhcp_use_routes; + bool dhcp_use_timezone; + bool dhcp_use_hostname; + DHCPUseDomains dhcp_use_domains; /* DHCP Server Support */ bool dhcp_server; From fa105ce6b7ddd94f8a079e825cef32393adb6d45 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:03:13 +0100 Subject: [PATCH 08/14] networkd: minor word extraction fixes Let's make the loops around extract_first_word() be more alike. --- src/network/networkd-network.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 1b269d15ce..e3a77c2152 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -909,13 +909,14 @@ int config_parse_dhcp_server_dns( struct in_addr a, *m; r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract word, ignoring: %s", rvalue); return 0; } - if (r == 0) - return 0; + break; if (inet_pton(AF_INET, w, &a) <= 0) { log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse DNS server address, ignoring: %s", w); @@ -929,6 +930,8 @@ int config_parse_dhcp_server_dns( m[n->n_dhcp_server_dns++] = a; n->dhcp_server_dns = m; } + + return 0; } int config_parse_dhcp_server_ntp( @@ -956,11 +959,12 @@ int config_parse_dhcp_server_ntp( struct in_addr a, *m; r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract word, ignoring: %s", rvalue); return 0; } - if (r == 0) return 0; From 5512a96316ef54b398a746428d4ded28bda2cccc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:04:26 +0100 Subject: [PATCH 09/14] networkd: store DNS servers configured per-network as parsed addresses DNS servers must be specified as IP addresses, hence let's store them as that internally, so that they are guaranteed to be fully normalized always, and invalid data cannot be stored. --- src/network/networkd-link.c | 35 ++++++++++++++------- src/network/networkd-manager.c | 56 ++++++++++++++++++++++++++++------ src/network/networkd-network.c | 24 +++++++++------ src/network/networkd-network.h | 5 ++- 4 files changed, 90 insertions(+), 30 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 0b634572a9..b7743557ec 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -854,28 +854,26 @@ static int address_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userda static int link_push_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) { _cleanup_free_ struct in_addr *addresses = NULL; size_t n_addresses = 0, n_allocated = 0; - char **a; + unsigned i; log_debug("Copying DNS server information from %s", link->ifname); if (!link->network) return 0; - STRV_FOREACH(a, link->network->dns) { - struct in_addr ia; + for (i = 0; i < link->network->n_dns; i++) { /* Only look for IPv4 addresses */ - if (inet_pton(AF_INET, *a, &ia) <= 0) + if (link->network->dns[i].family != AF_INET) continue; if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1)) return log_oom(); - addresses[n_addresses++] = ia; + addresses[n_addresses++] = link->network->dns[i].address.in; } - if (link->network->dhcp_use_dns && - link->dhcp_lease) { + if (link->network->dhcp_use_dns && link->dhcp_lease) { const struct in_addr *da = NULL; int n; @@ -919,8 +917,7 @@ static int link_push_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) { addresses[n_addresses++] = ia; } - if (link->network->dhcp_use_ntp && - link->dhcp_lease) { + if (link->network->dhcp_use_ntp && link->dhcp_lease) { const struct in_addr *da = NULL; int n; @@ -3235,7 +3232,7 @@ int link_save(Link *link) { if (r < 0) goto fail; - fchmod(fileno(f), 0644); + (void) fchmod(fileno(f), 0644); fprintf(f, "# This is private data. Do not parse.\n" @@ -3248,6 +3245,7 @@ int link_save(Link *link) { sd_dhcp6_lease *dhcp6_lease = NULL; const char *dhcp_domainname = NULL; char **dhcp6_domains = NULL; + unsigned j; if (link->dhcp6_client) { r = sd_dhcp6_client_get_lease(link->dhcp6_client, &dhcp6_lease); @@ -3259,7 +3257,22 @@ int link_save(Link *link) { fputs("DNS=", f); space = false; - fputstrv(f, link->network->dns, NULL, &space); + + for (j = 0; j < link->network->n_dns; j++) { + _cleanup_free_ char *b = NULL; + + r = in_addr_to_string(link->network->dns[j].family, + &link->network->dns[j].address, &b); + if (r < 0) { + log_debug_errno(r, "Failed to format address, ignoring: %m"); + continue; + } + + if (space) + fputc(' ', f); + fputs(b, f); + space = true; + } if (link->network->dhcp_use_dns && link->dhcp_lease) { diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index a1252c9b51..c3d3f48a3f 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -774,11 +774,48 @@ static int manager_connect_rtnl(Manager *m) { return 0; } -static int ordered_set_put_in_addr(OrderedSet *s, const struct in_addr *address) { +static int ordered_set_put_in_addr_data(OrderedSet *s, const struct in_addr_data *address) { char *p; int r; assert(s); + assert(address); + + r = in_addr_to_string(address->family, &address->address, &p); + if (r < 0) + return r; + + r = ordered_set_consume(s, p); + if (r == -EEXIST) + return 0; + + return r; +} + +static int ordered_set_put_in_addr_datav(OrderedSet *s, const struct in_addr_data *addresses, unsigned n) { + int r, c = 0; + unsigned i; + + assert(s); + assert(addresses || n == 0); + + for (i = 0; i < n; i++) { + r = ordered_set_put_in_addr_data(s, addresses+i); + if (r < 0) + return r; + + c += r; + } + + return c; +} + +static int ordered_set_put_in4_addr(OrderedSet *s, const struct in_addr *address) { + char *p; + int r; + + assert(s); + assert(address); r = in_addr_to_string(AF_INET, (const union in_addr_union*) address, &p); if (r < 0) @@ -791,14 +828,15 @@ static int ordered_set_put_in_addr(OrderedSet *s, const struct in_addr *address) return r; } -static int ordered_set_put_in_addrv(OrderedSet *s, const struct in_addr *addresses, int n) { - int r, i, c = 0; +static int ordered_set_put_in4_addrv(OrderedSet *s, const struct in_addr *addresses, unsigned n) { + int r, c = 0; + unsigned i; assert(s); - assert(n <= 0 || addresses); + assert(n == 0 || addresses); for (i = 0; i < n; i++) { - r = ordered_set_put_in_addr(s, addresses+i); + r = ordered_set_put_in4_addr(s, addresses+i); if (r < 0) return r; @@ -865,7 +903,7 @@ static int manager_save(Manager *m) { continue; /* First add the static configured entries */ - r = ordered_set_put_strdupv(dns, link->network->dns); + r = ordered_set_put_in_addr_datav(dns, link->network->dns, link->network->n_dns); if (r < 0) return r; @@ -890,7 +928,7 @@ static int manager_save(Manager *m) { r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); if (r > 0) { - r = ordered_set_put_in_addrv(dns, addresses, r); + r = ordered_set_put_in4_addrv(dns, addresses, r); if (r < 0) return r; } else if (r < 0 && r != -ENODATA) @@ -902,7 +940,7 @@ static int manager_save(Manager *m) { r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); if (r > 0) { - r = ordered_set_put_in_addrv(ntp, addresses, r); + r = ordered_set_put_in4_addrv(ntp, addresses, r); if (r < 0) return r; } else if (r < 0 && r != -ENODATA) @@ -934,7 +972,7 @@ static int manager_save(Manager *m) { if (r < 0) return r; - fchmod(fileno(f), 0644); + (void) fchmod(fileno(f), 0644); fprintf(f, "# This is private data. Do not parse.\n" diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index e3a77c2152..bc4dc95ff9 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -244,7 +244,7 @@ void network_free(Network *network) { free(network->mac); strv_free(network->ntp); - strv_free(network->dns); + free(network->dns); strv_free(network->search_domains); strv_free(network->route_domains); strv_free(network->bind_carrier); @@ -396,7 +396,7 @@ int network_apply(Network *network, Link *link) { route->protocol = RTPROT_STATIC; } - if (!strv_isempty(network->dns) || + if (network->n_dns > 0 || !strv_isempty(network->ntp) || !strv_isempty(network->search_domains) || !strv_isempty(network->route_domains)) @@ -1004,29 +1004,35 @@ int config_parse_dns( for (;;) { _cleanup_free_ char *w = NULL; union in_addr_union a; + struct in_addr_data *m; int family; - r = extract_first_word(&rvalue, &w, NULL, EXTRACT_QUOTES|EXTRACT_RETAIN_ESCAPE); - if (r == 0) - break; + r = extract_first_word(&rvalue, &w, NULL, 0); if (r == -ENOMEM) return log_oom(); if (r < 0) { log_syntax(unit, LOG_ERR, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); break; } + if (r == 0) + break; r = in_addr_from_string_auto(w, &family, &a); if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse dns server address, ignoring: %s", w); + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse dns server address, ignoring: %s", w); continue; } - r = strv_consume(&n->dns, w); - if (r < 0) + m = realloc(n->dns, (n->n_dns + 1) * sizeof(struct in_addr_data)); + if (!m) return log_oom(); - w = NULL; + m[n->n_dns++] = (struct in_addr_data) { + .family = family, + .address = a, + }; + + n->dns = m; } return 0; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 735d7f7a77..17cff956da 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -194,7 +194,10 @@ struct Network { Hashmap *routes_by_section; Hashmap *fdb_entries_by_section; - char **search_domains, **route_domains, **dns, **ntp, **bind_carrier; + struct in_addr_data *dns; + unsigned n_dns; + + char **search_domains, **route_domains, **ntp, **bind_carrier; ResolveSupport llmnr; ResolveSupport mdns; From a380b2d4c6615384c562e6f1dda48ee703498bf0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:07:52 +0100 Subject: [PATCH 10/14] =?UTF-8?q?networkd:=20rename=20link=5Fpush=5Fdns=5F?= =?UTF-8?q?to=5Fdhcp=5Fserver()=20=E2=86=92=20link=5Fpush=5Fuplink=5Fdns?= =?UTF-8?q?=5Fto=5Fdhcp=5Fserver()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let's clarify that this is only relevant for pushing uplink DNS config data, not in any other context. (Same for NTP) --- src/network/networkd-link.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index b7743557ec..216f4aab52 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -851,7 +851,7 @@ static int address_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userda return 1; } -static int link_push_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) { +static int link_push_uplink_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) { _cleanup_free_ struct in_addr *addresses = NULL; size_t n_addresses = 0, n_allocated = 0; unsigned i; @@ -894,7 +894,7 @@ static int link_push_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) { return sd_dhcp_server_set_dns(s, addresses, n_addresses); } -static int link_push_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) { +static int link_push_uplink_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) { _cleanup_free_ struct in_addr *addresses = NULL; size_t n_addresses = 0, n_allocated = 0; char **a; @@ -1031,7 +1031,7 @@ static int link_enter_set_addresses(Link *link) { log_link_debug(link, "Not emitting DNS server information on link, couldn't find suitable uplink."); r = 0; } else - r = link_push_dns_to_dhcp_server(uplink, link->dhcp_server); + r = link_push_uplink_dns_to_dhcp_server(uplink, link->dhcp_server); } if (r < 0) log_link_warning_errno(link, r, "Failed to set DNS server for DHCP server, ignoring: %m"); @@ -1050,7 +1050,7 @@ static int link_enter_set_addresses(Link *link) { log_link_debug(link, "Not emitting NTP server information on link, couldn't find suitable uplink."); r = 0; } else - r = link_push_ntp_to_dhcp_server(uplink, link->dhcp_server); + r = link_push_uplink_ntp_to_dhcp_server(uplink, link->dhcp_server); } if (r < 0) From 49ad68298a1c244b6acffff28e7648d803a57563 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:11:12 +0100 Subject: [PATCH 11/14] networkd: do not automatically propagate bogus DNS/NTP servers Never propagate DNS/NTP servers on the local link via the DHCP server. The DNS/NTP servers 0.0.0.0 and 127.0.0.1 only make sense in the local context, hence never propagate them automatically to other hosts. Fixes: #4524 --- src/network/networkd-link.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 216f4aab52..b38eec1ba7 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -862,15 +862,22 @@ static int link_push_uplink_dns_to_dhcp_server(Link *link, sd_dhcp_server *s) { return 0; for (i = 0; i < link->network->n_dns; i++) { + struct in_addr ia; /* Only look for IPv4 addresses */ if (link->network->dns[i].family != AF_INET) continue; + ia = link->network->dns[i].address.in; + + /* Never propagate obviously borked data */ + if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia)) + continue; + if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1)) return log_oom(); - addresses[n_addresses++] = link->network->dns[i].address.in; + addresses[n_addresses++] = ia; } if (link->network->dhcp_use_dns && link->dhcp_lease) { @@ -911,6 +918,10 @@ static int link_push_uplink_ntp_to_dhcp_server(Link *link, sd_dhcp_server *s) { if (inet_pton(AF_INET, *a, &ia) <= 0) continue; + /* Never propagate obviously borked data */ + if (in4_addr_is_null(&ia) || in4_addr_is_localhost(&ia)) + continue; + if (!GREEDY_REALLOC(addresses, n_allocated, n_addresses + 1)) return log_oom(); From d9ec2e632df4905201facf76d6a205edc952116a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:19:44 +0100 Subject: [PATCH 12/14] dhcp4: filter bogus DNS/NTP server addresses silently if we receive a bogus lease with a DNS/NTP server within local scope let's politely ignore. Fixes: #4524 --- src/libsystemd-network/sd-dhcp-lease.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 8387b185c0..7fed55c5fc 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -383,6 +383,23 @@ static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) { return 0; } +static void filter_bogus_addresses(struct in_addr *addresses, size_t *n) { + size_t i, j; + + /* Silently filter DNS/NTP servers supplied to us that do not make outside of the local scope. */ + + for (i = 0, j = 0; i < *n; i ++) { + + if (in4_addr_is_null(addresses+i) || + in4_addr_is_localhost(addresses+i)) + continue; + + addresses[j++] = addresses[i]; + } + + *n = j; +} + static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) { assert(option); assert(ret); @@ -404,6 +421,8 @@ static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_add if (!addresses) return -ENOMEM; + filter_bogus_addresses(addresses, &n_addresses); + free(*ret); *ret = addresses; *n_ret = n_addresses; From 640be8806e1ce366b9046a4828889515c98b72f9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 18 Nov 2016 17:31:49 +0100 Subject: [PATCH 13/14] man: make /etc/nsswitch.conf documentation for nss-resolve match example Fixes: #4683 --- man/nss-resolve.xml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/man/nss-resolve.xml b/man/nss-resolve.xml index 9f24f65019..4e102cec26 100644 --- a/man/nss-resolve.xml +++ b/man/nss-resolve.xml @@ -63,16 +63,13 @@ hostnames via DNS. To activate the NSS module, add resolve to the line starting with - hosts: in /etc/nsswitch.conf. - - It is recommended to place resolve early in /etc/nsswitch.conf' - hosts: line (but after the files or mymachines entries), - replacing the dns entry if it exists, to ensure DNS queries are always routed via - systemd-resolved8. - - Note that nss-resolve will chain-load nss-dns if - systemd-resolved.service is not running, ensuring that basic DNS resolution continues to work - if the service is down. + hosts: in /etc/nsswitch.conf. Specifcally, it is recommended to place + resolve early in /etc/nsswitch.conf' hosts: line (but + after the files or mymachines entries), right before the + dns entry if it exists, followed by [!UNAVAIL=return], to ensure DNS queries + are always routed via + systemd-resolved8 if it is + running, but are routed to nss-dns if this service is not available. @@ -94,9 +91,6 @@ ethers: db files rpc: db files netgroup: nis - - This keeps the dns module as a fallback for cases where the nss-resolve - module is not installed. From ef8b0084552e05f28b9132d5dfc75edae164a991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 19 Nov 2016 12:53:29 -0500 Subject: [PATCH 14/14] sd-dhcp-client: use free_and_strdup This changes the return value a bit: 1 will be returned if the value is changed. But the return value was not documented, and the change should be for the good anyway. Current callers don't care. --- src/libsystemd-network/sd-dhcp-client.c | 34 +++---------------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 9716168c1e..1423264806 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -386,49 +386,23 @@ int sd_dhcp_client_set_hostname( sd_dhcp_client *client, const char *hostname) { - char *new_hostname = NULL; - assert_return(client, -EINVAL); - if (!hostname) { - client->hostname = mfree(client->hostname); - return 0; - } - /* Refuse hostnames that neither qualify as DNS nor as Linux hosntames */ - if (!hostname_is_valid(hostname, false) && !dns_name_is_valid(hostname)) + if (hostname && + !(hostname_is_valid(hostname, false) || dns_name_is_valid(hostname) > 0)) return -EINVAL; - if (streq_ptr(client->hostname, hostname)) - return 0; - - new_hostname = strdup(hostname); - if (!new_hostname) - return -ENOMEM; - - free(client->hostname); - client->hostname = new_hostname; - - return 0; + return free_and_strdup(&client->hostname, hostname); } int sd_dhcp_client_set_vendor_class_identifier( sd_dhcp_client *client, const char *vci) { - char *new_vci = NULL; - assert_return(client, -EINVAL); - new_vci = strdup(vci); - if (!new_vci) - return -ENOMEM; - - free(client->vendor_class_identifier); - - client->vendor_class_identifier = new_vci; - - return 0; + return free_and_strdup(&client->vendor_class_identifier, vci); } int sd_dhcp_client_set_client_port(