summaryrefslogtreecommitdiff
path: root/src/resolve
diff options
context:
space:
mode:
Diffstat (limited to 'src/resolve')
-rw-r--r--src/resolve/resolv.conf11
-rw-r--r--src/resolve/resolved-conf.c4
-rw-r--r--src/resolve/resolved-dns-packet.c44
-rw-r--r--src/resolve/resolved-dns-packet.h31
-rw-r--r--src/resolve/resolved-dns-query.c10
-rw-r--r--src/resolve/resolved-dns-query.h4
-rw-r--r--src/resolve/resolved-dns-rr.h7
-rw-r--r--src/resolve/resolved-dns-scope.c37
-rw-r--r--src/resolve/resolved-dns-server.c14
-rw-r--r--src/resolve/resolved-dns-server.h2
-rw-r--r--src/resolve/resolved-dns-stream.c29
-rw-r--r--src/resolve/resolved-dns-stream.h23
-rw-r--r--src/resolve/resolved-dns-stub.c572
-rw-r--r--src/resolve/resolved-dns-stub.h31
-rw-r--r--src/resolve/resolved-dns-transaction.c13
-rw-r--r--src/resolve/resolved-link-bus.c3
-rw-r--r--src/resolve/resolved-llmnr.c33
-rw-r--r--src/resolve/resolved-manager.c62
-rw-r--r--src/resolve/resolved-manager.h9
-rw-r--r--src/resolve/resolved-resolv-conf.c11
-rw-r--r--src/resolve/resolved.c13
21 files changed, 887 insertions, 76 deletions
diff --git a/src/resolve/resolv.conf b/src/resolve/resolv.conf
new file mode 100644
index 0000000000..b8034d6829
--- /dev/null
+++ b/src/resolve/resolv.conf
@@ -0,0 +1,11 @@
+# This is a static resolv.conf file for connecting local clients to
+# systemd-resolved via its DNS stub listener on 127.0.0.53.
+#
+# Third party programs must not access this file directly, but only through the
+# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,
+# replace this symlink by a static file or a different symlink.
+#
+# See systemd-resolved.service(8) for details about the supported modes of
+# operation for /etc/resolv.conf.
+
+nameserver 127.0.0.53
diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c
index fecf7ecccf..dd233e7c4a 100644
--- a/src/resolve/resolved-conf.c
+++ b/src/resolve/resolved-conf.c
@@ -37,6 +37,10 @@ int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char
if (r < 0)
return r;
+ /* Silently filter out 0.0.0.0 and 127.0.0.53 (our own stub DNS listener) */
+ if (!dns_server_address_valid(family, &address))
+ return 0;
+
/* Filter out duplicates */
s = dns_server_find(manager_get_first_dns_server(m, type), family, &address, ifindex);
if (s) {
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index 2cf07a628b..ea0be56d98 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -264,6 +264,7 @@ int dns_packet_validate_query(DnsPacket *p) {
switch (p->protocol) {
case DNS_PROTOCOL_LLMNR:
+ case DNS_PROTOCOL_DNS:
/* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */
if (DNS_PACKET_QDCOUNT(p) != 1)
return -EBADMSG;
@@ -719,9 +720,8 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, in
goto fail;
/* RDLENGTH */
-
- if (edns0_do) {
- /* If DO is on, also append RFC6975 Algorithm data */
+ if (edns0_do & !DNS_PACKET_QR(p)) {
+ /* If DO is on and this is not a reply, also append RFC6975 Algorithm data */
static const uint8_t rfc6975[] = {
@@ -752,7 +752,6 @@ int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, in
r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL);
} else
r = dns_packet_append_uint16(p, 0, NULL);
-
if (r < 0)
goto fail;
@@ -2062,8 +2061,10 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) {
assert(rr->key->type == DNS_TYPE_OPT);
/* Check that the version is 0 */
- if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0)
- return false;
+ if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) {
+ *rfc6975 = false;
+ return true; /* if it's not version 0, it's OK, but we will ignore the OPT field contents */
+ }
p = rr->opt.data;
l = rr->opt.data_size;
@@ -2186,16 +2187,27 @@ int dns_packet_extract(DnsPacket *p) {
continue;
}
- if (has_rfc6975) {
- /* If the OPT RR contains RFC6975 algorithm data, then this is indication that
- * the server just copied the OPT it got from us (which contained that data)
- * back into the reply. If so, then it doesn't properly support EDNS, as
- * RFC6975 makes it very clear that the algorithm data should only be contained
- * in questions, never in replies. Crappy Belkin routers copy the OPT data for
- * example, hence let's detect this so that we downgrade early. */
- log_debug("OPT RR contained RFC6975 data, ignoring.");
- bad_opt = true;
- continue;
+ if (DNS_PACKET_QR(p)) {
+ /* Additional checks for responses */
+
+ if (!DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(rr)) {
+ /* If this is a reply and we don't know the EDNS version then something
+ * is weird... */
+ log_debug("EDNS version newer that our request, bad server.");
+ return -EBADMSG;
+ }
+
+ if (has_rfc6975) {
+ /* If the OPT RR contains RFC6975 algorithm data, then this is indication that
+ * the server just copied the OPT it got from us (which contained that data)
+ * back into the reply. If so, then it doesn't properly support EDNS, as
+ * RFC6975 makes it very clear that the algorithm data should only be contained
+ * in questions, never in replies. Crappy Belkin routers copy the OPT data for
+ * example, hence let's detect this so that we downgrade early. */
+ log_debug("OPT RR contained RFC6975 data, ignoring.");
+ bad_opt = true;
+ continue;
+ }
}
p->opt = dns_resource_record_ref(rr);
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index 1216bcb72d..7b7d4e14c9 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -118,6 +118,8 @@ static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) {
#define DNS_PACKET_AD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 5) & 1)
#define DNS_PACKET_CD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 4) & 1)
+#define DNS_PACKET_FLAG_TC (UINT16_C(1) << 9)
+
static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) {
uint16_t rcode;
@@ -126,7 +128,34 @@ static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) {
else
rcode = 0;
- return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15);
+ return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 0xF);
+}
+
+static inline uint16_t DNS_PACKET_PAYLOAD_SIZE_MAX(DnsPacket *p) {
+
+ /* Returns the advertised maximum datagram size for replies, or the DNS default if there's nothing defined. */
+
+ if (p->opt)
+ return MAX(DNS_PACKET_UNICAST_SIZE_MAX, p->opt->key->class);
+
+ return DNS_PACKET_UNICAST_SIZE_MAX;
+}
+
+static inline bool DNS_PACKET_DO(DnsPacket *p) {
+ if (!p->opt)
+ return false;
+
+ return !!(p->opt->ttl & (1U << 15));
+}
+
+static inline bool DNS_PACKET_VERSION_SUPPORTED(DnsPacket *p) {
+ /* Returns true if this packet is in a version we support. Which means either non-EDNS or EDNS(0), but not EDNS
+ * of any newer versions */
+
+ if (!p->opt)
+ return true;
+
+ return DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(p->opt);
}
/* LLMNR defines some bits differently */
diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c
index 8578774c37..c8af5579f0 100644
--- a/src/resolve/resolved-dns-query.c
+++ b/src/resolve/resolved-dns-query.c
@@ -404,6 +404,16 @@ DnsQuery *dns_query_free(DnsQuery *q) {
sd_bus_message_unref(q->request);
sd_bus_track_unref(q->bus_track);
+ dns_packet_unref(q->request_dns_packet);
+
+ if (q->request_dns_stream) {
+ /* Detach the stream from our query, in case something else keeps a reference to it. */
+ q->request_dns_stream->complete = NULL;
+ q->request_dns_stream->on_packet = NULL;
+ q->request_dns_stream->query = NULL;
+ dns_stream_unref(q->request_dns_stream);
+ }
+
free(q->request_address_string);
if (q->manager) {
diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h
index 53f48d462b..49a35b846b 100644
--- a/src/resolve/resolved-dns-query.h
+++ b/src/resolve/resolved-dns-query.h
@@ -99,6 +99,10 @@ struct DnsQuery {
unsigned block_all_complete;
char *request_address_string;
+ /* DNS stub information */
+ DnsPacket *request_dns_packet;
+ DnsStream *request_dns_stream;
+
/* Completion callback */
void (*complete)(DnsQuery* q);
unsigned block_ready;
diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h
index 8b2d4df9e7..42d39a1251 100644
--- a/src/resolve/resolved-dns-rr.h
+++ b/src/resolve/resolved-dns-rr.h
@@ -282,6 +282,13 @@ static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) {
return rr->wire_format_size - rr->wire_format_rdata_offset;
}
+static inline uint8_t DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(DnsResourceRecord *rr) {
+ assert(rr);
+ assert(rr->key->type == DNS_TYPE_OPT);
+
+ return ((rr->ttl >> 16) & 0xFF) == 0;
+}
+
DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name);
DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname);
int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index 66e763cb7d..ed0c6aa105 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -232,7 +232,7 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
if (fd < 0)
return fd;
- r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p);
+ r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, NULL, p);
if (r < 0)
return r;
@@ -257,7 +257,7 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
if (fd < 0)
return fd;
- r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p);
+ r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, NULL, p);
if (r < 0)
return r;
@@ -668,11 +668,11 @@ static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) {
}
void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
- _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL, *soa = NULL;
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
DnsResourceKey *key = NULL;
bool tentative = false;
- int r, fd;
+ int r;
assert(s);
assert(p);
@@ -694,7 +694,7 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
r = dns_packet_extract(p);
if (r < 0) {
- log_debug_errno(r, "Failed to extract resources from incoming packet: %m");
+ log_debug_errno(r, "Failed to extract resource records from incoming packet: %m");
return;
}
@@ -724,9 +724,21 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
return;
}
- if (stream)
+ if (stream) {
r = dns_stream_write_packet(stream, reply);
- else {
+ if (r < 0) {
+ log_debug_errno(r, "Failed to enqueue reply packet: %m");
+ return;
+ }
+
+ /* Let's take an extra reference on this stream, so that it stays around after returning. The reference
+ * will be dangling until the stream is disconnected, and the default completion handler of the stream
+ * will then unref the stream and destroy it */
+ if (DNS_STREAM_QUEUED(stream))
+ dns_stream_ref(stream);
+ } else {
+ int fd;
+
if (!ratelimit_test(&s->ratelimit))
return;
@@ -748,12 +760,11 @@ void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p) {
* verified uniqueness for all records. Also see RFC
* 4795, Section 2.7 */
- r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, reply);
- }
-
- if (r < 0) {
- log_debug_errno(r, "Failed to send reply packet: %m");
- return;
+ r = manager_send(s->manager, fd, p->ifindex, p->family, &p->sender, p->sender_port, NULL, reply);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to send reply packet: %m");
+ return;
+ }
}
}
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index bcbfa69aff..7226111c07 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -21,6 +21,7 @@
#include "alloc-util.h"
#include "resolved-dns-server.h"
+#include "resolved-dns-stub.h"
#include "resolved-resolv-conf.h"
#include "siphash24.h"
#include "string-table.h"
@@ -750,6 +751,19 @@ void manager_next_dns_server(Manager *m) {
manager_set_dns_server(m, m->dns_servers);
}
+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) */
+
+ if (in_addr_is_null(family, sa))
+ return false;
+
+ if (family == AF_INET && sa->in.s_addr == htobe32(INADDR_DNS_STUB))
+ return false;
+
+ return true;
+}
+
static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = {
[DNS_SERVER_SYSTEM] = "system",
[DNS_SERVER_FALLBACK] = "fallback",
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index 463c5724a7..7d07fa3e29 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -141,6 +141,8 @@ DnsServer *manager_set_dns_server(Manager *m, DnsServer *s);
DnsServer *manager_get_dns_server(Manager *m);
void manager_next_dns_server(Manager *m);
+bool dns_server_address_valid(int family, const union in_addr_union *sa);
+
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
extern const struct hash_ops dns_server_hash_ops;
diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c
index a1040aeff4..dd0e0b90e3 100644
--- a/src/resolve/resolved-dns-stream.c
+++ b/src/resolve/resolved-dns-stream.c
@@ -56,8 +56,8 @@ static int dns_stream_complete(DnsStream *s, int error) {
if (s->complete)
s->complete(s, error);
- else
- dns_stream_free(s);
+ else /* the default action if no completion function is set is to close the stream */
+ dns_stream_unref(s);
return 0;
}
@@ -323,10 +323,16 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use
return 0;
}
-DnsStream *dns_stream_free(DnsStream *s) {
+DnsStream *dns_stream_unref(DnsStream *s) {
if (!s)
return NULL;
+ assert(s->n_ref > 0);
+ s->n_ref--;
+
+ if (s->n_ref > 0)
+ return NULL;
+
dns_stream_stop(s);
if (s->manager) {
@@ -339,13 +345,23 @@ DnsStream *dns_stream_free(DnsStream *s) {
free(s);
- return 0;
+ return NULL;
}
-DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_unref);
+
+DnsStream *dns_stream_ref(DnsStream *s) {
+ if (!s)
+ return NULL;
+
+ assert(s->n_ref > 0);
+ s->n_ref++;
+
+ return s;
+}
int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
- _cleanup_(dns_stream_freep) DnsStream *s = NULL;
+ _cleanup_(dns_stream_unrefp) DnsStream *s = NULL;
int r;
assert(m);
@@ -358,6 +374,7 @@ int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) {
if (!s)
return -ENOMEM;
+ s->n_ref = 1;
s->fd = -1;
s->protocol = protocol;
diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h
index 5ccc842249..e6569678fa 100644
--- a/src/resolve/resolved-dns-stream.h
+++ b/src/resolve/resolved-dns-stream.h
@@ -26,8 +26,16 @@ typedef struct DnsStream DnsStream;
#include "resolved-dns-packet.h"
#include "resolved-dns-transaction.h"
+/* Streams are used by three subsystems:
+ *
+ * 1. The normal transaction logic when doing a DNS or LLMNR lookup via TCP
+ * 2. The LLMNR logic when accepting a TCP-based lookup
+ * 3. The DNS stub logic when accepting a TCP-based lookup
+ */
+
struct DnsStream {
Manager *manager;
+ int n_ref;
DnsProtocol protocol;
@@ -50,12 +58,23 @@ struct DnsStream {
int (*on_packet)(DnsStream *s);
int (*complete)(DnsStream *s, int error);
- DnsTransaction *transaction;
+ DnsTransaction *transaction; /* when used by the transaction logic */
+ DnsQuery *query; /* when used by the DNS stub logic */
LIST_FIELDS(DnsStream, streams);
};
int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd);
-DnsStream *dns_stream_free(DnsStream *s);
+DnsStream *dns_stream_unref(DnsStream *s);
+DnsStream *dns_stream_ref(DnsStream *s);
int dns_stream_write_packet(DnsStream *s, DnsPacket *p);
+
+static inline bool DNS_STREAM_QUEUED(DnsStream *s) {
+ assert(s);
+
+ if (s->fd < 0) /* already stopped? */
+ return false;
+
+ return !!s->write_packet;
+}
diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c
new file mode 100644
index 0000000000..d263cedcd9
--- /dev/null
+++ b/src/resolve/resolved-dns-stub.c
@@ -0,0 +1,572 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "fd-util.h"
+#include "resolved-dns-stub.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,
+ * IP and UDP header sizes */
+#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U)
+
+static int dns_stub_make_reply_packet(
+ uint16_t id,
+ int rcode,
+ DnsQuestion *q,
+ DnsAnswer *answer,
+ bool add_opt, /* add an OPT RR to this packet */
+ bool edns0_do, /* set the EDNS0 DNSSEC OK bit */
+ bool ad, /* set the DNSSEC authenticated data bit */
+ DnsPacket **ret) {
+
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ DnsResourceRecord *rr;
+ unsigned c = 0;
+ int r;
+
+ /* Note that we don't bother with any additional RRs, as this is stub is for local lookups only, and hence
+ * roundtrips aren't expensive. */
+
+ r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0);
+ if (r < 0)
+ return r;
+
+ /* If the client didn't do EDNS, clamp the rcode to 4 bit */
+ if (!add_opt && rcode > 0xF)
+ rcode = DNS_RCODE_SERVFAIL;
+
+ DNS_PACKET_HEADER(p)->id = id;
+ DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(
+ 1 /* qr */,
+ 0 /* opcode */,
+ 0 /* aa */,
+ 0 /* tc */,
+ 1 /* rd */,
+ 1 /* ra */,
+ ad /* ad */,
+ 0 /* cd */,
+ rcode));
+
+ r = dns_packet_append_question(p, q);
+ if (r < 0)
+ return r;
+ DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(q));
+
+ DNS_ANSWER_FOREACH(rr, answer) {
+ r = dns_question_matches_rr(q, rr, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto add;
+
+ r = dns_question_matches_cname_or_dname(q, rr, NULL);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ goto add;
+
+ continue;
+ add:
+ r = dns_packet_append_rr(p, rr, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ c++;
+ }
+ DNS_PACKET_HEADER(p)->ancount = htobe16(c);
+
+ if (add_opt) {
+ r = dns_packet_append_opt(p, ADVERTISE_DATAGRAM_SIZE_MAX, edns0_do, rcode, NULL);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = p;
+ p = NULL;
+
+ return 0;
+}
+
+static void dns_stub_detach_stream(DnsStream *s) {
+ assert(s);
+
+ s->complete = NULL;
+ s->on_packet = NULL;
+ s->query = NULL;
+}
+
+static int dns_stub_send(Manager *m, DnsStream *s, DnsPacket *p, DnsPacket *reply) {
+ int r;
+
+ assert(m);
+ assert(p);
+ assert(reply);
+
+ if (s)
+ r = dns_stream_write_packet(s, reply);
+ else {
+ int fd;
+
+ /* Truncate the message to the right size */
+ if (reply->size > DNS_PACKET_PAYLOAD_SIZE_MAX(p)) {
+ dns_packet_truncate(reply, DNS_PACKET_UNICAST_SIZE_MAX);
+ DNS_PACKET_HEADER(reply)->flags = htobe16(be16toh(DNS_PACKET_HEADER(reply)->flags) | DNS_PACKET_FLAG_TC);
+ }
+
+ fd = manager_dns_stub_udp_fd(m);
+ if (fd < 0)
+ return log_debug_errno(fd, "Failed to get reply socket: %m");
+
+ /* 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, fd, LOOPBACK_IFINDEX, p->family, &p->sender, p->sender_port, &p->destination, reply);
+ }
+ if (r < 0)
+ return log_debug_errno(r, "Failed to send reply packet: %m");
+
+ return 0;
+}
+
+static int dns_stub_send_failure(Manager *m, DnsStream *s, DnsPacket *p, int rcode) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+ int r;
+
+ assert(m);
+ assert(p);
+
+ r = dns_stub_make_reply_packet(DNS_PACKET_ID(p), rcode, p->question, NULL, !!p->opt, DNS_PACKET_DO(p), false, &reply);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to build failure packet: %m");
+
+ return dns_stub_send(m, s, p, reply);
+}
+
+static void dns_stub_query_complete(DnsQuery *q) {
+ int r;
+
+ assert(q);
+ assert(q->request_dns_packet);
+
+ switch (q->state) {
+
+ case DNS_TRANSACTION_SUCCESS: {
+ _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL;
+
+ r = dns_stub_make_reply_packet(
+ DNS_PACKET_ID(q->request_dns_packet),
+ q->answer_rcode,
+ q->question_idna,
+ q->answer,
+ !!q->request_dns_packet->opt,
+ DNS_PACKET_DO(q->request_dns_packet),
+ DNS_PACKET_DO(q->request_dns_packet) && q->answer_authenticated,
+ &reply);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to build reply packet: %m");
+ break;
+ }
+
+ (void) dns_stub_send(q->manager, q->request_dns_stream, q->request_dns_packet, reply);
+ break;
+ }
+
+ case DNS_TRANSACTION_RCODE_FAILURE:
+ (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, q->answer_rcode);
+ break;
+
+ case DNS_TRANSACTION_NOT_FOUND:
+ (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_NXDOMAIN);
+ break;
+
+ case DNS_TRANSACTION_TIMEOUT:
+ case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED:
+ /* Propagate a timeout as a no packet, i.e. that the client also gets a timeout */
+ break;
+
+ case DNS_TRANSACTION_NO_SERVERS:
+ case DNS_TRANSACTION_INVALID_REPLY:
+ case DNS_TRANSACTION_ERRNO:
+ case DNS_TRANSACTION_ABORTED:
+ case DNS_TRANSACTION_DNSSEC_FAILED:
+ case DNS_TRANSACTION_NO_TRUST_ANCHOR:
+ case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED:
+ case DNS_TRANSACTION_NETWORK_DOWN:
+ (void) dns_stub_send_failure(q->manager, q->request_dns_stream, q->request_dns_packet, DNS_RCODE_SERVFAIL);
+ break;
+
+ case DNS_TRANSACTION_NULL:
+ case DNS_TRANSACTION_PENDING:
+ case DNS_TRANSACTION_VALIDATING:
+ default:
+ assert_not_reached("Impossible state");
+ }
+
+ /* If there's a packet to write set, let's leave the stream around */
+ if (q->request_dns_stream && DNS_STREAM_QUEUED(q->request_dns_stream)) {
+
+ /* Detach the stream from our query (make it an orphan), but do not drop the reference to it. The
+ * default completion action of the stream will drop the reference. */
+
+ dns_stub_detach_stream(q->request_dns_stream);
+ q->request_dns_stream = NULL;
+ }
+
+ dns_query_free(q);
+}
+
+static int dns_stub_stream_complete(DnsStream *s, int error) {
+ assert(s);
+
+ log_debug_errno(error, "DNS TCP connection terminated, destroying query: %m");
+
+ assert(s->query);
+ dns_query_free(s->query);
+
+ return 0;
+}
+
+static void dns_stub_process_query(Manager *m, DnsStream *s, DnsPacket *p) {
+ DnsQuery *q = NULL;
+ int r;
+
+ assert(m);
+ assert(p);
+ assert(p->protocol == DNS_PROTOCOL_DNS);
+
+ /* Takes ownership of the *s stream object */
+
+ if (in_addr_is_localhost(p->family, &p->sender) <= 0 ||
+ in_addr_is_localhost(p->family, &p->destination) <= 0) {
+ log_error("Got packet on unexpected IP range, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
+ goto fail;
+ }
+
+ r = dns_packet_extract(p);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to extract resources from incoming packet, ignoring packet: %m");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_FORMERR);
+ goto fail;
+ }
+
+ if (!DNS_PACKET_VERSION_SUPPORTED(p)) {
+ log_debug("Got EDNS OPT field with unsupported version number.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_BADVERS);
+ goto fail;
+ }
+
+ if (dns_type_is_obsolete(p->question->keys[0]->type)) {
+ log_debug("Got message with obsolete key type, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
+ goto fail;
+ }
+
+ if (dns_type_is_zone_transer(p->question->keys[0]->type)) {
+ log_debug("Got request for zone transfer, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
+ goto fail;
+ }
+
+ if (!DNS_PACKET_RD(p)) {
+ /* If the "rd" bit is off (i.e. recursion was not requested), then refuse operation */
+ log_debug("Got request with recursion disabled, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_REFUSED);
+ goto fail;
+ }
+
+ if (DNS_PACKET_DO(p) && DNS_PACKET_CD(p)) {
+ log_debug("Got request with DNSSEC CD bit set, refusing.");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_NOTIMP);
+ goto fail;
+ }
+
+ r = dns_query_new(m, &q, p->question, p->question, 0, SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_NO_CNAME);
+ if (r < 0) {
+ log_error_errno(r, "Failed to generate query object: %m");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
+ goto fail;
+ }
+
+ /* Request that the TTL is corrected by the cached time for this lookup, so that we return vaguely useful TTLs */
+ q->clamp_ttl = true;
+
+ q->request_dns_packet = dns_packet_ref(p);
+ q->request_dns_stream = dns_stream_ref(s); /* make sure the stream stays around until we can send a reply through it */
+ q->complete = dns_stub_query_complete;
+
+ if (s) {
+ s->on_packet = NULL;
+ s->complete = dns_stub_stream_complete;
+ s->query = q;
+ }
+
+ r = dns_query_go(q);
+ if (r < 0) {
+ log_error_errno(r, "Failed to start query: %m");
+ dns_stub_send_failure(m, s, p, DNS_RCODE_SERVFAIL);
+ goto fail;
+ }
+
+ log_info("Processing query...");
+ return;
+
+fail:
+ if (s && DNS_STREAM_QUEUED(s))
+ dns_stub_detach_stream(s);
+
+ dns_query_free(q);
+}
+
+static int on_dns_stub_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
+ Manager *m = userdata;
+ int r;
+
+ r = manager_recv(m, fd, DNS_PROTOCOL_DNS, &p);
+ if (r <= 0)
+ return r;
+
+ if (dns_packet_validate_query(p) > 0) {
+ log_debug("Got DNS stub UDP query packet for id %u", DNS_PACKET_ID(p));
+
+ dns_stub_process_query(m, NULL, p);
+ } else
+ log_debug("Invalid DNS stub UDP packet, ignoring.");
+
+ return 0;
+}
+
+int manager_dns_stub_udp_fd(Manager *m) {
+ static const int one = 1;
+
+ union sockaddr_union sa = {
+ .in.sin_family = AF_INET,
+ .in.sin_port = htobe16(53),
+ .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB),
+ };
+
+ int r;
+
+ if (m->dns_stub_udp_fd >= 0)
+ return m->dns_stub_udp_fd;
+
+ m->dns_stub_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->dns_stub_udp_fd < 0)
+ return -errno;
+
+ r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Make sure no traffic from outside the local host can leak to onto this socket */
+ r = setsockopt(m->dns_stub_udp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->dns_stub_udp_fd, &sa.sa, sizeof(sa.in));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->dns_stub_udp_event_source, m->dns_stub_udp_fd, EPOLLIN, on_dns_stub_packet, m);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(m->dns_stub_udp_event_source, "dns-stub-udp");
+
+ return m->dns_stub_udp_fd;
+
+fail:
+ m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd);
+ return r;
+}
+
+static int on_dns_stub_stream_packet(DnsStream *s) {
+ assert(s);
+ assert(s->read_packet);
+
+ if (dns_packet_validate_query(s->read_packet) > 0) {
+ log_debug("Got DNS stub TCP query packet for id %u", DNS_PACKET_ID(s->read_packet));
+
+ dns_stub_process_query(s->manager, s, s->read_packet);
+ } else
+ log_debug("Invalid DNS stub TCP packet, ignoring.");
+
+ /* Drop the reference to the stream. Either a query was created and added its own reference to the stream now,
+ * or that didn't happen in which case we want to free the stream */
+ dns_stream_unref(s);
+
+ return 0;
+}
+
+static int on_dns_stub_stream(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ DnsStream *stream;
+ Manager *m = userdata;
+ int cfd, r;
+
+ cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ if (cfd < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
+ return -errno;
+ }
+
+ r = dns_stream_new(m, &stream, DNS_PROTOCOL_DNS, cfd);
+ if (r < 0) {
+ safe_close(cfd);
+ return r;
+ }
+
+ stream->on_packet = on_dns_stub_stream_packet;
+
+ /* We let the reference to the stream dangling here, it will either be dropped by the default "complete" action
+ * of the stream, or by our packet callback, or when the manager is shut down. */
+
+ return 0;
+}
+
+int manager_dns_stub_tcp_fd(Manager *m) {
+ static const int one = 1;
+
+ union sockaddr_union sa = {
+ .in.sin_family = AF_INET,
+ .in.sin_addr.s_addr = htobe32(INADDR_DNS_STUB),
+ .in.sin_port = htobe16(53),
+ };
+
+ int r;
+
+ if (m->dns_stub_tcp_fd >= 0)
+ return m->dns_stub_tcp_fd;
+
+ m->dns_stub_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (m->dns_stub_tcp_fd < 0)
+ return -errno;
+
+ r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = setsockopt(m->dns_stub_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ /* Make sure no traffic from outside the local host can leak to onto this socket */
+ r = setsockopt(m->dns_stub_tcp_fd, SOL_SOCKET, SO_BINDTODEVICE, "lo", 3);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = bind(m->dns_stub_tcp_fd, &sa.sa, sizeof(sa.in));
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = listen(m->dns_stub_tcp_fd, SOMAXCONN);
+ if (r < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ r = sd_event_add_io(m->event, &m->dns_stub_tcp_event_source, m->dns_stub_tcp_fd, EPOLLIN, on_dns_stub_stream, m);
+ if (r < 0)
+ goto fail;
+
+ (void) sd_event_source_set_description(m->dns_stub_tcp_event_source, "dns-stub-tcp");
+
+ return m->dns_stub_tcp_fd;
+
+fail:
+ m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd);
+ return r;
+}
+
+int manager_dns_stub_start(Manager *m) {
+ int r;
+
+ assert(m);
+
+ r = manager_dns_stub_udp_fd(m);
+ if (r == -EADDRINUSE)
+ goto eaddrinuse;
+ if (r < 0)
+ return r;
+
+ r = manager_dns_stub_tcp_fd(m);
+ if (r == -EADDRINUSE)
+ goto eaddrinuse;
+ if (r < 0)
+ return r;
+
+ return 0;
+
+eaddrinuse:
+ log_warning("Another process is already listening on 127.0.0.53:53. Turning off local DNS stub support.");
+ manager_dns_stub_stop(m);
+
+ return 0;
+}
+
+void manager_dns_stub_stop(Manager *m) {
+ assert(m);
+
+ m->dns_stub_udp_event_source = sd_event_source_unref(m->dns_stub_udp_event_source);
+ m->dns_stub_tcp_event_source = sd_event_source_unref(m->dns_stub_tcp_event_source);
+
+ m->dns_stub_udp_fd = safe_close(m->dns_stub_udp_fd);
+ m->dns_stub_tcp_fd = safe_close(m->dns_stub_tcp_fd);
+}
diff --git a/src/resolve/resolved-dns-stub.h b/src/resolve/resolved-dns-stub.h
new file mode 100644
index 0000000000..fce4d25ede
--- /dev/null
+++ b/src/resolve/resolved-dns-stub.h
@@ -0,0 +1,31 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2016 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "resolved-manager.h"
+
+/* 127.0.0.53 in native endian */
+#define INADDR_DNS_STUB ((in_addr_t) 0x7f000035U)
+
+int manager_dns_stub_udp_fd(Manager *m);
+int manager_dns_stub_tcp_fd(Manager *m);
+
+void manager_dns_stub_stop(Manager *m);
+int manager_dns_stub_start(Manager *m);
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 2d1767be0a..09f60d3e76 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -60,7 +60,14 @@ static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) {
static void dns_transaction_close_connection(DnsTransaction *t) {
assert(t);
- t->stream = dns_stream_free(t->stream);
+ if (t->stream) {
+ /* Let's detach the stream from our transaction, in case something else keeps a reference to it. */
+ t->stream->complete = NULL;
+ t->stream->on_packet = NULL;
+ t->stream->transaction = NULL;
+ t->stream = dns_stream_unref(t->stream);
+ }
+
t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
t->dns_udp_fd = safe_close(t->dns_udp_fd);
}
@@ -444,7 +451,7 @@ static int on_stream_complete(DnsStream *s, int error) {
t = s->transaction;
p = dns_packet_ref(s->read_packet);
- t->stream = dns_stream_free(t->stream);
+ dns_transaction_close_connection(t);
if (ERRNO_IS_DISCONNECT(error)) {
usec_t usec;
@@ -556,7 +563,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
r = dns_stream_write_packet(t->stream, t->sent);
if (r < 0) {
- t->stream = dns_stream_free(t->stream);
+ t->stream = dns_stream_unref(t->stream);
return r;
}
diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c
index acce8682de..364812250f 100644
--- a/src/resolve/resolved-link-bus.c
+++ b/src/resolve/resolved-link-bus.c
@@ -230,6 +230,9 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_
if (sz != FAMILY_ADDRESS_SIZE(family))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size");
+ if (!dns_server_address_valid(family, d))
+ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNS server address");
+
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c
index 8b1d71a3eb..3516af58ee 100644
--- a/src/resolve/resolved-llmnr.c
+++ b/src/resolve/resolved-llmnr.c
@@ -91,18 +91,19 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
DnsScope *scope;
int r;
+ assert(s);
+ assert(fd >= 0);
+ assert(m);
+
r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p);
if (r <= 0)
return r;
scope = manager_find_scope(m, p);
- if (!scope) {
+ if (!scope)
log_warning("Got LLMNR UDP packet on unknown scope. Ignoring.");
- return 0;
- }
-
- if (dns_packet_validate_reply(p) > 0) {
- log_debug("Got LLMNR reply packet for id %u", DNS_PACKET_ID(p));
+ else if (dns_packet_validate_reply(p) > 0) {
+ log_debug("Got LLMNR UDP reply packet for id %u", DNS_PACKET_ID(p));
dns_scope_check_conflicts(scope, p);
@@ -111,7 +112,7 @@ static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *u
dns_transaction_process_reply(t, p);
} else if (dns_packet_validate_query(p) > 0) {
- log_debug("Got LLMNR query packet for id %u", DNS_PACKET_ID(p));
+ log_debug("Got LLMNR UDP query packet for id %u", DNS_PACKET_ID(p));
dns_scope_process_query(scope, NULL, p);
} else
@@ -283,25 +284,19 @@ static int on_llmnr_stream_packet(DnsStream *s) {
DnsScope *scope;
assert(s);
+ assert(s->read_packet);
scope = manager_find_scope(s->manager, s->read_packet);
- if (!scope) {
+ if (!scope)
log_warning("Got LLMNR TCP packet on unknown scope. Ignoring.");
- return 0;
- }
-
- if (dns_packet_validate_query(s->read_packet) > 0) {
- log_debug("Got query packet for id %u", DNS_PACKET_ID(s->read_packet));
+ else if (dns_packet_validate_query(s->read_packet) > 0) {
+ log_debug("Got LLMNR TCP query packet for id %u", DNS_PACKET_ID(s->read_packet));
dns_scope_process_query(scope, s, s->read_packet);
-
- /* If no reply packet was set, we free the stream */
- if (s->write_packet)
- return 0;
} else
- log_debug("Invalid LLMNR TCP packet.");
+ log_debug("Invalid LLMNR TCP packet, ignoring.");
- dns_stream_free(s);
+ dns_stream_unref(s);
return 0;
}
diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c
index e8811fa1d8..30036049da 100644
--- a/src/resolve/resolved-manager.c
+++ b/src/resolve/resolved-manager.c
@@ -36,6 +36,7 @@
#include "random-util.h"
#include "resolved-bus.h"
#include "resolved-conf.h"
+#include "resolved-dns-stub.h"
#include "resolved-etc-hosts.h"
#include "resolved-llmnr.h"
#include "resolved-manager.h"
@@ -493,6 +494,7 @@ int manager_new(Manager **ret) {
m->llmnr_ipv4_udp_fd = m->llmnr_ipv6_udp_fd = -1;
m->llmnr_ipv4_tcp_fd = m->llmnr_ipv6_tcp_fd = -1;
m->mdns_ipv4_fd = m->mdns_ipv6_fd = -1;
+ m->dns_stub_udp_fd = m->dns_stub_tcp_fd = -1;
m->hostname_fd = -1;
m->llmnr_support = RESOLVE_SUPPORT_YES;
@@ -555,6 +557,10 @@ int manager_start(Manager *m) {
assert(m);
+ r = manager_dns_stub_start(m);
+ if (r < 0)
+ return r;
+
r = manager_llmnr_start(m);
if (r < 0)
return r;
@@ -584,6 +590,11 @@ Manager *manager_free(Manager *m) {
dns_scope_free(m->unicast_scope);
+ /* At this point only orphaned streams should remain. All others should have been freed already by their
+ * owners */
+ while (m->dns_streams)
+ dns_stream_unref(m->dns_streams);
+
hashmap_free(m->links);
hashmap_free(m->dns_transactions);
@@ -595,6 +606,7 @@ Manager *manager_free(Manager *m) {
manager_llmnr_stop(m);
manager_mdns_stop(m);
+ manager_dns_stub_stop(m);
sd_bus_slot_unref(m->prepare_for_sleep_slot);
sd_event_source_unref(m->bus_retry_event_source);
@@ -809,7 +821,14 @@ int manager_write(Manager *m, int fd, DnsPacket *p) {
return 0;
}
-static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) {
+static int manager_ipv4_send(
+ Manager *m,
+ int fd,
+ int ifindex,
+ const struct in_addr *destination,
+ uint16_t port,
+ const struct in_addr *source,
+ DnsPacket *p) {
union sockaddr_union sa = {
.in.sin_family = AF_INET,
};
@@ -822,14 +841,14 @@ static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_ad
assert(m);
assert(fd >= 0);
- assert(addr);
+ assert(destination);
assert(port > 0);
assert(p);
iov.iov_base = DNS_PACKET_DATA(p);
iov.iov_len = p->size;
- sa.in.sin_addr = *addr;
+ sa.in.sin_addr = *destination;
sa.in.sin_port = htobe16(port),
mh.msg_iov = &iov;
@@ -853,12 +872,23 @@ static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_ad
pi = (struct in_pktinfo*) CMSG_DATA(cmsg);
pi->ipi_ifindex = ifindex;
+
+ if (source)
+ pi->ipi_spec_dst = *source;
}
return sendmsg_loop(fd, &mh, 0);
}
-static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_addr *addr, uint16_t port, DnsPacket *p) {
+static int manager_ipv6_send(
+ Manager *m,
+ int fd,
+ int ifindex,
+ const struct in6_addr *destination,
+ uint16_t port,
+ const struct in6_addr *source,
+ DnsPacket *p) {
+
union sockaddr_union sa = {
.in6.sin6_family = AF_INET6,
};
@@ -871,14 +901,14 @@ static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_a
assert(m);
assert(fd >= 0);
- assert(addr);
+ assert(destination);
assert(port > 0);
assert(p);
iov.iov_base = DNS_PACKET_DATA(p);
iov.iov_len = p->size;
- sa.in6.sin6_addr = *addr;
+ sa.in6.sin6_addr = *destination;
sa.in6.sin6_port = htobe16(port),
sa.in6.sin6_scope_id = ifindex;
@@ -903,24 +933,36 @@ static int manager_ipv6_send(Manager *m, int fd, int ifindex, const struct in6_a
pi = (struct in6_pktinfo*) CMSG_DATA(cmsg);
pi->ipi6_ifindex = ifindex;
+
+ if (source)
+ pi->ipi6_addr = *source;
}
return sendmsg_loop(fd, &mh, 0);
}
-int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p) {
+int manager_send(
+ Manager *m,
+ int fd,
+ int ifindex,
+ int family,
+ const union in_addr_union *destination,
+ uint16_t port,
+ const union in_addr_union *source,
+ DnsPacket *p) {
+
assert(m);
assert(fd >= 0);
- assert(addr);
+ assert(destination);
assert(port > 0);
assert(p);
log_debug("Sending %s packet with id %" PRIu16 " on interface %i/%s.", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p), ifindex, af_to_name(family));
if (family == AF_INET)
- return manager_ipv4_send(m, fd, ifindex, &addr->in, port, p);
+ return manager_ipv4_send(m, fd, ifindex, &destination->in, port, &source->in, p);
if (family == AF_INET6)
- return manager_ipv6_send(m, fd, ifindex, &addr->in6, port, p);
+ return manager_ipv6_send(m, fd, ifindex, &destination->in6, port, &source->in6, p);
return -EAFNOSUPPORT;
}
diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h
index 0821904e84..114fec7927 100644
--- a/src/resolve/resolved-manager.h
+++ b/src/resolve/resolved-manager.h
@@ -128,6 +128,13 @@ struct Manager {
Set* etc_hosts_by_address;
Hashmap* etc_hosts_by_name;
usec_t etc_hosts_last, etc_hosts_mtime;
+
+ /* Local DNS stub on 127.0.0.53:53 */
+ int dns_stub_udp_fd;
+ int dns_stub_tcp_fd;
+
+ sd_event_source *dns_stub_udp_event_source;
+ sd_event_source *dns_stub_tcp_event_source;
};
/* Manager */
@@ -140,7 +147,7 @@ int manager_start(Manager *m);
uint32_t manager_find_mtu(Manager *m);
int manager_write(Manager *m, int fd, DnsPacket *p);
-int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *addr, uint16_t port, DnsPacket *p);
+int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p);
int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret);
int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr);
diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c
index 4eb5bba660..31b25ca50f 100644
--- a/src/resolve/resolved-resolv-conf.c
+++ b/src/resolve/resolved-resolv-conf.c
@@ -194,10 +194,13 @@ static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *doma
Iterator i;
fputs("# This file is managed by systemd-resolved(8). Do not edit.\n#\n"
- "# Third party programs must not access this file directly, but\n"
- "# only through the symlink at /etc/resolv.conf. To manage\n"
- "# resolv.conf(5) in a different way, replace the symlink by a\n"
- "# static file or a different symlink.\n\n", f);
+ "# This is a dynamic resolv.conf file for connecting local clients directly to\n"
+ "# all known DNS servers.\n#\n"
+ "# Third party programs must not access this file directly, but only through the\n"
+ "# symlink at /etc/resolv.conf. To manage resolv.conf(5) in a different way,\n"
+ "# replace this symlink by a static file or a different symlink.\n#\n"
+ "# See systemd-resolved.service(8) for details about the supported modes of\n"
+ "# operation for /etc/resolv.conf.\n\n", f);
if (ordered_set_isempty(dns))
fputs("# No DNS servers known.\n", f);
diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c
index 3a47b82d8a..deb75f9ae5 100644
--- a/src/resolve/resolved.c
+++ b/src/resolve/resolved.c
@@ -67,7 +67,11 @@ int main(int argc, char *argv[]) {
goto finish;
}
- r = drop_privileges(uid, gid, 0);
+ /* Drop privileges, but keep three caps. Note that we drop those too, later on (see below) */
+ r = drop_privileges(uid, gid,
+ (UINT64_C(1) << CAP_NET_RAW)| /* needed for SO_BINDTODEVICE */
+ (UINT64_C(1) << CAP_NET_BIND_SERVICE)| /* needed to bind on port 53 */
+ (UINT64_C(1) << CAP_SETPCAP) /* needed in order to drop the caps later */);
if (r < 0)
goto finish;
@@ -88,6 +92,13 @@ int main(int argc, char *argv[]) {
/* Write finish default resolv.conf to avoid a dangling symlink */
(void) manager_write_resolv_conf(m);
+ /* Let's drop the remaining caps now */
+ r = capability_bounding_set_drop(0, true);
+ if (r < 0) {
+ log_error_errno(r, "Failed to drop remaining caps: %m");
+ goto finish;
+ }
+
sd_notify(false,
"READY=1\n"
"STATUS=Processing requests...");