diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml
index 535a23f500a..338bee6c38a 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 6b992712457..3b2b4d2063e 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 ac3937cfaee..f8d16b5a8ed 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 03edbe26dc0..9cb87974588 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 f34e9db1af1..e589df38319 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 553da8d2518..b54fa1ba99b 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 17bdb67ed56..5c09de0c348 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 390fac4a523..a3a2e08123c 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/src/shared/socket-netlink.c b/src/shared/socket-netlink.c
index e117459c10d..32e45985b42 100644
--- a/src/shared/socket-netlink.c
+++ b/src/shared/socket-netlink.c
@@ -235,6 +235,33 @@ int socket_address_parse_and_warn(SocketAddress *a, const char *s) {
return 0;
}
+int socket_addr_port_from_string_auto(const char *s, uint16_t default_port, SocketAddress *a) {
+ union in_addr_union address;
+ uint16_t port = 0;
+ int family, r;
+
+ assert(a);
+ assert(s);
+
+ r = in_addr_port_ifindex_name_from_string_auto(s, &family, &address, &port, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ if (family == AF_INET) {
+ memcpy(&a->sockaddr.in.sin_addr, &address.in.s_addr, sizeof(a->sockaddr.in.sin_addr));
+ a->sockaddr.in.sin_family = AF_INET;
+ a->size = sizeof(struct sockaddr_in);
+ a->sockaddr.in.sin_port = port ? htobe16(port) : htobe16(default_port);
+ } else {
+ memcpy(&a->sockaddr.in6.sin6_addr, &address.in6, sizeof(a->sockaddr.in6.sin6_addr));
+ a->sockaddr.in6.sin6_family = AF_INET6;
+ a->sockaddr.in6.sin6_port = port ? htobe16(port) : htobe16(default_port);
+ a->size = sizeof(struct sockaddr_in6);
+ }
+
+ return 0;
+}
+
int socket_address_parse_netlink(SocketAddress *a, const char *s) {
_cleanup_free_ char *word = NULL;
unsigned group = 0;
diff --git a/src/shared/socket-netlink.h b/src/shared/socket-netlink.h
index e6cd7d9bf19..143fc8d41a8 100644
--- a/src/shared/socket-netlink.h
+++ b/src/shared/socket-netlink.h
@@ -16,6 +16,7 @@ int make_socket_fd(int log_level, const char* address, int type, int flags);
int socket_address_parse(SocketAddress *a, const char *s);
int socket_address_parse_and_warn(SocketAddress *a, const char *s);
int socket_address_parse_netlink(SocketAddress *a, const char *s);
+int socket_addr_port_from_string_auto(const char *s, uint16_t default_port, SocketAddress *a);
bool socket_address_is(const SocketAddress *a, const char *s, int type);
bool socket_address_is_netlink(const SocketAddress *a, const char *s);
diff --git a/src/test/test-socket-netlink.c b/src/test/test-socket-netlink.c
index 0a2007f0b7e..4335d2eb6c5 100644
--- a/src/test/test-socket-netlink.c
+++ b/src/test/test-socket-netlink.c
@@ -213,6 +213,41 @@ static void test_socket_address_is_netlink(void) {
assert_se(!socket_address_is_netlink(&a, "route 1"));
}
+static void test_socket_addr_port_from_string_auto_one(const char *in, uint16_t port, int ret, int family, const char *expected) {
+ _cleanup_free_ char *out = NULL;
+ SocketAddress a;
+ int r;
+
+ r = socket_addr_port_from_string_auto(in, port, &a);
+ if (r >= 0)
+ assert_se(sockaddr_pretty(&a.sockaddr.sa, a.size, false, true, &out) >= 0);
+
+ log_info("\"%s\" → %s → \"%s\" (expect \"%s\")", in,
+ r >= 0 ? "✓" : "✗", empty_to_dash(out), r >= 0 ? expected ?: in : "-");
+ assert_se(r == ret);
+ if (r >= 0) {
+ assert_se(a.sockaddr.sa.sa_family == family);
+ assert_se(streq(out, expected ?: in));
+ }
+}
+
+static void test_socket_addr_port_from_string_auto(void) {
+ log_info("/* %s */", __func__);
+
+ test_socket_addr_port_from_string_auto_one("junk", 51, -EINVAL, 0, NULL);
+ test_socket_addr_port_from_string_auto_one("192.168.1.1", 53, 0, AF_INET, "192.168.1.1:53");
+ test_socket_addr_port_from_string_auto_one(".168.1.1", 53, -EINVAL, 0, NULL);
+ test_socket_addr_port_from_string_auto_one("989.168.1.1", 53, -EINVAL, 0, NULL);
+
+ test_socket_addr_port_from_string_auto_one("[::1]", 53, -EINVAL, 0, NULL);
+ test_socket_addr_port_from_string_auto_one("[::1]8888", 53, -EINVAL, 0, NULL);
+ test_socket_addr_port_from_string_auto_one("2001:db8:3c4d:15::1a2f:1a2b", 53, 0, AF_INET6, "[2001:db8:3c4d:15::1a2f:1a2b]:53");
+ test_socket_addr_port_from_string_auto_one("[2001:db8:3c4d:15::1a2f:1a2b]:2001", 53, 0, AF_INET6, "[2001:db8:3c4d:15::1a2f:1a2b]:2001");
+ test_socket_addr_port_from_string_auto_one("[::1]:0", 53, -EINVAL, 0, NULL);
+ test_socket_addr_port_from_string_auto_one("[::1]:65536", 53, -ERANGE, 0, NULL);
+ test_socket_addr_port_from_string_auto_one("[a:b:1]:8888", 53, -EINVAL, 0, NULL);
+}
+
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
@@ -222,6 +257,7 @@ int main(int argc, char *argv[]) {
test_socket_address_get_path();
test_socket_address_is();
test_socket_address_is_netlink();
+ test_socket_addr_port_from_string_auto();
return 0;
}
diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service
index 224ccffb929..21a04cfba7c 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=