resolved: split oversized mDNS announcements across MTU-sized packets
dns_scope_announce() put the entire zone -- for DNS-SD every PTR/SRV/TXT record of every published service -- into a single reply packet. With a few hundred published services that grows far beyond the interface MTU, and beyond what RFC 6762 section 17 permits: a packet larger than the MTU is sent using fragments, and such a packet "MUST NOT contain more than one resource record". Pack the announcement into as many MTU-sized packets as needed instead. A single record that does not fit the MTU by itself is emitted alone in an oversized packet, relying on IP fragmentation, which the same section explicitly allows for that case -- bounded by its hard 9000-byte ceiling, headers included, which also caps jumbo-frame links. While the interface MTU is not known yet, packets are cut to the datagram size every host of the family must be able to accept -- RFC 791 section 3.1's 576 octets for IPv4, whole or reassembled, and RFC 8200 section 5's 1280-byte minimum link MTU for IPv6, which no link fragments -- rather than to that ceiling, so an announcement is not packed for deliberate fragmentation merely because the MTU was not read yet. A record that cannot be encoded at any size -- a character string past 255 bytes, say -- is skipped like one too large for the ceiling, rather than failing the whole announcement: dns_packet_append_rr() rolls the packet back on every failure, so its neighbours are unaffected either way. The packetizer returns how many it dropped and the scope warns on it, since nothing else would distinguish an announcement that carried everything from one that did not -- and for a withdrawal that matters: the caller drops the records from the zone either way, so one skipped here is never withdrawn at all and peers keep it for its full TTL. The packing itself lives in resolved-mdns.c as mdns_announcement_packetize(), taking an answer and the two size bounds and returning finalized packets, so it can be tested without a scope, a link or a socket. Emission is best effort per packet -- a failed send no longer withholds the announcement's remaining packets -- and each pass logs the packet and record counts it actually put on the wire. Each packet takes its own token from the scope's multicast rate limit: that limit bounds datagrams on the wire, and an announcement that would exhaust its burst is the very flood it exists to cap. dns_scope_send_goodbye(), the other unsolicited-response emitter, goes through the same packetizer: a withdrawal of many records had the same oversized-packet problem, and a single record failing to append abandoned the whole withdrawal. Its return value follows the emitter's now, so a withdrawal split across packets reports an error when any one of them fails to go out.
This commit is contained in:
committed by
Luca Boccassi
parent
c99af7968e
commit
d96c288ed4
@@ -297,6 +297,8 @@ DnsPacket *dns_packet_unref(DnsPacket *p) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DEFINE_POINTER_ARRAY_FREE_FUNC(DnsPacket*, dns_packet_unref);
|
||||
|
||||
int dns_packet_validate(DnsPacket *p) {
|
||||
assert(p);
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool trun
|
||||
|
||||
DnsPacket *dns_packet_ref(DnsPacket *p);
|
||||
DnsPacket *dns_packet_unref(DnsPacket *p);
|
||||
void dns_packet_unref_array(DnsPacket **array, size_t n);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user