]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: respond to local resolver requests on 127.0.0.53:53
authorLennart Poettering <lennart@poettering.net>
Mon, 20 Jun 2016 22:58:47 +0000 (00:58 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Jun 2016 12:15:23 +0000 (14:15 +0200)
In order to improve compatibility with local clients that speak DNS directly
(and do not use NSS or our bus API) listen locally on 127.0.0.53:53 and process
any queries made that way.

Note that resolved does not implement a full DNS server on this port, but
simply enough to allow normal, local clients to resolve RRs through resolved.
Specifically it does not implement queries without the RD bit set (these are
requests where recursive lookups are explicitly disabled), and neither queries
with DNSSEC DO set in combination with DNSSEC CD (i.e. DNSSEC lookups with
validation turned off). It also refuses zone transfers and obsolete RR types.
All lookups done this way will be rejected with a clean error code, so that the
client side can repeat the query with a reduced feature set.

The code will set the DNSSEC AD flag however, depending on whether the data
resolved has been validated (or comes from a local, trusted source).

Lookups made via this mechanisms are propagated to LLMNR and mDNS as necessary,
but this is only partially useful as DNS packets cannot carry IP scope data
(i.e. the ifindex), and hence link-local addresses returned cannot be used
properly (and given that LLMNR/mDNS are mostly about link-local communication
this is quite a limitation). Also, given that DNS tends to use IDNA for
non-ASCII names, while LLMNR/mDNS uses UTF-8 lookups cannot be mapped 1:1.

In general this should improve compatibility with clients bypassing NSS but
it is highly recommended for clients to instead use NSS or our native bus API.

This patch also beefs up the DnsStream logic, as it reuses the code for local
TCP listening. DnsStream now provides proper reference counting for its
objects.

In order to avoid feedback loops resolved will no silently ignore 127.0.0.53
specified as DNS server when reading configuration.

resolved listens on 127.0.0.53:53 instead of 127.0.0.1:53 in order to leave
the latter free for local, external DNS servers or forwarders.

This also changes the "etc.conf" tmpfiles snippet to create a symlink from
/etc/resolv.conf to /usr/lib/systemd/resolv.conf by default, thus making this
stub the default mode of operation if /etc is not populated.

24 files changed:
Makefile.am
src/resolve/resolv.conf [new file with mode: 0644]
src/resolve/resolved-conf.c
src/resolve/resolved-dns-packet.c
src/resolve/resolved-dns-packet.h
src/resolve/resolved-dns-query.c
src/resolve/resolved-dns-query.h
src/resolve/resolved-dns-rr.h
src/resolve/resolved-dns-scope.c
src/resolve/resolved-dns-server.c
src/resolve/resolved-dns-server.h
src/resolve/resolved-dns-stream.c
src/resolve/resolved-dns-stream.h
src/resolve/resolved-dns-stub.c [new file with mode: 0644]
src/resolve/resolved-dns-stub.h [new file with mode: 0644]
src/resolve/resolved-dns-transaction.c
src/resolve/resolved-link-bus.c
src/resolve/resolved-llmnr.c
src/resolve/resolved-manager.c
src/resolve/resolved-manager.h
src/resolve/resolved-resolv-conf.c
src/resolve/resolved.c
tmpfiles.d/etc.conf.m4
units/systemd-resolved.service.m4.in

index 3c13acf28d0a9bc3bff12757d5e9c6eddb3e0e5e..c7e4c20c49c351d42022aac7031988dfab5384bb 100644 (file)
@@ -125,6 +125,7 @@ dist_systemunit_DATA_busnames =
 dist_sysusers_DATA =
 check_PROGRAMS =
 check_DATA =
+dist_rootlibexec_DATA =
 tests=
 manual_tests =
 TEST_EXTENSIONS = .py
@@ -5147,7 +5148,7 @@ systemd_export_LDADD = \
        $(ZLIB_LIBS) \
        -lbz2
 
-dist_rootlibexec_DATA = \
+dist_rootlibexec_DATA += \
        src/import/import-pubring.gpg
 
 nodist_systemunit_DATA += \
@@ -5259,6 +5260,8 @@ systemd_resolved_SOURCES = \
        src/resolve/resolved-dns-stream.c \
        src/resolve/resolved-dns-trust-anchor.h \
        src/resolve/resolved-dns-trust-anchor.c \
+       src/resolve/resolved-dns-stub.h \
+       src/resolve/resolved-dns-stub.c \
        src/resolve/resolved-etc-hosts.h \
        src/resolve/resolved-etc-hosts.c \
        src/shared/gcrypt-util.c \
@@ -5411,6 +5414,9 @@ EXTRA_DIST += \
        units/systemd-resolved.service.m4.in \
        src/resolve/resolved.conf.in
 
+dist_rootlibexec_DATA += \
+       src/resolve/resolv.conf
+
 # ------------------------------------------------------------------------------
 if ENABLE_NETWORKD
 rootlibexec_PROGRAMS += \
diff --git a/src/resolve/resolv.conf b/src/resolve/resolv.conf
new file mode 100644 (file)
index 0000000..b8034d6
--- /dev/null
@@ -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
index fecf7ecccf4e55f79d4bdee25d12d1ff0933137a..dd233e7c4aa87e67c8d76de0c86ee2b287fec02d 100644 (file)
@@ -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) {
index 2cf07a628bb573092b5606c10ec15b8da21e3bb6..ea0be56d981ddb705a6fdae63381e679ffafbc57 100644 (file)
@@ -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);
index 1216bcb72d2d546e6a543ac99d82eecf9d2d6405..7b7d4e14c9689322cada0137e46e959fe5197bed 100644 (file)
@@ -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 */
index 8578774c37edd0bf291d1f72b847d2e0a34f850f..c8af5579f02f17f67b40fbbd7cec11ee2c7aa085 100644 (file)
@@ -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) {
index 53f48d462b8ac2dedec681f4a48675dfeecad997..49a35b846bdac6265a64e01437e67c6ec13bb757 100644 (file)
@@ -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;
index 8b2d4df9e7c16d682fd150b228c50129d9f1bf89..42d39a1251fb8ddc1dd0ff65a5dbde6e94a49f76 100644 (file)
@@ -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);
index 66e763cb7dc5d47e0ee37d0ad742bff3ca75b03f..ed0c6aa10589a8e6e452f9fb105d3b01d4f1b2c9 100644 (file)
@@ -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;
+                }
         }
 }
 
