From f2de9253dab0f9e12c3cacc4a6dde6be3dd7a1b9 Mon Sep 17 00:00:00 2001 From: Bertrand Jacquin Date: Sun, 27 Sep 2026 14:59:18 +0100 Subject: [PATCH] dns-packet: only set mDNS cache-flush bit in responses dns_packet_append_key() ORs MDNS_RR_CACHE_FLUSH_OR_QU into the CLASS field of any RR whose answer item carries DNS_ANSWER_CACHE_FLUSH, regardless of the packet the RR is appended to. Since 748a548e1b4f ("resolved: set cache-flush bit on mDNS responses"), every RR of an mDNS zone, except DNS-SD service enumeration PTRs, carries that flag. The Authority Section of mDNS probes (RFC 6762, section 8.2) is filled from our mDNS zone via dns_zone_lookup(), which applies the flag to items still in probing state too, hence probes are sent with class 0x8001 in their Authority Section. RFC 6762, section 10.2, scopes the cache-flush bit to announcements and responses. Peers ignore it for probe tiebreaking, so this is not an interoperability issue, but there is no reason to set it there. Only set the bit when the packet is a response. mDNS responses, announcements and goodbye packets are all built by dns_scope_make_reply_packet(), which sets QR before appending any RR, so they keep the bit. The Known-Answer list of queries is unaffected, as it is appended without flags already. See: https://github.com/systemd/systemd/pull/43627 (cherry picked from commit 1c57b40fc1451b1f518e6168dbd28bf1e5fa2b4d) --- src/resolve/test-dns-packet-append.c | 65 ++++++++++++++++++++++++++++ src/shared/dns-packet.c | 6 ++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/resolve/test-dns-packet-append.c b/src/resolve/test-dns-packet-append.c index 5bcace319b..5328a1a065 100644 --- a/src/resolve/test-dns-packet-append.c +++ b/src/resolve/test-dns-packet-append.c @@ -214,6 +214,71 @@ TEST(packet_append_key_single_soa_any_class) { ASSERT_EQ(memcmp(DNS_PACKET_DATA(packet), data, sizeof(data)), 0); } +TEST(packet_append_key_cache_flush_mdns) { + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + DnsResourceKey *key = NULL; + + ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_MDNS, 0, DNS_PACKET_SIZE_MAX)); + ASSERT_NOT_NULL(packet); + + DNS_PACKET_ID(packet) = htobe16(42); + DNS_PACKET_HEADER(packet)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(1, 0, 1, 0, 0, 0, 0, 0, DNS_RCODE_SUCCESS)); + DNS_PACKET_HEADER(packet)->ancount = htobe16(1); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "example.local"); + ASSERT_NOT_NULL(key); + ASSERT_OK(dns_packet_append_key(packet, key, DNS_ANSWER_CACHE_FLUSH, NULL)); + dns_resource_key_unref(key); + + const uint8_t data[] = { + 0x00, 0x2a, BIT_QR | BIT_AA, DNS_RCODE_SUCCESS, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + + /* name */ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x05, 'l', 'o', 'c', 'a', 'l', + 0x00, + /* A */ 0x00, 0x01, + /* IN + cache-flush */ + 0x80, 0x01 + }; + + ASSERT_EQ(packet->size, sizeof(data)); + ASSERT_EQ(memcmp(DNS_PACKET_DATA(packet), data, sizeof(data)), 0); +} + +/* mDNS probes carry the proposed RRs from our mDNS zone in the Authority Section. Those items carry + * DNS_ANSWER_CACHE_FLUSH, but the cache-flush bit is only set in responses, never in queries. See RFC 6762, + * sections 8.2 and 10.2. */ +TEST(packet_append_key_cache_flush_mdns_query) { + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + DnsResourceKey *key = NULL; + + ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_MDNS, 0, DNS_PACKET_SIZE_MAX)); + ASSERT_NOT_NULL(packet); + + DNS_PACKET_HEADER(packet)->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0, 0, 0, 0, 0, 0, 0, 0, DNS_RCODE_SUCCESS)); + DNS_PACKET_HEADER(packet)->nscount = htobe16(1); + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, "example.local"); + ASSERT_NOT_NULL(key); + ASSERT_OK(dns_packet_append_key(packet, key, DNS_ANSWER_CACHE_FLUSH, NULL)); + dns_resource_key_unref(key); + + const uint8_t data[] = { + 0x00, 0x00, 0x00, DNS_RCODE_SUCCESS, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + + /* name */ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x05, 'l', 'o', 'c', 'a', 'l', + 0x00, + /* A */ 0x00, 0x01, + /* IN */ 0x00, 0x01 + }; + + ASSERT_EQ(packet->size, sizeof(data)); + ASSERT_EQ(memcmp(DNS_PACKET_DATA(packet), data, sizeof(data)), 0); +} + /* ================================================================ * dns_packet_append_question() * ================================================================ */ diff --git a/src/shared/dns-packet.c b/src/shared/dns-packet.c index a0508b1c68..2a884d44f0 100644 --- a/src/shared/dns-packet.c +++ b/src/shared/dns-packet.c @@ -723,7 +723,11 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, const DnsAnswer if (r < 0) goto fail; - class = flags & DNS_ANSWER_CACHE_FLUSH ? k->class | MDNS_RR_CACHE_FLUSH_OR_QU : k->class; + /* The rules for setting the cache-flush bit apply to responses and announcements only, see RFC 6762, + * section 10.2. Hence never set it in queries (the Authority Section of probes is filled from our + * mDNS zone, see section 8.2). */ + class = DNS_PACKET_QR(p) && FLAGS_SET(flags, DNS_ANSWER_CACHE_FLUSH) ? + k->class | MDNS_RR_CACHE_FLUSH_OR_QU : k->class; r = dns_packet_append_uint16(p, class, NULL); if (r < 0) goto fail;