diff options
Diffstat (limited to 'lib/ns/interfacemgr.c')
-rw-r--r-- | lib/ns/interfacemgr.c | 1438 |
1 files changed, 1438 insertions, 0 deletions
diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c new file mode 100644 index 0000000..50b87df --- /dev/null +++ b/lib/ns/interfacemgr.c @@ -0,0 +1,1438 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <stdbool.h> + +#include <isc/interfaceiter.h> +#include <isc/netmgr.h> +#include <isc/os.h> +#include <isc/random.h> +#include <isc/string.h> +#include <isc/task.h> +#include <isc/util.h> + +#include <dns/acl.h> +#include <dns/dispatch.h> + +#include <ns/client.h> +#include <ns/interfacemgr.h> +#include <ns/log.h> +#include <ns/server.h> +#include <ns/stats.h> + +#ifdef HAVE_NET_ROUTE_H +#include <net/route.h> +#if defined(RTM_VERSION) && defined(RTM_NEWADDR) && defined(RTM_DELADDR) +#define MSGHDR rt_msghdr +#define MSGTYPE rtm_type +#endif /* if defined(RTM_VERSION) && defined(RTM_NEWADDR) && \ + * defined(RTM_DELADDR) */ +#endif /* ifdef HAVE_NET_ROUTE_H */ + +#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) +#define LINUX_NETLINK_AVAILABLE +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#if defined(RTM_NEWADDR) && defined(RTM_DELADDR) +#define MSGHDR nlmsghdr +#define MSGTYPE nlmsg_type +#endif /* if defined(RTM_NEWADDR) && defined(RTM_DELADDR) */ +#endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) \ + */ + +#define LISTENING(ifp) (((ifp)->flags & NS_INTERFACEFLAG_LISTENING) != 0) + +#ifdef TUNE_LARGE +#define UDPBUFFERS 32768 +#else /* ifdef TUNE_LARGE */ +#define UDPBUFFERS 1000 +#endif /* TUNE_LARGE */ + +#define IFMGR_MAGIC ISC_MAGIC('I', 'F', 'M', 'G') +#define NS_INTERFACEMGR_VALID(t) ISC_MAGIC_VALID(t, IFMGR_MAGIC) + +#define IFMGR_COMMON_LOGARGS \ + ns_lctx, NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR + +/*% nameserver interface manager structure */ +struct ns_interfacemgr { + unsigned int magic; /*%< Magic number */ + isc_refcount_t references; + isc_mutex_t lock; + isc_mem_t *mctx; /*%< Memory context */ + ns_server_t *sctx; /*%< Server context */ + isc_taskmgr_t *taskmgr; /*%< Task manager */ + isc_task_t *task; /*%< Task */ + isc_timermgr_t *timermgr; /*%< Timer manager */ + isc_nm_t *nm; /*%< Net manager */ + int ncpus; /*%< Number of workers */ + dns_dispatchmgr_t *dispatchmgr; + unsigned int generation; /*%< Current generation no */ + ns_listenlist_t *listenon4; + ns_listenlist_t *listenon6; + dns_aclenv_t *aclenv; /*%< Localhost/localnets ACLs */ + ISC_LIST(ns_interface_t) interfaces; /*%< List of interfaces */ + ISC_LIST(isc_sockaddr_t) listenon; + int backlog; /*%< Listen queue size */ + atomic_bool shuttingdown; /*%< Interfacemgr shutting down */ + ns_clientmgr_t **clientmgrs; /*%< Client managers */ + isc_nmhandle_t *route; +}; + +static void +purge_old_interfaces(ns_interfacemgr_t *mgr); + +static void +clearlistenon(ns_interfacemgr_t *mgr); + +static bool +need_rescan(ns_interfacemgr_t *mgr, struct MSGHDR *rtm, size_t len) { + if (rtm->MSGTYPE != RTM_NEWADDR && rtm->MSGTYPE != RTM_DELADDR) { + return (false); + } + +#ifndef LINUX_NETLINK_AVAILABLE + UNUSED(mgr); + UNUSED(len); + /* On most systems, any NEWADDR or DELADDR means we rescan */ + return (true); +#else /* LINUX_NETLINK_AVAILABLE */ + /* ...but on linux we need to check the messages more carefully */ + for (struct MSGHDR *nlh = rtm; + NLMSG_OK(nlh, len) && nlh->nlmsg_type != NLMSG_DONE; + nlh = NLMSG_NEXT(nlh, len)) + { + struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh); + struct rtattr *rth = IFA_RTA(ifa); + size_t rtl = IFA_PAYLOAD(nlh); + + while (rtl > 0 && RTA_OK(rth, rtl)) { + /* + * Look for IFA_ADDRESS to detect IPv6 interface + * state changes. + */ + if (rth->rta_type == IFA_ADDRESS && + ifa->ifa_family == AF_INET6) + { + bool existed = false; + bool was_listening = false; + isc_netaddr_t addr = { 0 }; + ns_interface_t *ifp = NULL; + + isc_netaddr_fromin6(&addr, RTA_DATA(rth)); + INSIST(isc_netaddr_getzone(&addr) == 0); + + /* + * Check whether we were listening on the + * address. We need to do this as the + * Linux kernel seems to issue messages + * containing IFA_ADDRESS far more often + * than the actual state changes (on + * router advertisements?) + */ + LOCK(&mgr->lock); + for (ifp = ISC_LIST_HEAD(mgr->interfaces); + ifp != NULL; + ifp = ISC_LIST_NEXT(ifp, link)) + { + isc_netaddr_t tmp = { 0 }; + isc_netaddr_fromsockaddr(&tmp, + &ifp->addr); + if (tmp.family != AF_INET6) { + continue; + } + + /* + * We have to nullify the zone (IPv6 + * scope ID) because we haven't got one + * from the kernel. Otherwise match + * could fail even for an existing + * address. + */ + isc_netaddr_setzone(&tmp, 0); + if (isc_netaddr_equal(&tmp, &addr)) { + was_listening = LISTENING(ifp); + existed = true; + break; + } + } + UNLOCK(&mgr->lock); + + /* + * Do rescan if the state of the interface + * has changed. + */ + if ((!existed && rtm->MSGTYPE == RTM_NEWADDR) || + (existed && was_listening && + rtm->MSGTYPE == RTM_DELADDR)) + { + return (true); + } + } else if (rth->rta_type == IFA_ADDRESS && + ifa->ifa_family == AF_INET) + { + /* + * It seems that the IPv4 P2P link state + * has changed. + */ + return (true); + } else if (rth->rta_type == IFA_LOCAL) { + /* + * Local address state has changed - do + * rescan. + */ + return (true); + } + rth = RTA_NEXT(rth, rtl); + } + } +#endif /* LINUX_NETLINK_AVAILABLE */ + + return (false); +} + +static void +route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region, + void *arg) { + ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)arg; + struct MSGHDR *rtm = NULL; + size_t rtmlen; + + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(9), "route_recv: %s", + isc_result_totext(eresult)); + + if (handle == NULL) { + return; + } + + if (eresult != ISC_R_SUCCESS) { + if (eresult != ISC_R_CANCELED && eresult != ISC_R_SHUTTINGDOWN) + { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "automatic interface scanning " + "terminated: %s", + isc_result_totext(eresult)); + } + isc_nmhandle_detach(&mgr->route); + ns_interfacemgr_detach(&mgr); + return; + } + + rtm = (struct MSGHDR *)region->base; + rtmlen = region->length; + +#ifdef RTM_VERSION + if (rtm->rtm_version != RTM_VERSION) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "automatic interface rescanning disabled: " + "rtm->rtm_version mismatch (%u != %u) " + "recompile required", + rtm->rtm_version, RTM_VERSION); + isc_nmhandle_detach(&mgr->route); + ns_interfacemgr_detach(&mgr); + return; + } +#endif /* ifdef RTM_VERSION */ + + REQUIRE(mgr->route != NULL); + + if (need_rescan(mgr, rtm, rtmlen) && mgr->sctx->interface_auto) { + ns_interfacemgr_scan(mgr, false, false); + } + + isc_nm_read(handle, route_recv, mgr); + return; +} + +static void +route_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) { + ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)arg; + + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(9), + "route_connected: %s", isc_result_totext(eresult)); + + if (eresult != ISC_R_SUCCESS) { + ns_interfacemgr_detach(&mgr); + return; + } + + INSIST(mgr->route == NULL); + + isc_nmhandle_attach(handle, &mgr->route); + isc_nm_read(handle, route_recv, mgr); +} + +isc_result_t +ns_interfacemgr_create(isc_mem_t *mctx, ns_server_t *sctx, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, + isc_nm_t *nm, dns_dispatchmgr_t *dispatchmgr, + isc_task_t *task, dns_geoip_databases_t *geoip, + int ncpus, bool scan, ns_interfacemgr_t **mgrp) { + isc_result_t result; + ns_interfacemgr_t *mgr = NULL; + + UNUSED(task); + + REQUIRE(mctx != NULL); + REQUIRE(mgrp != NULL); + REQUIRE(*mgrp == NULL); + + mgr = isc_mem_get(mctx, sizeof(*mgr)); + *mgr = (ns_interfacemgr_t){ .taskmgr = taskmgr, + .timermgr = timermgr, + .nm = nm, + .dispatchmgr = dispatchmgr, + .generation = 1, + .ncpus = ncpus }; + + isc_mem_attach(mctx, &mgr->mctx); + ns_server_attach(sctx, &mgr->sctx); + + isc_mutex_init(&mgr->lock); + + result = isc_task_create_bound(taskmgr, 0, &mgr->task, 0); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + atomic_init(&mgr->shuttingdown, false); + + ISC_LIST_INIT(mgr->interfaces); + ISC_LIST_INIT(mgr->listenon); + + /* + * The listen-on lists are initially empty. + */ + result = ns_listenlist_create(mctx, &mgr->listenon4); + if (result != ISC_R_SUCCESS) { + goto cleanup_task; + } + ns_listenlist_attach(mgr->listenon4, &mgr->listenon6); + + result = dns_aclenv_create(mctx, &mgr->aclenv); + if (result != ISC_R_SUCCESS) { + goto cleanup_listenon; + } +#if defined(HAVE_GEOIP2) + mgr->aclenv->geoip = geoip; +#else /* if defined(HAVE_GEOIP2) */ + UNUSED(geoip); +#endif /* if defined(HAVE_GEOIP2) */ + + isc_refcount_init(&mgr->references, 1); + mgr->magic = IFMGR_MAGIC; + *mgrp = mgr; + + mgr->clientmgrs = isc_mem_get(mgr->mctx, + mgr->ncpus * sizeof(mgr->clientmgrs[0])); + for (size_t i = 0; i < (size_t)mgr->ncpus; i++) { + result = ns_clientmgr_create(mgr->sctx, mgr->taskmgr, + mgr->timermgr, mgr->aclenv, (int)i, + &mgr->clientmgrs[i]); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + } + + if (scan) { + ns_interfacemgr_t *imgr = NULL; + + ns_interfacemgr_attach(mgr, &imgr); + + result = isc_nm_routeconnect(nm, route_connected, imgr, 0); + if (result == ISC_R_NOTIMPLEMENTED) { + ns_interfacemgr_detach(&imgr); + } + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO, + "unable to open route socket: %s", + isc_result_totext(result)); + } + } + + return (ISC_R_SUCCESS); + +cleanup_listenon: + ns_listenlist_detach(&mgr->listenon4); + ns_listenlist_detach(&mgr->listenon6); +cleanup_task: + isc_task_detach(&mgr->task); +cleanup_lock: + isc_mutex_destroy(&mgr->lock); + ns_server_detach(&mgr->sctx); + isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr)); + return (result); +} + +static void +ns_interfacemgr_destroy(ns_interfacemgr_t *mgr) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + isc_refcount_destroy(&mgr->references); + + dns_aclenv_detach(&mgr->aclenv); + ns_listenlist_detach(&mgr->listenon4); + ns_listenlist_detach(&mgr->listenon6); + clearlistenon(mgr); + isc_mutex_destroy(&mgr->lock); + for (size_t i = 0; i < (size_t)mgr->ncpus; i++) { + ns_clientmgr_detach(&mgr->clientmgrs[i]); + } + isc_mem_put(mgr->mctx, mgr->clientmgrs, + mgr->ncpus * sizeof(mgr->clientmgrs[0])); + + if (mgr->sctx != NULL) { + ns_server_detach(&mgr->sctx); + } + isc_task_detach(&mgr->task); + mgr->magic = 0; + isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr)); +} + +void +ns_interfacemgr_setbacklog(ns_interfacemgr_t *mgr, int backlog) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + LOCK(&mgr->lock); + mgr->backlog = backlog; + UNLOCK(&mgr->lock); +} + +dns_aclenv_t * +ns_interfacemgr_getaclenv(ns_interfacemgr_t *mgr) { + dns_aclenv_t *aclenv = NULL; + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + LOCK(&mgr->lock); + aclenv = mgr->aclenv; + UNLOCK(&mgr->lock); + + return (aclenv); +} + +void +ns_interfacemgr_attach(ns_interfacemgr_t *source, ns_interfacemgr_t **target) { + REQUIRE(NS_INTERFACEMGR_VALID(source)); + isc_refcount_increment(&source->references); + *target = source; +} + +void +ns_interfacemgr_detach(ns_interfacemgr_t **targetp) { + ns_interfacemgr_t *target = *targetp; + *targetp = NULL; + REQUIRE(target != NULL); + REQUIRE(NS_INTERFACEMGR_VALID(target)); + if (isc_refcount_decrement(&target->references) == 1) { + ns_interfacemgr_destroy(target); + } +} + +void +ns_interfacemgr_shutdown(ns_interfacemgr_t *mgr) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + /*% + * Shut down and detach all interfaces. + * By incrementing the generation count, we make + * purge_old_interfaces() consider all interfaces "old". + */ + mgr->generation++; + atomic_store(&mgr->shuttingdown, true); + + purge_old_interfaces(mgr); + + if (mgr->route != NULL) { + isc_nm_cancelread(mgr->route); + } + + for (size_t i = 0; i < (size_t)mgr->ncpus; i++) { + ns_clientmgr_shutdown(mgr->clientmgrs[i]); + } +} + +static void +interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, + ns_interface_t **ifpret) { + ns_interface_t *ifp = NULL; + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + ifp = isc_mem_get(mgr->mctx, sizeof(*ifp)); + *ifp = (ns_interface_t){ .generation = mgr->generation, .addr = *addr }; + + strlcpy(ifp->name, name, sizeof(ifp->name)); + + isc_mutex_init(&ifp->lock); + + isc_refcount_init(&ifp->ntcpaccepting, 0); + isc_refcount_init(&ifp->ntcpactive, 0); + + ISC_LINK_INIT(ifp, link); + + ns_interfacemgr_attach(mgr, &ifp->mgr); + ifp->magic = IFACE_MAGIC; + + LOCK(&mgr->lock); + ISC_LIST_APPEND(mgr->interfaces, ifp, link); + UNLOCK(&mgr->lock); + + *ifpret = ifp; +} + +static isc_result_t +ns_interface_listenudp(ns_interface_t *ifp) { + isc_result_t result; + + /* Reserve space for an ns_client_t with the netmgr handle */ + result = isc_nm_listenudp(ifp->mgr->nm, &ifp->addr, ns__client_request, + ifp, sizeof(ns_client_t), + &ifp->udplistensocket); + return (result); +} + +static isc_result_t +ns_interface_listentcp(ns_interface_t *ifp) { + isc_result_t result; + + result = isc_nm_listentcpdns( + ifp->mgr->nm, &ifp->addr, ns__client_request, ifp, + ns__client_tcpconn, ifp, sizeof(ns_client_t), ifp->mgr->backlog, + &ifp->mgr->sctx->tcpquota, &ifp->tcplistensocket); + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "creating TCP socket: %s", + isc_result_totext(result)); + } + + /* + * We call this now to update the tcp-highwater statistic: + * this is necessary because we are adding to the TCP quota just + * by listening. + */ + result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp); + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "connecting TCP socket: %s", + isc_result_totext(result)); + } + + return (result); +} + +/* + * XXXWPK we should probably pass a complete object with key, cert, and other + * TLS related options. + */ +static isc_result_t +ns_interface_listentls(ns_interface_t *ifp, isc_tlsctx_t *sslctx) { + isc_result_t result; + + result = isc_nm_listentlsdns( + ifp->mgr->nm, &ifp->addr, ns__client_request, ifp, + ns__client_tcpconn, ifp, sizeof(ns_client_t), ifp->mgr->backlog, + &ifp->mgr->sctx->tcpquota, sslctx, &ifp->tcplistensocket); + + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "creating TLS socket: %s", + isc_result_totext(result)); + return (result); + } + + /* + * We call this now to update the tcp-highwater statistic: + * this is necessary because we are adding to the TCP quota just + * by listening. + */ + result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp); + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "updating TCP stats: %s", + isc_result_totext(result)); + } + + return (result); +} + +#ifdef HAVE_LIBNGHTTP2 +static isc_result_t +load_http_endpoints(isc_nm_http_endpoints_t *epset, ns_interface_t *ifp, + char **eps, size_t neps) { + isc_result_t result = ISC_R_FAILURE; + + for (size_t i = 0; i < neps; i++) { + result = isc_nm_http_endpoints_add(epset, eps[i], + ns__client_request, ifp, + sizeof(ns_client_t)); + if (result != ISC_R_SUCCESS) { + break; + } + } + + return (result); +} +#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) { +#if HAVE_LIBNGHTTP2 + isc_result_t result = ISC_R_FAILURE; + isc_nmsocket_t *sock = NULL; + isc_nm_http_endpoints_t *epset = NULL; + isc_quota_t *quota = NULL; + + epset = isc_nm_http_endpoints_new(ifp->mgr->mctx); + + result = load_http_endpoints(epset, ifp, eps, neps); + + if (result == ISC_R_SUCCESS) { + quota = isc_mem_get(ifp->mgr->mctx, sizeof(*quota)); + isc_quota_init(quota, max_clients); + result = isc_nm_listenhttp( + ifp->mgr->nm, &ifp->addr, ifp->mgr->backlog, quota, + sslctx, epset, max_concurrent_streams, &sock); + } + + isc_nm_http_endpoints_detach(&epset); + + if (quota != NULL) { + if (result != ISC_R_SUCCESS) { + isc_quota_destroy(quota); + isc_mem_put(ifp->mgr->mctx, quota, sizeof(*quota)); + } else { + ifp->http_quota = quota; + ns_server_append_http_quota(ifp->mgr->sctx, quota); + } + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "creating %s socket: %s", + sslctx ? "HTTPS" : "HTTP", + isc_result_totext(result)); + return (result); + } + + if (sslctx) { + ifp->http_secure_listensocket = sock; + } else { + ifp->http_listensocket = sock; + } + + /* + * We call this now to update the tcp-highwater statistic: + * this is necessary because we are adding to the TCP quota just + * by listening. + */ + result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp); + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "updating TCP stats: %s", + isc_result_totext(result)); + } + + return (result); +#else + UNUSED(ifp); + UNUSED(sslctx); + UNUSED(eps); + UNUSED(neps); + UNUSED(max_clients); + UNUSED(max_concurrent_streams); + return (ISC_R_NOTIMPLEMENTED); +#endif +} + +static isc_result_t +interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name, + ns_interface_t **ifpret, ns_listenelt_t *elt, + bool *addr_in_use) { + isc_result_t result; + ns_interface_t *ifp = NULL; + + REQUIRE(ifpret != NULL); + REQUIRE(addr_in_use == NULL || !*addr_in_use); + + ifp = *ifpret; + + if (ifp == NULL) { + interface_create(mgr, addr, name, &ifp); + } else { + REQUIRE(!LISTENING(ifp)); + } + + ifp->flags |= NS_INTERFACEFLAG_LISTENING; + + if (elt->is_http) { + result = ns_interface_listenhttp( + ifp, elt->sslctx, elt->http_endpoints, + elt->http_endpoints_number, elt->http_max_clients, + elt->max_concurrent_streams); + if (result != ISC_R_SUCCESS) { + goto cleanup_interface; + } + *ifpret = ifp; + return (result); + } + + if (elt->sslctx != NULL) { + result = ns_interface_listentls(ifp, elt->sslctx); + if (result != ISC_R_SUCCESS) { + goto cleanup_interface; + } + *ifpret = ifp; + return (result); + } + + result = ns_interface_listenudp(ifp); + if (result != ISC_R_SUCCESS) { + if ((result == ISC_R_ADDRINUSE) && (addr_in_use != NULL)) { + *addr_in_use = true; + } + goto cleanup_interface; + } + + if (((mgr->sctx->options & NS_SERVER_NOTCP) == 0)) { + result = ns_interface_listentcp(ifp); + if (result != ISC_R_SUCCESS) { + if ((result == ISC_R_ADDRINUSE) && + (addr_in_use != NULL)) + { + *addr_in_use = true; + } + + /* + * XXXRTH We don't currently have a way to easily stop + * dispatch service, so we currently return + * ISC_R_SUCCESS (the UDP stuff will work even if TCP + * creation failed). This will be fixed later. + */ + result = ISC_R_SUCCESS; + } + } + *ifpret = ifp; + return (result); + +cleanup_interface: + ns_interface_shutdown(ifp); + return (result); +} + +void +ns_interface_shutdown(ns_interface_t *ifp) { + ifp->flags &= ~NS_INTERFACEFLAG_LISTENING; + + if (ifp->udplistensocket != NULL) { + isc_nm_stoplistening(ifp->udplistensocket); + isc_nmsocket_close(&ifp->udplistensocket); + } + if (ifp->tcplistensocket != NULL) { + isc_nm_stoplistening(ifp->tcplistensocket); + isc_nmsocket_close(&ifp->tcplistensocket); + } + if (ifp->http_listensocket != NULL) { + isc_nm_stoplistening(ifp->http_listensocket); + isc_nmsocket_close(&ifp->http_listensocket); + } + if (ifp->http_secure_listensocket != NULL) { + isc_nm_stoplistening(ifp->http_secure_listensocket); + isc_nmsocket_close(&ifp->http_secure_listensocket); + } + ifp->http_quota = NULL; +} + +static void +interface_destroy(ns_interface_t **interfacep) { + ns_interface_t *ifp = NULL; + ns_interfacemgr_t *mgr = NULL; + + REQUIRE(interfacep != NULL); + + ifp = *interfacep; + *interfacep = NULL; + + REQUIRE(NS_INTERFACE_VALID(ifp)); + + mgr = ifp->mgr; + + ns_interface_shutdown(ifp); + + ifp->magic = 0; + isc_mutex_destroy(&ifp->lock); + ns_interfacemgr_detach(&ifp->mgr); + isc_refcount_destroy(&ifp->ntcpactive); + isc_refcount_destroy(&ifp->ntcpaccepting); + + isc_mem_put(mgr->mctx, ifp, sizeof(*ifp)); +} + +/*% + * Search the interface list for an interface whose address and port + * both match those of 'addr'. Return a pointer to it, or NULL if not found. + */ +static ns_interface_t * +find_matching_interface(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr) { + ns_interface_t *ifp; + LOCK(&mgr->lock); + for (ifp = ISC_LIST_HEAD(mgr->interfaces); ifp != NULL; + ifp = ISC_LIST_NEXT(ifp, link)) + { + if (isc_sockaddr_equal(&ifp->addr, addr)) { + break; + } + } + UNLOCK(&mgr->lock); + return (ifp); +} + +/*% + * Remove any interfaces whose generation number is not the current one. + */ +static void +purge_old_interfaces(ns_interfacemgr_t *mgr) { + ns_interface_t *ifp = NULL, *next = NULL; + ISC_LIST(ns_interface_t) interfaces; + + ISC_LIST_INIT(interfaces); + + LOCK(&mgr->lock); + for (ifp = ISC_LIST_HEAD(mgr->interfaces); ifp != NULL; ifp = next) { + INSIST(NS_INTERFACE_VALID(ifp)); + next = ISC_LIST_NEXT(ifp, link); + if (ifp->generation != mgr->generation) { + ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link); + ISC_LIST_APPEND(interfaces, ifp, link); + } + } + UNLOCK(&mgr->lock); + + for (ifp = ISC_LIST_HEAD(interfaces); ifp != NULL; ifp = next) { + next = ISC_LIST_NEXT(ifp, link); + if (LISTENING(ifp)) { + char sabuf[256]; + isc_sockaddr_format(&ifp->addr, sabuf, sizeof(sabuf)); + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO, + "no longer listening on %s", sabuf); + ns_interface_shutdown(ifp); + } + ISC_LIST_UNLINK(interfaces, ifp, link); + interface_destroy(&ifp); + } +} + +static bool +listenon_is_ip6_any(ns_listenelt_t *elt) { + REQUIRE(elt && elt->acl); + return (dns_acl_isany(elt->acl)); +} + +static isc_result_t +setup_locals(isc_interface_t *interface, dns_acl_t *localhost, + dns_acl_t *localnets) { + isc_result_t result; + unsigned int prefixlen; + isc_netaddr_t *netaddr; + + netaddr = &interface->address; + + /* First add localhost address */ + prefixlen = (netaddr->family == AF_INET) ? 32 : 128; + result = dns_iptable_addprefix(localhost->iptable, netaddr, prefixlen, + true); + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* Then add localnets prefix */ + result = isc_netaddr_masktoprefixlen(&interface->netmask, &prefixlen); + + /* Non contiguous netmasks not allowed by IPv6 arch. */ + if (result != ISC_R_SUCCESS && netaddr->family == AF_INET6) { + return (result); + } + + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_WARNING, + "omitting IPv4 interface %s from " + "localnets ACL: %s", + interface->name, isc_result_totext(result)); + return (ISC_R_SUCCESS); + } + + if (prefixlen == 0U) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_WARNING, + "omitting %s interface %s from localnets ACL: " + "zero prefix length detected", + (netaddr->family == AF_INET) ? "IPv4" : "IPv6", + interface->name); + return (ISC_R_SUCCESS); + } + + result = dns_iptable_addprefix(localnets->iptable, netaddr, prefixlen, + true); + if (result != ISC_R_SUCCESS) { + return (result); + } + + return (ISC_R_SUCCESS); +} + +static void +setup_listenon(ns_interfacemgr_t *mgr, isc_interface_t *interface, + in_port_t port) { + isc_sockaddr_t *addr; + isc_sockaddr_t *old; + + addr = isc_mem_get(mgr->mctx, sizeof(*addr)); + + isc_sockaddr_fromnetaddr(addr, &interface->address, port); + + LOCK(&mgr->lock); + for (old = ISC_LIST_HEAD(mgr->listenon); old != NULL; + old = ISC_LIST_NEXT(old, link)) + { + if (isc_sockaddr_equal(addr, old)) { + /* We found an existing address */ + isc_mem_put(mgr->mctx, addr, sizeof(*addr)); + goto unlock; + } + } + + ISC_LIST_APPEND(mgr->listenon, addr, link); +unlock: + UNLOCK(&mgr->lock); +} + +static void +clearlistenon(ns_interfacemgr_t *mgr) { + ISC_LIST(isc_sockaddr_t) listenon; + isc_sockaddr_t *old; + + ISC_LIST_INIT(listenon); + + LOCK(&mgr->lock); + ISC_LIST_MOVE(listenon, mgr->listenon); + UNLOCK(&mgr->lock); + + old = ISC_LIST_HEAD(listenon); + while (old != NULL) { + ISC_LIST_UNLINK(listenon, old, link); + isc_mem_put(mgr->mctx, old, sizeof(*old)); + old = ISC_LIST_HEAD(listenon); + } +} + +static void +replace_listener_tlsctx(ns_interface_t *ifp, isc_tlsctx_t *newctx) { + char sabuf[ISC_SOCKADDR_FORMATSIZE]; + + isc_sockaddr_format(&ifp->addr, sabuf, sizeof(sabuf)); + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO, + "updating TLS context on %s", sabuf); + if (ifp->tcplistensocket != NULL) { + /* 'tcplistensocket' is used for DoT */ + isc_nmsocket_set_tlsctx(ifp->tcplistensocket, newctx); + } else if (ifp->http_secure_listensocket != NULL) { + isc_nmsocket_set_tlsctx(ifp->http_secure_listensocket, newctx); + } +} + +#ifdef HAVE_LIBNGHTTP2 +static void +update_http_settings(ns_interface_t *ifp, ns_listenelt_t *le) { + isc_result_t result; + isc_nmsocket_t *listener; + isc_nm_http_endpoints_t *epset; + + REQUIRE(le->is_http); + + INSIST(ifp->http_quota != NULL); + isc_quota_max(ifp->http_quota, le->http_max_clients); + + if (ifp->http_secure_listensocket != NULL) { + listener = ifp->http_secure_listensocket; + } else { + INSIST(ifp->http_listensocket != NULL); + listener = ifp->http_listensocket; + } + + isc_nmsocket_set_max_streams(listener, le->max_concurrent_streams); + + epset = isc_nm_http_endpoints_new(ifp->mgr->mctx); + + result = load_http_endpoints(epset, ifp, le->http_endpoints, + le->http_endpoints_number); + + if (result == ISC_R_SUCCESS) { + isc_nm_http_set_endpoints(listener, epset); + } + + isc_nm_http_endpoints_detach(&epset); +} +#endif /* HAVE_LIBNGHTTP2 */ + +static void +update_listener_configuration(ns_interfacemgr_t *mgr, ns_interface_t *ifp, + ns_listenelt_t *le) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + REQUIRE(NS_INTERFACE_VALID(ifp)); + REQUIRE(le != NULL); + + LOCK(&mgr->lock); + /* + * We need to update the TLS contexts + * inside the TLS/HTTPS listeners during + * a reconfiguration because the + * certificates could have been changed. + */ + if (le->sslctx != NULL) { + replace_listener_tlsctx(ifp, le->sslctx); + } + +#ifdef HAVE_LIBNGHTTP2 + /* + * Let's update HTTP listener settings + * on reconfiguration. + */ + if (le->is_http) { + update_http_settings(ifp, le); + } +#endif /* HAVE_LIBNGHTTP2 */ + + UNLOCK(&mgr->lock); +} + +static isc_result_t +do_scan(ns_interfacemgr_t *mgr, bool verbose, bool config) { + isc_interfaceiter_t *iter = NULL; + bool scan_ipv4 = false; + bool scan_ipv6 = false; + bool ipv6only = true; + bool ipv6pktinfo = true; + isc_result_t result; + isc_netaddr_t zero_address, zero_address6; + ns_listenelt_t *le = NULL; + isc_sockaddr_t listen_addr; + ns_interface_t *ifp = NULL; + bool log_explicit = false; + bool dolistenon; + char sabuf[ISC_SOCKADDR_FORMATSIZE]; + bool tried_listening; + bool all_addresses_in_use; + dns_acl_t *localhost = NULL; + dns_acl_t *localnets = NULL; + + if (isc_net_probeipv6() == ISC_R_SUCCESS) { + scan_ipv6 = true; + } else if ((mgr->sctx->options & NS_SERVER_DISABLE6) == 0) { + isc_log_write(IFMGR_COMMON_LOGARGS, + verbose ? ISC_LOG_INFO : ISC_LOG_DEBUG(1), + "no IPv6 interfaces found"); + } + + if (isc_net_probeipv4() == ISC_R_SUCCESS) { + scan_ipv4 = true; + } else if ((mgr->sctx->options & NS_SERVER_DISABLE4) == 0) { + isc_log_write(IFMGR_COMMON_LOGARGS, + verbose ? ISC_LOG_INFO : ISC_LOG_DEBUG(1), + "no IPv4 interfaces found"); + } + + /* + * A special, but typical case; listen-on-v6 { any; }. + * When we can make the socket IPv6-only, open a single wildcard + * socket for IPv6 communication. Otherwise, make separate + * socket for each IPv6 address in order to avoid accepting IPv4 + * packets as the form of mapped addresses unintentionally + * unless explicitly allowed. + */ + if (scan_ipv6 && isc_net_probe_ipv6only() != ISC_R_SUCCESS) { + ipv6only = false; + log_explicit = true; + } + if (scan_ipv6 && isc_net_probe_ipv6pktinfo() != ISC_R_SUCCESS) { + ipv6pktinfo = false; + log_explicit = true; + } + if (scan_ipv6 && ipv6only && ipv6pktinfo) { + for (le = ISC_LIST_HEAD(mgr->listenon6->elts); le != NULL; + le = ISC_LIST_NEXT(le, link)) + { + struct in6_addr in6a; + + if (!listenon_is_ip6_any(le)) { + continue; + } + + in6a = in6addr_any; + isc_sockaddr_fromin6(&listen_addr, &in6a, le->port); + + ifp = find_matching_interface(mgr, &listen_addr); + if (ifp != NULL) { + ifp->generation = mgr->generation; + if (LISTENING(ifp)) { + if (config) { + update_listener_configuration( + mgr, ifp, le); + } + continue; + } + } + + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO, + "listening on IPv6 " + "interfaces, port %u", + le->port); + result = interface_setup(mgr, &listen_addr, "<any>", + &ifp, le, NULL); + if (result == ISC_R_SUCCESS) { + ifp->flags |= NS_INTERFACEFLAG_ANYADDR; + } else { + isc_log_write(IFMGR_COMMON_LOGARGS, + ISC_LOG_ERROR, + "listening on all IPv6 " + "interfaces failed"); + } + /* Continue. */ + } + } + + isc_netaddr_any(&zero_address); + isc_netaddr_any6(&zero_address6); + + result = isc_interfaceiter_create(mgr->mctx, &iter); + if (result != ISC_R_SUCCESS) { + return (result); + } + + result = dns_acl_create(mgr->mctx, 0, &localhost); + if (result != ISC_R_SUCCESS) { + goto cleanup_iter; + } + result = dns_acl_create(mgr->mctx, 0, &localnets); + if (result != ISC_R_SUCCESS) { + goto cleanup_localhost; + } + + clearlistenon(mgr); + + tried_listening = false; + all_addresses_in_use = true; + for (result = isc_interfaceiter_first(iter); result == ISC_R_SUCCESS; + result = isc_interfaceiter_next(iter)) + { + isc_interface_t interface; + ns_listenlist_t *ll = NULL; + unsigned int family; + + result = isc_interfaceiter_current(iter, &interface); + if (result != ISC_R_SUCCESS) { + break; + } + + family = interface.address.family; + if (family != AF_INET && family != AF_INET6) { + continue; + } + if (!scan_ipv4 && family == AF_INET) { + continue; + } + if (!scan_ipv6 && family == AF_INET6) { + continue; + } + + /* + * Test for the address being nonzero rather than testing + * INTERFACE_F_UP, because on some systems the latter + * follows the media state and we could end up ignoring + * the interface for an entire rescan interval due to + * a temporary media glitch at rescan time. + */ + if (family == AF_INET && + isc_netaddr_equal(&interface.address, &zero_address)) + { + continue; + } + if (family == AF_INET6 && + isc_netaddr_equal(&interface.address, &zero_address6)) + { + continue; + } + + /* + * If running with -T fixedlocal, then we only + * want 127.0.0.1 and ::1 in the localhost ACL. + */ + if (((mgr->sctx->options & NS_SERVER_FIXEDLOCAL) != 0) && + !isc_netaddr_isloopback(&interface.address)) + { + goto listenon; + } + + result = setup_locals(&interface, localhost, localnets); + if (result != ISC_R_SUCCESS) { + goto ignore_interface; + } + + listenon: + ll = (family == AF_INET) ? mgr->listenon4 : mgr->listenon6; + dolistenon = true; + for (le = ISC_LIST_HEAD(ll->elts); le != NULL; + le = ISC_LIST_NEXT(le, link)) + { + int match; + bool addr_in_use = false; + bool ipv6_wildcard = false; + isc_sockaddr_t listen_sockaddr; + + isc_sockaddr_fromnetaddr(&listen_sockaddr, + &interface.address, le->port); + + /* + * See if the address matches the listen-on statement; + * if not, ignore the interface, but store it in + * the interface table so we know we've seen it + * before. + */ + (void)dns_acl_match(&interface.address, NULL, le->acl, + mgr->aclenv, &match, NULL); + if (match <= 0) { + ns_interface_t *new = NULL; + interface_create(mgr, &listen_sockaddr, + interface.name, &new); + continue; + } + + if (dolistenon) { + setup_listenon(mgr, &interface, le->port); + dolistenon = false; + } + + /* + * The case of "any" IPv6 address will require + * special considerations later, so remember it. + */ + if (family == AF_INET6 && ipv6only && ipv6pktinfo && + listenon_is_ip6_any(le)) + { + ipv6_wildcard = true; + } + + ifp = find_matching_interface(mgr, &listen_sockaddr); + if (ifp != NULL) { + ifp->generation = mgr->generation; + if (LISTENING(ifp)) { + if (config) { + update_listener_configuration( + mgr, ifp, le); + } + continue; + } + } + + if (ipv6_wildcard) { + continue; + } + + if (log_explicit && family == AF_INET6 && + listenon_is_ip6_any(le)) + { + isc_log_write(IFMGR_COMMON_LOGARGS, + verbose ? ISC_LOG_INFO + : ISC_LOG_DEBUG(1), + "IPv6 socket API is " + "incomplete; explicitly " + "binding to each IPv6 " + "address separately"); + log_explicit = false; + } + isc_sockaddr_format(&listen_sockaddr, sabuf, + sizeof(sabuf)); + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO, + "listening on %s interface " + "%s, %s", + (family == AF_INET) ? "IPv4" : "IPv6", + interface.name, sabuf); + + result = interface_setup(mgr, &listen_sockaddr, + interface.name, &ifp, le, + &addr_in_use); + + tried_listening = true; + if (!addr_in_use) { + all_addresses_in_use = false; + } + + if (result != ISC_R_SUCCESS) { + isc_log_write( + IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "creating %s interface " + "%s failed; interface ignored", + (family == AF_INET) ? "IPv4" : "IPv6", + interface.name); + } + /* Continue. */ + } + continue; + + ignore_interface: + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "ignoring %s interface %s: %s", + (family == AF_INET) ? "IPv4" : "IPv6", + interface.name, isc_result_totext(result)); + continue; + } + if (result != ISC_R_NOMORE) { + UNEXPECTED_ERROR("interface iteration failed: %s", + isc_result_totext(result)); + } else { + result = ((tried_listening && all_addresses_in_use) + ? ISC_R_ADDRINUSE + : ISC_R_SUCCESS); + } + + dns_aclenv_set(mgr->aclenv, localhost, localnets); + + /* cleanup_localnets: */ + dns_acl_detach(&localnets); + +cleanup_localhost: + dns_acl_detach(&localhost); + +cleanup_iter: + isc_interfaceiter_destroy(&iter); + return (result); +} + +isc_result_t +ns_interfacemgr_scan(ns_interfacemgr_t *mgr, bool verbose, bool config) { + isc_result_t result; + bool purge = true; + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + REQUIRE(isc_nm_tid() == 0); + + mgr->generation++; /* Increment the generation count. */ + + result = do_scan(mgr, verbose, config); + if ((result != ISC_R_SUCCESS) && (result != ISC_R_ADDRINUSE)) { + purge = false; + } + + /* + * Now go through the interface list and delete anything that + * does not have the current generation number. This is + * how we catch interfaces that go away or change their + * addresses. + */ + if (purge) { + purge_old_interfaces(mgr); + } + + /* + * Warn if we are not listening on any interface. + */ + if (ISC_LIST_EMPTY(mgr->interfaces)) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_WARNING, + "not listening on any interfaces"); + } + + return (result); +} + +bool +ns_interfacemgr_islistening(ns_interfacemgr_t *mgr) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + return (ISC_LIST_EMPTY(mgr->interfaces) ? false : true); +} + +void +ns_interfacemgr_setlistenon4(ns_interfacemgr_t *mgr, ns_listenlist_t *value) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + LOCK(&mgr->lock); + ns_listenlist_detach(&mgr->listenon4); + ns_listenlist_attach(value, &mgr->listenon4); + UNLOCK(&mgr->lock); +} + +void +ns_interfacemgr_setlistenon6(ns_interfacemgr_t *mgr, ns_listenlist_t *value) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + LOCK(&mgr->lock); + ns_listenlist_detach(&mgr->listenon6); + ns_listenlist_attach(value, &mgr->listenon6); + UNLOCK(&mgr->lock); +} + +void +ns_interfacemgr_dumprecursing(FILE *f, ns_interfacemgr_t *mgr) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + LOCK(&mgr->lock); + for (size_t i = 0; i < (size_t)mgr->ncpus; i++) { + ns_client_dumprecursing(f, mgr->clientmgrs[i]); + } + UNLOCK(&mgr->lock); +} + +bool +ns_interfacemgr_listeningon(ns_interfacemgr_t *mgr, + const isc_sockaddr_t *addr) { + isc_sockaddr_t *old; + bool result = false; + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + /* + * If the manager is shutting down it's safer to + * return true. + */ + if (atomic_load(&mgr->shuttingdown)) { + return (true); + } + LOCK(&mgr->lock); + for (old = ISC_LIST_HEAD(mgr->listenon); old != NULL; + old = ISC_LIST_NEXT(old, link)) + { + if (isc_sockaddr_equal(old, addr)) { + result = true; + break; + } + } + UNLOCK(&mgr->lock); + + return (result); +} + +ns_server_t * +ns_interfacemgr_getserver(ns_interfacemgr_t *mgr) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + return (mgr->sctx); +} + +ns_clientmgr_t * +ns_interfacemgr_getclientmgr(ns_interfacemgr_t *mgr) { + int tid = isc_nm_tid(); + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + REQUIRE(tid >= 0); + REQUIRE(tid < mgr->ncpus); + + return (mgr->clientmgrs[tid]); +} |