Merge pull request #2043 from teg/resolved-edns0-5

resolved: add edns0 support
This commit is contained in:
Lennart Poettering
2015-11-27 19:29:04 +01:00
8 changed files with 283 additions and 23 deletions
+58 -1
View File
@@ -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)
+6
View File
@@ -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);
+34 -2
View File
@@ -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");
}
+1 -1
View File
@@ -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);
+112 -6
View File
@@ -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);
}
s->max_rtt = rtt;
s->resend_timeout = MIN(MAX(DNS_TIMEOUT_MIN_USEC, s->max_rtt * 2), DNS_TIMEOUT_MAX_USEC);
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);
}
}
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);
+28 -3
View File
@@ -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;
+41 -10
View File
@@ -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);
+3
View File
@@ -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;