]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
DoH: Set the "max-age" "Cache-Control" HTTP header value
authorArtem Boldariev <artem@boldariev.com>
Tue, 12 Oct 2021 13:58:45 +0000 (16:58 +0300)
committerArtem Boldariev <artem@boldariev.com>
Fri, 5 Nov 2021 12:14:59 +0000 (14:14 +0200)
This commit makes BIND set the "max-age" value of the "Cache-Control"
HTTP header to the minimal TTL from the Answer section for positive
answers, as RFC 8484 advises in section 5.1.

We calculate the minimal TTL as a side effect of rendering the
response DNS message, so it does not change the code flow much, nor
should it have any measurable negative impact on the performance.

For negative answers, the "max-age" value is set using the TTL and
SOA-minimum values from an SOA record in the Authority section.

lib/dns/include/dns/message.h
lib/dns/message.c
lib/ns/client.c

index 43528dea828bf7bdc9eeb88e978382e8d1aac5d3..864306b78310519ee61bd47ff0d6ffcd1f11f6a4 100644 (file)
@@ -199,6 +199,11 @@ struct dns_sortlist_arg {
        const dns_aclelement_t *element;
 };
 
+typedef struct dns_minttl {
+       bool      is_set;
+       dns_ttl_t ttl;
+} dns_minttl_t;
+
 struct dns_message {
        /* public from here down */
        unsigned int   magic;
@@ -279,6 +284,8 @@ struct dns_message {
        dns_sortlist_arg_t      order_arg;
 
        dns_indent_t indent;
+
+       dns_minttl_t minttl[DNS_SECTION_MAX];
 };
 
 struct dns_ednsopt {
@@ -1483,4 +1490,28 @@ dns_message_clonebuffer(dns_message_t *msg);
  * \li   msg be a valid message.
  */
 
+isc_result_t
+dns_message_minttl(dns_message_t *msg, const dns_section_t sectionid,
+                  dns_ttl_t *pttl);
+/*%<
+ * Get the smallest TTL from the 'sectionid' section of a rendered
+ * message.
+ *
+ * Requires:
+ * \li   msg be a valid rendered message;
+ * \li   'pttl != NULL'.
+ */
+
+isc_result_t
+dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl);
+/*%<
+ * Get the smalled TTL from the Answer section of 'msg', or if empty, try
+ * the MIN(SOA TTL, SOA MINIMUM) value from an SOA record in the Authority
+ * section. If neither of these are set, return ISC_R_NOTFOUND.
+ *
+ * Requires:
+ * \li   msg be a valid rendered message;
+ * \li   'pttl != NULL'.
+ */
+
 ISC_LANG_ENDDECLS
index 3ecf79b2731cb7dafab9f043b43ed8c1ac1f11a1..afc1441948a84c5ce77a44dd2ae6b530dacdad9e 100644 (file)
@@ -38,6 +38,7 @@
 #include <dns/rdatalist.h>
 #include <dns/rdataset.h>
 #include <dns/rdatastruct.h>
+#include <dns/soa.h>
 #include <dns/tsig.h>
 #include <dns/ttl.h>
 #include <dns/view.h>
@@ -1913,6 +1914,18 @@ maybe_clear_ad(dns_message_t *msg, dns_section_t sectionid) {
        }
 }
 
+static void
+update_min_section_ttl(dns_message_t *restrict msg,
+                      const dns_section_t sectionid,
+                      dns_rdataset_t *restrict rdataset) {
+       if (!msg->minttl[sectionid].is_set ||
+           rdataset->ttl < msg->minttl[sectionid].ttl)
+       {
+               msg->minttl[sectionid].is_set = true;
+               msg->minttl[sectionid].ttl = rdataset->ttl;
+       }
+}
+
 isc_result_t
 dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
                          unsigned int options) {
@@ -2012,6 +2025,9 @@ dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
                                msg->counts[sectionid] += total;
                                return (result);
                        }
+
+                       update_min_section_ttl(msg, sectionid, rdataset);
+
                        rdataset->attributes |= DNS_RDATASETATTR_RENDERED;
                }
        }
@@ -2107,6 +2123,9 @@ dns_message_rendersection(dns_message_t *msg, dns_section_t sectionid,
                                        msg->flags &= ~DNS_MESSAGEFLAG_AD;
                                }
 
+                               update_min_section_ttl(msg, sectionid,
+                                                      rdataset);
+
                                rdataset->attributes |=
                                        DNS_RDATASETATTR_RENDERED;
 
@@ -4692,3 +4711,108 @@ dns_message_clonebuffer(dns_message_t *msg) {
                msg->free_query = 1;
        }
 }
