resolve: allow configurable bind address

This commit is contained in:
Susant Sahani 2020-08-29 07:12:10 +00:00
parent 1f1f3210c9
commit 1f05101fb6
9 changed files with 398 additions and 32 deletions

View File

@ -269,6 +269,31 @@
in use.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>DNSStubListenerExtra=</varname></term>
<listitem><para>Takes an IPv4 or IPv6 address to listen on. The address may optionally be prefixed by <literal>:</literal> and
a protocol name (<literal>udp</literal> or <literal>tcp</literal>). 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 <literal>:</literal>.
If the protocol is not specified, the service will listen on both <literal>UDP</literal> and <literal>TCP</literal>. 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 <varname>DNSStubListener=</varname>, and only configures <emphasis>additional</emphasis>
sockets to listen on. Defaults to unset.</para>
<para>If the string in the format <literal>udp</literal>:[<replaceable>x</replaceable>]:<replaceable>y</replaceable>,
it is read as protocol <literal>udp</literal> and IPv6 address x on a port y.</para>
<para>If the string in the format [<replaceable>x</replaceable>]:<replaceable>y</replaceable>, it is read as both protocol
<literal>udp</literal> and <literal>tcp</literal>, IPv6 address x on a port y.</para>
<para>If the string in the format <replaceable>x</replaceable>, it is read as protocol both <literal>udp</literal> and
<literal>tcp</literal>, IPv6/IPv4 address x on a port 53.</para>
<para>If the string in the format <literal>udp</literal>:<replaceable>x</replaceable>:<replaceable>y</replaceable>,
it is read as protocol <literal>udp</literal> and IPv4 address x on a port y.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>ReadEtcHosts=</varname></term>
<listitem><para>Takes a boolean argument. If <literal>yes</literal> (the default),

View File

@ -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;

View File

@ -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_;

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -385,6 +385,7 @@ DNSLifetimeSec=
DNSSEC=
DNSSECNegativeTrustAnchors=
DNSStubListener=
DNSStubListenerExtra=
DUIDRawData=
DUIDType=
DefaultLeaseTimeSec=