resolved: delay server feature detection

Some fields of the DnsPacket are not populated until we extract an
answer, like p->opt, despite being referenced by macros like
DNS_PACKET_RCODE. We can reorder some of the basic checks to follow
dns_packet_extract.
This commit is contained in:
Ronan Pigott 2023-12-20 15:16:41 -07:00
parent 980cb160eb
commit 3fcd83645a

View file

@ -1121,6 +1121,83 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
}
}
if (DNS_PACKET_TC(p)) {
/* Truncated packets for mDNS are not allowed. Give up immediately. */
if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
/* Response was truncated, let's try again with good old TCP */
log_debug("Reply truncated, retrying via TCP.");
retry_with_tcp = true;
} else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
DNS_PACKET_IS_FRAGMENTED(p)) {
/* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
if (t->server)
dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
/* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
* issues. (We don't do that on the lowest feature level however, since crappy DNS
* servers often do not implement TCP, hence falling back to TCP on fragmentation is
* counter-productive there.) */
log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)",
p->fragsize, p->size);
retry_with_tcp = true;
}
}
if (retry_with_tcp) {
r = dns_transaction_emit_tcp(t);
if (r == -ESRCH) {
/* No servers found? Damn! */
dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
return;
}
if (r == -EOPNOTSUPP) {
/* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
return;
}
if (r < 0) {
/* On LLMNR, if we cannot connect to the host,
* we immediately give up */
if (t->scope->protocol != DNS_PROTOCOL_DNS)
goto fail;
/* On DNS, couldn't send? Try immediately again, with a new server */
if (dns_transaction_limited_retry(t))
return;
/* No new server to try, give up */
dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
}
return;
}
/* After the superficial checks, actually parse the message. */
r = dns_packet_extract(p);
if (r < 0) {
if (t->server) {
dns_server_packet_invalid(t->server, t->current_feature_level);
r = dns_transaction_maybe_restart(t);
if (r < 0)
goto fail;
if (r > 0) /* Transaction got restarted... */
return;
}
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
switch (t->scope->protocol) {
case DNS_PROTOCOL_DNS:
@ -1206,83 +1283,6 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt
assert_not_reached();
}
if (DNS_PACKET_TC(p)) {
/* Truncated packets for mDNS are not allowed. Give up immediately. */
if (t->scope->protocol == DNS_PROTOCOL_MDNS) {
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
/* Response was truncated, let's try again with good old TCP */
log_debug("Reply truncated, retrying via TCP.");
retry_with_tcp = true;
} else if (t->scope->protocol == DNS_PROTOCOL_DNS &&
DNS_PACKET_IS_FRAGMENTED(p)) {
/* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */
if (t->server)
dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p));
if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) {
/* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack
* issues. (We don't do that on the lowest feature level however, since crappy DNS
* servers often do not implement TCP, hence falling back to TCP on fragmentation is
* counter-productive there.) */
log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)",
p->fragsize, p->size);
retry_with_tcp = true;
}
}
if (retry_with_tcp) {
r = dns_transaction_emit_tcp(t);
if (r == -ESRCH) {
/* No servers found? Damn! */
dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS);
return;
}
if (r == -EOPNOTSUPP) {
/* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */
dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED);
return;
}
if (r < 0) {
/* On LLMNR, if we cannot connect to the host,
* we immediately give up */
if (t->scope->protocol != DNS_PROTOCOL_DNS)
goto fail;
/* On DNS, couldn't send? Try immediately again, with a new server */
if (dns_transaction_limited_retry(t))
return;
/* No new server to try, give up */
dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED);
}
return;
}
/* After the superficial checks, actually parse the message. */
r = dns_packet_extract(p);
if (r < 0) {
if (t->server) {
dns_server_packet_invalid(t->server, t->current_feature_level);
r = dns_transaction_maybe_restart(t);
if (r < 0)
goto fail;
if (r > 0) /* Transaction got restarted... */
return;
}
dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY);
return;
}
if (t->server) {
/* Report that we successfully received a valid packet with a good rcode after we initially got a bad
* rcode and subsequently downgraded the protocol */