diff options
Diffstat (limited to 'bin/named/interfacemgr.c')
-rw-r--r-- | bin/named/interfacemgr.c | 1246 |
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); +} |