From be808ea083fa07271116b4519c3c27fd20c5f077 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 6 Jul 2015 08:15:25 +0200 Subject: resolved: fallback to TCP if UDP fails This is inspired by the logic in BIND [0], follow-up patches will implement the reset of that scheme. If we get a server error back, or if after several attempts we don't get a reply at all, we switch from UDP to TCP for the given server for the current and all subsequent requests. However, if we ever successfully received a reply over UDP, we never fall back to TCP, and once a grace-period has passed, we try to upgrade again to using UDP. The grace-period starts off at five minutes after the current feature level was verified and then grows exponentially to six hours. This is to mitigate problems due to temporary lack of network connectivity, but at the same time avoid flooding the network with retries when the feature attempted feature level genuinely does not work. Note that UDP is likely much more commonly supported than TCP, but depending on the path between the client and the server, we may have more luck with TCP in case something is wrong. We really do prefer UDP though, as that is much more lightweight, that is why TCP is only the last resort. [0]: --- src/resolve/resolved-dns-scope.c | 5 ++ src/resolve/resolved-dns-server.c | 90 +++++++++++++++++++++++++++++++--- src/resolve/resolved-dns-server.h | 26 ++++++++-- src/resolve/resolved-dns-transaction.c | 34 +++++++++---- src/resolve/resolved-dns-transaction.h | 3 ++ 5 files changed, 140 insertions(+), 18 deletions(-) diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index fc4ae57ce0..a8c0ae1569 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -243,6 +243,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); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 0ebd22fe22..5fc7c3187f 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,9 @@ 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->type = type; s->family = family; s->address = *in_addr; @@ -212,18 +223,29 @@ 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) { assert(s); - if (rtt <= s->max_rtt) - return; + 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; - 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 +253,56 @@ void dns_server_packet_lost(DnsServer *s, usec_t usec) { s->resend_timeout = MIN(s->resend_timeout * 2, DNS_TIMEOUT_MAX_USEC); } +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 +464,9 @@ 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", +}; +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..f82b14e8fb 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -31,8 +31,21 @@ 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_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 +62,11 @@ struct DnsServer { usec_t max_rtt; bool marked:1; + DnsServerFeatureLevel verified_features; + DnsServerFeatureLevel possible_features; + 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 +87,8 @@ 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); +void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t usec); DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr); @@ -86,4 +104,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..0e09a339aa 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -418,7 +418,7 @@ 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); + dns_server_packet_received(t->server, t->current_features, ts - t->start_usec); break; case DNS_PROTOCOL_LLMNR: @@ -534,6 +534,9 @@ static int dns_transaction_emit(DnsTransaction *t) { if (r < 0) return r; + if (t->server) + t->current_features = t->server->possible_features; + return 0; } @@ -544,15 +547,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 +748,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; -- cgit v1.2.3-54-g00ecf From 4e0b8b17a7465653f4e7b819dad5f8e30d64c0c4 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Thu, 16 Jul 2015 14:39:55 +0200 Subject: resolved: degrade the feature level on explicit failure Previously, we would only degrade on packet loss, but when adding EDNS0 support, we also have to handle the case where the server replies with an explicit error. --- src/resolve/resolved-dns-server.c | 10 ++++++++++ src/resolve/resolved-dns-server.h | 1 + src/resolve/resolved-dns-transaction.c | 17 ++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 5fc7c3187f..c5396a03c8 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -253,6 +253,16 @@ void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t 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; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index f82b14e8fb..a3e8cbcc52 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -89,6 +89,7 @@ void dns_server_move_back_and_unmark(DnsServer *s); void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt); 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); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 0e09a339aa..4398c2cb99 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, t->current_features, 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); break; case DNS_PROTOCOL_LLMNR: -- cgit v1.2.3-54-g00ecf From dc913c9a1f243bca291d47b1a5d8e270c471d113 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 13 Jul 2015 01:51:03 +0200 Subject: resolved: rr - add OPT pseudo-rr support Needed for EDNS0. --- src/resolve/resolved-dns-packet.c | 55 +++++++++++++++++++++++++++++++++++++++ src/resolve/resolved-dns-packet.h | 1 + 2 files changed, 56 insertions(+) diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 40b662246f..49c36e38f3 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -609,6 +609,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, 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 */ + r = dns_packet_append_uint16(p, 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 +1501,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..ca94a208ba 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -160,6 +160,7 @@ 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, size_t *start); 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); -- cgit v1.2.3-54-g00ecf From 9c5e12a4314e7192e834e1b855e5e80111e636a6 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Tue, 23 Jun 2015 23:06:09 +0200 Subject: resolved: implement minimal EDNS0 support This is a minimal implementation of RFC6891. Only default values are used, so in reality this will be a noop. EDNS0 support is dependent on the current server's feature level, so appending the OPT pseudo RR is done when the packet is emitted, rather than when it is assembled. To handle different feature levels on retransmission, we strip off the OPT RR again after sending the packet. Similarly, to how we fall back to TCP if UDP fails, we fall back to plain UDP if EDNS0 fails (but if EDNS0 ever succeeded we never fall back again, and after a timeout we will retry EDNS0). --- src/resolve/resolved-dns-packet.c | 2 +- src/resolve/resolved-dns-packet.h | 2 ++ src/resolve/resolved-dns-scope.c | 21 +++++++++++++++++++-- src/resolve/resolved-dns-scope.h | 2 +- src/resolve/resolved-dns-server.c | 1 + src/resolve/resolved-dns-server.h | 1 + src/resolve/resolved-dns-transaction.c | 2 +- 7 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 49c36e38f3..cb713f1f85 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -267,7 +267,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; diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index ca94a208ba..385a8af796 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -162,6 +162,8 @@ 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, 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); int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index a8c0ae1569..42478e41e2 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,19 @@ 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) { + r = dns_packet_append_opt_rr(p, DNS_PACKET_UNICAST_SIZE_MAX, &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 +202,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: @@ -739,7 +756,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 c5396a03c8..f8c921e4c8 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -478,5 +478,6 @@ void manager_next_dns_server(Manager *m) { 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", }; 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 a3e8cbcc52..e9b425430f 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -34,6 +34,7 @@ typedef enum DnsServerType { typedef enum DnsServerFeatureLevel { DNS_SERVER_FEATURE_LEVEL_TCP, DNS_SERVER_FEATURE_LEVEL_UDP, + DNS_SERVER_FEATURE_LEVEL_EDNS0, _DNS_SERVER_FEATURE_LEVEL_MAX, _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 } DnsServerFeatureLevel; diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index 4398c2cb99..f81461a4fe 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -545,7 +545,7 @@ 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; -- cgit v1.2.3-54-g00ecf From 7586f4d172dd9c3ccc3126fc47dca9e49adec132 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Wed, 24 Jun 2015 15:08:40 +0200 Subject: resolved: set the DNSSEC OK (DO) flag This indicates that we can handle DNSSEC records (per RFC3225), even if all we do is silently drop them. This feature requires EDNS0 support. As we do not yet support larger UDP packets, this feature increases the risk of getting truncated packets. Similarly to how we fall back to plain UDP if EDNS0 fails, we will fall back to plain EDNS0 if EDNS0+DO fails (with the same logic of remembering success and retrying after a grace period after failure). --- src/resolve/resolved-dns-packet.c | 8 +++++--- src/resolve/resolved-dns-packet.h | 2 +- src/resolve/resolved-dns-scope.c | 6 +++++- src/resolve/resolved-dns-server.c | 1 + src/resolve/resolved-dns-server.h | 1 + 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index cb713f1f85..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; @@ -610,7 +612,7 @@ fail: } /* Append the OPT pseudo-RR described in RFC6891 */ -int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, size_t *start) { +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; @@ -640,8 +642,8 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, size_t *start) if (r < 0) goto fail; - /* flags */ - r = dns_packet_append_uint16(p, 0, NULL); + /* flags: DNSSEC OK (DO), see RFC3225 */ + r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); if (r < 0) goto fail; diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index 385a8af796..feded09db3 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -160,7 +160,7 @@ 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, 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); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 42478e41e2..80070da2b9 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -185,7 +185,11 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { return -EOPNOTSUPP; if (server->possible_features >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { - r = dns_packet_append_opt_rr(p, DNS_PACKET_UNICAST_SIZE_MAX, &saved_size); + bool edns_do; + + edns_do = server->possible_features >= DNS_SERVER_FEATURE_LEVEL_DO; + + r = dns_packet_append_opt_rr(p, DNS_PACKET_UNICAST_SIZE_MAX, edns_do, &saved_size); if (r < 0) return r; diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index f8c921e4c8..916f5dadb8 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -479,5 +479,6 @@ static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVE [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", }; 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 e9b425430f..9dd4961d5f 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -35,6 +35,7 @@ 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_MAX, _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 } DnsServerFeatureLevel; -- cgit v1.2.3-54-g00ecf From d74fb368b18f0fbd9a4fe6f15691bbea7f3c4a01 Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Mon, 6 Jul 2015 16:48:24 +0200 Subject: resolved: announce support for large UDP packets This is often needed for proper DNSSEC support, and even to handle AAAA records without falling back to TCP. If the path between the client and server is fully compliant, this should always work, however, that is not the case, and overlarge packets will get mysteriously lost in some cases. For that reason, we use a similar fallback mechanism as we do for palin EDNS0, EDNS0+DO, etc.: The large UDP size feature is different from the other supported feature, as we cannot simply verify that it works based on receiving a reply (as the server will usually send us much smaller packets than what we claim to support, so simply receiving a reply does not mean much). For that reason, we keep track of the largest UDP packet we ever received, as this is the smallest known good size (defaulting to the standard 512 bytes). If announcing the default large size of 4096 fails (in the same way as the other features), we fall back to the known good size. The same logic of retrying after a grace-period applies. --- src/resolve/resolved-dns-packet.h | 3 +++ src/resolve/resolved-dns-scope.c | 8 +++++++- src/resolve/resolved-dns-server.c | 20 ++++++++++++++++++-- src/resolve/resolved-dns-server.h | 4 +++- src/resolve/resolved-dns-transaction.c | 2 +- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index feded09db3..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 { diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index 80070da2b9..09e6872d26 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -186,10 +186,16 @@ int dns_scope_emit(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) { 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; - r = dns_packet_append_opt_rr(p, DNS_PACKET_UNICAST_SIZE_MAX, edns_do, &saved_size); + 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; diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 916f5dadb8..d565f99c09 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -71,6 +71,7 @@ int dns_server_new( 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; @@ -223,10 +224,18 @@ void dns_server_move_back_and_unmark(DnsServer *s) { } } -void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt) { +void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, usec_t rtt, size_t size) { assert(s); - if (s->verified_features < features) { + 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); } @@ -234,6 +243,12 @@ void dns_server_packet_received(DnsServer *s, DnsServerFeatureLevel features, us 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; + 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); @@ -480,5 +495,6 @@ static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVE [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 9dd4961d5f..00366a48c9 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -36,6 +36,7 @@ typedef enum DnsServerFeatureLevel { 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; @@ -66,6 +67,7 @@ struct DnsServer { 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; @@ -89,7 +91,7 @@ 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, DnsServerFeatureLevel features, usec_t rtt); +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); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index f81461a4fe..90133cb332 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -433,7 +433,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { return; } else - dns_server_packet_received(t->server, t->current_features, ts - t->start_usec); + dns_server_packet_received(t->server, t->current_features, ts - t->start_usec, p->size); break; case DNS_PROTOCOL_LLMNR: -- cgit v1.2.3-54-g00ecf