diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h index b517eaaec2..8388b0a51c 100644 --- a/lib/isc/include/isc/netmgr.h +++ b/lib/isc/include/isc/netmgr.h @@ -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); /*%< diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index d37d788e87..1056bdc093 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -32,7 +32,12 @@ #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= - * (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, diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index 2c5305f37b..a281fbde76 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -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); diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c index 10b0d23457..0c727a7bf7 100644 --- a/lib/isc/netmgr/netmgr.c +++ b/lib/isc/netmgr/netmgr.c @@ -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