minix/external/bsd/bind/dist/bin/named/interfacemgr.c
David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

1212 lines
31 KiB
C

/* $NetBSD: interfacemgr.c,v 1.10 2015/07/08 17:28:55 christos Exp $ */
/*
* Copyright (C) 2004-2009, 2011-2015 Internet Systems Consortium, Inc. ("ISC")
* Copyright (C) 1999-2002 Internet Software Consortium.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/* Id: interfacemgr.c,v 1.101 2011/11/09 18:44:03 each Exp */
/*! \file */
#include <config.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;
isc_boolean_t done = ISC_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 = ISC_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 = ISC_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 = ISC_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;
strncpy(ifp->name, name, sizeof(ifp->name));
ifp->name[sizeof(ifp->name)-1] = '\0';
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, ISC_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, ISC_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,
ISC_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 (ISC_R_SUCCESS);
}
static isc_result_t
ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
const char *name, ns_interface_t **ifpret,
isc_boolean_t accept_tcp, isc_dscp_t dscp)
{
isc_result_t result;
ns_interface_t *ifp = NULL;
REQUIRE(ifpret != NULL && *ifpret == NULL);
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)
goto cleanup_interface;
if (!ns_g_notcp && accept_tcp == ISC_TRUE) {
result = ns_interface_accepttcp(ifp);
if (result != ISC_R_SUCCESS) {
/*
* 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 = ISC_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 = ISC_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 isc_boolean_t
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, ISC_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, ISC_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,
isc_boolean_t verbose)
{
isc_interfaceiter_t *iter = NULL;
isc_boolean_t scan_ipv4 = ISC_FALSE;
isc_boolean_t scan_ipv6 = ISC_FALSE;
isc_boolean_t adjusting = ISC_FALSE;
isc_boolean_t ipv6only = ISC_TRUE;
isc_boolean_t ipv6pktinfo = ISC_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;
isc_boolean_t log_explicit = ISC_FALSE;
isc_boolean_t dolistenon;
char sabuf[ISC_SOCKADDR_FORMATSIZE];
if (ext_listen != NULL)
adjusting = ISC_TRUE;
if (isc_net_probeipv6() == ISC_R_SUCCESS)
scan_ipv6 = ISC_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 = ISC_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 == ISC_TRUE &&
isc_net_probe_ipv6only() != ISC_R_SUCCESS) {
ipv6only = ISC_FALSE;
log_explicit = ISC_TRUE;
}
#endif
if (scan_ipv6 == ISC_TRUE &&
isc_net_probe_ipv6pktinfo() != ISC_R_SUCCESS) {
ipv6pktinfo = ISC_FALSE;
log_explicit = ISC_TRUE;
}
if (scan_ipv6 == ISC_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,
ISC_TRUE,
le->dscp);
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 == ISC_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);
}
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 == ISC_FALSE && family == AF_INET)
continue;
if (scan_ipv6 == ISC_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 == ISC_FALSE) {
result = setup_locals(mgr, &interface);
if (result != ISC_R_SUCCESS)
goto ignore_interface;
}
ll = (family == AF_INET) ? mgr->listenon4 : mgr->listenon6;
dolistenon = ISC_TRUE;
for (le = ISC_LIST_HEAD(ll->elts);
le != NULL;
le = ISC_LIST_NEXT(le, link))
{
int match;
isc_boolean_t ipv6_wildcard = ISC_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 == ISC_FALSE && dolistenon == ISC_TRUE) {
setup_listenon(mgr, &interface, le->port);
dolistenon = ISC_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 = ISC_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 == ISC_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 == ISC_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 {
if (adjusting == ISC_FALSE &&
ipv6_wildcard == ISC_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 = ISC_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 == ISC_TRUE) ?
"additionally " : "",
(family == AF_INET) ?
"IPv4" : "IPv6",
interface.name, sabuf);
result = ns_interface_setup(mgr,
&listen_sockaddr,
interface.name,
&ifp,
(adjusting == ISC_TRUE) ?
ISC_FALSE : ISC_TRUE,
le->dscp);
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 = ISC_R_SUCCESS;
cleanup_iter:
isc_interfaceiter_destroy(&iter);
return (result);
}
static void
ns_interfacemgr_scan0(ns_interfacemgr_t *mgr, ns_listenlist_t *ext_listen,
isc_boolean_t verbose)
{
isc_boolean_t purge = ISC_TRUE;
REQUIRE(NS_INTERFACEMGR_VALID(mgr));
mgr->generation++; /* Increment the generation count. */
if (do_scan(mgr, ext_listen, verbose) != ISC_R_SUCCESS)
purge = ISC_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");
}
}
void
ns_interfacemgr_scan(ns_interfacemgr_t *mgr, isc_boolean_t verbose) {
ns_interfacemgr_scan0(mgr, NULL, verbose);
}
void
ns_interfacemgr_adjust(ns_interfacemgr_t *mgr, ns_listenlist_t *list,
isc_boolean_t verbose)
{
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);
}
isc_boolean_t
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 (ISC_TRUE);
return (ISC_FALSE);
}