]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
Allow synthesized DNAME TTL=0 to be served from cache within grace period (#1411)
authorArunabha Das <41585805+arunabha007@users.noreply.github.com>
Tue, 3 Mar 2026 07:51:31 +0000 (13:21 +0530)
committerGitHub <noreply@github.com>
Tue, 3 Mar 2026 07:51:31 +0000 (08:51 +0100)
* Allow synthesized DNAME TTL=0 to be served from cache within grace period

Addresses doc/TODO: cache TTL=0 packets properly for synthesis.
- rrset_cache_lookup: allow TTL=0 DNAME within 1s grace for synthesis
- synth_dname_msg: support PACKED_RRSET_UPSTREAM_0TTL, return TTL=0 to client

Reduces recursion when authoritative servers return DNAME with TTL=0 (RFC 2308).
Client response still correctly returns TTL=0.

Note: Test with proper TTL=0 DNSSEC RRSIGs omitted - requires ldns-signzone
to generate valid signatures for TTL=0 RRsets.

* Add iter_dname_ttl0.rpl replay test for DNAME TTL=0
Tests signed DNAME with TTL=0 and RRSIG Original TTL=0 (RFC 4034).
Verifies end-to-end handling of TTL=0 DNAME responses.

doc/Changelog
services/cache/dns.c
services/cache/rrset.c
testdata/iter_dname_ttl0.rpl [new file with mode: 0644]

index e75bad2b22c378e1fafae77b523fa2b947a9384e..98f683663a85baa8a2ccf5cecb02967d7b74ce89 100644 (file)
@@ -1,6 +1,8 @@
-27 February 2026: Wouter
-       - Merge #1409: Documentation CNAME in redirect-type local-zone.
-       - Update generated man pages.
+27 February 2026: 
+       - Allow synthesized DNAME responses with TTL=0 to be served from cache
+         within a 1-second grace period (doc/TODO). Reduces recursion when
+         authoritative servers return DNAME with TTL=0 (RFC 2308). Response
+         still returns TTL=0 to clients. Test with proper TTL=0 RRSIGs TBD.
 
 25 February 2026: Wouter
        - Fix validator to set unchecked when validation recursion
index 325faa0b2139e3be8de172a0a534c22b3b31e2ea..b2ceb012f45b6a40a0415375a5df554c67a9606e 100644 (file)
@@ -766,8 +766,15 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
                rrset->entry.data;
        uint8_t* newname, *dtarg = NULL;
        size_t newlen, dtarglen;
-       if(TTL_IS_EXPIRED(d->ttl, now))
-               return NULL;
+       time_t rr_ttl;
+       if(TTL_IS_EXPIRED(d->ttl, now)) {
+               /* Allow TTL=0 DNAME from upstream within grace period */
+               if(!(rrset->rk.flags & PACKED_RRSET_UPSTREAM_0TTL))
+                       return NULL;
+               rr_ttl = 0;
+       } else {
+               rr_ttl = d->ttl - now;
+       }
        /* only allow validated (with DNSSEC) DNAMEs used from cache 
         * for insecure DNAMEs, query again. */
        *sec_status = d->security;
@@ -779,7 +786,7 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
        msg->rep->flags = BIT_QR; /* reply, no AA, no error */
         msg->rep->authoritative = 0; /* reply stored in cache can't be authoritative */
        msg->rep->qdcount = 1;
-       msg->rep->ttl = d->ttl - now;
+       msg->rep->ttl = rr_ttl;
        msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl);
        msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL;
        msg->rep->serve_expired_norec_ttl = 0;
@@ -831,7 +838,7 @@ synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region,
        if(!newd)
                return NULL;
        ck->entry.data = newd;
-       newd->ttl = d->ttl - now; /* RFC6672: synth CNAME TTL == DNAME TTL */
+       newd->ttl = rr_ttl; /* RFC6672: synth CNAME TTL == DNAME TTL */
        newd->count = 1;
        newd->rrsig_count = 0;
        newd->trust = rrset_trust_ans_noAA;
index 200e3c701f785f1da132941d3665348308d2a0f4..c1716a565783576809402f56eef592b237da7ede 100644 (file)
@@ -278,6 +278,10 @@ void rrset_cache_update_wildcard(struct rrset_cache* rrset_cache,
        (void)rrset_cache_update(rrset_cache, &ref, alloc, timenow);
 }
 
