dns-packet: only set mDNS cache-flush bit on mDNS packets

Since 748a548e1b ("resolved: set cache-flush bit on mDNS responses"),
every RR of an mDNS zone, except DNS-SD service enumeration PTRs,
carries DNS_ANSWER_CACHE_FLUSH, and dns_packet_append_key() turns it
into the cache-flush bit regardless of the protocol of the packet.

When a lookup via the DNS stub is answered from our own mDNS zone, e.g.
the reverse lookup of one of our link-local addresses, the RR is sent
with class 0x8001. In unicast DNS bit 15 is part of the class value, so
clients see CLASS32769 instead of IN:

  $ dig +nocmd +nocookie -x fe80::d1f:2970:a730:5ab8 @127.0.0.53
  ;; Warning: Message parser reports malformed message packet.
  ;; Got answer:
  ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30061
  ;; flags: qr aa rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

  ;; OPT PSEUDOSECTION:
  ; EDNS: version: 0, flags:; udp: 65494
  ;; QUESTION SECTION:
  ;8.b.a.5.0.3.7.a.0.7.9.2.f.1.d.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa. IN PTR

  ;; ANSWER SECTION:
  8.b.a.5.0.3.7.a.0.7.9.2.f.1.d.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa. 120 CLASS32769 PTR lady-voodoo.local.

  ;; Query time: 1 msec
  ;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
  ;; WHEN: Wed Sep 02 21:40:55 IST 2026
  ;; MSG SIZE  rcvd: 132

The cache-flush bit is defined for mDNS only, hence only set it on mDNS
packets.

See: https://github.com/systemd/systemd/pull/43627
(cherry picked from commit 21159c052bbf28983115d842dbc2597869177d42)
This commit is contained in:
Bertrand Jacquin
2026-10-16 17:24:50 +01:00
committed by Luca Boccassi
parent f2de9253da
commit 17f3e1fb3b
2 changed files with 92 additions and 4 deletions
+86
View File
@@ -246,6 +246,40 @@ TEST(packet_append_key_cache_flush_mdns) {
ASSERT_EQ(memcmp(DNS_PACKET_DATA(packet), data, sizeof(data)), 0);
}
/* The cache-flush bit is defined for mDNS only. RRs taken from our mDNS zone carry
* DNS_ANSWER_CACHE_FLUSH, but when they are sent out via the DNS stub the bit must not end up in the
* CLASS field, where it would be parsed as class 32769. See RFC 6762, section 10.2. */
TEST(packet_append_key_cache_flush_dns) {
_cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
DnsResourceKey *key = NULL;
ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 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 */ 0x00, 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. */
@@ -890,6 +924,58 @@ TEST(packet_append_answer_single_ptr) {
ASSERT_EQ(memcmp(DNS_PACKET_DATA(packet), data, sizeof(data)), 0);
}
/* Reverse lookups for link-local addresses are answered from resolved's own mDNS zone, whose answer
* items carry DNS_ANSWER_CACHE_FLUSH. When such an RR is handed to a client of the DNS stub the
* cache-flush bit must be dropped, otherwise the client sees CLASS32769 instead of IN. */
TEST(packet_append_answer_single_ptr_ip6_cache_flush) {
_cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
DnsResourceRecord *rr = NULL;
rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR,
"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa");
ASSERT_NOT_NULL(rr);
rr->ttl = 120;
rr->ptr.name = strdup("example.local");
answer = dns_answer_new(1);
ASSERT_NOT_NULL(answer);
dns_answer_add(answer, rr, 1, DNS_ANSWER_CACHE_FLUSH, NULL);
dns_resource_record_unref(rr);
ASSERT_OK(dns_packet_new(&packet, DNS_PROTOCOL_DNS, 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, 1, 1, 0, 0, DNS_RCODE_SUCCESS));
DNS_PACKET_HEADER(packet)->ancount = htobe16(dns_answer_size(answer));
ASSERT_OK(dns_packet_append_answer(packet, answer, NULL));
const uint8_t data[] = {
0x00, 0x2a, BIT_QR | BIT_AA | BIT_RD, BIT_RA | DNS_RCODE_SUCCESS,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
/* name */ 0x01, '1', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0',
0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0',
0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0',
0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '0', 0x01, '8', 0x01, 'e', 0x01, 'f',
0x03, 'i', 'p', '6',
0x04, 'a', 'r', 'p', 'a',
0x00,
/* PTR */ 0x00, 0x0c,
/* IN */ 0x00, 0x01,
/* ttl */ 0x00, 0x00, 0x00, 0x78,
/* rdata */ 0x00, 0x0f,
/* name */ 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x05, 'l', 'o', 'c', 'a', 'l',
0x00
};
ASSERT_EQ(packet->size, sizeof(data));
ASSERT_EQ(memcmp(DNS_PACKET_DATA(packet), data, sizeof(data)), 0);
}
TEST(packet_append_answer_single_mx) {
_cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL;
_cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL;
+6 -4
View File
@@ -723,10 +723,12 @@ int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, const DnsAnswer
if (r < 0)
goto fail;
/* 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) ?
/* Bit 15 of the CLASS field of an RR is the cache-flush bit in mDNS only, and the rules for setting
* it apply to responses and announcements only, see RFC 6762, section 10.2. In unicast DNS it is
* part of the class value itself. Hence set it neither in unicast DNS packets (the RR might come
* from our mDNS zone, but be sent out via the DNS stub), nor in mDNS queries (the Authority Section
* of probes is filled from our mDNS zone too, see section 8.2). */
class = p->protocol == DNS_PROTOCOL_MDNS && 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)