]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
DoH: Add isc_nm_set_min_answer_ttl()
authorArtem Boldariev <artem@boldariev.com>
Wed, 6 Oct 2021 11:09:53 +0000 (14:09 +0300)
committerArtem Boldariev <artem@boldariev.com>
Fri, 5 Nov 2021 12:14:59 +0000 (14:14 +0200)
This commit adds an isc_nm_set_min_answer_ttl() function which is
intended to to be used to give a hint to the underlying transport
regarding the answer TTL.

The interface is intentionally kept generic because over time more
transports might benefit from this functionality, but currently it is
intended for DoH to set "max-age" value within "Cache-Control" HTTP
header (as recommended in the RFC8484, section 5.1 "Cache
Interaction").

It is no-op for other DNS transports for the time being.

lib/isc/include/isc/netmgr.h
lib/isc/netmgr/http.c
lib/isc/netmgr/netmgr-int.h
lib/isc/netmgr/netmgr.c

index b517eaaec2f8e4760306b893eaf124b1be071368..8388b0a51cc64cca052af3eb26a3dafb1406d578 100644 (file)
@@ -621,6 +621,20 @@ isc_nm_xfr_allowed(isc_nmhandle_t *handle);
  * \li 'handle' is a valid connection handle.
  */
 
+void
+isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl);
+/*%<
+ * Set the minimal time to live from the server's response Answer
+ * section as a hint to the underlying transport.
+ *
+ * NOTE: The function currently is no-op for any protocol except HTTP/2.
+ *
+ * Requires:
+ *
+ * \li 'handle' is a valid netmgr handle object associated with an accepted
+ * connection.
+ */
+
 void
 isc_nm_task_enqueue(isc_nm_t *mgr, isc_task_t *task, int threadid);
 /*%<
index d37d788e874bc38ae52a73fb9f842153a18400c0..1056bdc0931d65845d0011849081a632deae24b5 100644 (file)
 
 #define DNS_MEDIA_TYPE "application/dns-message"
 
-#define DEFAULT_CACHE_CONTROL "no-cache, no-store"
+/*
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
+ * for additional details. Basically it means "avoid caching by any
+ * means."
+ */
+#define DEFAULT_CACHE_CONTROL "no-cache, no-store, must-revalidate"
 
 /*
  * If server during request processing surpasses any of the limits
@@ -1952,6 +1957,9 @@ server_send_error_response(const isc_http_error_responses_t error,
                isc_buffer_initnull(&socket->h2.rbuf);
        }
 
+       /* We do not want the error response to be cached anywhere. */
+       socket->h2.min_ttl = 0;
+
        for (size_t i = 0;
             i < sizeof(error_responses) / sizeof(error_responses[0]); i++)
        {
@@ -2144,7 +2152,7 @@ client_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock,
 static void
 server_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock,
                isc__nm_uvreq_t *req) {
-       size_t len;
+       size_t content_len_buf_len, cache_control_buf_len;
        isc_result_t result = ISC_R_SUCCESS;
        isc_nm_cb_t cb = req->cb.send;
        void *cbarg = req->cbarg;
@@ -2161,18 +2169,27 @@ server_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock,
        isc_buffer_init(&sock->h2.wbuf, req->uvbuf.base, req->uvbuf.len);
        isc_buffer_add(&sock->h2.wbuf, req->uvbuf.len);
 
-       len = snprintf(sock->h2.clenbuf, sizeof(sock->h2.clenbuf), "%lu",
-                      (unsigned long)req->uvbuf.len);
-       const nghttp2_nv hdrs[] = {
-               MAKE_NV2(":status", "200"),
-               MAKE_NV2("Content-Type", DNS_MEDIA_TYPE),
-               MAKE_NV("Content-Length", sock->h2.clenbuf, len),
-               /*
-                * TODO: implement Cache-Control: max-age=<seconds>
-                * (https://tools.ietf.org/html/rfc8484#section-5.1)
-                */
-               MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL)
-       };
+       content_len_buf_len = snprintf(sock->h2.clenbuf,
+                                      sizeof(sock->h2.clenbuf), "%lu",
+                                      (unsigned long)req->uvbuf.len);
+       if (sock->h2.min_ttl == 0) {
+               cache_control_buf_len =
+                       snprintf(sock->h2.cache_control_buf,
+                                sizeof(sock->h2.cache_control_buf), "%s",
+                                DEFAULT_CACHE_CONTROL);
+       } else {
+               cache_control_buf_len =
+                       snprintf(sock->h2.cache_control_buf,
+                                sizeof(sock->h2.cache_control_buf),
+                                "max-age=%" PRIu32, sock->h2.min_ttl);
+       }
+       const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "200"),
+                                   MAKE_NV2("Content-Type", DNS_MEDIA_TYPE),
+                                   MAKE_NV("Content-Length", sock->h2.clenbuf,
+                                           content_len_buf_len),
+                                   MAKE_NV("Cache-Control",
+                                           sock->h2.cache_control_buf,
+                                           cache_control_buf_len) };
 
        result = server_send_response(handle->httpsession->ngsession,
                                      sock->h2.stream_id, hdrs,
@@ -2838,6 +2855,23 @@ isc_nm_is_http_handle(isc_nmhandle_t *handle) {
        return (handle->sock->type == isc_nm_httpsocket);
 }
 
+void
+isc__nm_http_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) {
+       isc_nm_http_session_t *session;
+       isc_nmsocket_t *sock;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMSOCK(handle->sock));
+
+       sock = handle->sock;
+       session = sock->h2.session;
+
+       INSIST(VALID_HTTP2_SESSION(session));
+       INSIST(!session->client);
+
+       sock->h2.min_ttl = ttl;
+}
+
 static const bool base64url_validation_table[256] = {
        false, false, false, false, false, false, false, false, false, false,
        false, false, false, false, false, false, false, false, false, false,
index 2c5305f37b0bd8b2d4f6f4c28d39c41f4a83b4bc..a281fbde769f3b29dde677846597ed6d1e073ae5 100644 (file)
@@ -815,12 +815,16 @@ typedef struct isc_nmsocket_h2 {
        /* maximum concurrent streams (server-side) */
        uint32_t max_concurrent_streams;
 
+       uint32_t min_ttl; /* used to set "max-age" in responses */
+
        isc_http_request_type_t request_type;
        isc_http_scheme_type_t request_scheme;
 
        size_t content_length;
        char clenbuf[128];
 
+       char cache_control_buf[128];
+
        int headers_error_code;
        size_t headers_data_processed;
 
@@ -1706,6 +1710,9 @@ isc__nm_http_bad_request(isc_nmhandle_t *handle);
  * socket
  */
 
+void
+isc__nm_http_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl);
+
 void
 isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0);
 