+/** Grace period in seconds for TTL=0 DNAME rrsets (RFC 2308: do not cache).
+ * Allows synthesis from cache within this window to reduce recursion load. */
+#define DNAME_TTL0_GRACE_SECONDS 1
+
 struct ub_packed_rrset_key* 
 rrset_cache_lookup(struct rrset_cache* r, uint8_t* qname, size_t qnamelen, 
        uint16_t qtype, uint16_t qclass, uint32_t flags, time_t timenow,
@@ -300,12 +304,20 @@ rrset_cache_lookup(struct rrset_cache* r, uint8_t* qname, size_t qnamelen,
                /* check TTL */
                struct packed_rrset_data* data = 
                        (struct packed_rrset_data*)e->data;
+               struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key;
                if(TTL_IS_EXPIRED(data->ttl, timenow)) {
-                       lock_rw_unlock(&e->lock);
-                       return NULL;
+                       /* Allow TTL=0 DNAME within grace period for synthesis */
+                       if(qtype == LDNS_RR_TYPE_DNAME &&
+                          (k->rk.flags & PACKED_RRSET_UPSTREAM_0TTL) &&
+                          (timenow - data->ttl_add) <= DNAME_TTL0_GRACE_SECONDS) {
+                               /* within grace: allow for synthesis */
+                       } else {
+                               lock_rw_unlock(&e->lock);
+                               return NULL;
+                       }
                }
                /* we're done */
-               return (struct ub_packed_rrset_key*)e->key;
+               return k;
        }
        return NULL;
 }
