diff --git a/bin/named/server.c b/bin/named/server.c index b57dc5058e..5af25d1c09 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -413,7 +413,8 @@ static isc_result_t listenelt_http(const cfg_obj_t *http, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, isc_tlsctx_cache_t *tlsctx_cache, in_port_t port, - isc_mem_t *mctx, ns_listenelt_t **target); + isc_mem_t *mctx, isc_nm_proxy_type_t proxy, + ns_listenelt_t **target); #endif static isc_result_t @@ -10791,6 +10792,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, const cfg_obj_t *tlsobj = NULL, *httpobj = NULL; const cfg_obj_t *portobj = NULL; const cfg_obj_t *http_server = NULL; + const cfg_obj_t *proxyobj = NULL; in_port_t port = 0; const char *key = NULL, *cert = NULL, *ca_file = NULL, *dhparam_file = NULL, *ciphers = NULL; @@ -10802,6 +10804,7 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, uint32_t tls_protos = 0; ns_listen_tls_params_t tls_params = { 0 }; const char *tlsname = NULL; + isc_nm_proxy_type_t proxy = ISC_NM_PROXY_NONE; REQUIRE(target != NULL && *target == NULL); @@ -10985,16 +10988,31 @@ listenelt_fromconfig(const cfg_obj_t *listener, const cfg_obj_t *config, port = (in_port_t)cfg_obj_asuint32(portobj); } + proxyobj = cfg_tuple_get(ltup, "proxy"); + if (proxyobj != NULL && cfg_obj_isstring(proxyobj)) { + const char *proxyval = cfg_obj_asstring(proxyobj); + + if (strcasecmp(proxyval, "encrypted") == 0) { + INSIST(do_tls == true); + proxy = ISC_NM_PROXY_ENCRYPTED; + } else if (strcasecmp(proxyval, "plain") == 0) { + proxy = ISC_NM_PROXY_PLAIN; + } else { + UNREACHABLE(); + } + } + #ifdef HAVE_LIBNGHTTP2 if (http) { CHECK(listenelt_http(http_server, family, do_tls, &tls_params, - tlsctx_cache, port, mctx, &delt)); + tlsctx_cache, port, mctx, proxy, &delt)); } #endif /* HAVE_LIBNGHTTP2 */ if (!http) { CHECK(ns_listenelt_create(mctx, port, NULL, family, do_tls, - &tls_params, tlsctx_cache, &delt)); + &tls_params, tlsctx_cache, proxy, + &delt)); } result = cfg_acl_fromconfig(cfg_tuple_get(listener, "acl"), config, @@ -11015,7 +11033,8 @@ static isc_result_t listenelt_http(const cfg_obj_t *http, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, isc_tlsctx_cache_t *tlsctx_cache, in_port_t port, - isc_mem_t *mctx, ns_listenelt_t **target) { + isc_mem_t *mctx, isc_nm_proxy_type_t proxy, + ns_listenelt_t **target) { isc_result_t result = ISC_R_SUCCESS; ns_listenelt_t *delt = NULL; char **endpoints = NULL; @@ -11080,9 +11099,9 @@ listenelt_http(const cfg_obj_t *http, const uint16_t family, bool tls, INSIST(i == len); - result = ns_listenelt_create_http(mctx, port, NULL, family, tls, - tls_params, tlsctx_cache, endpoints, - len, max_clients, max_streams, &delt); + result = ns_listenelt_create_http( + mctx, port, NULL, family, tls, tls_params, tlsctx_cache, proxy, + endpoints, len, max_clients, max_streams, &delt); if (result != ISC_R_SUCCESS) { goto error; } diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index eb72f1e5ae..16c46183c6 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -3066,9 +3066,10 @@ queries may be specified using the :any:`listen-on` and :any:`listen-on-v6` opti :tags: server :short: Specifies the IPv6 addresses on which a server listens for DNS queries. - The :any:`listen-on` and :any:`listen-on-v6` statements can each take an optional - port, TLS configuration identifier, and/or HTTP configuration identifier, - in addition to an :term:`address_match_list`. + The :any:`listen-on` and :any:`listen-on-v6` statements can each + take an optional port, PROXYv2 support switch, TLS configuration + identifier, and/or HTTP configuration identifier, in addition to an + :term:`address_match_list`. The :term:`address_match_list` in :any:`listen-on` specifies the IPv4 addresses on which the server will listen. (IPv6 addresses are ignored, with a @@ -3081,6 +3082,52 @@ queries may be specified using the :any:`listen-on` and :any:`listen-on-v6` opti If no :any:`listen-on-v6` is specified, the default is to listen for standard DNS queries on port 53 of all IPv6 interfaces. + When specified, the PROXYv2 support switch ``proxy`` allows + enabling the PROXYv2 protocol support. The PROXYv2 protocol + provides the means for passing connection information, such as a + client's source and destination addresses and ports, across + multiple layers of NAT or TCP/UDP proxies to back-end servers. The + addresses passed to by the PROXYv2 protocol are then used instead + of the peer and interface addresses provided by the operating + system. + + The ``proxy`` switch can have the following values: + + * ``plain`` - accept plain PROXYv2 headers. It is the only valid + option for transports that do not employ encryption. In the case + of transports that employ encryption, it instructs BIND that + PROXYv2 headers are sent without encryption before the TLS + handshake. In that case, only PROXYv2 headers are not encrypted. + * ``encrypted`` - accept encrypted PROXYv2 headers. In the case of + transports that employ encryption, it instructs BIND that PROXYv2 + headers are sent encrypted immediately after the TLS + handshake. The option is valid only for the transports that employ + encryption. + + You must consult your proxying front-end software documentation to + decide which value you need to use. If in doubt, use ``plain`` for + encrypted transports, especially for DNS-over-HTTPS (DoH), but + DNS-specific software is likely to need ``encrypted``. + + It should be noted that when PROXYv2 is enabled on a listener, it + loses the ability to accept regular DNS queries without associated + PROXYv2 headers. + + In some cases, PROXYv2 headers might not contain usable source and + destination addresses. In particular, that happens when the headers + use ``LOCAL`` command or the headers that use unspecified or + unsupported by BIND address types. If otherwise correct, such + headers are accepted by BIND and the real endpoint addresses are + used in these cases. + + The PROXYv2 protocol is designed to be extensible and can carry + additional information in the form of type-length-values + (TLVs). Many of the types are defined in the protocol + specification, and for some of these, we do a reasonable amount of + validation in order to detect and reject ill-formed or hand-crafted + headers. Apart from that, this additional data, while accepted, is + not currently used by BIND for anything else. + If a TLS configuration is specified, :iscman:`named` will listen for DNS-over-TLS (DoT) connections, using the key and certificate specified in the referenced :any:`tls` statement. If the name ``ephemeral`` is used, @@ -3110,6 +3157,9 @@ queries may be specified using the :any:`listen-on` and :any:`listen-on-v6` opti listen-on port 1234 { !1.2.3.4; 1.2/16; }; listen-on port 8853 tls ephemeral { 4.3.2.1; }; listen-on port 8453 tls ephemeral http myserver { 8.7.6.5; }; + listen-on port 5300 proxy plain { !1.2.3.4; 1.2/16; }; + listen-on port 8953 proxy encrypted tls ephemeral { 4.3.2.1; }; + listen-on port 8553 proxy plain tls ephemeral http myserver { 8.7.6.5; }; The first two lines instruct the name server to listen for standard DNS queries on port 53 of the IP address 5.6.7.8 and on port 1234 of an address @@ -3126,9 +3176,12 @@ queries may be specified using the :any:`listen-on` and :any:`listen-on-v6` opti listen-on-v6 { any; }; listen-on-v6 port 1234 { !2001:db8::/32; any; }; - listen-on port 8853 tls example-tls { 2001:db8::100; }; - listen-on port 8453 tls example-tls http default { 2001:db8::100; }; - listen-on port 8000 tls none http myserver { 2001:db8::100; }; + listen-on-v6 port 8853 tls example-tls { 2001:db8::100; }; + listen-on-v6 port 8453 tls example-tls http default { 2001:db8::100; }; + listen-on-v6 port 8000 tls none http myserver { 2001:db8::100; }; + listen-on-v6 port 53000 proxy plain { !2001:db8::/32; any; }; + listen-on-v6 port 8953 proxy encrypted tls example-tls { 2001:db8::100; }; + listen-on-v6 port 8553 proxy plain tls example-tls http default { 2001:db8::100; }; The first two lines instruct the name server to listen for standard DNS queries on port 53 of any IPv6 addresses, and on port 1234 of IPv6 diff --git a/doc/misc/options b/doc/misc/options index 337744fdf0..4055c9596c 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -161,8 +161,8 @@ options { keep-response-order { ; ... }; // obsolete key-directory ; lame-ttl ; - listen-on [ port ] [ tls ] [ http ] { ; ... }; // may occur multiple times - listen-on-v6 [ port ] [ tls ] [ http ] { ; ... }; // may occur multiple times + listen-on [ port ] [ proxy ] [ tls ] [ http ] { ; ... }; // may occur multiple times + listen-on-v6 [ port ] [ proxy ] [ tls ] [ http ] { ; ... }; // may occur multiple times lmdb-mapsize ; managed-keys-directory ; masterfile-format ( raw | text ); diff --git a/lib/isccfg/aclconf.c b/lib/isccfg/aclconf.c index 2914106482..e2b5b065b4 100644 --- a/lib/isccfg/aclconf.c +++ b/lib/isccfg/aclconf.c @@ -701,7 +701,8 @@ cfg_acl_fromconfig(const cfg_obj_t *acl_data, const cfg_obj_t *cctx, if (strcasecmp(cfg_obj_asstring(obj_transport), "udp") == 0) { - transports = isc_nm_udpsocket; + transports = isc_nm_udpsocket | + isc_nm_proxyudpsocket; encrypted = false; } else if (strcasecmp(cfg_obj_asstring(obj_transport), "tcp") == 0) @@ -713,7 +714,8 @@ cfg_acl_fromconfig(const cfg_obj_t *acl_data, const cfg_obj_t *cctx, { /* Good ol' DNS over port 53 */ transports = isc_nm_streamdnssocket | - isc_nm_udpsocket; + isc_nm_udpsocket | + isc_nm_proxyudpsocket; encrypted = false; } else if (strcasecmp(cfg_obj_asstring(obj_transport), "tls") == 0) diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index 4e167ba444..cd5df28c33 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -1033,6 +1033,7 @@ check_listener(const cfg_obj_t *listener, const cfg_obj_t *config, const cfg_obj_t *tlsobj = NULL, *httpobj = NULL; const cfg_obj_t *portobj = NULL; const cfg_obj_t *http_server = NULL; + const cfg_obj_t *proxyobj = NULL; bool do_tls = false, no_tls = false; dns_acl_t *acl = NULL; @@ -1097,6 +1098,36 @@ check_listener(const cfg_obj_t *listener, const cfg_obj_t *config, } } + proxyobj = cfg_tuple_get(ltup, "proxy"); + if (proxyobj != NULL && cfg_obj_isstring(proxyobj)) { + const char *proxyval = cfg_obj_asstring(proxyobj); + if (proxyval == NULL || + (strcasecmp(proxyval, "encrypted") != 0 && + strcasecmp(proxyval, "plain") != 0)) + { + cfg_obj_log(proxyobj, logctx, ISC_LOG_ERROR, + "'proxy' must have one of the following " + "values: 'plain', 'encrypted'"); + + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + + if (proxyval != NULL && + strcasecmp(proxyval, "encrypted") == 0 && !do_tls) + { + cfg_obj_log(proxyobj, logctx, ISC_LOG_ERROR, + "'proxy encrypted' can be used only when " + "encryption is enabled by setting 'tls' to " + "a defined value or to 'ephemeral'"); + + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } + } + tresult = cfg_acl_fromconfig(cfg_tuple_get(listener, "acl"), config, logctx, actx, mctx, 0, &acl); if (result == ISC_R_SUCCESS) { diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 5554b3f208..b042d5a620 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -150,6 +150,11 @@ static cfg_type_t cfg_type_zone; static cfg_tuplefielddef_t listenon_tuple_fields[] = { { "port", &cfg_type_optional_port, 0 }, + /* + * Let's follow the protocols encapsulation order (lower->upper), at + * least roughly. + */ + { "proxy", &cfg_type_astring, CFG_CLAUSEFLAG_EXPERIMENTAL }, { "tls", &cfg_type_astring, 0 }, #if HAVE_LIBNGHTTP2 { "http", &cfg_type_astring, 0 }, diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h index 5d0a19b285..0f06347058 100644 --- a/lib/ns/include/ns/listenlist.h +++ b/lib/ns/include/ns/listenlist.h @@ -29,6 +29,7 @@ #include #include +#include #include #include @@ -51,6 +52,7 @@ struct ns_listenelt { size_t http_endpoints_number; uint32_t http_max_clients; uint32_t max_concurrent_streams; + isc_nm_proxy_type_t proxy; ISC_LINK(ns_listenelt_t) link; }; @@ -82,7 +84,8 @@ isc_result_t ns_listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, - isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target); + isc_tlsctx_cache_t *tlsctx_cache, isc_nm_proxy_type_t proxy, + ns_listenelt_t **target); /*%< * Create a listen-on list element. * @@ -96,7 +99,8 @@ isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, dns_acl_t *acl, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, - isc_tlsctx_cache_t *tlsctx_cache, char **endpoints, + isc_tlsctx_cache_t *tlsctx_cache, + isc_nm_proxy_type_t proxy, char **endpoints, size_t nendpoints, const uint32_t max_clients, const uint32_t max_streams, ns_listenelt_t **target); /*%< diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c index 39c4bca9f1..b94d8600c2 100644 --- a/lib/ns/interfacemgr.c +++ b/lib/ns/interfacemgr.c @@ -471,25 +471,31 @@ ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, } static isc_result_t -ns_interface_listenudp(ns_interface_t *ifp) { +ns_interface_listenudp(ns_interface_t *ifp, isc_nm_proxy_type_t proxy) { isc_result_t result; /* Reserve space for an ns_client_t with the netmgr handle */ - result = isc_nm_listenudp(ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, - ns_client_request, ifp, - &ifp->udplistensocket); + if (proxy == ISC_NM_PROXY_NONE) { + result = isc_nm_listenudp(ifp->mgr->nm, ISC_NM_LISTEN_ALL, + &ifp->addr, ns_client_request, ifp, + &ifp->udplistensocket); + } else { + INSIST(proxy == ISC_NM_PROXY_PLAIN); + result = isc_nm_listenproxyudp(ifp->mgr->nm, ISC_NM_LISTEN_ALL, + &ifp->addr, ns_client_request, + ifp, &ifp->udplistensocket); + } return (result); } static isc_result_t -ns_interface_listentcp(ns_interface_t *ifp) { +ns_interface_listentcp(ns_interface_t *ifp, isc_nm_proxy_type_t proxy) { isc_result_t result; result = isc_nm_listenstreamdns( ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, ns_client_request, ifp, ns__client_tcpconn, ifp, ifp->mgr->backlog, - &ifp->mgr->sctx->tcpquota, NULL, ISC_NM_PROXY_NONE, - &ifp->tcplistensocket); + &ifp->mgr->sctx->tcpquota, NULL, proxy, &ifp->tcplistensocket); if (result != ISC_R_SUCCESS) { isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, "creating TCP socket: %s", @@ -516,13 +522,14 @@ ns_interface_listentcp(ns_interface_t *ifp) { * TLS related options. */ static isc_result_t -ns_interface_listentls(ns_interface_t *ifp, isc_tlsctx_t *sslctx) { +ns_interface_listentls(ns_interface_t *ifp, isc_nm_proxy_type_t proxy, + isc_tlsctx_t *sslctx) { isc_result_t result; result = isc_nm_listenstreamdns( ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, ns_client_request, ifp, ns__client_tcpconn, ifp, ifp->mgr->backlog, - &ifp->mgr->sctx->tcpquota, sslctx, ISC_NM_PROXY_NONE, + &ifp->mgr->sctx->tcpquota, sslctx, proxy, &ifp->tcplistensocket); if (result != ISC_R_SUCCESS) { @@ -566,9 +573,9 @@ load_http_endpoints(isc_nm_http_endpoints_t *epset, ns_interface_t *ifp, #endif /* HAVE_LIBNGHTTP2 */ static isc_result_t -ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps, - size_t neps, uint32_t max_clients, - uint32_t max_concurrent_streams) { +ns_interface_listenhttp(ns_interface_t *ifp, isc_nm_proxy_type_t proxy, + isc_tlsctx_t *sslctx, char **eps, size_t neps, + uint32_t max_clients, uint32_t max_concurrent_streams) { #if HAVE_LIBNGHTTP2 isc_result_t result = ISC_R_FAILURE; isc_nmsocket_t *sock = NULL; @@ -585,7 +592,7 @@ ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps, result = isc_nm_listenhttp( ifp->mgr->nm, ISC_NM_LISTEN_ALL, &ifp->addr, ifp->mgr->backlog, quota, sslctx, epset, - max_concurrent_streams, ISC_NM_PROXY_NONE, &sock); + max_concurrent_streams, proxy, &sock); } isc_nm_http_endpoints_detach(&epset); @@ -629,6 +636,7 @@ ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps, return (result); #else UNUSED(ifp); + UNUSED(proxy); UNUSED(sslctx); UNUSED(eps); UNUSED(neps); @@ -660,7 +668,7 @@ interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, if (elt->is_http) { result = ns_interface_listenhttp( - ifp, elt->sslctx, elt->http_endpoints, + ifp, elt->proxy, elt->sslctx, elt->http_endpoints, elt->http_endpoints_number, elt->http_max_clients, elt->max_concurrent_streams); if (result != ISC_R_SUCCESS) { @@ -671,7 +679,7 @@ interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, } if (elt->sslctx != NULL) { - result = ns_interface_listentls(ifp, elt->sslctx); + result = ns_interface_listentls(ifp, elt->proxy, elt->sslctx); if (result != ISC_R_SUCCESS) { goto cleanup_interface; } @@ -679,7 +687,7 @@ interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, return (result); } - result = ns_interface_listenudp(ifp); + result = ns_interface_listenudp(ifp, elt->proxy); if (result != ISC_R_SUCCESS) { if ((result == ISC_R_ADDRINUSE) && (addr_in_use != NULL)) { *addr_in_use = true; @@ -688,7 +696,7 @@ interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, } if (((mgr->sctx->options & NS_SERVER_NOTCP) == 0)) { - result = ns_interface_listentcp(ifp); + result = ns_interface_listentcp(ifp, elt->proxy); if (result != ISC_R_SUCCESS) { if ((result == ISC_R_ADDRINUSE) && (addr_in_use != NULL)) diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c index a4a7c2b2e4..d267ebe1ea 100644 --- a/lib/ns/listenlist.c +++ b/lib/ns/listenlist.c @@ -31,7 +31,8 @@ static isc_result_t listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl, const uint16_t family, const bool is_http, bool tls, const ns_listen_tls_params_t *tls_params, - isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target) { + isc_tlsctx_cache_t *tlsctx_cache, isc_nm_proxy_type_t proxy, + ns_listenelt_t **target) { ns_listenelt_t *elt = NULL; isc_result_t result = ISC_R_SUCCESS; isc_tlsctx_t *sslctx = NULL; @@ -192,6 +193,7 @@ listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl, elt->http_endpoints_number = 0; elt->http_max_clients = 0; elt->max_concurrent_streams = 0; + elt->proxy = proxy; *target = elt; return (ISC_R_SUCCESS); @@ -210,16 +212,18 @@ isc_result_t ns_listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, - isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target) { + isc_tlsctx_cache_t *tlsctx_cache, isc_nm_proxy_type_t proxy, + ns_listenelt_t **target) { return listenelt_create(mctx, port, acl, family, false, tls, tls_params, - tlsctx_cache, target); + tlsctx_cache, proxy, target); } isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, dns_acl_t *acl, const uint16_t family, bool tls, const ns_listen_tls_params_t *tls_params, - isc_tlsctx_cache_t *tlsctx_cache, char **endpoints, + isc_tlsctx_cache_t *tlsctx_cache, + isc_nm_proxy_type_t proxy, char **endpoints, size_t nendpoints, const uint32_t max_clients, const uint32_t max_streams, ns_listenelt_t **target) { isc_result_t result; @@ -229,7 +233,7 @@ ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, dns_acl_t *acl, REQUIRE(nendpoints > 0); result = listenelt_create(mctx, http_port, acl, family, true, tls, - tls_params, tlsctx_cache, target); + tls_params, tlsctx_cache, proxy, target); if (result == ISC_R_SUCCESS) { (*target)->is_http = true; (*target)->http_endpoints = endpoints; @@ -334,7 +338,7 @@ ns_listenlist_default(isc_mem_t *mctx, in_port_t port, bool enabled, } result = ns_listenelt_create(mctx, port, acl, family, false, NULL, NULL, - &elt); + ISC_NM_PROXY_NONE, &elt); if (result != ISC_R_SUCCESS) { goto cleanup_acl; }