index 10b0d2345767346792b938ab7d51ba27ad4fce30..0c727a7bf7e28088f5003a4867258abc3adcf468 100644 (file)
@@ -3456,6 +3456,38 @@ isc_nm_is_tlsdns_handle(isc_nmhandle_t *handle) {
        return (handle->sock->type == isc_nm_tlsdnssocket);
 }
 
+void
+isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) {
+       isc_nmsocket_t *sock;
+
+       REQUIRE(VALID_NMHANDLE(handle));
+       REQUIRE(VALID_NMSOCK(handle->sock));
+       REQUIRE(!atomic_load(&handle->sock->client));
+
+       sock = handle->sock;
+       switch (sock->type) {
+#if HAVE_LIBNGHTTP2
+       case isc_nm_httpsocket:
+               isc__nm_http_set_maxage(handle, ttl);
+               break;
+#endif /* HAVE_LIBNGHTTP2 */
+       case isc_nm_udpsocket:
+       case isc_nm_tcpdnssocket:
+       case isc_nm_tlsdnssocket:
+               return;
+               break;
+
+       case isc_nm_tcpsocket:
+#if HAVE_LIBNGHTTP2
+       case isc_nm_tlssocket:
+#endif /* HAVE_LIBNGHTTP2 */
+       default:
+               INSIST(0);
+               ISC_UNREACHABLE();
+               break;
+       }
+}
+
 #ifdef NETMGR_TRACE
 /*
  * Dump all active sockets in netmgr. We output to stderr