diff --git a/src/basic/meson.build b/src/basic/meson.build index 31625b1785..a025f1403e 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -192,6 +192,8 @@ basic_sources = files(''' strv.h strxcpyx.c strxcpyx.h + sysctl-util.c + sysctl-util.h syslog-util.c syslog-util.h terminal-util.c diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 7f8066123b..5915677af9 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -29,6 +29,7 @@ #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "sysctl-util.h" #include "user-util.h" #include "utf8.h" #include "util.h" @@ -487,10 +488,48 @@ const char* socket_address_get_path(const SocketAddress *a) { } bool socket_ipv6_is_supported(void) { - if (access("/proc/net/if_inet6", F_OK) != 0) + static int cached = -1; + + if (cached < 0) { + + if (access("/proc/net/if_inet6", F_OK) < 0) { + + if (errno != ENOENT) { + log_debug_errno(errno, "Unexpected error when checking whether /proc/net/if_inet6 exists: %m"); + return false; + } + + cached = false; + } else + cached = true; + } + + return cached; +} + +bool socket_ipv6_is_enabled(void) { + _cleanup_free_ char *v = NULL; + int r; + + /* Much like socket_ipv6_is_supported(), but also checks that the sysctl that disables IPv6 on all + * interfaces isn't turned on */ + + if (!socket_ipv6_is_supported()) return false; - return true; + r = sysctl_read_ip_property(AF_INET6, "all", "disable_ipv6", &v); + if (r < 0) { + log_debug_errno(r, "Unexpected error reading 'net.ipv6.conf.all.disable_ipv6' sysctl: %m"); + return true; + } + + r = parse_boolean(v); + if (r < 0) { + log_debug_errno(r, "Failed to pare 'net.ipv6.conf.all.disable_ipv6' sysctl: %m"); + return true; + } + + return !r; } bool socket_address_matches_fd(const SocketAddress *a, int fd) { diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 30baba6c03..dcf00bf316 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -99,6 +99,7 @@ bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_ const char* socket_address_get_path(const SocketAddress *a); bool socket_ipv6_is_supported(void); +bool socket_ipv6_is_enabled(void); int sockaddr_port(const struct sockaddr *_sa, unsigned *port); diff --git a/src/shared/sysctl-util.c b/src/basic/sysctl-util.c similarity index 66% rename from src/shared/sysctl-util.c rename to src/basic/sysctl-util.c index 326652273c..62d450a84b 100644 --- a/src/shared/sysctl-util.c +++ b/src/basic/sysctl-util.c @@ -10,6 +10,7 @@ #include "fileio.h" #include "log.h" #include "macro.h" +#include "alloc-util.h" #include "string-util.h" #include "sysctl-util.h" @@ -60,12 +61,34 @@ int sysctl_write(const char *property, const char *value) { return 0; } -int sysctl_read(const char *property, char **content) { +int sysctl_read(const char *property, char **ret) { char *p; assert(property); - assert(content); + assert(ret); p = strjoina("/proc/sys/", property); - return read_full_file(p, content, NULL); + return read_full_virtual_file(p, ret, NULL); +} + +int sysctl_read_ip_property(int af, const char *ifname, const char *property, char **ret) { + _cleanup_free_ char *value = NULL; + const char *p; + int r; + + assert(IN_SET(af, AF_INET, AF_INET6)); + assert(property); + + p = strjoina("/proc/sys/net/ipv", af == AF_INET ? "4" : "6", + ifname ? "/conf/" : "", strempty(ifname), + property[0] == '/' ? "" : "/", property); + + r = read_one_line_file(p, &value); + if (r < 0) + return r; + + if (ret) + *ret = TAKE_PTR(value); + + return r; } diff --git a/src/shared/sysctl-util.h b/src/basic/sysctl-util.h similarity index 68% rename from src/shared/sysctl-util.h rename to src/basic/sysctl-util.h index fd7c78b2b8..22f52f8421 100644 --- a/src/shared/sysctl-util.h +++ b/src/basic/sysctl-util.h @@ -5,3 +5,4 @@ char *sysctl_normalize(char *s); int sysctl_read(const char *property, char **value); int sysctl_write(const char *property, const char *value); +int sysctl_read_ip_property(int af, const char *ifname, const char *property, char **ret); diff --git a/src/nss-myhostname/nss-myhostname.c b/src/nss-myhostname/nss-myhostname.c index e491351dee..1590a6722d 100644 --- a/src/nss-myhostname/nss-myhostname.c +++ b/src/nss-myhostname/nss-myhostname.c @@ -13,6 +13,7 @@ #include "macro.h" #include "nss-util.h" #include "signal-util.h" +#include "socket-util.h" #include "string-util.h" #include "util.h" @@ -64,10 +65,8 @@ enum nss_status _nss_myhostname_gethostbyname4_r( } else if (is_gateway_hostname(name)) { n_addresses = local_gateways(NULL, 0, AF_UNSPEC, &addresses); - if (n_addresses <= 0) { - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } + if (n_addresses <= 0) + goto not_found; canonical = "_gateway"; @@ -81,10 +80,8 @@ enum nss_status _nss_myhostname_gethostbyname4_r( } /* We respond to our local host name, our hostname suffixed with a single dot. */ - if (!streq(name, hn) && !streq_ptr(startswith(name, hn), ".")) { - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } + if (!streq(name, hn) && !streq_ptr(startswith(name, hn), ".")) + goto not_found; n_addresses = local_addresses(NULL, 0, AF_UNSPEC, &addresses); if (n_addresses < 0) @@ -95,7 +92,7 @@ enum nss_status _nss_myhostname_gethostbyname4_r( } l = strlen(canonical); - ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * (n_addresses > 0 ? n_addresses : 2); + ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * (n_addresses > 0 ? n_addresses : 1 + socket_ipv6_is_enabled()); if (buflen < ms) { UNPROTECT_ERRNO; *errnop = ERANGE; @@ -111,15 +108,17 @@ enum nss_status _nss_myhostname_gethostbyname4_r( assert(n_addresses >= 0); if (n_addresses == 0) { /* Second, fill in IPv6 tuple */ - r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = r_tuple_prev; - r_tuple->name = r_name; - r_tuple->family = AF_INET6; - memcpy(r_tuple->addr, LOCALADDRESS_IPV6, 16); - r_tuple->scopeid = 0; - - idx += ALIGN(sizeof(struct gaih_addrtuple)); - r_tuple_prev = r_tuple; + if (socket_ipv6_is_enabled()) { + r_tuple = (struct gaih_addrtuple*) (buffer + idx); + r_tuple->next = r_tuple_prev; + r_tuple->name = r_name; + r_tuple->family = AF_INET6; + memcpy(r_tuple->addr, LOCALADDRESS_IPV6, 16); + r_tuple->scopeid = 0; + + idx += ALIGN(sizeof(struct gaih_addrtuple)); + r_tuple_prev = r_tuple; + } /* Third, fill in IPv4 tuple */ r_tuple = (struct gaih_addrtuple*) (buffer + idx); @@ -164,6 +163,10 @@ enum nss_status _nss_myhostname_gethostbyname4_r( h_errno = 0; return NSS_STATUS_SUCCESS; + +not_found: + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; } static enum nss_status fill_in_hostent( @@ -183,6 +186,7 @@ static enum nss_status fill_in_hostent( unsigned n, c; assert(canonical); + assert(IN_SET(af, AF_INET, AF_INET6)); assert(result); assert(buffer); assert(errnop); @@ -202,8 +206,8 @@ static enum nss_status fill_in_hostent( (additional ? ALIGN(l_additional+1) : 0) + sizeof(char*) + (additional ? sizeof(char*) : 0) + - (c > 0 ? c : 1) * ALIGN(alen) + - (c > 0 ? c+1 : 2) * sizeof(char*); + (c > 0 ? c : af == AF_INET ? 1 : socket_ipv6_is_enabled()) * ALIGN(alen) + + (c > 0 ? c+1 : af == AF_INET ? 2 : (unsigned) socket_ipv6_is_enabled() + 1) * sizeof(char*); if (buflen < ms) { UNPROTECT_ERRNO; @@ -249,12 +253,12 @@ static enum nss_status fill_in_hostent( assert(i == c); idx += c*ALIGN(alen); - } else { - if (af == AF_INET) - *(uint32_t*) r_addr = local_address_ipv4; - else - memcpy(r_addr, LOCALADDRESS_IPV6, 16); + } else if (af == AF_INET) { + *(uint32_t*) r_addr = local_address_ipv4; + idx += ALIGN(alen); + } else if (socket_ipv6_is_enabled()) { + memcpy(r_addr, LOCALADDRESS_IPV6, 16); idx += ALIGN(alen); } @@ -269,10 +273,13 @@ static enum nss_status fill_in_hostent( ((char**) r_addr_list)[i] = NULL; idx += (c+1) * sizeof(char*); - } else { + } else if (af == AF_INET || socket_ipv6_is_enabled()) { ((char**) r_addr_list)[0] = r_addr; ((char**) r_addr_list)[1] = NULL; idx += 2 * sizeof(char*); + } else { + ((char**) r_addr_list)[0] = NULL; + idx += sizeof(char*); } /* Verify the size matches */ @@ -333,16 +340,17 @@ enum nss_status _nss_myhostname_gethostbyname3_r( } if (is_localhost(name)) { + if (af == AF_INET6 && !socket_ipv6_is_enabled()) + goto not_found; + canonical = "localhost"; local_address_ipv4 = htobe32(INADDR_LOOPBACK); } else if (is_gateway_hostname(name)) { n_addresses = local_gateways(NULL, 0, af, &addresses); - if (n_addresses <= 0) { - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } + if (n_addresses <= 0) + goto not_found; canonical = "_gateway"; @@ -355,10 +363,8 @@ enum nss_status _nss_myhostname_gethostbyname3_r( return NSS_STATUS_TRYAGAIN; } - if (!streq(name, hn) && !streq_ptr(startswith(name, hn), ".")) { - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } + if (!streq(name, hn) && !streq_ptr(startswith(name, hn), ".")) + goto not_found; n_addresses = local_addresses(NULL, 0, af, &addresses); if (n_addresses < 0) @@ -381,6 +387,10 @@ enum nss_status _nss_myhostname_gethostbyname3_r( errnop, h_errnop, ttlp, canonp); + +not_found: + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; } enum nss_status _nss_myhostname_gethostbyaddr2_r( @@ -436,6 +446,9 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r( } else { assert(af == AF_INET6); + if (socket_ipv6_is_enabled()) + goto not_found; + if (memcmp(addr, LOCALADDRESS_IPV6, 16) == 0) { canonical = "localhost"; additional_from_hostname = true; @@ -443,28 +456,21 @@ enum nss_status _nss_myhostname_gethostbyaddr2_r( } } - n_addresses = local_addresses(NULL, 0, AF_UNSPEC, &addresses); - for (a = addresses, n = 0; (int) n < n_addresses; n++, a++) { - if (af != a->family) - continue; - + n_addresses = local_addresses(NULL, 0, af, &addresses); + for (a = addresses, n = 0; (int) n < n_addresses; n++, a++) if (memcmp(addr, &a->address, FAMILY_ADDRESS_SIZE(af)) == 0) goto found; - } addresses = mfree(addresses); - n_addresses = local_gateways(NULL, 0, AF_UNSPEC, &addresses); - for (a = addresses, n = 0; (int) n < n_addresses; n++, a++) { - if (af != a->family) - continue; - + n_addresses = local_gateways(NULL, 0, af, &addresses); + for (a = addresses, n = 0; (int) n < n_addresses; n++, a++) if (memcmp(addr, &a->address, FAMILY_ADDRESS_SIZE(af)) == 0) { canonical = "_gateway"; goto found; } - } +not_found: *h_errnop = HOST_NOT_FOUND; return NSS_STATUS_NOTFOUND; diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c index 68fb3b9eac..35be02ddf7 100644 --- a/src/resolve/resolved-dns-question.c +++ b/src/resolve/resolved-dns-question.c @@ -4,6 +4,7 @@ #include "dns-domain.h" #include "dns-type.h" #include "resolved-dns-question.h" +#include "socket-util.h" DnsQuestion *dns_question_new(size_t n) { DnsQuestion *q; @@ -287,6 +288,11 @@ int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bo if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) return -EAFNOSUPPORT; + /* If IPv6 is off and the request has an unspecified lookup family, restrict it automatically to + * IPv4. */ + if (family == AF_UNSPEC && !socket_ipv6_is_enabled()) + family = AF_INET; + if (convert_idna) { r = dns_name_apply_idna(name, &buf); if (r < 0) diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index 9a8ce7ae2d..6c1358fa9b 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -80,7 +80,7 @@ static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int if return r; } - if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { + if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY) && socket_ipv6_is_enabled()) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, dns_resource_key_name(key)); @@ -233,7 +233,7 @@ static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, .address.in.s_addr = htobe32(0x7F000002), }; - if (IN_SET(af, AF_INET6, AF_UNSPEC)) + if (IN_SET(af, AF_INET6, AF_UNSPEC) && socket_ipv6_is_enabled()) buffer[n++] = (struct local_address) { .family = AF_INET6, .ifindex = dns_synthesize_ifindex(ifindex), diff --git a/src/shared/meson.build b/src/shared/meson.build index fed08571d1..2a40b07ef7 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -86,8 +86,6 @@ shared_sources = files(''' specifier.h switch-root.c switch-root.h - sysctl-util.c - sysctl-util.h tests.c tests.h tomoyo-util.c diff --git a/src/test/test-nss.c b/src/test/test-nss.c index e0e7bb300d..ad91a2ccea 100644 --- a/src/test/test-nss.c +++ b/src/test/test-nss.c @@ -7,6 +7,8 @@ #include "log.h" #include "nss-util.h" #include "path-util.h" +#include "socket-util.h" +#include "stdio-util.h" #include "string-util.h" #include "alloc-util.h" #include "in-addr-util.h" @@ -17,6 +19,7 @@ #include "errno-list.h" #include "hostname-util.h" #include "local-addresses.h" +#include "env-util.h" static const char* nss_status_to_string(enum nss_status status, char *buf, size_t buf_len) { switch (status) { @@ -170,9 +173,18 @@ static void test_gethostbyname4_r(void *handle, const char *module, const char * if (STR_IN_SET(module, "resolve", "mymachines") && status == NSS_STATUS_UNAVAIL) return; - if (STR_IN_SET(module, "myhostname", "resolve") && streq(name, "localhost")) { - assert_se(status == NSS_STATUS_SUCCESS); - assert_se(n == 2); + if (streq(name, "localhost")) { + if (streq(module, "myhostname")) { + assert_se(status == NSS_STATUS_SUCCESS); + assert_se(n == socket_ipv6_is_enabled() + 1); + + } else if (streq(module, "resolve") && getenv_bool_secure("SYSTEMD_NSS_RESOLVE_SYNTHESIZE") != 0) { + assert_se(status == NSS_STATUS_SUCCESS); + if (socket_ipv6_is_enabled()) + assert_se(n == 2); + else + assert_se(n <= 2); /* Even if IPv6 is disabled, /etc/hosts may contain ::1. */ + } } } diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index c545622c09..6467816b08 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -697,6 +697,11 @@ static void test_send_emptydata(void) { assert_se(fd == -999); } +static void test_ipv6_enabled(void) { + log_info("IPv6 supported: %s", yes_no(socket_ipv6_is_supported())); + log_info("IPv6 enabled: %s", yes_no(socket_ipv6_is_enabled())); +} + int main(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); @@ -730,6 +735,7 @@ int main(int argc, char *argv[]) { test_receive_nopassfd(); test_send_nodata_nofd(); test_send_emptydata(); + test_ipv6_enabled(); return 0; }