resolved: rework OPT RR generation logic

This moves management of the OPT RR out of the scope management and into
the server and packet management. There are now explicit calls for
appending and truncating the OPT RR from a packet
(dns_packet_append_opt() and dns_packet_truncate_opt()) as well as a
call to do the right thing depending on a DnsServer's feature level
(dns_server_adjust_opt()).

This also unifies the code to pick a server between the TCP and UDP code
paths, and makes sure the feature level used for the transaction is
selected at the time the server is picked, and not changed until the
next time we pick a server. The server selction code is now unified in
dns_transaction_pick_server().

This all fixes problems when changing between UDP and TCP communication
for the same server, and makes sure the UDP and TCP codepaths are more
alike. It also makes sure we never keep the UDP port open when switchung
to TCP, so that we don't have to handle incoming datagrams on the latter
we don't expect.

As the new code picks the DNS server at the time we make a connection,
we don't need to invalidate the DNS server anymore when changing to the
next one, thus dns_transaction_next_dns_server() has been removed.
This commit is contained in:
Lennart Poettering 2015-12-26 18:49:32 +01:00
parent 97cc656cf4
commit 519ef04651
7 changed files with 178 additions and 105 deletions

View File

@ -58,6 +58,7 @@ int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) {
p->size = p->rindex = DNS_PACKET_HEADER_SIZE;
p->allocated = a;
p->protocol = protocol;
p->opt_start = p->opt_size = (size_t) -1;
p->n_ref = 1;
*ret = p;
@ -681,7 +682,7 @@ fail:
}
/* 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) {
int dns_packet_append_opt(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start) {
size_t saved_size;
int r;
@ -689,6 +690,11 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do,
/* we must never advertise supported packet size smaller than the legacy max */
assert(max_udp_size >= DNS_PACKET_UNICAST_SIZE_MAX);
if (p->opt_start != (size_t) -1)
return -EBUSY;
assert(p->opt_size == (size_t) -1);
saved_size = p->size;
/* empty name */
@ -721,6 +727,11 @@ int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do,
if (r < 0)
goto fail;
DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1);
p->opt_start = saved_size;
p->opt_size = p->size - saved_size;
if (start)
*start = saved_size;
@ -731,6 +742,27 @@ fail:
return r;
}
int dns_packet_truncate_opt(DnsPacket *p) {
assert(p);
if (p->opt_start == (size_t) -1) {
assert(p->opt_size == (size_t) -1);
return 0;
}
assert(p->opt_size != (size_t) -1);
assert(DNS_PACKET_ARCOUNT(p) > 0);
if (p->opt_start + p->opt_size != p->size)
return -EBUSY;
dns_packet_truncate(p, p->opt_start);
DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1);
p->opt_start = p->opt_size = (size_t) -1;
return 1;
}
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start) {
size_t saved_size, rdlength_offset, end, rdlength, rds;
int r;
@ -1046,7 +1078,6 @@ fail:
return r;
}
int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) {
assert(p);

View File

@ -76,6 +76,7 @@ struct DnsPacket {
size_t size, allocated, rindex;
void *_data; /* don't access directly, use DNS_PACKET_DATA()! */
Hashmap *names; /* For name compression */
size_t opt_start, opt_size;
/* Parsed data */
DnsQuestion *question;
@ -173,9 +174,10 @@ 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, size_t *start);
int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, size_t *start, size_t *rdata_start);
int dns_packet_append_opt_rr(DnsPacket *p, uint16_t max_udp_size, bool edns0_do, size_t *start);
int dns_packet_append_opt(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_truncate_opt(DnsPacket *p);
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);

View File

