diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 535a23f500..338bee6c38 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -269,6 +269,31 @@ in use. + + DNSStubListenerExtra= + Takes an IPv4 or IPv6 address to listen on. The address may optionally be prefixed by : and + a protocol name (udp or tcp). When an IPv6 address is specified with a port number, then the + address must be in the square brackets. This option can be specified multiple times. If an empty string is assigned, then the all + previous assignments are cleared. It may also be optionally suffixed by a numeric port number with separator :. + If the protocol is not specified, the service will listen on both UDP and TCP. If the port is not + specified, then the service takes port as 53. This option may be used multiple times. Note that this is independent of the + primary DNS stub configured with DNSStubListener=, and only configures additional + sockets to listen on. Defaults to unset. + + If the string in the format udp:[x]:y, + it is read as protocol udp and IPv6 address x on a port y. + + If the string in the format [x]:y, it is read as both protocol + udp and tcp, IPv6 address x on a port y. + + If the string in the format x, it is read as protocol both udp and + tcp, IPv6/IPv4 address x on a port 53. + + If the string in the format udp:x:y, + it is read as protocol udp and IPv4 address x on a port y. + + + ReadEtcHosts= Takes a boolean argument. If yes (the default), diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 6b99271245..3b2b4d2063 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -10,11 +10,13 @@ #include "resolved-dnssd.h" #include "resolved-manager.h" #include "resolved-dns-search-domain.h" +#include "resolved-dns-stub.h" #include "dns-domain.h" #include "socket-netlink.h" #include "specifier.h" #include "string-table.h" #include "string-util.h" +#include "strv.h" #include "utf8.h" DEFINE_CONFIG_PARSE_ENUM(config_parse_dns_stub_listener_mode, dns_stub_listener_mode, DnsStubListenerMode, "Failed to parse DNS stub listener mode setting"); @@ -27,6 +29,51 @@ static const char* const dns_stub_listener_mode_table[_DNS_STUB_LISTENER_MODE_MA }; DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_stub_listener_mode, DnsStubListenerMode, DNS_STUB_LISTENER_YES); +static void dns_stub_listener_extra_hash_func(const DNSStubListenerExtra *a, struct siphash *state) { + unsigned port; + + assert(a); + + siphash24_compress(&a->mode, sizeof(a->mode), state); + siphash24_compress(&socket_address_family(&a->address), sizeof(a->address.type), state); + siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(socket_address_family(&a->address)), state); + + (void) sockaddr_port(&a->address.sockaddr.sa, &port); + siphash24_compress(&port, sizeof(port), state); +} + +static int dns_stub_listener_extra_compare_func(const DNSStubListenerExtra *a, const DNSStubListenerExtra *b) { + unsigned p, q; + int r; + + assert(a); + assert(b); + + r = CMP(a->mode, b->mode); + if (r != 0) + return r; + + r = CMP(socket_address_family(&a->address), socket_address_family(&b->address)); + if (r != 0) + return r; + + r = memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(socket_address_family(&a->address))); + if (r != 0) + return r; + + (void) sockaddr_port(&a->address.sockaddr.sa, &p); + (void) sockaddr_port(&b->address.sockaddr.sa, &q); + + return CMP(p, q); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + dns_stub_listener_extra_hash_ops, + DNSStubListenerExtra, + dns_stub_listener_extra_hash_func, + dns_stub_listener_extra_compare_func, + free); + static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { _cleanup_free_ char *server_name = NULL; union in_addr_union address; @@ -385,6 +432,111 @@ int config_parse_dnssd_txt(const char *unit, const char *filename, unsigned line return 0; } +int config_parse_dns_stub_listener_extra( + 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) { + + _cleanup_free_ DNSStubListenerExtra *udp = NULL, *tcp = NULL; + _cleanup_free_ char *word = NULL; + Manager *m = userdata; + bool both = false; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners); + return 0; + } + + p = rvalue; + r = extract_first_word(&p, &word, ":", 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid DNSStubListenExtra='%s', ignoring assignment", rvalue); + return 0; + } + + /* First look for udp/tcp. If not specified then turn both TCP and UDP */ + if (!STR_IN_SET(word, "tcp", "udp")) { + both = true; + p = rvalue; + } + + if (streq(word, "tcp") || both) { + r = dns_stub_extra_new(&tcp); + if (r < 0) + return log_oom(); + + tcp->mode = DNS_STUB_LISTENER_TCP; + } + + if (streq(word, "udp") || both) { + r = dns_stub_extra_new(&udp); + if (r < 0) + return log_oom(); + + udp->mode = DNS_STUB_LISTENER_UDP; + } + + if (tcp) { + r = socket_addr_port_from_string_auto(p, 53, &tcp->address); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address in DNSStubListenExtra='%s', ignoring", rvalue); + return 0; + } + } + + if (udp) { + r = socket_addr_port_from_string_auto(p, 53, &udp->address); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address in DNSStubListenExtra='%s', ignoring", rvalue); + return 0; + } + } + + if (tcp) { + r = ordered_set_ensure_put(&m->dns_extra_stub_listeners, &dns_stub_listener_extra_hash_ops, tcp); + if (r < 0) { + if (r == -ENOMEM) + return log_oom(); + + log_warning_errno(r, "Failed to store TCP DNSStubListenExtra='%s', ignoring assignment: %m", rvalue); + return 0; + } + } + + if (udp) { + r = ordered_set_ensure_put(&m->dns_extra_stub_listeners, &dns_stub_listener_extra_hash_ops, udp); + if (r < 0) { + if (r == -ENOMEM) + return log_oom(); + + log_warning_errno(r, "Failed to store UDP DNSStubListenExtra='%s', ignoring assignment: %m", rvalue); + return 0; + } + } + + TAKE_PTR(tcp); + TAKE_PTR(udp); + + return 0; +} + int manager_parse_config_file(Manager *m) { int r; diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index ac3937cfae..f8d16b5a8e 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -30,6 +30,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_mode); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_name); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_type); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_txt); +CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_extra); const char* dns_stub_listener_mode_to_string(DnsStubListenerMode p) _const_; DnsStubListenerMode dns_stub_listener_mode_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 03edbe26dc..9cb8797458 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -4,6 +4,7 @@ #include "fd-util.h" #include "missing_network.h" #include "resolved-dns-stub.h" +#include "socket-netlink.h" #include "socket-util.h" /* The MTU of the loopback device is 64K on Linux, advertise that as maximum datagram size, but subtract the Ethernet, @@ -13,6 +14,22 @@ static int manager_dns_stub_udp_fd(Manager *m); static int manager_dns_stub_tcp_fd(Manager *m); +int dns_stub_extra_new(DNSStubListenerExtra **ret) { + DNSStubListenerExtra *l; + + l = new(DNSStubListenerExtra, 1); + if (!l) + return -ENOMEM; + + *l = (DNSStubListenerExtra) { + .fd = -1, + }; + + *ret = TAKE_PTR(l); + + return 0; +} + static int dns_stub_make_reply_packet( DnsPacket **p, size_t max_size, @@ -386,6 +403,22 @@ static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void return 0; } +static int set_dns_stub_common_socket_options(int fd) { + int r; + + assert(fd >= 0); + + r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + + return setsockopt_int(fd, IPPROTO_IP, IP_RECVTTL, true); +} + static int manager_dns_stub_udp_fd(Manager *m) { union sockaddr_union sa = { .in.sin_family = AF_INET, @@ -402,15 +435,7 @@ static int manager_dns_stub_udp_fd(Manager *m) { if (fd < 0) return -errno; - r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); - if (r < 0) - return r; - - r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); - if (r < 0) - return r; - - r = setsockopt_int(fd, IPPROTO_IP, IP_RECVTTL, true); + r = set_dns_stub_common_socket_options(fd); if (r < 0) return r; @@ -431,6 +456,64 @@ static int manager_dns_stub_udp_fd(Manager *m) { return m->dns_stub_udp_fd = TAKE_FD(fd); } +static int manager_dns_stub_udp_fd_extra(Manager *m, DNSStubListenerExtra *l) { + _cleanup_free_ char *pretty = NULL; + _cleanup_close_ int fd = -1; + int r; + + if (l->fd >= 0) + return 0; + + fd = socket(socket_address_family(&l->address), SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + r = -errno; + goto fail; + } + + r = setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, true); + if (r < 0) + goto fail; + + r = set_dns_stub_common_socket_options(fd); + if (r < 0) + goto fail; + + if (bind(fd, &l->address.sockaddr.sa, l->address.size) < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &l->dns_stub_extra_event_source, fd, EPOLLIN, on_dns_stub_packet, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(l->dns_stub_extra_event_source, "dns-stub-udp-extra"); + + l->fd = TAKE_FD(fd); + + if (DEBUG_LOGGING) { + (void) sockaddr_pretty(&l->address.sockaddr.sa, FAMILY_ADDRESS_SIZE(l->address.sockaddr.sa.sa_family), true, true, &pretty); + log_debug("Listening on UDP socket %s.", strnull(pretty)); + } + + return 0; + + fail: + (void) sockaddr_pretty(&l->address.sockaddr.sa, FAMILY_ADDRESS_SIZE(l->address.sockaddr.sa.sa_family), true, true, &pretty); + if (r == -EADDRINUSE) + return log_warning_errno(r, + "Another process is already listening on UDP socket %s.\n" + "Turning off local DNS stub extra support.", strnull(pretty)); + if (r == -EPERM) + return log_warning_errno(r, + "Failed to listen on UDP socket %s: %m.\n" + "Turning off local DNS stub extra support.", strnull(pretty)); + + assert(r < 0); + + return log_warning_errno(r, "Failed to listen on UDP socket %s, ignoring: %m", strnull(pretty)); +} + static int on_dns_stub_stream_packet(DnsStream *s) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; @@ -492,22 +575,14 @@ static int manager_dns_stub_tcp_fd(Manager *m) { if (fd < 0) return -errno; + r = set_dns_stub_common_socket_options(fd); + if (r < 0) + return r; + r = setsockopt_int(fd, IPPROTO_IP, IP_TTL, true); if (r < 0) return r; - r = setsockopt_int(fd, SOL_SOCKET, SO_REUSEADDR, true); - if (r < 0) - return r; - - r = setsockopt_int(fd, IPPROTO_IP, IP_PKTINFO, true); - if (r < 0) - return r; - - r = setsockopt_int(fd, IPPROTO_IP, IP_RECVTTL, true); - if (r < 0) - return r; - /* Make sure no traffic from outside the local host can leak to onto this socket */ r = socket_bind_to_ifindex(fd, LOOPBACK_IFINDEX); if (r < 0) @@ -528,6 +603,73 @@ static int manager_dns_stub_tcp_fd(Manager *m) { return m->dns_stub_tcp_fd = TAKE_FD(fd); } +static int manager_dns_stub_tcp_fd_extra(Manager *m, DNSStubListenerExtra *l) { + _cleanup_free_ char *pretty = NULL; + _cleanup_close_ int fd = -1; + int r; + + if (l->fd >= 0) + return 0; + + fd = socket(socket_address_family(&l->address), SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + r = -errno; + goto fail; + } + + r = set_dns_stub_common_socket_options(fd); + if (r < 0) + goto fail; + + r = setsockopt_int(fd, IPPROTO_IP, IP_TTL, true); + if (r < 0) + goto fail; + + r = setsockopt_int(fd, IPPROTO_IP, IP_FREEBIND, true); + if (r < 0) + goto fail; + + if (bind(fd, &l->address.sockaddr.sa, l->address.size) < 0) { + r = -errno; + goto fail; + } + + if (listen(fd, SOMAXCONN) < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &l->dns_stub_extra_event_source, fd, EPOLLIN, on_dns_stub_packet, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(l->dns_stub_extra_event_source, "dns-stub-tcp-extra"); + + l->fd = TAKE_FD(fd); + + if (DEBUG_LOGGING) { + (void) sockaddr_pretty(&l->address.sockaddr.sa, FAMILY_ADDRESS_SIZE(l->address.sockaddr.sa.sa_family), true, true, &pretty); + log_debug("Listening on TCP socket %s.", strnull(pretty)); + } + + return 0; + + fail: + (void) sockaddr_pretty(&l->address.sockaddr.sa, FAMILY_ADDRESS_SIZE(l->address.sockaddr.sa.sa_family), true, true, &pretty); + if (r == -EADDRINUSE) + return log_warning_errno(r, + "Another process is already listening on TCP socket %s.\n" + "Turning off local DNS stub extra support.", strnull(pretty)); + if (r == -EPERM) + return log_warning_errno(r, + "Failed to listen on TCP socket %s: %m.\n" + "Turning off local DNS stub extra support.", strnull(pretty)); + + assert(r < 0); + + return log_warning_errno(r, "Failed to listen on TCP socket %s, ignoring: %m", strnull(pretty)); +} + int manager_dns_stub_start(Manager *m) { const char *t = "UDP"; int r = 0; @@ -564,6 +706,22 @@ int manager_dns_stub_start(Manager *m) { } else if (r < 0) return log_error_errno(r, "Failed to listen on %s socket 127.0.0.53:53: %m", t); + if (!ordered_set_isempty(m->dns_extra_stub_listeners)) { + DNSStubListenerExtra *l; + Iterator i; + + log_debug("Creating stub listener extra using %s.", + m->dns_stub_listener_mode == DNS_STUB_LISTENER_UDP ? "UDP" : + m->dns_stub_listener_mode == DNS_STUB_LISTENER_TCP ? "TCP" : + "UDP/TCP"); + + ORDERED_SET_FOREACH(l, m->dns_extra_stub_listeners, i) + if (l->mode == DNS_STUB_LISTENER_UDP) + (void) manager_dns_stub_udp_fd_extra(m, l); + else + (void) manager_dns_stub_tcp_fd_extra(m, l); + } + return 0; } @@ -576,3 +734,15 @@ void manager_dns_stub_stop(Manager *m) { m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd); m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd); } + +void manager_dns_stub_stop_extra(Manager *m) { + DNSStubListenerExtra *l; + Iterator i; + + assert(m); + + ORDERED_SET_FOREACH(l, m->dns_extra_stub_listeners, i) { + l->dns_stub_extra_event_source = sd_event_source_unref(l->dns_stub_extra_event_source); + l->fd = safe_close(l->fd); + } +} diff --git a/src/resolve/resolved-dns-stub.h b/src/resolve/resolved-dns-stub.h index f34e9db1af..e589df3831 100644 --- a/src/resolve/resolved-dns-stub.h +++ b/src/resolve/resolved-dns-stub.h @@ -3,5 +3,8 @@ #include "resolved-manager.h" +int dns_stub_extra_new(DNSStubListenerExtra **ret); + void manager_dns_stub_stop(Manager *m); +void manager_dns_stub_stop_extra(Manager *m); int manager_dns_stub_start(Manager *m); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf index 553da8d251..b54fa1ba99 100644 --- a/src/resolve/resolved-gperf.gperf +++ b/src/resolve/resolved-gperf.gperf @@ -18,14 +18,15 @@ struct ConfigPerfItem; %struct-type %includes %% -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_resolve_support, 0, offsetof(Manager, llmnr_support) -Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) -Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) -Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Manager, dns_over_tls_mode) -Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache) -Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) -Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts) -Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label) +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_resolve_support, 0, offsetof(Manager, llmnr_support) +Resolve.MulticastDNS, config_parse_resolve_support, 0, offsetof(Manager, mdns_support) +Resolve.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Manager, dnssec_mode) +Resolve.DNSOverTLS, config_parse_dns_over_tls_mode, 0, offsetof(Manager, dns_over_tls_mode) +Resolve.Cache, config_parse_dns_cache_mode, DNS_CACHE_MODE_YES, offsetof(Manager, enable_cache) +Resolve.DNSStubListener, config_parse_dns_stub_listener_mode, 0, offsetof(Manager, dns_stub_listener_mode) +Resolve.ReadEtcHosts, config_parse_bool, 0, offsetof(Manager, read_etc_hosts) +Resolve.ResolveUnicastSingleLabel, config_parse_bool, 0, offsetof(Manager, resolve_unicast_single_label) +Resolve.DNSStubListenerExtra, config_parse_dns_stub_listener_extra, 0, offsetof(Manager, dns_extra_stub_listeners) diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index 17bdb67ed5..5c09de0c34 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -701,6 +701,7 @@ Manager *manager_free(Manager *m) { hashmap_free(m->links); hashmap_free(m->dns_transactions); + ordered_set_free(m->dns_extra_stub_listeners); sd_event_source_unref(m->network_event_source); sd_network_monitor_unref(m->network_monitor); @@ -713,6 +714,8 @@ Manager *manager_free(Manager *m) { manager_dns_stub_stop(m); manager_varlink_done(m); + manager_dns_stub_stop_extra(m); + bus_verify_polkit_async_registry_free(m->polkit_registry); sd_bus_flush_close_unref(m->bus); diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 390fac4a52..a3a2e08123 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -31,6 +31,14 @@ typedef struct EtcHosts { Set *no_address; } EtcHosts; +typedef struct DNSStubListenerExtra { + int fd; + + DnsStubListenerMode mode; + SocketAddress address; + sd_event_source *dns_stub_extra_event_source; +} DNSStubListenerExtra; + struct Manager { sd_event *event; @@ -137,6 +145,8 @@ struct Manager { int dns_stub_udp_fd; int dns_stub_tcp_fd; + OrderedSet *dns_extra_stub_listeners; + sd_event_source *dns_stub_udp_event_source; sd_event_source *dns_stub_tcp_event_source; diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index 224ccffb92..21a04cfba7 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -385,6 +385,7 @@ DNSLifetimeSec= DNSSEC= DNSSECNegativeTrustAnchors= DNSStubListener= +DNSStubListenerExtra= DUIDRawData= DUIDType= DefaultLeaseTimeSec=