+
+static isc_result_t
+message_authority_soa_min(dns_message_t *msg, dns_ttl_t *pttl) {
+       isc_result_t result;
+       dns_rdataset_t *rdataset = NULL;
+       dns_name_t *name = NULL;
+
+       if (msg->counts[DNS_SECTION_AUTHORITY] == 0) {
+               return (ISC_R_NOTFOUND);
+       }
+
+       for (result = dns_message_firstname(msg, DNS_SECTION_AUTHORITY);
+            result == ISC_R_SUCCESS;
+            result = dns_message_nextname(msg, DNS_SECTION_AUTHORITY))
+       {
+               name = NULL;
+               dns_message_currentname(msg, DNS_SECTION_AUTHORITY, &name);
+               for (rdataset = ISC_LIST_HEAD(name->list); rdataset != NULL;
+                    rdataset = ISC_LIST_NEXT(rdataset, link))
+               {
+                       isc_result_t tresult;
+
+                       if ((rdataset->attributes &
+                            DNS_RDATASETATTR_RENDERED) == 0) {
+                               continue;
+                       }
+
+                       /* loop over the rdatas */
+                       for (tresult = dns_rdataset_first(rdataset);
+                            tresult == ISC_R_SUCCESS;
+                            tresult = dns_rdataset_next(rdataset))
+                       {
+                               dns_name_t tmp;
+                               isc_region_t r = { 0 };
+                               dns_rdata_t rdata = DNS_RDATA_INIT;
+
+                               dns_rdataset_current(rdataset, &rdata);
+
+                               switch (rdata.type) {
+                               case dns_rdatatype_soa:
+                                       /* SOA rdataset */
+                                       break;
+                               case dns_rdatatype_none:
+                                       /*
+                                        * Negative cache rdataset: we need
+                                        * to inspect the rdata to determine
+                                        * whether it's an SOA.
+                                        */
+                                       dns_rdata_toregion(&rdata, &r);
+                                       dns_name_init(&tmp, NULL);
+                                       dns_name_fromregion(&tmp, &r);
+                                       isc_region_consume(&r, tmp.length);
+                                       if (r.length < 2) {
+                                               continue;
+                                       }
+                                       rdata.type = r.base[0] << 8 | r.base[1];
+                                       if (rdata.type != dns_rdatatype_soa) {
+                                               continue;
+                                       }
+                                       break;
+                               default:
+                                       continue;
+                               }
+
+                               if (rdata.type == dns_rdatatype_soa) {
+                                       *pttl = ISC_MIN(
+                                               rdataset->ttl,
+                                               dns_soa_getminimum(&rdata));
+                                       return (ISC_R_SUCCESS);
+                               }
+                       }
+               }
+       }
+
+       return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+dns_message_minttl(dns_message_t *msg, const dns_section_t sectionid,
+                  dns_ttl_t *pttl) {
+       REQUIRE(DNS_MESSAGE_VALID(msg));
+       REQUIRE(pttl != NULL);
+
+       if (!msg->minttl[sectionid].is_set) {
+               return (ISC_R_NOTFOUND);
+       }
+
+       *pttl = msg->minttl[sectionid].ttl;
+       return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+dns_message_response_minttl(dns_message_t *msg, dns_ttl_t *pttl) {
+       isc_result_t result;
+
+       REQUIRE(DNS_MESSAGE_VALID(msg));
+       REQUIRE(pttl != NULL);
+
+       result = dns_message_minttl(msg, DNS_SECTION_ANSWER, pttl);
+       if (result != ISC_R_SUCCESS) {
+               return (message_authority_soa_min(msg, pttl));
+       }
+
+       return (ISC_R_SUCCESS);
+}
index 83b53695308f3a36a935f6de41f54e57d24a91fb..233a9dfade161192bbce61fcfe41e2519e907f10 100644 (file)
@@ -324,12 +324,21 @@ client_allocsendbuf(ns_client_t *client, isc_buffer_t *buffer,
 
 static void
 client_sendpkg(ns_client_t *client, isc_buffer_t *buffer) {
+       isc_result_t result;
        isc_region_t r;
+       dns_ttl_t min_ttl = 0;
 
        REQUIRE(client->sendhandle == NULL);
 
        isc_buffer_usedregion(buffer, &r);
        isc_nmhandle_attach(client->handle, &client->sendhandle);
+
+       if (isc_nm_is_http_handle(client->handle)) {
+               result = dns_message_response_minttl(client->message, &min_ttl);
+               if (result == ISC_R_SUCCESS) {
+                       isc_nm_set_maxage(client->handle, min_ttl);
+               }
+       }
        isc_nm_send(client->handle, &r, client_senddone, client);
 }