index bcbfa69aff915669e9df27360e01f1ca32bc692f..7226111c075ea922cb165b524f587868edf8f997 100644 (file)
@@ -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",
index 463c5724a78a31d27979207283e064f7f3aa6bac..7d07fa3e29dd8fbca85b39b85c2f6e1c9fed1543 100644 (file)
@@ -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;
index a1040aeff4594f2465c4c152b51d13d210c80883..dd0e0b90e35ab50d8f8d7d268d8bb16ec48508d1 100644 (file)
@@ -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;
 
index 5ccc842249564ed9860824256324bbc3b80d68d1..e6569678fa4680c8491a07efe8145ab1ae98a347 100644 (file)
@@ -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 (file)
index 0000000..d263ced
--- /dev/null
@@ -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 (file)
index 0000000..fce4d25
--- /dev/null
@@ -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);
index 2d1767be0a6a762f4342eba6a473adb73cf76541..09f60d3e76e5c9cdf2a033a6eeb6ec90e3cbeb07 100644 (file)
@@ -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;
         }
 
index acce8682debfd2f2bdcba29c08443b91ee393254..364812250fad69486e76e0ff04042eda827c3b1a 100644 (file)
@@ -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;
index 8b1d71a3eb331e295bfab02c88ccc5191d274823..3516af58ee6d1e3d02cd51e854d25d9dfb0b32f6 100644 (file)
@@ -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;
 }
 
index e8811fa1d8e30e19720a025312763748d228ba33..30036049da6e58b1775954b73d40322797c728ef 100644 (file)
@@ -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;
 }
index 0821904e8492b953455b44851a654f509e4eccc3..114fec79277298ac10f448b1a876b73698614641 100644 (file)
@@ -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);
index 4eb5bba6607edf73b4318af7d1a9625134ca3456..31b25ca50f2e4fb64ef1fa0aa9c6d4435af2b2d4 100644 (file)
@@ -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);
index 3a47b82d8a670b58a2ccbe36183b9983ca24982d..deb75f9ae54842c25c716a3bbfea058a8a3d14fb 100644 (file)
@@ -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...");
index ef7b9b954178ee7092a9028fa7da911409c9f79f..064eae94f195fee3ce18182a5ae370c53bb5b857 100644 (file)
@@ -14,7 +14,7 @@ m4_ifdef(`HAVE_SMACK_RUN_LABEL',
 t /etc/mtab - - - - security.SMACK64=_
 )m4_dnl
 m4_ifdef(`ENABLE_RESOLVED',
-L! /etc/resolv.conf - - - - ../run/systemd/resolve/resolv.conf
+L! /etc/resolv.conf - - - - ../usr/lib/systemd/resolv.conf
 )m4_dnl
 C /etc/nsswitch.conf - - - -
 m4_ifdef(`HAVE_PAM',
index a9cc3988edb8eba210104388970c4144c134ee3d..15ab56a066865ffd4d90e47af3cadf08d32e6145 100644 (file)
@@ -23,7 +23,7 @@ Type=notify
 Restart=always
 RestartSec=0
 ExecStart=@rootlibexecdir@/systemd-resolved
-CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER
+CapabilityBoundingSet=CAP_SETUID CAP_SETGID CAP_SETPCAP CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_NET_RAW CAP_NET_BIND_SERVICE
 ProtectSystem=full
 ProtectHome=yes
 WatchdogSec=3min