From: Lennart Poettering Date: Thu, 12 Nov 2020 19:47:35 +0000 (+0100) Subject: resolved: include NSID support to DNS stub X-Git-Tag: v248-rc1~86 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4a6eb82445e6b8a1ce1e6c28d4a99eb3747dff2c;p=thirdparty%2Fsystemd.git resolved: include NSID support to DNS stub 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 ``` --- diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index b1a68a74e2e..6db82ba217f 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -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", diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index c7b88310e52..ee069537c3b 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -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, diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index a561035faae..da509a2c96b 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -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) { diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 2e92795f3c1..58845ccf8f6 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -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");