summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2015-11-27 19:29:04 +0100
committerLennart Poettering <lennart@poettering.net>2015-11-27 19:29:04 +0100
commite900a8467962da3483dd7b62cc7f41043a4584cf (patch)
tree11dd69aab0bee6ffdee4d7562f03c5ce5b977040
parentf5edf80e297e4ba499db57779af2f121922f372a (diff)
parentd74fb368b18f0fbd9a4fe6f15691bbea7f3c4a01 (diff)
Merge pull request #2043 from teg/resolved-edns0-5
resolved: add edns0 support
-rw-r--r--src/resolve/resolved-dns-packet.c59
-rw-r--r--src/resolve/resolved-dns-packet.h6
-rw-r--r--src/resolve/resolved-dns-scope.c36
-rw-r--r--src/resolve/resolved-dns-scope.h2
-rw-r--r--src/resolve/resolved-dns-server.c118
-rw-r--r--src/resolve/resolved-dns-server.h31
-rw-r--r--src/resolve/resolved-dns-transaction.c51
-rw-r--r--src/resolve/resolved-dns-transaction.h3
8 files changed, 283 insertions, 23 deletions
diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c
index 40b662246f..75ca23fd08 100644
--- a/src/resolve/resolved-dns-packet.c
+++ b/src/resolve/resolved-dns-packet.c
@@ -28,6 +28,8 @@
#include "utf8.h"
#include "util.h"
+#define EDNS0_OPT_DO (1<<15)
+
int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
DnsPacket *p;
size_t a;
@@ -267,7 +269,7 @@ static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start
return 0;
}
-static void dns_packet_truncate(DnsPacket *p, size_t sz) {
+void dns_packet_truncate(DnsPacket *p, size_t sz) {
Iterator i;
char *s;
void *n;
@@ -609,6 +611,57 @@ fail:
return r;
}
+/* Append the OPT pseudo-RR described in RFC6891 */
+int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
+ size_t saved_size;
+ int r;
+
+ assert(p);
+ /* we must never advertise supported packet size smaller than the legacy max */
+ assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
+
+ saved_size = p->size;
+
+ /* empty name */
+ r = dns_packet_append_uint8(p, 0, NULL);
+ if (r < 0)
+ return r;
+
+ /* type */
+ r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL);
+ if (r < 0)
+ goto fail;
+
+ /* maximum udp packet that can be received */
+ r = dns_packet_append_uint16(p, max_udp_size, NULL);
+ if (r < 0)
+ goto fail;
+
+ /* extended RCODE and VERSION */
+ r = dns_packet_append_uint16(p, 0, NULL);
+ if (r < 0)
+ goto fail;
+
+ /* flags: DNSSEC OK (DO), see RFC3225 */
+ r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL);
+ if (r < 0)
+ goto fail;
+
+ /* RDLENGTH */
+ r = dns_packet_append_uint16(p, 0, NULL);
+ if (r < 0)
+ goto fail;
+
+ if (start)
+ *start = saved_size;
+
+ return 0;
+
+fail:
+ dns_packet_truncate(p, saved_size);
+ return r;
+}
+
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start) {
size_t saved_size, rdlength_offset, end, rdlength;
int r;
@@ -1450,6 +1503,10 @@ int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, size_t *start) {
r = dns_packet_read_name(p, &rr->ptr.name, true, NULL);
break;
+ case DNS_TYPE_OPT: /* we only care about the header */
+ r = 0;
+ break;
+
case DNS_TYPE_HINFO:
r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL);
if (r < 0)
diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h
index 90b5a7c8bd..25dfb2642f 100644
--- a/src/resolve/resolved-dns-packet.h
+++ b/src/resolve/resolved-dns-packet.h
@@ -65,6 +65,9 @@ struct DnsPacketHeader {
/* RFC 1035 say 512 is the maximum, for classic unicast DNS */
#define DNS_PACKET_UNICAST_SIZE_MAX 512
+/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */
+#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096
+
#define DNS_PACKET_SIZE_START 512
struct DnsPacket {
@@ -160,6 +163,9 @@ int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, size_t *start
int dns_packet_append_name(DnsPacket *p, const char *name, bool allow_compression, size_t *start);
int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *key, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start);
+int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
+
+void dns_packet_truncate(DnsPacket *p, size_t sz);
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start);
int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start);
diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c
index fc4ae57ce0..09e6872d26 100644
--- a/src/resolve/resolved-dns-scope.c
+++ b/src/resolve/resolved-dns-scope.c
@@ -158,12 +158,13 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) {
s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC);
}
-int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) {
+int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
union in_addr_union addr;
int ifindex = 0, r;
int family;
uint16_t port;
uint32_t mtu;
+ size_t saved_size = 0;
assert(s);
assert(p);
@@ -178,9 +179,29 @@ int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) {
switch (s->protocol) {
case DNS_PROTOCOL_DNS:
+ assert(server);
+
if (DNS_PACKET_QDCOUNT(p) > 1)
return -EOPNOTSUPP;
+ if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_EDNS0) {
+ bool edns_do;
+ size_t packet_size;
+
+ edns_do = server->possible_features >= DNS_SERVER_FEATURE_LEVEL_DO;
+
+ if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_LARGE)
+ packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
+ else
+ packet_size = server->received_udp_packet_max;
+
+ r = dns_packet_append_opt_rr(p, packet_size, edns_do, &saved_size);
+ if (r < 0)
+ return r;
+
+ DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) + 1);
+ }
+
if (p->size > DNS_PACKET_UNICAST_SIZE_MAX)
return -EMSGSIZE;
@@ -191,6 +212,12 @@ int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p) {
if (r < 0)
return r;
+ if (saved_size > 0) {
+ dns_packet_truncate(p, saved_size);
+
+ DNS_PACKET_HEADER(p)->arcount = htobe16(be16toh(DNS_PACKET_HEADER(p)->arcount) - 1);
+ }
+
break;
case DNS_PROTOCOL_LLMNR:
@@ -243,6 +270,11 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad
if (!srv)
return -ESRCH;
+ srv->possible_features = dns_server_possible_features(srv);
+
+ if (type == SOCK_DGRAM && srv->possible_features < DNS_SERVER_FEATURE_LEVEL_UDP)
+ return -EAGAIN;
+
sa.sa.sa_family = srv->family;
if (srv->family == AF_INET) {
sa.in.sin_port = htobe16(port);
@@ -734,7 +766,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata
return 0;
}
- r = dns_scope_emit(scope, -1, p);
+ r = dns_scope_emit(scope, -1, NULL, p);
if (r < 0)
log_debug_errno(r, "Failed to send conflict packet: %m");
}
diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h
index 7876410b7d..0480f702f8 100644
--- a/src/resolve/resolved-dns-scope.h
+++ b/src/resolve/resolved-dns-scope.h
@@ -80,7 +80,7 @@ DnsScope* dns_scope_free(DnsScope *s);
void dns_scope_packet_received(DnsScope *s, usec_t rtt);
void dns_scope_packet_lost(DnsScope *s, usec_t usec);
-int dns_scope_emit(DnsScope *s, int fd, DnsPacket *p);
+int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p);
int dns_scope_tcp_socket(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server);
int dns_scope_udp_dns_socket(DnsScope *s, DnsServer **server);
diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c
index 0ebd22fe22..d565f99c09 100644
--- a/src/resolve/resolved-dns-server.c
+++ b/src/resolve/resolved-dns-server.c
@@ -23,12 +23,20 @@
#include "resolved-dns-server.h"
#include "resolved-resolv-conf.h"
#include "siphash24.h"
+#include "string-table.h"
#include "string-util.h"
/* After how much time to repeat classic DNS requests */
#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC)
#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC)
+/* The amount of time to wait before retrying with a full feature set */
+#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR)
+#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE)
+
+/* The number of times we will attempt a certain feature set before degrading */
+#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3
+
int dns_server_new(
Manager *m,
DnsServer **ret,
@@ -60,6 +68,10 @@ int dns_server_new(
s->n_ref = 1;
s->manager = m;
+ s->verified_features = _DNS_SERVER_FEATURE_LEVEL_INVALID;
+ s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
+ s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC;
+ s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->type = type;
s->family = family;
s->address = *in_addr;
@@ -212,18 +224,43 @@ void dns_server_move_back_and_unmark(DnsServer *s) {
}
}
-void dns_server_packet_received(DnsServer *s, usec_t rtt) {
+void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size) {
assert(s);
- if (rtt <= s->max_rtt)
- return;
+ if (features == DNS_SERVER_FEATURE_LEVEL_LARGE) {
+ /* even if we successfully receive a reply to a request announcing
+ support for large packets, that does not mean we can necessarily
+ receive large packets. */
+ if (s->verified_features < DNS_SERVER_FEATURE_LEVEL_LARGE - 1) {
+ s->verified_features = DNS_SERVER_FEATURE_LEVEL_LARGE - 1;
+ assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
+ }
+ } else if (s->verified_features < features) {
+ s->verified_features = features;
+ assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0);
+ }
+
+ if (s->possible_features == features)
+ s->n_failed_attempts = 0;
+
+ /* Remember the size of the largest UDP packet we received from a server,
+ we know that we can always announce support for packets with at least
+ this size. */
+ if (s->received_udp_packet_max < size)
+ s->received_udp_packet_max = size;
- s->max_rtt = rtt;
- s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC);
+ if (s->max_rtt < rtt) {
+ s->max_rtt = rtt;
+ s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC);
+ }
}
-void dns_server_packet_lost(DnsServer *s, usec_t usec) {
+void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec) {
assert(s);
+ assert(s->manager);
+
+ if (s->possible_features == features)
+ s->n_failed_attempts ++;
if (s->resend_timeout > usec)
return;
@@ -231,6 +268,66 @@ void dns_server_packet_lost(DnsServer *s, usec_t usec) {
s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC);
}
+void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features) {
+ assert(s);
+ assert(s->manager);
+
+ if (s->possible_features != features)
+ return;
+
+ s->n_failed_attempts = (unsigned) -1;
+}
+
+static bool dns_server_grace_period_expired(DnsServer *s) {
+ usec_t ts;
+
+ assert(s);
+ assert(s->manager);
+
+ if (s->verified_usec == 0)
+ return false;
+
+ assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0);
+
+ if (s->verified_usec + s->features_grace_period_usec > ts)
+ return false;
+
+ s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC);
+
+ return true;
+}
+
+DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) {
+ assert(s);
+
+ if (s->possible_features != DNS_SERVER_FEATURE_LEVEL_BEST &&
+ dns_server_grace_period_expired(s)) {
+ _cleanup_free_ char *ip = NULL;
+
+ s->possible_features = DNS_SERVER_FEATURE_LEVEL_BEST;
+ s->n_failed_attempts = 0;
+ s->verified_usec = 0;
+
+ in_addr_to_string(s->family, &s->address, &ip);
+ log_info("Grace period over, resuming full feature set for DNS server %s", strna(ip));
+ } else if (s->possible_features <= s->verified_features)
+ s->possible_features = s->verified_features;
+ else if (s->n_failed_attempts >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS &&
+ s->possible_features > DNS_SERVER_FEATURE_LEVEL_WORST) {
+ _cleanup_free_ char *ip = NULL;
+
+ s->possible_features --;
+ s->n_failed_attempts = 0;
+ s->verified_usec = 0;
+
+ in_addr_to_string(s->family, &s->address, &ip);
+ log_warning("Using degraded feature set (%s) for DNS server %s",
+ dns_server_feature_level_to_string(s->possible_features), strna(ip));
+ }
+
+ return s->possible_features;
+}
+
static void dns_server_hash_func(const void *p, struct siphash *state) {
const DnsServer *s = p;
@@ -392,3 +489,12 @@ void manager_next_dns_server(Manager *m) {
else
manager_set_dns_server(m, m->dns_servers);
}
+
+static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = {
+ [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP",
+ [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP",
+ [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0",
+ [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO",
+ [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE",
+};
+DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel);
diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h
index 3a78d4a3b5..00366a48c9 100644
--- a/src/resolve/resolved-dns-server.h
+++ b/src/resolve/resolved-dns-server.h
@@ -31,8 +31,24 @@ typedef enum DnsServerType {
DNS_SERVER_LINK,
} DnsServerType;
-#include "resolved-manager.h"
+typedef enum DnsServerFeatureLevel {
+ DNS_SERVER_FEATURE_LEVEL_TCP,
+ DNS_SERVER_FEATURE_LEVEL_UDP,
+ DNS_SERVER_FEATURE_LEVEL_EDNS0,
+ DNS_SERVER_FEATURE_LEVEL_DO,
+ DNS_SERVER_FEATURE_LEVEL_LARGE,
+ _DNS_SERVER_FEATURE_LEVEL_MAX,
+ _DNS_SERVER_FEATURE_LEVEL_INVALID = -1
+} DnsServerFeatureLevel;
+
+#define DNS_SERVER_FEATURE_LEVEL_WORST 0
+#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1)
+
+const char* dns_server_feature_level_to_string(int i) _const_;
+int dns_server_feature_level_from_string(const char *s) _pure_;
+
#include "resolved-link.h"
+#include "resolved-manager.h"
struct DnsServer {
Manager *manager;
@@ -49,6 +65,12 @@ struct DnsServer {
usec_t max_rtt;
bool marked:1;
+ DnsServerFeatureLevel verified_features;
+ DnsServerFeatureLevel possible_features;
+ size_t received_udp_packet_max;
+ unsigned n_failed_attempts;
+ usec_t verified_usec;
+ usec_t features_grace_period_usec;
/* If linked is set, then this server appears in the servers linked list */
bool linked:1;
@@ -69,8 +91,9 @@ DnsServer* dns_server_unref(DnsServer *s);
void dns_server_unlink(DnsServer *s);
void dns_server_move_back_and_unmark(DnsServer *s);
-void dns_server_packet_received(DnsServer *s, usec_t rtt);
-void dns_server_packet_lost(DnsServer *s, usec_t usec);
+void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size);
+void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec);
+void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features);
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr);
@@ -86,4 +109,6 @@ void manager_next_dns_server(Manager *m);
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
+DnsServerFeatureLevel dns_server_possible_features(DnsServer *s);
+
extern const struct hash_ops dns_server_hash_ops;
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 8c4f23a4da..90133cb332 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -418,7 +418,22 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
case DNS_PROTOCOL_DNS:
assert(t->server);
- dns_server_packet_received(t->server, ts - t->start_usec);
+ if (IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) {
+
+ /* request failed, immediately try again with reduced features */
+ log_debug("Server returned error: %s", dns_rcode_to_string(DNS_PACKET_RCODE(p)));
+
+ dns_server_packet_failed(t->server, t->current_features);
+
+ r = dns_transaction_go(t);
+ if (r < 0) {
+ dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
+ return;
+ }
+
+ return;
+ } else
+ dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size);
break;
case DNS_PROTOCOL_LLMNR:
@@ -530,10 +545,13 @@ static int dns_transaction_emit(DnsTransaction *t) {
t->server = dns_server_ref(server);
}
- r = dns_scope_emit(t->scope, t->dns_udp_fd, t->sent);
+ r = dns_scope_emit(t->scope, t->dns_udp_fd, t->server, t->sent);
if (r < 0)
return r;
+ if (t->server)
+ t->current_features = t->server->possible_features;
+
return 0;
}
@@ -544,15 +562,26 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
assert(s);
assert(t);
- /* Timeout reached? Try again, with a new server */
- dns_transaction_next_dns_server(t);
+ /* Timeout reached? Increase the timeout for the server used */
+ switch (t->scope->protocol) {
+ case DNS_PROTOCOL_DNS:
+ assert(t->server);
- /* ... and possibly increased timeout */
- if (t->server)
- dns_server_packet_lost(t->server, usec - t->start_usec);
- else
+ dns_server_packet_lost(t->server, t->current_features, usec - t->start_usec);
+
+ break;
+ case DNS_PROTOCOL_LLMNR:
+ case DNS_PROTOCOL_MDNS:
dns_scope_packet_lost(t->scope, usec - t->start_usec);
+ break;
+ default:
+ assert_not_reached("Invalid DNS protocol.");
+ }
+
+ /* ...and try again with a new server */
+ dns_transaction_next_dns_server(t);
+
r = dns_transaction_go(t);
if (r < 0)
dns_transaction_complete(t, DNS_TRANSACTION_RESOURCES);
@@ -734,11 +763,13 @@ int dns_transaction_go(DnsTransaction *t) {
* always be made via TCP on LLMNR */
r = dns_transaction_open_tcp(t);
} else {
- /* Try via UDP, and if that fails due to large size try via TCP */
+ /* Try via UDP, and if that fails due to large size or lack of
+ * support try via TCP */
r = dns_transaction_emit(t);
- if (r == -EMSGSIZE)
+ if (r == -EMSGSIZE || r == -EAGAIN)
r = dns_transaction_open_tcp(t);
}
+
if (r == -ESRCH) {
/* No servers to send this to? */
dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h
index ee80dcf5a9..5778913cc8 100644
--- a/src/resolve/resolved-dns-transaction.h
+++ b/src/resolve/resolved-dns-transaction.h
@@ -79,6 +79,9 @@ struct DnsTransaction {
/* The active server */
DnsServer *server;
+ /* the features of the DNS server at time of transaction start */
+ DnsServerFeatureLevel current_features;
+
/* TCP connection logic, if we need it */
DnsStream *stream;