@ -162,17 +162,15 @@ void dns_scope_packet_lost(DnsScope *s, usec_t usec) {
s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC);
}
static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) {
union in_addr_union addr;
int ifindex = 0, r;
int family;
uint32_t mtu;
size_t saved_size = 0;
assert(s);
assert(p);
assert(p->protocol == s->protocol);
assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0));
if (s->link) {
mtu = s->link->mtu;
@ -181,30 +179,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
mtu = manager_find_mtu(s->manager);
switch (s->protocol) {
case DNS_PROTOCOL_DNS:
assert(server);
assert(fd >= 0);
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;
@ -215,15 +196,11 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
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:
assert(fd < 0);
if (DNS_PACKET_QDCOUNT(p) > 1)
return -EOPNOTSUPP;
@ -250,6 +227,8 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
break;
case DNS_PROTOCOL_MDNS:
assert(fd < 0);
if (!ratelimit_test(&s->ratelimit))
return -EBUSY;
@ -279,13 +258,13 @@ static int dns_scope_emit_one(DnsScope *s, int fd, DnsServer *server, DnsPacket
return 1;
}
int dns_scope_emit_udp(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) {
int r;
assert(s);
assert(p);
assert(p->protocol == s->protocol);
assert((s->protocol == DNS_PROTOCOL_DNS) != (fd < 0));
assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0));
do {
/* If there are multiple linked packets, set the TC bit in all but the last of them */
@ -294,18 +273,24 @@ int dns_scope_emit_udp(DnsScope *s, int fd, DnsServer *server, DnsPacket *p) {
dns_packet_set_flags(p, true, true);
}
r = dns_scope_emit_one(s, fd, server, p);
r = dns_scope_emit_one(s, fd, p);
if (r < 0)
return r;
p = p->more;
} while(p);
} while (p);
return 0;
}
static int dns_scope_socket(DnsScope *s, int type, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) {
DnsServer *srv = NULL;
static int dns_scope_socket(
DnsScope *s,
int type,
int family,
const union in_addr_union *address,
DnsServer *server,
uint16_t port) {
_cleanup_close_ int fd = -1;
union sockaddr_union sa = {};
socklen_t salen;
@ -313,32 +298,27 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad
int ret, r;
assert(s);
assert((family == AF_UNSPEC) == !address);
if (family == AF_UNSPEC) {
srv = dns_scope_get_dns_server(s);
if (!srv)
return -ESRCH;
if (server) {
assert(family == AF_UNSPEC);
assert(!address);
/* Determine current feature level to use */
(void) 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.sa.sa_family = server->family;
if (server->family == AF_INET) {
sa.in.sin_port = htobe16(port);
sa.in.sin_addr = srv->address.in;
sa.in.sin_addr = server->address.in;
salen = sizeof(sa.in);
} else if (srv->family == AF_INET6) {
} else if (server->family == AF_INET6) {
sa.in6.sin6_port = htobe16(port);
sa.in6.sin6_addr = srv->address.in6;
sa.in6.sin6_addr = server->address.in6;
sa.in6.sin6_scope_id = s->link ? s->link->ifindex : 0;
salen = sizeof(sa.in6);
} else
return -EAFNOSUPPORT;
} else {
assert(family != AF_UNSPEC);
assert(address);
sa.sa.sa_family = family;
if (family == AF_INET) {
@ -396,21 +376,18 @@ static int dns_scope_socket(DnsScope *s, int type, int family, const union in_ad
if (r < 0 && errno != EINPROGRESS)
return -errno;
if (server)
*server = srv;
ret = fd;
fd = -1;
return ret;
}
int dns_scope_socket_udp(DnsScope *s, DnsServer **server) {
return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, 53, server);
int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) {
return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port);
}
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server) {
return dns_scope_socket(s, SOCK_STREAM, family, address, port, server);
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) {
return dns_scope_socket(s, SOCK_STREAM, family, address, server, port);
}
DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) {
@ -869,7 +846,7 @@ static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata
return 0;
}
r = dns_scope_emit_udp(scope, -1, NULL, p);
r = dns_scope_emit_udp(scope, -1, p);
if (r < 0)
log_debug_errno(r, "Failed to send conflict packet: %m");
}

View File

@ -82,9 +82,9 @@ 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_udp(DnsScope *s, int fd, DnsServer *server, DnsPacket *p);
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, uint16_t port, DnsServer **server);
int dns_scope_socket_udp(DnsScope *s, DnsServer **server);
int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p);
int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port);
int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port);
DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain);
int dns_scope_good_key(DnsScope *s, DnsResourceKey *key);

View File

@ -342,6 +342,34 @@ DnsServerFeatureLevel dns_server_possible_features(DnsServer *s) {
return s->possible_features;
}
int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) {
size_t packet_size;
bool edns_do;
int r;
assert(server);
assert(packet);
assert(packet->protocol == DNS_PROTOCOL_DNS);
/* Fix the OPT field in the packet to match our current feature level. */
r = dns_packet_truncate_opt(packet);
if (r < 0)
return r;
if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0)
return 0;
edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO;
if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE)
packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
else
packet_size = server->received_udp_packet_max;
return dns_packet_append_opt(packet, packet_size, edns_do, NULL);
}
static void dns_server_hash_func(const void *p, struct siphash *state) {
const DnsServer *s = p;

View File

@ -104,6 +104,8 @@ void dns_server_packet_lost(DnsServer *s, DnsServerFeatureLevel features, usec_t
void dns_server_packet_failed(DnsServer *s, DnsServerFeatureLevel features);
void dns_server_packet_rrsig_missing(DnsServer *s);
int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level);
DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr);
void dns_server_unlink_all(DnsServer *first);

View File

