return has_nsid;
}
+size_t dns_packet_size_unfragmented(DnsPacket *p) {
+ assert(p);
+
+ if (p->fragsize == 0) /* Wasn't fragmented */
+ return p->size;
+
+ /* The fragment size (p->fragsize) covers the whole (fragmented) IP packet, while the regular packet
+ * size (p->size) only covers the DNS part. Thus, subtract the UDP header from the largest fragment
+ * size, in order to determine which size of DNS packet would have gone through without
+ * fragmenting. */
+
+ return LESS_BY(p->fragsize, udp_header_size(p->family));
+}
+
static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = {
[DNS_RCODE_SUCCESS] = "SUCCESS",
[DNS_RCODE_FORMERR] = "FORMERR",
return DNS_RESOURCE_RECORD_OPT_VERSION_SUPPORTED(p->opt);
}
+static inline bool DNS_PACKET_IS_FRAGMENTED(DnsPacket *p) {
+ assert(p);
+
+ /* For ingress packets: was this packet fragmented according to our knowledge? */
+
+ return p->fragsize != 0;
+}
+
/* LLMNR defines some bits differently */
#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p)
#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p)
assert_not_reached("Unexpected address family");
}
}
+
+size_t dns_packet_size_unfragmented(DnsPacket *p);
* incomplete. */
}
-void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t size) {
+void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t fragsize) {
assert(s);
if (protocol == IPPROTO_UDP) {
dns_server_verified(s, level);
- /* 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 (protocol == IPPROTO_UDP && s->received_udp_packet_max < size)
- s->received_udp_packet_max = size;
+ /* Remember the size of the largest UDP packet fragment we received from a server, we know that we
+ * can always announce support for packets with at least this size. */
+ if (protocol == IPPROTO_UDP && s->received_udp_fragment_max < fragsize)
+ s->received_udp_fragment_max = fragsize;
}
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level) {
s->packet_do_off = true;
}
+void dns_server_packet_udp_fragmented(DnsServer *s, size_t fragsize) {
+ assert(s);
+
+ /* Invoked whenever we got a fragmented UDP packet. Let's do two things: keep track of the largest
+ * fragment we ever received from the server, and remember this, so that we can use it to lower the
+ * advertised packet size in EDNS0 */
+
+ if (s->received_udp_fragment_max < fragsize)
+ s->received_udp_fragment_max = fragsize;
+
+ s->packet_fragmented = true;
+}
+
static bool dns_server_grace_period_expired(DnsServer *s) {
usec_t ts;
if (level == DNS_SERVER_FEATURE_LEVEL_LARGE)
packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
else
- packet_size = server->received_udp_packet_max;
+ packet_size = server->received_udp_fragment_max;
return dns_packet_append_opt(packet, packet_size, edns_do, /* include_rfc6975 = */ true, NULL, 0, NULL);
}
s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID;
s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST;
- s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX;
+ s->received_udp_fragment_max = DNS_PACKET_UNICAST_SIZE_MAX;
s->packet_bad_opt = false;
s->packet_rrsig_missing = false;
fputc('\n', f);
fprintf(f,
- "\tMaximum UDP packet size received: %zu\n"
+ "\tMaximum UDP fragment size received: %zu\n"
"\tFailed UDP attempts: %u\n"
"\tFailed TCP attempts: %u\n"
"\tSeen truncated packet: %s\n"
"\tSeen RRSIG RR missing: %s\n"
"\tSeen invalid packet: %s\n"
"\tServer dropped DO flag: %s\n",
- s->received_udp_packet_max,
+ s->received_udp_fragment_max,
s->n_failed_udp,
s->n_failed_tcp,
yes_no(s->packet_truncated),
DnsServerFeatureLevel verified_feature_level;
DnsServerFeatureLevel possible_feature_level;
- size_t received_udp_packet_max;
+ size_t received_udp_fragment_max; /* largest packet or fragment (without IP/UDP header) we saw so far */
unsigned n_failed_udp;
unsigned n_failed_tcp;
bool packet_rrsig_missing:1; /* Set when RRSIG was missing */
bool packet_invalid:1; /* Set when we failed to parse a reply */
bool packet_do_off:1; /* Set when the server didn't copy DNSSEC DO flag from request to response */
+ bool packet_fragmented:1; /* Set when we ever saw a fragmented packet */
usec_t verified_usec;
usec_t features_grace_period_usec;
void dns_server_unlink(DnsServer *s);
void dns_server_move_back_and_unmark(DnsServer *s);
-void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t size);
+void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, size_t fragsize);
void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level);
void dns_server_packet_truncated(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_rcode_downgrade(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_invalid(DnsServer *s, DnsServerFeatureLevel level);
void dns_server_packet_do_off(DnsServer *s, DnsServerFeatureLevel level);
+void dns_server_packet_udp_fragmented(DnsServer *s, size_t fragsize);
DnsServerFeatureLevel dns_server_possible_feature_level(DnsServer *s);
}
void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypted) {
+ bool retry_with_tcp = false;
int r;
assert(t);
return;
}
+ /* Response was truncated, let's try again with good old TCP */
log_debug("Reply truncated, retrying via TCP.");
+ retry_with_tcp = true;
- /* Response was truncated, let's try again with good old TCP */
+ } 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.");
+ retry_with_tcp = true;
+ }
+ }
+
+ if (retry_with_tcp) {
r = dns_transaction_emit_tcp(t);
if (r == -ESRCH) {
/* No servers found? Damn! */
if (DNS_PACKET_DO(t->sent) && !DNS_PACKET_DO(t->received))
dns_server_packet_do_off(t->server, t->current_feature_level);
- /* Report that we successfully received a packet */
- dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, p->size);
+ /* Report that we successfully received a packet. We keep track of the largest packet
+ * size/fragment size we got. Which is useful for announcing the EDNS(0) packet size we can
+ * receive to our server. */
+ dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, dns_packet_size_unfragmented(p));
}
/* See if we know things we didn't know before that indicate we better restart the lookup immediately. */