]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: take fragment size into consideration when determining EDNS0 udp packet... 18664/head
authorLennart Poettering <lennart@poettering.net>
Mon, 16 Nov 2020 20:02:06 +0000 (21:02 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 18 Feb 2021 14:55:58 +0000 (15:55 +0100)
src/resolve/resolved-dns-server.c
src/resolve/resolved-dns-server.h

index 7b7e50092fc0b0418ca4c08da0cf014210903561..875d2079ab39eb58b34dad3ab881889cd24c04e9 100644 (file)
@@ -617,11 +617,48 @@ int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeature
 
         edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO;
 
-        if (level == DNS_SERVER_FEATURE_LEVEL_LARGE)
-                packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX;
-        else
+        if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) {
+                size_t udp_size;
+
+                /* In large mode, advertise the local MTU, in order to avoid fragmentation (for security
+                 * reasons) – except if we are talking to localhost (where the security considerations don't
+                 * matter). If we see fragmentation, lower the reported size to the largest fragment, to
+                 * avoid it. */
+
+                udp_size = udp_header_size(server->family);
+
+                if (in_addr_is_localhost(server->family, &server->address) > 0)
+                        packet_size = 65536 - udp_size; /* force linux loopback MTU if localhost address */
+                else {
+                        /* Use the MTU pointing to the server, subtract the IP/UDP header size */
+                        packet_size = LESS_BY(dns_server_get_mtu(server), udp_size);
+
+                        /* On the Internet we want to avoid fragmentation for security reasons. If we saw
+                         * fragmented packets, the above was too large, let's clamp it to the largest
+                         * fragment we saw */
+                        if (server->packet_fragmented)
+                                packet_size = MIN(server->received_udp_fragment_max, packet_size);
+
+                        /* Let's not pick ridiculously large sizes, i.e. not more than 4K. Noone appears to
+                         * ever use such large sized on the Internet IRL, hence let's not either. */
+                        packet_size = MIN(packet_size, 4096U);
+                }
+
+                /* Strictly speaking we quite possibly can receive larger datagrams than the MTU (since the
+                 * MTU is for egress, not for ingress), but more often than not the value is symmetric, and
+                 * we want something that does the right thing in the majority of cases, and not just in the
+                 * theoretical edge case. */
+        } else
+                /* In non-large mode, let's advertise the size of the largest fragment we ever managed to accept. */
                 packet_size = server->received_udp_fragment_max;
 
+        /* Safety clamp, never advertise less than 512 or more than 65535 */
+        packet_size = CLAMP(packet_size,
+                            DNS_PACKET_UNICAST_SIZE_MAX,
+                            DNS_PACKET_SIZE_MAX);
+
+        log_debug("Announcing packet size %zu in egress EDNS(0) packet.", packet_size);
+
         return dns_packet_append_opt(packet, packet_size, edns_do, /* include_rfc6975 = */ true, NULL, 0, NULL);
 }
 
@@ -713,6 +750,15 @@ void dns_server_warn_downgrade(DnsServer *server) {
         server->warned_downgrade = true;
 }
 
+size_t dns_server_get_mtu(DnsServer *s) {
+        assert(s);
+
+        if (s->link && s->link->mtu != 0)
+                return s->link->mtu;
+
+        return manager_find_mtu(s->manager);
+}
+
 static void dns_server_hash_func(const DnsServer *s, struct siphash *state) {
         assert(s);
 
index ccc109bc834a4236816bfc0d57f8ab00aedfac8a..bbf9f868b1edddf0ac3fbfa88a7c00247a3d8908 100644 (file)
@@ -157,6 +157,8 @@ void manager_next_dns_server(Manager *m, DnsServer *if_current);
 DnssecMode dns_server_get_dnssec_mode(DnsServer *s);
 DnsOverTlsMode dns_server_get_dns_over_tls_mode(DnsServer *s);
 
+size_t dns_server_get_mtu(DnsServer *s);
+
 DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref);
 
 extern const struct hash_ops dns_server_hash_ops;