]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: include NSID support to DNS stub
authorLennart Poettering <lennart@poettering.net>
Thu, 12 Nov 2020 19:47:35 +0000 (20:47 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 17 Feb 2021 08:42:07 +0000 (09:42 +0100)
This adds minimal support for RFC5001 NSID to the stub resolver. This
useful to identify systemd-resolved when talking to the stub resolver,
and distuingishing the packets resolved answers itself (where NSID is
now set) from those which it proxies 1:1 upstream (where NSID will not
be set, or set to whatever the upstream server has it set to).

The NSID chosen consist of two parts:

1. The first part is derived from /etc/machine-id and identifies the
   resolved instance in a stable way.

2. The second part is the fixed string ".resolved.systemd.io".

This thus maybe used for a veriety of checks:

a. Am I talking to a resolved stub?
b. Am I talking to the same stub as last time?
c. Am I talking to the local resolved?

Given that the first part leaks the identity of the system in away two
protections are in place:

I) The NSID is only included on the main stub, not the extra stub. The
   main stub has with a TTL of 1 and other protections a lot of safety
   in place that the datagrams never leave the local system, thus the
   identifying info is only accessible to the local system — but
   /etc/machine-id is accessible to local software anyway.

II) The NSID is hashed from /etc/machine-id in a non-invertable way, so
    that the machine ID itself isn't leaked, but only an identifier
    derived from it.

Example dig run:

```
$ dig +nsid localhost @127.0.0.53

; <<>> DiG 9.11.23-RedHat-9.11.23-1.fc33 <<>> +nsid localhost @127.0.0.53
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 46917
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
; NSID: 35 33 64 34 61 34 66 63 32 31 32 65 34 31 61 30 39 66 30 39 65 33 32 34 63 64 64 38 30 36 32 33 2e 72 65 73 6f 6c 76 65 64 2e 73 79 73 74 65 6d 64 2e 69 6f ("53d4a4fc212e41a09f09e324cdd80623.resolved.systemd.io")
;; QUESTION SECTION:
;localhost. IN A

;; ANSWER SECTION:
localhost. 0 IN A 127.0.0.1

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Do Nov 12 20:57:16 CET 2020
;; MSG SIZE  rcvd: 110
```

src/resolve/resolved-dns-packet.c
src/resolve/resolved-dns-packet.h
src/resolve/resolved-dns-server.c
src/resolve/resolved-dns-stub.c

index b1a68a74e2ead9c2562e2749ca4b1492419f1d28..6db82ba217fd876436930acda44726ebe79df8eb 100644 (file)
@@ -726,8 +726,9 @@ int dns_packet_append_opt(
                 uint16_t max_udp_size,
                 bool edns0_do,
                 bool include_rfc6975,
+                const char *nsid,
                 int rcode,
-                size_t *start) {
+                size_t *ret_start) {
 
         size_t saved_size;
         int r;
@@ -770,7 +771,6 @@ int dns_packet_append_opt(
         if (r < 0)
                 goto fail;
 
-        /* RDLENGTH */
         if (edns0_do && include_rfc6975) {
                 /* If DO is on and this is requested, also append RFC6975 Algorithm data. This is supposed to
                  * be done on queries, not on replies, hencer callers should turn this off when finishing off
@@ -805,11 +805,32 @@ int dns_packet_append_opt(
                         NSEC3_ALGORITHM_SHA1,
                 };
 
-                r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL);
+                r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); /* RDLENGTH */
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); /* the payload, as defined above */
+
+        } else if (nsid) {
+
+                if (strlen(nsid) > UINT16_MAX - 4) {
+                        r = -E2BIG;
+                        goto fail;
+                }
+
+                r = dns_packet_append_uint16(p, 4 + strlen(nsid), NULL); /* RDLENGTH */
                 if (r < 0)
                         goto fail;
 
-                r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL);
+                r = dns_packet_append_uint16(p, 3, NULL); /* OPTION-CODE: NSID */
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_uint16(p, strlen(nsid), NULL); /* OPTION-LENGTH */
+                if (r < 0)
+                        goto fail;
+
+                r = dns_packet_append_blob(p, nsid, strlen(nsid), NULL);
         } else
                 r = dns_packet_append_uint16(p, 0, NULL);
         if (r < 0)
@@ -820,8 +841,8 @@ int dns_packet_append_opt(
         p->opt_start = saved_size;
         p->opt_size = p->size - saved_size;
 
-        if (start)
-                *start = saved_size;
+        if (ret_start)
+                *ret_start = saved_size;
 
         return 0;
 
@@ -2559,6 +2580,52 @@ bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b) {
         return dns_packet_compare_func(a, b) == 0;
 }
 
+int dns_packet_has_nsid_request(DnsPacket *p) {
+        bool has_nsid = false;
+        const uint8_t *d;
+        size_t l;
+
+        assert(p);
+
+        if (!p->opt)
+                return false;
+
+        d = p->opt->opt.data;
+        l = p->opt->opt.data_size;
+
+        while (l > 0) {
+                uint16_t code, length;
+
+                if (l < 4U)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                               "EDNS0 variable part has invalid size.");
+
+                code = unaligned_read_be16(d);
+                length = unaligned_read_be16(d + 2);
+
+                if (l < 4U + length)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                               "Truncated option in EDNS0 variable part.");
+
+                if (code == 3) {
+                        if (has_nsid)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                                       "Duplicate NSID option in EDNS0 variable part.");
+
+                        if (length != 0)
+                                return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                                       "Non-empty NSID option in DNS request.");
+
+                        has_nsid = true;
+                }
+
+                d += 4U + length;
+                l -= 4U + length;
+        }
+
+        return has_nsid;
+}
+
 static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
         [DNS_RCODE_SUCCESS] = "SUCCESS",
         [DNS_RCODE_FORMERR] = "FORMERR",
