mirror of
https://github.com/systemd/systemd.git
synced 2024-10-27 01:55:22 +03:00
resolved: add "proxy-only" stub on 127.0.0.54
This beefs up the DNS stub logic to listen on two IP addresses: 127.0.0.53 (as before) + 127.0.0.54 (new). When the latter is contact our stub will operate in "bypass" mode only, i.e we'll try to pass DNS requests as unmodified upstream as we can (and not do mDNS/LLMNR and such, also no DNSSEC validation – but we'll still do DNS-over-TLS wrapping). This is supposed to be useful for container environments or tethering: this stub could be exposed (via NAT redirect) to clients of this system and we'll try to stay out of the way with doing too much DNS magic ourselves, but still expose whatever the current DNS server is from upstream under a stable address/port. How to use this: # iptables -t nat -I PREROUTING -p udp -i <interface> --dport 53 -j DNAT --to 127.0.0.54:53 # echo 1 > /proc/sys/net/ipv4/conf/<interface>/route_localnet
This commit is contained in:
parent
c1b91f06b9
commit
a8d0906344
@ -265,11 +265,13 @@
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>DNSStubListener=</varname></term>
|
||||
<listitem><para>Takes a boolean argument or one of <literal>udp</literal> and <literal>tcp</literal>. If
|
||||
<literal>udp</literal>, a DNS stub resolver will listen for UDP requests on address 127.0.0.53
|
||||
port 53. If <literal>tcp</literal>, the stub will listen for TCP requests on the same address and port. If
|
||||
<literal>yes</literal> (the default), the stub listens for both UDP and TCP requests. If <literal>no</literal>, the stub
|
||||
listener is disabled.</para>
|
||||
<listitem><para>Takes a boolean argument or one of <literal>udp</literal> and
|
||||
<literal>tcp</literal>. If <literal>udp</literal>, a DNS stub resolver will listen for UDP requests
|
||||
on addresses 127.0.0.53 and 127.0.0.54, port 53. If <literal>tcp</literal>, the stub will listen for
|
||||
TCP requests on the same addresses and port. If <literal>yes</literal> (the default), the stub listens
|
||||
for both UDP and TCP requests. If <literal>no</literal>, the stub listener is disabled.</para>
|
||||
|
||||
<xi:include href="systemd-resolved.service.xml" xpointer="proxy-stub" />
|
||||
|
||||
<para>Note that the DNS stub listener is turned off implicitly when its listening address and port are already
|
||||
in use.</para></listitem>
|
||||
|
@ -59,12 +59,19 @@
|
||||
<command>systemd-resolved</command>.</para></listitem>
|
||||
|
||||
<listitem><para>Additionally, <command>systemd-resolved</command> provides a local DNS stub listener on
|
||||
IP address 127.0.0.53 on the local loopback interface. Programs issuing DNS requests directly,
|
||||
bypassing any local API may be directed to this stub, in order to connect them to
|
||||
the IP addresses 127.0.0.53 and 127.0.0.54 on the local loopback interface. Programs issuing DNS
|
||||
requests directly, bypassing any local API may be directed to this stub, in order to connect them to
|
||||
<command>systemd-resolved</command>. Note however that it is strongly recommended that local programs
|
||||
use the glibc NSS or bus APIs instead (as described above), as various network resolution concepts
|
||||
(such as link-local addressing, or LLMNR Unicode domains) cannot be mapped to the unicast DNS
|
||||
protocol.</para></listitem>
|
||||
protocol.</para>
|
||||
|
||||
<para id="proxy-stub">The DNS stub resolver on 127.0.0.53 provides the full feature set of the local
|
||||
resolver, which includes offering LLMNR/MulticastDNS resolution. The DNS stub resolver on 127.0.0.54
|
||||
provides a more limited resolver, that operates in "proxy" mode only, i.e. it will pass most DNS
|
||||
messages relatively unmodified to the current upstream DNS servers and back, but not try to process the
|
||||
messages locally, and hence does not validate DNSSEC, or offer up LLMNR/MulticastDNS. (It will
|
||||
translate to DNS-over-TLS communication if needed however.)</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>The DNS servers contacted are determined from the global settings in
|
||||
|
@ -35,7 +35,7 @@ static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, cons
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Silently filter out 0.0.0.0 and 127.0.0.53 (our own stub DNS listener) */
|
||||
/* Silently filter out 0.0.0.0, 127.0.0.53, 127.0.0.54 (our own stub DNS listener) */
|
||||
if (!dns_server_address_valid(family, &address))
|
||||
return 0;
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
#define ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX DNS_PACKET_UNICAST_SIZE_LARGE_MAX
|
||||
|
||||
static int manager_dns_stub_fd_extra(Manager *m, DnsStubListenerExtra *l, int type);
|
||||
static int manager_dns_stub_fd(Manager *m, int family, const union in_addr_union *listen_address, int type);
|
||||
|
||||
static void dns_stub_listener_extra_hash_func(const DnsStubListenerExtra *a, struct siphash *state) {
|
||||
assert(a);
|
||||
@ -483,6 +484,34 @@ static int dns_stub_finish_reply_packet(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool address_is_proxy(int family, const union in_addr_union *a) {
|
||||
assert(a);
|
||||
|
||||
/* Returns true if the specified address is the DNS "proxy" stub, i.e. where we unconditionally enable bypass mode */
|
||||
|
||||
if (family != AF_INET)
|
||||
return false;
|
||||
|
||||
return be32toh(a->in.s_addr) == INADDR_DNS_PROXY_STUB;
|
||||
}
|
||||
|
||||
static int find_socket_fd(
|
||||
Manager *m,
|
||||
DnsStubListenerExtra *l,
|
||||
int family,
|
||||
const union in_addr_union *listen_address,
|
||||
int type) {
|
||||
|
||||
assert(m);
|
||||
|
||||
/* Finds the right socket to use for sending. If we know the extra listener, otherwise go via the
|
||||
* address to send from */
|
||||
if (l)
|
||||
return manager_dns_stub_fd_extra(m, l, type);
|
||||
|
||||
return manager_dns_stub_fd(m, family, listen_address, type);
|
||||
}
|
||||
|
||||
static int dns_stub_send(
|
||||
Manager *m,
|
||||
DnsStubListenerExtra *l,
|
||||
@ -498,15 +527,22 @@ static int dns_stub_send(
|
||||
|
||||
if (s)
|
||||
r = dns_stream_write_packet(s, reply);
|
||||
else
|
||||
/* Note that it is essential here that we explicitly choose the source IP address for this packet. This
|
||||
* is because otherwise the kernel will choose it automatically based on the routing table and will
|
||||
* thus pick 127.0.0.1 rather than 127.0.0.53. */
|
||||
else {
|
||||
int fd;
|
||||
|
||||
fd = find_socket_fd(m, l, p->family, &p->sender, SOCK_DGRAM);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
/* Note that it is essential here that we explicitly choose the source IP address for this
|
||||
* packet. This is because otherwise the kernel will choose it automatically based on the
|
||||
* routing table and will thus pick 127.0.0.1 rather than 127.0.0.53. */
|
||||
r = manager_send(m,
|
||||
manager_dns_stub_fd_extra(m, l, SOCK_DGRAM),
|
||||
l ? p->ifindex : LOOPBACK_IFINDEX, /* force loopback iface if this is the main listener stub */
|
||||
fd,
|
||||
l || address_is_proxy(p->family, &p->destination) ? p->ifindex : LOOPBACK_IFINDEX, /* force loopback iface if this is the main listener stub */
|
||||
p->family, &p->sender, p->sender_port, &p->destination,
|
||||
reply);
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to send reply packet: %m");
|
||||
|
||||
@ -841,9 +877,11 @@ static int dns_stub_stream_complete(DnsStream *s, int error) {
|
||||
}
|
||||
|
||||
static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStream *s, DnsPacket *p) {
|
||||
uint64_t protocol_flags = SD_RESOLVED_PROTOCOLS_ALL;
|
||||
_cleanup_(dns_query_freep) DnsQuery *q = NULL;
|
||||
Hashmap **queries_by_packet;
|
||||
DnsQuery *existing;
|
||||
bool bypass = false;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
@ -851,6 +889,7 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
|
||||
assert(p->protocol == DNS_PROTOCOL_DNS);
|
||||
|
||||
if (!l && /* l == NULL if this is the main stub */
|
||||
!address_is_proxy(p->family, &p->destination) && /* don't restrict needlessly for 127.0.0.54 */
|
||||
(in_addr_is_localhost(p->family, &p->sender) <= 0 ||
|
||||
in_addr_is_localhost(p->family, &p->destination) <= 0)) {
|
||||
log_warning("Got packet on unexpected (i.e. non-localhost) IP range, ignoring.");
|
||||
@ -907,19 +946,34 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea
|
||||
return;
|
||||
}
|
||||
|
||||
if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) {
|
||||
log_debug("Got request with DNSSEC checking disabled, enabling bypass logic.");
|
||||
if (address_is_proxy(p->family, &p->destination)) {
|
||||
_cleanup_free_ char *dipa = NULL;
|
||||
|
||||
r = in_addr_to_string(p->family, &p->destination, &dipa);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to format destination address: %m");
|
||||
return;
|
||||
}
|
||||
|
||||
log_debug("Got request to DNS proxy address 127.0.0.54, enabling bypass logic.");
|
||||
bypass = true;
|
||||
protocol_flags = SD_RESOLVED_DNS|SD_RESOLVED_NO_ZONE; /* Turn off mDNS/LLMNR for proxy stub. */
|
||||
} else if ((DNS_PACKET_DO(p) && DNS_PACKET_CD(p))) {
|
||||
log_debug("Got request with DNSSEC checking disabled, enabling bypass logic.");
|
||||
bypass = true;
|
||||
}
|
||||
|
||||
if (bypass)
|
||||
r = dns_query_new(m, &q, NULL, NULL, p, 0,
|
||||
SD_RESOLVED_PROTOCOLS_ALL|
|
||||
protocol_flags|
|
||||
SD_RESOLVED_NO_CNAME|
|
||||
SD_RESOLVED_NO_SEARCH|
|
||||
SD_RESOLVED_NO_VALIDATE|
|
||||
SD_RESOLVED_REQUIRE_PRIMARY|
|
||||
SD_RESOLVED_CLAMP_TTL);
|
||||
} else
|
||||
else
|
||||
r = dns_query_new(m, &q, p->question, p->question, NULL, 0,
|
||||
SD_RESOLVED_PROTOCOLS_ALL|
|
||||
protocol_flags|
|
||||
SD_RESOLVED_NO_SEARCH|
|
||||
(DNS_PACKET_DO(p) ? SD_RESOLVED_REQUIRE_PRIMARY : 0)|
|
||||
SD_RESOLVED_CLAMP_TTL);
|
||||
@ -1085,26 +1139,32 @@ static int set_dns_stub_common_tcp_socket_options(int fd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_dns_stub_fd(Manager *m, int type) {
|
||||
union sockaddr_union sa = {
|
||||
.in.sin_family = AF_INET,
|
||||
.in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB),
|
||||
.in.sin_port = htobe16(53),
|
||||
};
|
||||
static int manager_dns_stub_fd(
|
||||
Manager *m,
|
||||
int family,
|
||||
const union in_addr_union *listen_addr,
|
||||
int type) {
|
||||
|
||||
sd_event_source **event_source;
|
||||
_cleanup_close_ int fd = -1;
|
||||
union sockaddr_union sa;
|
||||
int r;
|
||||
|
||||
assert(IN_SET(type, SOCK_DGRAM, SOCK_STREAM));
|
||||
if (type == SOCK_DGRAM)
|
||||
event_source = address_is_proxy(family, listen_addr) ? &m->dns_proxy_stub_udp_event_source : &m->dns_stub_udp_event_source;
|
||||
else if (type == SOCK_STREAM)
|
||||
event_source = address_is_proxy(family, listen_addr) ? &m->dns_proxy_stub_tcp_event_source : &m->dns_stub_tcp_event_source;
|
||||
else
|
||||
return -EPROTONOSUPPORT;
|
||||
|
||||
sd_event_source **event_source = type == SOCK_DGRAM ? &m->dns_stub_udp_event_source : &m->dns_stub_tcp_event_source;
|
||||
if (*event_source)
|
||||
return sd_event_source_get_io_fd(*event_source);
|
||||
|
||||
fd = socket(AF_INET, type | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
|
||||
fd = socket(family, type | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
r = set_dns_stub_common_socket_options(fd, AF_INET);
|
||||
r = set_dns_stub_common_socket_options(fd, family);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1114,12 +1174,30 @@ static int manager_dns_stub_fd(Manager *m, int type) {
|
||||
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)
|
||||
return r;
|
||||
/* Set slightly different socket options for the non-proxy and the proxy binding. The former we want
|
||||
* to be accessible only from the local host, for the latter it's OK if people use NAT redirects or
|
||||
* so to redirect external traffic to it. */
|
||||
|
||||
r = setsockopt_int(fd, IPPROTO_IP, IP_TTL, 1);
|
||||
if (!address_is_proxy(family, listen_addr)) {
|
||||
/* 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)
|
||||
return r;
|
||||
|
||||
r = socket_set_ttl(fd, family, 1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
} else if (type == SOCK_DGRAM) {
|
||||
r = socket_disable_pmtud(fd, family);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to disable UDP PMTUD, ignoring: %m");
|
||||
|
||||
r = socket_set_recvfragsize(fd, family, true);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to enable fragment size reception, ignoring: %m");
|
||||
}
|
||||
|
||||
r = sockaddr_set_in_addr(&sa, family, listen_addr, 53);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1153,11 +1231,9 @@ static int manager_dns_stub_fd_extra(Manager *m, DnsStubListenerExtra *l, int ty
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
assert(l);
|
||||
assert(IN_SET(type, SOCK_DGRAM, SOCK_STREAM));
|
||||
|
||||
if (!l)
|
||||
return manager_dns_stub_fd(m, type);
|
||||
|
||||
sd_event_source **event_source = type == SOCK_DGRAM ? &l->udp_event_source : &l->tcp_event_source;
|
||||
if (*event_source)
|
||||
return sd_event_source_get_io_fd(*event_source);
|
||||
@ -1251,39 +1327,64 @@ fail:
|
||||
}
|
||||
|
||||
int manager_dns_stub_start(Manager *m) {
|
||||
const char *t = "UDP";
|
||||
int r = 0;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
if (m->dns_stub_listener_mode == DNS_STUB_LISTENER_NO)
|
||||
log_debug("Not creating stub listener.");
|
||||
else
|
||||
else {
|
||||
static const struct {
|
||||
uint32_t addr;
|
||||
int socket_type;
|
||||
} stub_sockets[] = {
|
||||
{ INADDR_DNS_STUB, SOCK_DGRAM },
|
||||
{ INADDR_DNS_STUB, SOCK_STREAM },
|
||||
{ INADDR_DNS_PROXY_STUB, SOCK_DGRAM },
|
||||
{ INADDR_DNS_PROXY_STUB, SOCK_STREAM },
|
||||
};
|
||||
|
||||
log_debug("Creating stub listener using %s.",
|
||||
m->dns_stub_listener_mode == DNS_STUB_LISTENER_UDP ? "UDP" :
|
||||
m->dns_stub_listener_mode == DNS_STUB_LISTENER_TCP ? "TCP" :
|
||||
"UDP/TCP");
|
||||
|
||||
if (FLAGS_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_UDP))
|
||||
r = manager_dns_stub_fd(m, SOCK_DGRAM);
|
||||
for (size_t i = 0; i < ELEMENTSOF(stub_sockets); i++) {
|
||||
union in_addr_union a = {
|
||||
.in.s_addr = htobe32(stub_sockets[i].addr),
|
||||
};
|
||||
|
||||
if (r >= 0 &&
|
||||
FLAGS_SET(m->dns_stub_listener_mode, DNS_STUB_LISTENER_TCP)) {
|
||||
t = "TCP";
|
||||
r = manager_dns_stub_fd(m, SOCK_STREAM);
|
||||
if (m->dns_stub_listener_mode == DNS_STUB_LISTENER_UDP && stub_sockets[i].socket_type == SOCK_STREAM)
|
||||
continue;
|
||||
if (m->dns_stub_listener_mode == DNS_STUB_LISTENER_TCP && stub_sockets[i].socket_type == SOCK_DGRAM)
|
||||
continue;
|
||||
|
||||
r = manager_dns_stub_fd(m, AF_INET, &a, stub_sockets[i].socket_type);
|
||||
if (r < 0) {
|
||||
_cleanup_free_ char *busy_socket = NULL;
|
||||
|
||||
if (asprintf(&busy_socket,
|
||||
"%s socket " IPV4_ADDRESS_FMT_STR ":53",
|
||||
stub_sockets[i].socket_type == SOCK_DGRAM ? "UDP" : "TCP",
|
||||
IPV4_ADDRESS_FMT_VAL(a.in)) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (IN_SET(r, -EADDRINUSE, -EPERM)) {
|
||||
log_warning_errno(r,
|
||||
r == -EADDRINUSE ? "Another process is already listening on %s.\n"
|
||||
"Turning off local DNS stub support." :
|
||||
"Failed to listen on %s: %m.\n"
|
||||
"Turning off local DNS stub support.",
|
||||
busy_socket);
|
||||
manager_dns_stub_stop(m);
|
||||
break;
|
||||
}
|
||||
|
||||
return log_error_errno(r, "Failed to listen on %s: %m", busy_socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IN_SET(r, -EADDRINUSE, -EPERM)) {
|
||||
log_warning_errno(r,
|
||||
r == -EADDRINUSE ? "Another process is already listening on %s socket 127.0.0.53:53.\n"
|
||||
"Turning off local DNS stub support." :
|
||||
"Failed to listen on %s socket 127.0.0.53:53: %m.\n"
|
||||
"Turning off local DNS stub support.",
|
||||
t);
|
||||
manager_dns_stub_stop(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;
|
||||
|
||||
@ -1305,6 +1406,8 @@ void manager_dns_stub_stop(Manager *m) {
|
||||
|
||||
m->dns_stub_udp_event_source = sd_event_source_disable_unref(m->dns_stub_udp_event_source);
|
||||
m->dns_stub_tcp_event_source = sd_event_source_disable_unref(m->dns_stub_tcp_event_source);
|
||||
m->dns_proxy_stub_udp_event_source = sd_event_source_disable_unref(m->dns_proxy_stub_udp_event_source);
|
||||
m->dns_proxy_stub_tcp_event_source = sd_event_source_disable_unref(m->dns_proxy_stub_tcp_event_source);
|
||||
}
|
||||
|
||||
static const char* const dns_stub_listener_mode_table[_DNS_STUB_LISTENER_MODE_MAX] = {
|
||||
|
@ -140,6 +140,10 @@ struct Manager {
|
||||
sd_event_source *dns_stub_udp_event_source;
|
||||
sd_event_source *dns_stub_tcp_event_source;
|
||||
|
||||
/* Local DNS proxy stub on 127.0.0.54:53 */
|
||||
sd_event_source *dns_proxy_stub_udp_event_source;
|
||||
sd_event_source *dns_proxy_stub_tcp_event_source;
|
||||
|
||||
Hashmap *polkit_registry;
|
||||
|
||||
VarlinkServer *varlink_server;
|
||||
|
@ -31,12 +31,12 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dns_over_tls_mode, DnsOverTlsMode, DNS_O
|
||||
|
||||
bool dns_server_address_valid(int family, const union in_addr_union *sa) {
|
||||
|
||||
/* Refuses the 0 IP addresses as well as 127.0.0.53 (which is our own DNS stub) */
|
||||
/* Refuses the 0 IP addresses as well as 127.0.0.53/127.0.0.54 (which is our own DNS stub) */
|
||||
|
||||
if (!in_addr_is_set(family, sa))
|
||||
return false;
|
||||
|
||||
if (family == AF_INET && sa->in.s_addr == htobe32(INADDR_DNS_STUB))
|
||||
if (family == AF_INET && IN_SET(be32toh(sa->in.s_addr), INADDR_DNS_STUB, INADDR_DNS_PROXY_STUB))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -5,9 +5,12 @@
|
||||
#include "in-addr-util.h"
|
||||
#include "macro.h"
|
||||
|
||||
/* 127.0.0.53 in native endian */
|
||||
/* 127.0.0.53 in native endian (The IP address we listen on with the full DNS stub, i.e. that does LLMNR/mDNS, and stuff) */
|
||||
#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U)
|
||||
|
||||
/* 127.0.0.54 in native endian (The IP address we listen on we only implement "proxy" mode) */
|
||||
#define INADDR_DNS_PROXY_STUB ((in_addr_t) 0x7f000036U)
|
||||
|
||||
typedef enum DnsCacheMode DnsCacheMode;
|
||||
|
||||
enum DnsCacheMode {
|
||||
|
Loading…
Reference in New Issue
Block a user