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
```
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;
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
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)
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;
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",
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);
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,
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) {
#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,
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,
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)
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");
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");