index c7b88310e521c17aa55841ec41873af52d09bebe..ee069537c3ba7eee6d2a9810e16f4831f5122823 100644 (file)
@@ -201,7 +201,7 @@ int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonica
 int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, bool canonical_candidate, size_t *start);
 int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, const DnsAnswerFlags flags, size_t *start);
 int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAnswerFlags flags, size_t *start, size_t *rdata_start);
-int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, bool include_rfc6975, int rcode, size_t *start);
+int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, bool include_rfc6975, const char *nsid, int rcode, size_t *ret_start);
 int dns_packet_append_question(DnsPacket *p, DnsQuestion *q);
 int dns_packet_append_answer(DnsPacket *p, DnsAnswer *a, unsigned *completed);
 
@@ -229,6 +229,8 @@ int dns_packet_extract(DnsPacket *p);
 
 bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b);
 
+int dns_packet_has_nsid_request(DnsPacket *p);
+
 /* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */
 enum {
         DNS_RCODE_SUCCESS = 0,
index a561035faae1060cbdc0f24bd6c948d8e87771bb..da509a2c96b7eb63aab04bd5f6d85b7f469926d7 100644 (file)
@@ -587,7 +587,7 @@ int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeature
         else
                 packet_size = server->received_udp_packet_max;
 
-        return dns_packet_append_opt(packet, packet_size, edns_do, /* include_rfc6975 = */ true, 0, NULL);
+        return dns_packet_append_opt(packet, packet_size, edns_do, /* include_rfc6975 = */ true, NULL, 0, NULL);
 }
 
 int dns_server_ifindex(const DnsServer *s) {
index 2e92795f3c1800fee9de1183224ac415001c945e..58845ccf8f6ea540b8f1963f62ee4e84e28e05c1 100644 (file)
@@ -9,6 +9,7 @@
 #include "resolved-dns-stub.h"
 #include "socket-netlink.h"
 #include "socket-util.h"
+#include "stdio-util.h"
 #include "string-table.h"
 
 /* The MTU of the loopback device is 64K on Linux, advertise that as maximum datagram size, but subtract the Ethernet,
@@ -393,6 +394,34 @@ static int dns_stub_add_reply_packet_body(
         return 0;
 }
 
+static const char *nsid_string(void) {
+        static char buffer[SD_ID128_STRING_MAX + STRLEN(".resolved.systemd.io")] = "";
+        sd_id128_t id;
+        int r;
+
+        /* Let's generate a string that we can use as RFC5001 NSID identifier. The string shall identify us
+         * as systemd-resolved, and return a different string for each resolved instance without leaking host
+         * identity. Hence let's use a fixed suffix that identifies resolved, and a prefix generated from the
+         * machine ID but from which the machine ID cannot be determined.
+         *
+         * Clients can use this to determine whether an answer is originating locally or is proxied from
+         * upstream. */
+
+        if (!isempty(buffer))
+                return buffer;
+
+        r = sd_id128_get_machine_app_specific(
+                        SD_ID128_MAKE(ed,d3,12,5d,16,b9,41,f9,a1,49,5f,ab,15,62,ab,27),
+                        &id);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to determine machine ID, igoring: %m");
+                return NULL;
+        }
+
+        xsprintf(buffer, SD_ID128_FORMAT_STR ".resolved.systemd.io", SD_ID128_FORMAT_VAL(id));
+        return buffer;
+}
+
 static int dns_stub_finish_reply_packet(
                 DnsPacket *p,
                 uint16_t id,
@@ -402,14 +431,15 @@ static int dns_stub_finish_reply_packet(
                 bool edns0_do,  /* set the EDNS0 DNSSEC OK bit? */
                 bool ad,        /* set the DNSSEC authenticated data bit? */
                 bool cd,        /* set the DNSSEC checking disabled bit? */
-                uint16_t max_udp_size) { /* The maximum UDP datagram size to advertise to clients */
+                uint16_t max_udp_size, /* The maximum UDP datagram size to advertise to clients */
+                bool nsid) {    /* whether to add NSID */
 
         int r;
 
         assert(p);
 
         if (add_opt) {
-                r = dns_packet_append_opt(p, max_udp_size, edns0_do, /* include_rfc6975 = */ false, rcode, NULL);
+                r = dns_packet_append_opt(p, max_udp_size, edns0_do, /* include_rfc6975 = */ false, nsid ? nsid_string() : NULL, rcode, NULL);
                 if (r == -EMSGSIZE) /* Hit the size limit? then indicate truncation */
                         tc = true;
                 else if (r < 0)
@@ -529,7 +559,8 @@ static int dns_stub_send_reply(
                         edns0_do,
                         DNS_PACKET_AD(q->request_packet) && dns_query_fully_authenticated(q),
                         DNS_PACKET_CD(q->request_packet),
-                        q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
+                        q->stub_listener_extra ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX,
+                        dns_packet_has_nsid_request(q->request_packet) > 0 && !q->stub_listener_extra);
         if (r < 0)
                 return log_debug_errno(r, "Failed to build failure packet: %m");
 
@@ -568,7 +599,8 @@ static int dns_stub_send_failure(
                         DNS_PACKET_DO(p),
                         DNS_PACKET_AD(p) && authenticated,
                         DNS_PACKET_CD(p),
-                        l ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX);
+                        l ? ADVERTISE_EXTRA_DATAGRAM_SIZE_MAX : ADVERTISE_DATAGRAM_SIZE_MAX,
+                        dns_packet_has_nsid_request(p) > 0 && !l);
         if (r < 0)
                 return log_debug_errno(r, "Failed to build failure packet: %m");