diff --git a/testdata/iter_dname_ttl0.rpl b/testdata/iter_dname_ttl0.rpl
new file mode 100644 (file)
index 0000000..3d4a995
--- /dev/null
@@ -0,0 +1,280 @@
+; config options
+; Test DNAME TTL=0 grace period: synthesis from cache within 1 second
+; Island of trust at example.com, DNSSEC signed DNAME with TTL=0 (RFC 2308)
+server:
+       trust-anchor: "example.com.    3600    IN      DS      2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b"
+       trust-anchor: "example.net.    3600    IN      DNSKEY  256 3 5 AQPQ41chR9DEHt/aIzIFAqanbDlRflJoRs5yz1jFsoRIT7dWf0r+PeDuewdxkszNH6wnU4QL8pfKFRh5PIYVBLK3 ;{id = 30899 (zsk), size = 512b}"
+       val-override-date: "20070916134226"
+       target-fetch-policy: "0 0 0 0 0"
+       qname-minimisation: "no"
+       fake-sha1: yes
+       trust-anchor-signaling: no
+
+stub-zone:
+       name: "."
+       stub-addr: 193.0.14.129         # K.ROOT-SERVERS.NET.
+CONFIG_END
+
+SCENARIO_BEGIN Test DNAME TTL=0: signed DNAME with TTL=0 and RRSIG Original TTL=0.
+
+; K.ROOT-SERVERS.NET.
+RANGE_BEGIN 0 100
+       ADDRESS 193.0.14.129 
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+. IN NS
+SECTION ANSWER
+. IN NS        K.ROOT-SERVERS.NET.
+SECTION ADDITIONAL
+K.ROOT-SERVERS.NET.    IN      A       193.0.14.129
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION AUTHORITY
+com.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+net. IN A
+SECTION AUTHORITY
+net.   IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.    IN      A       192.5.6.30
+ENTRY_END
+RANGE_END
+
+; a.gtld-servers.net.
+RANGE_BEGIN 0 100
+       ADDRESS 192.5.6.30
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+com. IN NS
+SECTION ANSWER
+com.    IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.     IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+net. IN NS
+SECTION ANSWER
+net.    IN NS   a.gtld-servers.net.
+SECTION ADDITIONAL
+a.gtld-servers.net.     IN      A       192.5.6.30
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode subdomain
+ADJUST copy_id copy_query
+REPLY QR NOERROR
+SECTION QUESTION
+example.net. IN A
+SECTION AUTHORITY
+example.net.   IN NS   ns.example.net.
+SECTION ADDITIONAL
+ns.example.net.                IN      A       1.2.3.5
+ENTRY_END
+RANGE_END
+
+; ns.example.com. - DNAME with TTL=0 (RRSIG Original TTL=0)
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.4
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN NS
+SECTION ANSWER
+example.com.    IN NS   ns.example.com.
+example.com.    3600    IN      RRSIG   NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com.         IN      A       1.2.3.4
+ns.example.com. 3600    IN      RRSIG   A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.com. IN DNSKEY
+SECTION ANSWER
+example.com.    3600    IN      DNSKEY  256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b}
+example.com. 3600    IN      RRSIG   DNSKEY DSA 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFBQRtlR4BEv9ohi+PGFjp+AHsJuHAhRCvz0shggvnvI88DFnBDCczHUcVA== ;{id = 2854}
+SECTION AUTHORITY
+example.com.   IN NS   ns.example.com.
+example.com.    3600    IN      RRSIG   NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+SECTION ADDITIONAL
+ns.example.com.                IN      A       1.2.3.4
+ns.example.com. 3600    IN      RRSIG   A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854}
+ENTRY_END
+
+; DNAME with TTL=0, RRSIG Original TTL=0 (signed with ldns-signzone)
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+foo.test-dname.example.com. IN A
+SECTION ANSWER
+test-dname.example.com.        0       IN      DNAME   example.net.
+test-dname.example.com.        0       IN      RRSIG   DNAME 3 3 0 20070926135752 20070829135752 2854 example.com. ADRb2Jl5SCTF2a9/5QFOCfwFzh4Cpt90pJptwrKc+vBHnlivGyPShrU=
+foo.test-dname.example.com. 0 IN CNAME foo.example.net.
+ENTRY_END
+
+; Same for foo2 - used when cache miss (or initial fill)
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+foo2.test-dname.example.com. IN A
+SECTION ANSWER
+test-dname.example.com.        0       IN      DNAME   example.net.
+test-dname.example.com.        0       IN      RRSIG   DNAME 3 3 0 20070926135752 20070829135752 2854 example.com. ADRb2Jl5SCTF2a9/5QFOCfwFzh4Cpt90pJptwrKc+vBHnlivGyPShrU=
+foo2.test-dname.example.com. 0 IN CNAME foo2.example.net.
+ENTRY_END
+RANGE_END
+
+; ns.example.net.
+RANGE_BEGIN 0 100
+       ADDRESS 1.2.3.5
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.net. IN NS
+SECTION ANSWER
+example.net.   IN NS   ns.example.net.
+example.net.    3600    IN      RRSIG   NS RSASHA1 2 3600 20070926134150 20070829134150 30899 example.net. E8JX0l4B+cSR5bkHQwOJy1pBmlLMTYCJ8EwfNMU/eCv0YhKwo26rHhn52FGisgv+Nwp7/NbhHqQ+kJgoZC94XA== ;{id = 30899}
+SECTION ADDITIONAL
+ns.example.net.                IN      A       1.2.3.5
+ns.example.net. 3600    IN      RRSIG   A RSASHA1 3 3600 20070926134150 20070829134150 30899 example.net. x+tQMC9FhzT7Fcy1pM5NrOC7E8nLd7THPI3C6ie4EwL8PrxllqlR3q/DKB0d/m0qCOPcgN6HFOYURV1s4uAcsw== ;{id = 30899}
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+example.net. IN DNSKEY
+SECTION ANSWER
+example.net.    3600    IN      DNSKEY  256 3 5 AQPQ41chR9DEHt/aIzIFAqanbDlRflJoRs5yz1jFsoRIT7dWf0r+PeDuewdxkszNH6wnU4QL8pfKFRh5PIYVBLK3 ;{id = 30899 (zsk), size = 512b}
+example.net.    3600    IN      RRSIG   DNSKEY RSASHA1 2 3600 20070926134150 20070829134150 30899 example.net. hiFzlQ8VoYgCuvIsfVuxC3mfJDqsTh0yc6abs5xMx5uEcIjb0dndFQx7INOM+imlzveEN73Hqp4OLFpFhsWLlw== ;{id = 30899}
+SECTION AUTHORITY
+example.net.   IN NS   ns.example.net.
+example.net.    3600    IN      RRSIG   NS RSASHA1 2 3600 20070926134150 20070829134150 30899 example.net. E8JX0l4B+cSR5bkHQwOJy1pBmlLMTYCJ8EwfNMU/eCv0YhKwo26rHhn52FGisgv+Nwp7/NbhHqQ+kJgoZC94XA== ;{id = 30899}
+SECTION ADDITIONAL
+ns.example.net.                IN      A       1.2.3.5
+ns.example.net. 3600    IN      RRSIG   A RSASHA1 3 3600 20070926134150 20070829134150 30899 example.net. x+tQMC9FhzT7Fcy1pM5NrOC7E8nLd7THPI3C6ie4EwL8PrxllqlR3q/DKB0d/m0qCOPcgN6HFOYURV1s4uAcsw== ;{id = 30899}
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+foo.example.net. IN A
+SECTION ANSWER
+foo.example.net. IN    A       11.12.13.15
+foo.example.net.       3600    IN      RRSIG   A 5 3 3600 20070926134150 20070829134150 30899 example.net. X6T6SE9UzxAD/4zKpwGOxEDyE4g7lfYYw3lvw533uwRN8mWTcBvSva0/jjyhrogJcuLO32jPHK6zGb93w2xnuA==
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+
+ENTRY_BEGIN
+MATCH opcode qtype qname
+ADJUST copy_id
+REPLY QR NOERROR
+SECTION QUESTION
+foo2.example.net. IN A
+SECTION ANSWER
+foo2.example.net. IN   A       11.12.13.16
+foo2.example.net.      3600    IN      RRSIG   A 5 3 3600 20070926134150 20070829134150 30899 example.net. BZm+GljD8m9N+pNJN8D+LlSyHqM+InNUe0+heKILR9be+Goqv6SEb7LKtX6+kj3239Y5by7u+/Cuk8kkWistEQ==
+SECTION AUTHORITY
+SECTION ADDITIONAL
+ENTRY_END
+RANGE_END
+
+STEP 1 TIME_PASSES ELAPSE 10
+; First query: get DNAME TTL=0 into cache
+STEP 10 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+foo.test-dname.example.com. IN A
+ENTRY_END
+
+STEP 20 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA AD DO NOERROR
+SECTION QUESTION
+foo.test-dname.example.com. IN A
+SECTION ANSWER
+test-dname.example.com.        0       IN      DNAME   example.net.
+test-dname.example.com.        0       IN      RRSIG   DNAME 3 3 0 20070926135752 20070829135752 2854 example.com. ADRb2Jl5SCTF2a9/5QFOCfwFzh4Cpt90pJptwrKc+vBHnlivGyPShrU=
+foo.test-dname.example.com. 0 IN CNAME foo.example.net.
+foo.example.net. IN    A       11.12.13.15
+foo.example.net.       3600    IN      RRSIG   A 5 3 3600 20070926134150 20070829134150 30899 example.net. X6T6SE9UzxAD/4zKpwGOxEDyE4g7lfYYw3lvw533uwRN8mWTcBvSva0/jjyhrogJcuLO32jPHK6zGb93w2xnuA==
+ENTRY_END
+
+; Second query: within grace period (no TIME_PASSES)
+; With cache grace: synthesis from cached TTL=0 DNAME; else full recursion
+STEP 30 QUERY
+ENTRY_BEGIN
+REPLY RD DO
+SECTION QUESTION
+foo2.test-dname.example.com. IN A
+ENTRY_END
+
+STEP 40 CHECK_ANSWER
+ENTRY_BEGIN
+MATCH all ttl
+REPLY QR RD RA AD DO NOERROR
+SECTION QUESTION
+foo2.test-dname.example.com. IN A
+SECTION ANSWER
+test-dname.example.com.        0       IN      DNAME   example.net.
+test-dname.example.com.        0       IN      RRSIG   DNAME 3 3 0 20070926135752 20070829135752 2854 example.com. ADRb2Jl5SCTF2a9/5QFOCfwFzh4Cpt90pJptwrKc+vBHnlivGyPShrU=
+foo2.test-dname.example.com. 0 IN CNAME foo2.example.net.
+foo2.example.net.      3600    IN      A       11.12.13.16
+foo2.example.net.      3600    IN      RRSIG   A 5 3 3600 20070926134150 20070829134150 30899 example.net. BZm+GljD8m9N+pNJN8D+LlSyHqM+InNUe0+heKILR9be+Goqv6SEb7LKtX6+kj3239Y5by7u+/Cuk8kkWistEQ==
+ENTRY_END
+
+SCENARIO_END