@ -308,6 +308,27 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
dns_transaction_gc(t);
}
static int dns_transaction_pick_server(DnsTransaction *t) {
DnsServer *server;
assert(t);
assert(t->scope->protocol == DNS_PROTOCOL_DNS);
server = dns_scope_get_dns_server(t->scope);
if (!server)
return -ESRCH;
t->current_features = dns_server_possible_features(server);
if (server == t->server)
return 0;
dns_server_unref(t->server);
t->server = dns_server_ref(server);
return 1;
}
static int on_stream_complete(DnsStream *s, int error) {
_cleanup_(dns_packet_unrefp) DnsPacket *p = NULL;
DnsTransaction *t;
@ -344,7 +365,9 @@ static int on_stream_complete(DnsStream *s, int error) {
dns_transaction_process_reply(t, p);
t->block_gc--;
/* If the response wasn't useful, then complete the transition now */
/* If the response wasn't useful, then complete the transition
* now. After all, we are the worst feature set now with TCP
* sockets, and there's really no point in retrying. */
if (t->state == DNS_TRANSACTION_PENDING)
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
else
@ -354,24 +377,31 @@ static int on_stream_complete(DnsStream *s, int error) {
}
static int dns_transaction_open_tcp(DnsTransaction *t) {
DnsServer *server = NULL;
_cleanup_close_ int fd = -1;
int r;
assert(t);
if (t->stream)
return 0;
dns_transaction_close_connection(t);
switch (t->scope->protocol) {
case DNS_PROTOCOL_DNS:
fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, 53, &server);
r = dns_transaction_pick_server(t);
if (r < 0)
return r;
r = dns_server_adjust_opt(t->server, t->sent, t->current_features);
if (r < 0)
return r;
fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53);
break;
case DNS_PROTOCOL_LLMNR:
/* When we already received a reply to this (but it was truncated), send to its sender address */
if (t->received)
fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, t->received->sender_port, NULL);
fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port);
else {
union in_addr_union address;
int family = AF_UNSPEC;
@ -388,7 +418,7 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
if (family != t->scope->family)
return -ESRCH;
fd = dns_scope_socket_tcp(t->scope, family, &address, LLMNR_PORT, NULL);
fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT);
}
break;
@ -403,7 +433,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd);
if (r < 0)
return r;
fd = -1;
r = dns_stream_write_packet(t->stream, t->sent);
@ -412,11 +441,6 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
return r;
}
dns_server_unref(t->server);
t->server = dns_server_ref(server);
dns_transaction_reset_answer(t);
t->stream->complete = on_stream_complete;
t->stream->transaction = t;
@ -426,21 +450,13 @@ static int dns_transaction_open_tcp(DnsTransaction *t) {
if (t->scope->link)
t->stream->ifindex = t->scope->link->ifindex;
dns_transaction_reset_answer(t);
t->tried_stream = true;
return 0;
}
static void dns_transaction_next_dns_server(DnsTransaction *t) {
assert(t);
t->server = dns_server_unref(t->server);
t->dns_udp_event_source = sd_event_source_unref(t->dns_udp_event_source);
t->dns_udp_fd = safe_close(t->dns_udp_fd);
dns_scope_next_dns_server(t->scope);
}
static void dns_transaction_cache_answer(DnsTransaction *t) {
assert(t);
@ -662,7 +678,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) {
}
/* On DNS, couldn't send? Try immediately again, with a new server */
dns_transaction_next_dns_server(t);
dns_scope_next_dns_server(t->scope);
r = dns_transaction_go(t);
if (r < 0) {
@ -743,29 +759,44 @@ static int dns_transaction_emit_udp(DnsTransaction *t) {
assert(t);
if (t->scope->protocol == DNS_PROTOCOL_DNS && !t->server) {
DnsServer *server = NULL;
_cleanup_close_ int fd = -1;
if (t->scope->protocol == DNS_PROTOCOL_DNS) {
fd = dns_scope_socket_udp(t->scope, &server);
if (fd < 0)
return fd;
r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t);
r = dns_transaction_pick_server(t);
if (r < 0)
return r;
t->dns_udp_fd = fd;
fd = -1;
t->server = dns_server_ref(server);
}
if (t->current_features < DNS_SERVER_FEATURE_LEVEL_UDP)
return -EAGAIN;
r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->server, t->sent);
if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */
int fd;
dns_transaction_close_connection(t);
fd = dns_scope_socket_udp(t->scope, t->server, 53);
if (fd < 0)
return fd;
r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t);
if (r < 0) {
safe_close(fd);
return r;
}
t->dns_udp_fd = fd;
}
r = dns_server_adjust_opt(t->server, t->sent, t->current_features);
if (r < 0)
return r;
} else
dns_transaction_close_connection(t);
r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent);
if (r < 0)
return r;
if (t->server)
t->current_features = t->server->possible_features;
dns_transaction_reset_answer(t);
return 0;
}
@ -802,7 +833,7 @@ static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdat
log_debug("Timeout reached on transaction %" PRIu16 ".", t->id);
/* ...and try again with a new server */
dns_transaction_next_dns_server(t);
dns_scope_next_dns_server(t->scope);
r = dns_transaction_go(t);
if (r < 0)
@ -1084,10 +1115,12 @@ int dns_transaction_go(DnsTransaction *t) {
random_bytes(&jitter, sizeof(jitter));
switch (t->scope->protocol) {
case DNS_PROTOCOL_LLMNR:
jitter %= LLMNR_JITTER_INTERVAL_USEC;
accuracy = LLMNR_JITTER_INTERVAL_USEC;
break;
case DNS_PROTOCOL_MDNS:
jitter %= MDNS_JITTER_RANGE_USEC;
jitter += MDNS_JITTER_MIN_USEC;
@ -1152,7 +1185,7 @@ int dns_transaction_go(DnsTransaction *t) {
}
/* Couldn't send? Try immediately again, with a new server */
dns_transaction_next_dns_server(t);
dns_scope_next_dns_server(t->scope);
return dns_transaction_go(t);
}