2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-26 12:08:05 +00:00
bind/lib/ns/interfacemgr.c
Evan Hunt 75427139ad attach the interface manager when activating a route socket
it was possible for the route socket's udp_recv() callback to fire
after the interfacemgr was detached, causing an assertion failure.
this has now been fixed by referencing the interfacemgr when setting up
the route socket, and dereferencing it when shutting it down.
2021-10-18 14:21:03 -07:00

1331 lines
33 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* 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)
#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) \
*/
#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_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 void
scan_event(isc_task_t *task, isc_event_t *event) {
ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)event->ev_arg;
UNUSED(task);
ns_interfacemgr_scan(mgr, false);
isc_event_free(&event);
}
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;
bool done = true;
isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(3), "route_recv: %s",
isc_result_totext(eresult));
if (handle == NULL) {
return;
}
if (eresult == ISC_R_SHUTTINGDOWN) {
/*
* The mgr->route and mgr is detached in
* ns_interfacemgr_shutdown()
*/
return;
}
if (eresult != ISC_R_SUCCESS) {
if (eresult != ISC_R_CANCELED) {
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;
#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 */
switch (rtm->MSGTYPE) {
case RTM_NEWADDR:
case RTM_DELADDR:
if (mgr->route != NULL && mgr->sctx->interface_auto) {
isc_event_t *event = NULL;
event = isc_event_allocate(mgr->mctx, mgr,
NS_EVENT_IFSCAN, scan_event,
mgr, sizeof(*event));
isc_task_send(mgr->excl, &event);
}
break;
default:
break;
}
LOCK(&mgr->lock);
if (mgr->route != NULL) {
isc_nm_read(handle, route_recv, mgr);
done = false;
}
UNLOCK(&mgr->lock);
if (done) {
isc_nmhandle_detach(&mgr->route);
ns_interfacemgr_detach(&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(3),
"route_connected: %s", isc_result_totext(eresult));
if (eresult != ISC_R_SUCCESS) {
return;
}
INSIST(mgr->route == NULL);
ns_interfacemgr_attach(mgr, &(ns_interfacemgr_t *){ 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_taskmgr_excltask(taskmgr, &mgr->excl);
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_sctx;
}
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) */
if (scan) {
result = isc_nm_routeconnect(nm, route_connected, mgr, 0);
if (result != ISC_R_SUCCESS) {
isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO,
"unable to open route socket: %s",
isc_result_totext(result));
}
}
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);
}
return (ISC_R_SUCCESS);
cleanup_listenon:
ns_listenlist_detach(&mgr->listenon4);
ns_listenlist_detach(&mgr->listenon6);
cleanup_lock:
isc_mutex_destroy(&mgr->lock);
cleanup_sctx:
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_destroy(&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);
}
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);
purge_old_interfaces(mgr);
if (mgr->route != NULL) {
isc_nmhandle_detach(&mgr->route);
ns_interfacemgr_detach(&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;
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);
/*
* 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);
LOCK(&mgr->lock);
ISC_LIST_APPEND(mgr->interfaces, ifp, link);
UNLOCK(&mgr->lock);
isc_refcount_init(&ifp->references, 1);
ifp->magic = IFACE_MAGIC;
ifp->tcplistensocket = NULL;
ifp->http_listensocket = NULL;
ifp->http_secure_listensocket = NULL;
*ifpret = ifp;
return (ISC_R_SUCCESS);
}
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);
}
/*
* 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);
}
static isc_result_t
ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps,
size_t neps, isc_quota_t *quota,
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;
epset = isc_nm_http_endpoints_new(ifp->mgr->mctx);
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;
}
}
if (result == ISC_R_SUCCESS) {
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 (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(quota);
UNUSED(max_concurrent_streams);
return (ISC_R_NOTIMPLEMENTED);
#endif
}
static isc_result_t
ns_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 && *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 = elt->dscp;
if (elt->is_http) {
result = ns_interface_listenhttp(
ifp, elt->sslctx, elt->http_endpoints,
elt->http_endpoints_number, elt->http_quota,
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:
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->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);
}
}
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);
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,
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, &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) {
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_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);
}
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]);
}