diff options
Diffstat (limited to '')
-rw-r--r-- | lib/ns/interfacemgr.c | 1263 |
1 files changed, 1263 insertions, 0 deletions
diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c new file mode 100644 index 0000000..216e274 --- /dev/null +++ b/lib/ns/interfacemgr.c @@ -0,0 +1,1263 @@ +/* + * 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 USE_ROUTE_SOCKET 1 +#define ROUTE_SOCKET_PROTOCOL PF_ROUTE +#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) +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#if defined(RTM_NEWADDR) && defined(RTM_DELADDR) +#define USE_ROUTE_SOCKET 1 +#define ROUTE_SOCKET_PROTOCOL PF_NETLINK +#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) \ + */ + +#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 *excl; /*%< Exclusive task. */ + isc_timermgr_t *timermgr; /*%< Timer manager. */ + isc_socketmgr_t *socketmgr; /*%< Socket 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 */ + unsigned int udpdisp; /*%< UDP dispatch count */ + atomic_bool shuttingdown; /*%< Interfacemgr is shutting + * down */ +#ifdef USE_ROUTE_SOCKET + isc_task_t *task; + isc_socket_t *route; + unsigned char buf[2048]; +#endif /* ifdef USE_ROUTE_SOCKET */ +}; + +static void +purge_old_interfaces(ns_interfacemgr_t *mgr); + +static void +clearlistenon(ns_interfacemgr_t *mgr); + +#ifdef USE_ROUTE_SOCKET +static void +route_event(isc_task_t *task, isc_event_t *event) { + isc_socketevent_t *sevent = NULL; + ns_interfacemgr_t *mgr = NULL; + isc_region_t r; + isc_result_t result; + struct MSGHDR *rtm; + bool done = true; + + UNUSED(task); + + REQUIRE(event->ev_type == ISC_SOCKEVENT_RECVDONE); + mgr = event->ev_arg; + sevent = (isc_socketevent_t *)event; + + if (sevent->result != ISC_R_SUCCESS) { + if (sevent->result != ISC_R_CANCELED) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "automatic interface scanning " + "terminated: %s", + isc_result_totext(sevent->result)); + } + ns_interfacemgr_detach(&mgr); + isc_event_free(&event); + return; + } + + rtm = (struct MSGHDR *)mgr->buf; +#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); + ns_interfacemgr_detach(&mgr); + isc_event_free(&event); + return; + } +#endif /* ifdef RTM_VERSION */ + + switch (rtm->MSGTYPE) { + case RTM_NEWADDR: + case RTM_DELADDR: + if (mgr->route != NULL && mgr->sctx->interface_auto) { + ns_interfacemgr_scan(mgr, false); + } + break; + default: + break; + } + + LOCK(&mgr->lock); + if (mgr->route != NULL) { + /* + * Look for next route event. + */ + r.base = mgr->buf; + r.length = sizeof(mgr->buf); + result = isc_socket_recv(mgr->route, &r, 1, mgr->task, + route_event, mgr); + if (result == ISC_R_SUCCESS) { + done = false; + } + } + UNLOCK(&mgr->lock); + + if (done) { + ns_interfacemgr_detach(&mgr); + } + isc_event_free(&event); + return; +} +#endif /* ifdef USE_ROUTE_SOCKET */ + +isc_result_t +ns_interfacemgr_create(isc_mem_t *mctx, ns_server_t *sctx, + isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr, + isc_socketmgr_t *socketmgr, isc_nm_t *nm, + dns_dispatchmgr_t *dispatchmgr, isc_task_t *task, + unsigned int udpdisp, dns_geoip_databases_t *geoip, + int ncpus, ns_interfacemgr_t **mgrp) { + isc_result_t result; + ns_interfacemgr_t *mgr; + +#ifndef USE_ROUTE_SOCKET + UNUSED(task); +#endif /* ifndef USE_ROUTE_SOCKET */ + + REQUIRE(mctx != NULL); + REQUIRE(mgrp != NULL); + REQUIRE(*mgrp == NULL); + + mgr = isc_mem_get(mctx, sizeof(*mgr)); + + mgr->mctx = NULL; + isc_mem_attach(mctx, &mgr->mctx); + + mgr->sctx = NULL; + ns_server_attach(sctx, &mgr->sctx); + + isc_mutex_init(&mgr->lock); + + mgr->excl = NULL; + result = isc_taskmgr_excltask(taskmgr, &mgr->excl); + if (result != ISC_R_SUCCESS) { + goto cleanup_lock; + } + + mgr->taskmgr = taskmgr; + mgr->timermgr = timermgr; + mgr->socketmgr = socketmgr; + mgr->nm = nm; + mgr->dispatchmgr = dispatchmgr; + mgr->generation = 1; + mgr->listenon4 = NULL; + mgr->listenon6 = NULL; + mgr->udpdisp = udpdisp; + mgr->ncpus = ncpus; + 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_ctx; + } + ns_listenlist_attach(mgr->listenon4, &mgr->listenon6); + + result = dns_aclenv_init(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) */ + +#ifdef USE_ROUTE_SOCKET + mgr->route = NULL; + result = isc_socket_create(mgr->socketmgr, ROUTE_SOCKET_PROTOCOL, + isc_sockettype_raw, &mgr->route); + switch (result) { + case ISC_R_NOPERM: + case ISC_R_SUCCESS: + case ISC_R_NOTIMPLEMENTED: + case ISC_R_FAMILYNOSUPPORT: + break; + default: + goto cleanup_aclenv; + } + + mgr->task = NULL; + if (mgr->route != NULL) { + isc_task_attach(task, &mgr->task); + } + isc_refcount_init(&mgr->references, (mgr->route != NULL) ? 2 : 1); +#else /* ifdef USE_ROUTE_SOCKET */ + isc_refcount_init(&mgr->references, 1); +#endif /* ifdef USE_ROUTE_SOCKET */ + mgr->magic = IFMGR_MAGIC; + *mgrp = mgr; + +#ifdef USE_ROUTE_SOCKET + if (mgr->route != NULL) { + isc_region_t r = { mgr->buf, sizeof(mgr->buf) }; + + result = isc_socket_recv(mgr->route, &r, 1, mgr->task, + route_event, mgr); + if (result != ISC_R_SUCCESS) { + isc_task_detach(&mgr->task); + isc_socket_detach(&mgr->route); + ns_interfacemgr_detach(&mgr); + } + } +#endif /* ifdef USE_ROUTE_SOCKET */ + return (ISC_R_SUCCESS); + +#ifdef USE_ROUTE_SOCKET +cleanup_aclenv: + dns_aclenv_destroy(&mgr->aclenv); +#endif /* ifdef USE_ROUTE_SOCKET */ +cleanup_listenon: + ns_listenlist_detach(&mgr->listenon4); + ns_listenlist_detach(&mgr->listenon6); +cleanup_lock: + isc_mutex_destroy(&mgr->lock); +cleanup_ctx: + 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); + +#ifdef USE_ROUTE_SOCKET + if (mgr->route != NULL) { + isc_socket_detach(&mgr->route); + } + if (mgr->task != NULL) { + isc_task_detach(&mgr->task); + } +#endif /* ifdef USE_ROUTE_SOCKET */ + dns_aclenv_destroy(&mgr->aclenv); + ns_listenlist_detach(&mgr->listenon4); + ns_listenlist_detach(&mgr->listenon6); + clearlistenon(mgr); + isc_mutex_destroy(&mgr->lock); + if (mgr->sctx != NULL) { + ns_server_detach(&mgr->sctx); + } + if (mgr->excl != NULL) { + isc_task_detach(&mgr->excl); + } + 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) { + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + return (&mgr->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); +#ifdef USE_ROUTE_SOCKET + LOCK(&mgr->lock); + if (mgr->route != NULL) { + isc_socket_cancel(mgr->route, mgr->task, ISC_SOCKCANCEL_RECV); + isc_socket_detach(&mgr->route); + isc_task_detach(&mgr->task); + } + UNLOCK(&mgr->lock); +#endif /* ifdef USE_ROUTE_SOCKET */ + purge_old_interfaces(mgr); +} + +static isc_result_t +ns_interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, + const char *name, ns_interface_t **ifpret) { + ns_interface_t *ifp = NULL; + isc_result_t result; + int disp; + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + ifp = isc_mem_get(mgr->mctx, sizeof(*ifp)); + *ifp = (ns_interface_t){ .generation = mgr->generation, + .addr = *addr, + .dscp = -1 }; + + strlcpy(ifp->name, name, sizeof(ifp->name)); + + isc_mutex_init(&ifp->lock); + + for (disp = 0; disp < MAX_UDP_DISPATCH; disp++) { + ifp->udpdispatch[disp] = NULL; + } + + /* + * Create a single TCP client object. It will replace itself + * with a new one as soon as it gets a connection, so the actual + * connections will be handled in parallel even though there is + * only one client initially. + */ + isc_refcount_init(&ifp->ntcpaccepting, 0); + isc_refcount_init(&ifp->ntcpactive, 0); + + ISC_LINK_INIT(ifp, link); + + ns_interfacemgr_attach(mgr, &ifp->mgr); + isc_refcount_init(&ifp->references, 1); + ifp->magic = IFACE_MAGIC; + + LOCK(&mgr->lock); + ISC_LIST_APPEND(mgr->interfaces, ifp, link); + UNLOCK(&mgr->lock); + + result = ns_clientmgr_create(mgr->mctx, mgr->sctx, mgr->taskmgr, + mgr->timermgr, ifp, mgr->ncpus, + &ifp->clientmgr); + if (result != ISC_R_SUCCESS) { + isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR, + "ns_clientmgr_create() failed: %s", + isc_result_totext(result)); + goto failure; + } + + *ifpret = ifp; + + return (ISC_R_SUCCESS); + +failure: + LOCK(&ifp->mgr->lock); + ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link); + UNLOCK(&ifp->mgr->lock); + + ifp->magic = 0; + ns_interfacemgr_detach(&ifp->mgr); + isc_refcount_decrement(&ifp->references); + isc_refcount_destroy(&ifp->references); + isc_mutex_destroy(&ifp->lock); + + isc_mem_put(mgr->mctx, ifp, sizeof(*ifp)); + return (ISC_R_UNEXPECTED); +} + +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)); + } + +#if 0 +#ifndef ISC_ALLOW_MAPPED + isc_socket_ipv6only(ifp->tcpsocket,true); +#endif /* ifndef ISC_ALLOW_MAPPED */ + + if (ifp->dscp != -1) { + isc_socket_dscp(ifp->tcpsocket,ifp->dscp); + } + + (void)isc_socket_filter(ifp->tcpsocket,"dataready"); +#endif /* if 0 */ + return (result); +} + +static isc_result_t +ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, + const char *name, ns_interface_t **ifpret, isc_dscp_t dscp, + bool *addr_in_use) { + isc_result_t result; + ns_interface_t *ifp = NULL; + REQUIRE(ifpret != NULL && *ifpret == NULL); + REQUIRE(addr_in_use == NULL || !*addr_in_use); + + result = ns_interface_create(mgr, addr, name, &ifp); + if (result != ISC_R_SUCCESS) { + return (result); + } + + ifp->dscp = dscp; + + 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: + LOCK(&ifp->mgr->lock); + ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link); + UNLOCK(&ifp->mgr->lock); + ns_interface_shutdown(ifp); + ns_interface_detach(&ifp); + return (result); +} + +void +ns_interface_shutdown(ns_interface_t *ifp) { + 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->clientmgr != NULL) { + ns_clientmgr_shutdown(ifp->clientmgr); + ns_clientmgr_destroy(&ifp->clientmgr); + } +} + +static void +ns_interface_destroy(ns_interface_t *ifp) { + REQUIRE(NS_INTERFACE_VALID(ifp)); + + isc_mem_t *mctx = ifp->mgr->mctx; + + ns_interface_shutdown(ifp); + + for (int disp = 0; disp < ifp->nudpdispatch; disp++) { + if (ifp->udpdispatch[disp] != NULL) { + dns_dispatch_changeattributes( + ifp->udpdispatch[disp], 0, + DNS_DISPATCHATTR_NOLISTEN); + dns_dispatch_detach(&(ifp->udpdispatch[disp])); + } + } + + if (ifp->tcpsocket != NULL) { + isc_socket_detach(&ifp->tcpsocket); + } + + isc_mutex_destroy(&ifp->lock); + + ns_interfacemgr_detach(&ifp->mgr); + + isc_refcount_destroy(&ifp->ntcpactive); + isc_refcount_destroy(&ifp->ntcpaccepting); + + ifp->magic = 0; + + isc_mem_put(mctx, ifp, sizeof(*ifp)); +} + +void +ns_interface_attach(ns_interface_t *source, ns_interface_t **target) { + REQUIRE(NS_INTERFACE_VALID(source)); + isc_refcount_increment(&source->references); + *target = source; +} + +void +ns_interface_detach(ns_interface_t **targetp) { + ns_interface_t *target = *targetp; + *targetp = NULL; + REQUIRE(target != NULL); + REQUIRE(NS_INTERFACE_VALID(target)); + if (isc_refcount_decrement(&target->references) == 1) { + ns_interface_destroy(target); + } +} + +/*% + * 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, *next; + 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) { + char sabuf[256]; + ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link); + 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); + ns_interface_detach(&ifp); + } + } + UNLOCK(&mgr->lock); +} + +static isc_result_t +clearacl(isc_mem_t *mctx, dns_acl_t **aclp) { + dns_acl_t *newacl = NULL; + isc_result_t result; + result = dns_acl_create(mctx, 0, &newacl); + if (result != ISC_R_SUCCESS) { + return (result); + } + dns_acl_detach(aclp); + dns_acl_attach(newacl, aclp); + dns_acl_detach(&newacl); + return (ISC_R_SUCCESS); +} + +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(ns_interfacemgr_t *mgr, isc_interface_t *interface) { + 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(mgr->aclenv.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(mgr->aclenv.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)) { + break; + } + } + + if (old != NULL) { + isc_mem_put(mgr->mctx, addr, sizeof(*addr)); + } else { + ISC_LIST_APPEND(mgr->listenon, addr, link); + } + UNLOCK(&mgr->lock); +} + +static void +clearlistenon(ns_interfacemgr_t *mgr) { + isc_sockaddr_t *old; + + LOCK(&mgr->lock); + old = ISC_LIST_HEAD(mgr->listenon); + while (old != NULL) { + ISC_LIST_UNLINK(mgr->listenon, old, link); + isc_mem_put(mgr->mctx, old, sizeof(*old)); + old = ISC_LIST_HEAD(mgr->listenon); + } + UNLOCK(&mgr->lock); +} + +static isc_result_t +do_scan(ns_interfacemgr_t *mgr, bool verbose) { + 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; + isc_sockaddr_t listen_addr; + ns_interface_t *ifp; + bool log_explicit = false; + bool dolistenon; + char sabuf[ISC_SOCKADDR_FORMATSIZE]; + bool tried_listening; + bool all_addresses_in_use; + + 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. + */ +#ifndef ISC_ALLOW_MAPPED + if (scan_ipv6 && isc_net_probe_ipv6only() != ISC_R_SUCCESS) { + ipv6only = false; + log_explicit = true; + } +#endif /* ifndef ISC_ALLOW_MAPPED */ + 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 (le->dscp != -1 && ifp->dscp == -1) { + ifp->dscp = le->dscp; + } else if (le->dscp != ifp->dscp) { + isc_sockaddr_format(&listen_addr, sabuf, + sizeof(sabuf)); + isc_log_write(IFMGR_COMMON_LOGARGS, + ISC_LOG_WARNING, + "%s: conflicting DSCP " + "values, using %d", + sabuf, ifp->dscp); + } + } else { + isc_log_write(IFMGR_COMMON_LOGARGS, + ISC_LOG_INFO, + "listening on IPv6 " + "interfaces, port %u", + le->port); + result = ns_interface_setup(mgr, &listen_addr, + "<any>", &ifp, + le->dscp, 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 = clearacl(mgr->mctx, &mgr->aclenv.localhost); + if (result != ISC_R_SUCCESS) { + goto cleanup_iter; + } + result = clearacl(mgr->mctx, &mgr->aclenv.localnets); + if (result != ISC_R_SUCCESS) { + goto cleanup_iter; + } + 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; + 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(mgr, &interface); + 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 ipv6_wildcard = false; + isc_netaddr_t listen_netaddr; + isc_sockaddr_t listen_sockaddr; + + /* + * Construct a socket address for this IP/port + * combination. + */ + if (family == AF_INET) { + isc_netaddr_fromin(&listen_netaddr, + &interface.address.type.in); + } else { + isc_netaddr_fromin6( + &listen_netaddr, + &interface.address.type.in6); + isc_netaddr_setzone(&listen_netaddr, + interface.address.zone); + } + isc_sockaddr_fromnetaddr(&listen_sockaddr, + &listen_netaddr, le->port); + + /* + * See if the address matches the listen-on statement; + * if not, ignore the interface. + */ + (void)dns_acl_match(&listen_netaddr, NULL, le->acl, + &mgr->aclenv, &match, NULL); + if (match <= 0) { + 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 (le->dscp != -1 && ifp->dscp == -1) { + ifp->dscp = le->dscp; + } else if (le->dscp != ifp->dscp) { + isc_sockaddr_format(&listen_sockaddr, + sabuf, + sizeof(sabuf)); + isc_log_write(IFMGR_COMMON_LOGARGS, + ISC_LOG_WARNING, + "%s: conflicting DSCP " + "values, using %d", + sabuf, ifp->dscp); + } + } else { + bool addr_in_use = false; + + 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 = ns_interface_setup( + mgr, &listen_sockaddr, interface.name, + &ifp, le->dscp, &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) ? "IP" + "v4" + : "IP" + "v" + "6", + 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(__FILE__, __LINE__, + "interface iteration failed: %s", + isc_result_totext(result)); + } else { + result = ((tried_listening && all_addresses_in_use) + ? ISC_R_ADDRINUSE + : ISC_R_SUCCESS); + } +cleanup_iter: + isc_interfaceiter_destroy(&iter); + return (result); +} + +static isc_result_t +ns_interfacemgr_scan0(ns_interfacemgr_t *mgr, bool verbose) { + isc_result_t result; + bool purge = true; + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + mgr->generation++; /* Increment the generation count. */ + + result = do_scan(mgr, verbose); + 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); +} + +isc_result_t +ns_interfacemgr_scan(ns_interfacemgr_t *mgr, bool verbose) { + isc_result_t result; + bool unlock = false; + + /* + * Check for success because we may already be task-exclusive + * at this point. Only if we succeed at obtaining an exclusive + * lock now will we need to relinquish it later. + */ + result = isc_task_beginexclusive(mgr->excl); + if (result == ISC_R_SUCCESS) { + unlock = true; + } + + result = ns_interfacemgr_scan0(mgr, verbose); + + if (unlock) { + isc_task_endexclusive(mgr->excl); + } + + return (result); +} + +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) { + ns_interface_t *interface; + + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + + LOCK(&mgr->lock); + interface = ISC_LIST_HEAD(mgr->interfaces); + while (interface != NULL) { + if (interface->clientmgr != NULL) { + ns_client_dumprecursing(f, interface->clientmgr); + } + interface = ISC_LIST_NEXT(interface, link); + } + 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_interface_t * +ns__interfacemgr_getif(ns_interfacemgr_t *mgr) { + ns_interface_t *head; + REQUIRE(NS_INTERFACEMGR_VALID(mgr)); + LOCK(&mgr->lock); + head = ISC_LIST_HEAD(mgr->interfaces); + UNLOCK(&mgr->lock); + return (head); +} + +ns_interface_t * +ns__interfacemgr_nextif(ns_interface_t *ifp) { + ns_interface_t *next; + LOCK(&ifp->lock); + next = ISC_LIST_NEXT(ifp, link); + UNLOCK(&ifp->lock); + return (next); +} |