summaryrefslogtreecommitdiffstats
path: root/bin/named/interfacemgr.c
diff options
context:
space:
mode:
Diffstat (limited to 'bin/named/interfacemgr.c')
-rw-r--r--bin/named/interfacemgr.c1246
1 files changed, 1246 insertions, 0 deletions
diff --git a/bin/named/interfacemgr.c b/bin/named/interfacemgr.c
new file mode 100644
index 0000000..419927b
--- /dev/null
+++ b/bin/named/interfacemgr.c
@@ -0,0 +1,1246 @@
+/*
+ * 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 http://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <config.h>
+
+#include <stdbool.h>
+
+#include <isc/interfaceiter.h>
+#include <isc/os.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/dispatch.h>
+
+#include <named/client.h>
+#include <named/log.h>
+#include <named/interfacemgr.h>
+#include <named/server.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
+#endif
+
+#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
+#endif
+
+#ifdef TUNE_LARGE
+#define UDPBUFFERS 32768
+#else
+#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_g_lctx, NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR
+
+/*% nameserver interface manager structure */
+struct ns_interfacemgr {
+ unsigned int magic; /*%< Magic number. */
+ int references;
+ isc_mutex_t lock;
+ isc_mem_t * mctx; /*%< Memory context. */
+ isc_taskmgr_t * taskmgr; /*%< Task manager. */
+ isc_socketmgr_t * socketmgr; /*%< Socket manager. */
+ 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;
+#ifdef USE_ROUTE_SOCKET
+ isc_task_t * task;
+ isc_socket_t * route;
+ unsigned char buf[2048];
+#endif
+};
+
+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
+
+ switch (rtm->MSGTYPE) {
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ if (mgr->route != NULL && ns_g_server->interface_auto)
+ ns_server_scan_interfaces(ns_g_server);
+ 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
+
+isc_result_t
+ns_interfacemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
+ isc_socketmgr_t *socketmgr,
+ dns_dispatchmgr_t *dispatchmgr,
+ isc_task_t *task, ns_interfacemgr_t **mgrp)
+{
+ isc_result_t result;
+ ns_interfacemgr_t *mgr;
+
+#ifndef USE_ROUTE_SOCKET
+ UNUSED(task);
+#endif
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(mgrp != NULL);
+ REQUIRE(*mgrp == NULL);
+
+ mgr = isc_mem_get(mctx, sizeof(*mgr));
+ if (mgr == NULL)
+ return (ISC_R_NOMEMORY);
+
+ mgr->mctx = NULL;
+ isc_mem_attach(mctx, &mgr->mctx);
+
+ result = isc_mutex_init(&mgr->lock);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup_mem;
+
+ mgr->taskmgr = taskmgr;
+ mgr->socketmgr = socketmgr;
+ mgr->dispatchmgr = dispatchmgr;
+ mgr->generation = 1;
+ mgr->listenon4 = NULL;
+ mgr->listenon6 = NULL;
+
+ 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_mem;
+ ns_listenlist_attach(mgr->listenon4, &mgr->listenon6);
+
+ result = dns_aclenv_init(mctx, &mgr->aclenv);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup_listenon;
+#ifdef HAVE_GEOIP
+ mgr->aclenv.geoip = ns_g_geoip;
+#endif
+
+#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);
+ mgr->references = (mgr->route != NULL) ? 2 : 1;
+#else
+ mgr->references = 1;
+#endif
+ 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
+ return (ISC_R_SUCCESS);
+
+#ifdef USE_ROUTE_SOCKET
+ cleanup_aclenv:
+ dns_aclenv_destroy(&mgr->aclenv);
+#endif
+ cleanup_listenon:
+ ns_listenlist_detach(&mgr->listenon4);
+ ns_listenlist_detach(&mgr->listenon6);
+ cleanup_mem:
+ isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr));
+ return (result);
+}
+
+static void
+ns_interfacemgr_destroy(ns_interfacemgr_t *mgr) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+#ifdef USE_ROUTE_SOCKET
+ if (mgr->route != NULL)
+ isc_socket_detach(&mgr->route);
+ if (mgr->task != NULL)
+ isc_task_detach(&mgr->task);
+#endif
+ dns_aclenv_destroy(&mgr->aclenv);
+ ns_listenlist_detach(&mgr->listenon4);
+ ns_listenlist_detach(&mgr->listenon6);
+ clearlistenon(mgr);
+ DESTROYLOCK(&mgr->lock);
+ mgr->magic = 0;
+ isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr));
+}
+
+dns_aclenv_t *
+ns_interfacemgr_getaclenv(ns_interfacemgr_t *mgr) {
+ return (&mgr->aclenv);
+}
+
+void
+ns_interfacemgr_attach(ns_interfacemgr_t *source, ns_interfacemgr_t **target) {
+ REQUIRE(NS_INTERFACEMGR_VALID(source));
+ LOCK(&source->lock);
+ INSIST(source->references > 0);
+ source->references++;
+ UNLOCK(&source->lock);
+ *target = source;
+}
+
+void
+ns_interfacemgr_detach(ns_interfacemgr_t **targetp) {
+ isc_result_t need_destroy = false;
+ ns_interfacemgr_t *target = *targetp;
+ REQUIRE(target != NULL);
+ REQUIRE(NS_INTERFACEMGR_VALID(target));
+ LOCK(&target->lock);
+ REQUIRE(target->references > 0);
+ target->references--;
+ if (target->references == 0)
+ need_destroy = true;
+ UNLOCK(&target->lock);
+ if (need_destroy)
+ ns_interfacemgr_destroy(target);
+ *targetp = NULL;
+}
+
+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++;
+#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
+ 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;
+ isc_result_t result;
+ int disp;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ ifp = isc_mem_get(mgr->mctx, sizeof(*ifp));
+ if (ifp == NULL)
+ return (ISC_R_NOMEMORY);
+
+ ifp->mgr = NULL;
+ ifp->generation = mgr->generation;
+ ifp->addr = *addr;
+ ifp->flags = 0;
+ strlcpy(ifp->name, name, sizeof(ifp->name));
+ ifp->clientmgr = NULL;
+
+ result = isc_mutex_init(&ifp->lock);
+ if (result != ISC_R_SUCCESS)
+ goto lock_create_failure;
+
+ result = ns_clientmgr_create(mgr->mctx, mgr->taskmgr,
+ ns_g_timermgr,
+ &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 clientmgr_create_failure;
+ }
+
+ for (disp = 0; disp < MAX_UDP_DISPATCH; disp++)
+ ifp->udpdispatch[disp] = NULL;
+
+ ifp->tcpsocket = 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.
+ */
+ ifp->ntcptarget = 1;
+ ifp->ntcpcurrent = 0;
+ ifp->nudpdispatch = 0;
+
+ ifp->dscp = -1;
+
+ ISC_LINK_INIT(ifp, link);
+
+ ns_interfacemgr_attach(mgr, &ifp->mgr);
+ ISC_LIST_APPEND(mgr->interfaces, ifp, link);
+
+ ifp->references = 1;
+ ifp->magic = IFACE_MAGIC;
+ *ifpret = ifp;
+
+ return (ISC_R_SUCCESS);
+
+ clientmgr_create_failure:
+ DESTROYLOCK(&ifp->lock);
+
+ lock_create_failure:
+ ifp->magic = 0;
+ 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;
+ unsigned int attrs;
+ unsigned int attrmask;
+ int disp, i;
+
+ attrs = 0;
+ attrs |= DNS_DISPATCHATTR_UDP;
+ if (isc_sockaddr_pf(&ifp->addr) == AF_INET)
+ attrs |= DNS_DISPATCHATTR_IPV4;
+ else
+ attrs |= DNS_DISPATCHATTR_IPV6;
+ attrs |= DNS_DISPATCHATTR_NOLISTEN;
+ attrmask = 0;
+ attrmask |= DNS_DISPATCHATTR_UDP | DNS_DISPATCHATTR_TCP;
+ attrmask |= DNS_DISPATCHATTR_IPV4 | DNS_DISPATCHATTR_IPV6;
+
+ ifp->nudpdispatch = ISC_MIN(ns_g_udpdisp, MAX_UDP_DISPATCH);
+ for (disp = 0; disp < ifp->nudpdispatch; disp++) {
+ result = dns_dispatch_getudp_dup(ifp->mgr->dispatchmgr,
+ ns_g_socketmgr,
+ ns_g_taskmgr, &ifp->addr,
+ 4096, UDPBUFFERS,
+ 32768, 8219, 8237,
+ attrs, attrmask,
+ &ifp->udpdispatch[disp],
+ disp == 0
+ ? NULL
+ : ifp->udpdispatch[0]);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "could not listen on UDP socket: %s",
+ isc_result_totext(result));
+ goto udp_dispatch_failure;
+ }
+
+ }
+
+ result = ns_clientmgr_createclients(ifp->clientmgr, ifp->nudpdispatch,
+ ifp, false);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "UDP ns_clientmgr_createclients(): %s",
+ isc_result_totext(result));
+ goto addtodispatch_failure;
+ }
+
+ return (ISC_R_SUCCESS);
+
+ addtodispatch_failure:
+ for (i = disp - 1; i >= 0; i--) {
+ dns_dispatch_changeattributes(ifp->udpdispatch[i], 0,
+ DNS_DISPATCHATTR_NOLISTEN);
+ dns_dispatch_detach(&(ifp->udpdispatch[i]));
+ }
+ ifp->nudpdispatch = 0;
+
+ udp_dispatch_failure:
+ return (result);
+}
+
+static isc_result_t
+ns_interface_accepttcp(ns_interface_t *ifp) {
+ isc_result_t result;
+
+ /*
+ * Open a TCP socket.
+ */
+ result = isc_socket_create(ifp->mgr->socketmgr,
+ isc_sockaddr_pf(&ifp->addr),
+ isc_sockettype_tcp,
+ &ifp->tcpsocket);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "creating TCP socket: %s",
+ isc_result_totext(result));
+ goto tcp_socket_failure;
+ }
+ isc_socket_setname(ifp->tcpsocket, "dispatcher", NULL);
+#ifndef ISC_ALLOW_MAPPED
+ isc_socket_ipv6only(ifp->tcpsocket, true);
+#endif
+ result = isc_socket_bind(ifp->tcpsocket, &ifp->addr,
+ ISC_SOCKET_REUSEADDRESS);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "binding TCP socket: %s",
+ isc_result_totext(result));
+ goto tcp_bind_failure;
+ }
+
+ if (ifp->dscp != -1)
+ isc_socket_dscp(ifp->tcpsocket, ifp->dscp);
+
+ result = isc_socket_listen(ifp->tcpsocket, ns_g_listen);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "listening on TCP socket: %s",
+ isc_result_totext(result));
+ goto tcp_listen_failure;
+ }
+
+ /*
+ * If/when there a multiple filters listen to the
+ * result.
+ */
+ (void)isc_socket_filter(ifp->tcpsocket, "dataready");
+
+ result = ns_clientmgr_createclients(ifp->clientmgr,
+ ifp->ntcptarget, ifp,
+ true);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(__FILE__, __LINE__,
+ "TCP ns_clientmgr_createclients(): %s",
+ isc_result_totext(result));
+ goto accepttcp_failure;
+ }
+ return (ISC_R_SUCCESS);
+
+ accepttcp_failure:
+ tcp_listen_failure:
+ tcp_bind_failure:
+ isc_socket_detach(&ifp->tcpsocket);
+ tcp_socket_failure:
+ return (result);
+}
+
+static isc_result_t
+ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
+ const char *name, ns_interface_t **ifpret,
+ bool accept_tcp, 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 == false);
+
+ 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 (!ns_g_notcp && accept_tcp == true) {
+ result = ns_interface_accepttcp(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:
+ ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link);
+ ns_interface_detach(&ifp);
+ return (result);
+}
+
+void
+ns_interface_shutdown(ns_interface_t *ifp) {
+ if (ifp->clientmgr != NULL)
+ ns_clientmgr_destroy(&ifp->clientmgr);
+}
+
+static void
+ns_interface_destroy(ns_interface_t *ifp) {
+ isc_mem_t *mctx = ifp->mgr->mctx;
+ int disp;
+
+ REQUIRE(NS_INTERFACE_VALID(ifp));
+
+ ns_interface_shutdown(ifp);
+
+ for (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);
+
+ DESTROYLOCK(&ifp->lock);
+
+ ns_interfacemgr_detach(&ifp->mgr);
+
+ 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));
+ LOCK(&source->lock);
+ INSIST(source->references > 0);
+ source->references++;
+ UNLOCK(&source->lock);
+ *target = source;
+}
+
+void
+ns_interface_detach(ns_interface_t **targetp) {
+ isc_result_t need_destroy = false;
+ ns_interface_t *target = *targetp;
+ REQUIRE(target != NULL);
+ REQUIRE(NS_INTERFACE_VALID(target));
+ LOCK(&target->lock);
+ REQUIRE(target->references > 0);
+ target->references--;
+ if (target->references == 0)
+ need_destroy = true;
+ UNLOCK(&target->lock);
+ if (need_destroy)
+ ns_interface_destroy(target);
+ *targetp = NULL;
+}
+
+/*%
+ * 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;
+ for (ifp = ISC_LIST_HEAD(mgr->interfaces); ifp != NULL;
+ ifp = ISC_LIST_NEXT(ifp, link)) {
+ if (isc_sockaddr_equal(&ifp->addr, addr))
+ break;
+ }
+ 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;
+ 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);
+ }
+ }
+}
+
+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));
+ if (addr == NULL)
+ return;
+
+ isc_sockaddr_fromnetaddr(addr, &interface->address, port);
+
+ 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);
+}
+
+static void
+clearlistenon(ns_interfacemgr_t *mgr) {
+ isc_sockaddr_t *old;
+
+ 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);
+ }
+}
+
+static isc_result_t
+do_scan(ns_interfacemgr_t *mgr, ns_listenlist_t *ext_listen,
+ bool verbose)
+{
+ isc_interfaceiter_t *iter = NULL;
+ bool scan_ipv4 = false;
+ bool scan_ipv6 = false;
+ bool adjusting = 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 (ext_listen != NULL)
+ adjusting = true;
+
+ if (isc_net_probeipv6() == ISC_R_SUCCESS)
+ scan_ipv6 = true;
+#ifdef WANT_IPV6
+ else if (!ns_g_disable6)
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ verbose ? ISC_LOG_INFO : ISC_LOG_DEBUG(1),
+ "no IPv6 interfaces found");
+#endif
+
+ if (isc_net_probeipv4() == ISC_R_SUCCESS)
+ scan_ipv4 = true;
+ else if (!ns_g_disable4)
+ 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 == true &&
+ isc_net_probe_ipv6only() != ISC_R_SUCCESS) {
+ ipv6only = false;
+ log_explicit = true;
+ }
+#endif
+ if (scan_ipv6 == true &&
+ isc_net_probe_ipv6pktinfo() != ISC_R_SUCCESS) {
+ ipv6pktinfo = false;
+ log_explicit = true;
+ }
+ if (scan_ipv6 == true && 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,
+ true,
+ 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);
+
+ if (adjusting == false) {
+ 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 == false && family == AF_INET)
+ continue;
+ if (scan_ipv6 == false && 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 (adjusting == false) {
+ /*
+ * If running with -T fixedlocal, then we only
+ * want 127.0.0.1 and ::1 in the localhost ACL.
+ */
+ if (ns_g_fixedlocal &&
+ !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 (adjusting == false && dolistenon == true) {
+ 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;
+
+ /*
+ * When adjusting interfaces with extra a listening
+ * list, see if the address matches the extra list.
+ * If it does, and is also covered by a wildcard
+ * interface, we need to listen on the address
+ * explicitly.
+ */
+ if (adjusting == true) {
+ ns_listenelt_t *ele;
+
+ match = 0;
+ for (ele = ISC_LIST_HEAD(ext_listen->elts);
+ ele != NULL;
+ ele = ISC_LIST_NEXT(ele, link)) {
+ (void)dns_acl_match(&listen_netaddr,
+ NULL, ele->acl,
+ NULL, &match, NULL);
+ if (match > 0 &&
+ (ele->port == le->port ||
+ ele->port == 0))
+ break;
+ else
+ match = 0;
+ }
+ if (ipv6_wildcard == true && match == 0)
+ continue;
+ }
+
+ 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 (adjusting == false &&
+ ipv6_wildcard == true)
+ continue;
+
+ if (log_explicit && family == AF_INET6 &&
+ !adjusting && 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,
+ "%s"
+ "listening on %s interface "
+ "%s, %s",
+ (adjusting == true) ?
+ "additionally " : "",
+ (family == AF_INET) ?
+ "IPv4" : "IPv6",
+ interface.name, sabuf);
+
+ result = ns_interface_setup(mgr,
+ &listen_sockaddr,
+ interface.name,
+ &ifp,
+ (adjusting == true) ?
+ false : true,
+ 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) ?
+ "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(__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, ns_listenlist_t *ext_listen,
+ bool verbose)
+{
+ isc_result_t result;
+ bool purge = true;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ mgr->generation++; /* Increment the generation count. */
+
+ result = do_scan(mgr, ext_listen, 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, unless
+ * we're in lwresd-only mode, in which case that is to
+ * be expected.
+ */
+ if (ext_listen == NULL &&
+ ISC_LIST_EMPTY(mgr->interfaces) && ! ns_g_lwresdonly) {
+ 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) {
+ return (ns_interfacemgr_scan0(mgr, NULL, verbose));
+}
+
+isc_result_t
+ns_interfacemgr_adjust(ns_interfacemgr_t *mgr, ns_listenlist_t *list,
+ bool verbose)
+{
+ return (ns_interfacemgr_scan0(mgr, list, verbose));
+}
+
+void
+ns_interfacemgr_setlistenon4(ns_interfacemgr_t *mgr, ns_listenlist_t *value) {
+ 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) {
+ 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;
+
+ 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, isc_sockaddr_t *addr) {
+ isc_sockaddr_t *old;
+
+ for (old = ISC_LIST_HEAD(mgr->listenon);
+ old != NULL;
+ old = ISC_LIST_NEXT(old, link))
+ if (isc_sockaddr_equal(old, addr))
+ return (true);
+ return (false);
+}