/*
 * Copyright (C) 1999-2001  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and 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 INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM 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.54.2.1 2001/01/09 22:31:52 bwelling Exp $ */

#include <config.h>

#include <isc/interfaceiter.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>

#define IFMGR_MAGIC		0x49464D47U	/* IFMG. */
#define NS_INTERFACEMGR_VALID(t) ISC_MAGIC_VALID(t, IFMGR_MAGIC)

#define IFMGR_COMMON_LOGARGS \
	ns_g_lctx, NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR

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. */
};

static void
purge_old_interfaces(ns_interfacemgr_t *mgr);

isc_result_t
ns_interfacemgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
		       isc_socketmgr_t *socketmgr,
		       dns_dispatchmgr_t *dispatchmgr,
		       ns_interfacemgr_t **mgrp)
{
	isc_result_t result;
	ns_interfacemgr_t *mgr;

	REQUIRE(mctx != NULL);
	REQUIRE(mgrp != NULL);
	REQUIRE(*mgrp == NULL);

	mgr = isc_mem_get(mctx, sizeof(*mgr));
	if (mgr == NULL)
		return (ISC_R_NOMEMORY);

	result = isc_mutex_init(&mgr->lock);
	if (result != ISC_R_SUCCESS)
		goto cleanup_mem;

	mgr->mctx = mctx;
	mgr->taskmgr = taskmgr;
	mgr->socketmgr = socketmgr;
	mgr->dispatchmgr = dispatchmgr;
	mgr->generation = 1;
	mgr->listenon4 = NULL;
	mgr->listenon6 = NULL;
	
	ISC_LIST_INIT(mgr->interfaces);

	/*
	 * 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;

	mgr->references = 1;
	mgr->magic = IFMGR_MAGIC;
	*mgrp = mgr;
	return (ISC_R_SUCCESS);

 cleanup_listenon:
	ns_listenlist_detach(&mgr->listenon4);
	ns_listenlist_detach(&mgr->listenon6);
 cleanup_mem:
	isc_mem_put(mctx, mgr, sizeof(*mgr));
	return (result);
}

static void
ns_interfacemgr_destroy(ns_interfacemgr_t *mgr) {
	REQUIRE(NS_INTERFACEMGR_VALID(mgr));
	dns_aclenv_destroy(&mgr->aclenv);
	ns_listenlist_detach(&mgr->listenon4);
	ns_listenlist_detach(&mgr->listenon6);
	DESTROYLOCK(&mgr->lock);
	mgr->magic = 0;
	isc_mem_put(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++;
	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;

	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;
	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;
	}

	ifp->udpdispatch = 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;

	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;

	attrs = 0;
	attrs |= DNS_DISPATCHATTR_UDP;
	if (isc_sockaddr_pf(&ifp->addr) == AF_INET)
		attrs |= DNS_DISPATCHATTR_IPV4;
	else
		attrs |= DNS_DISPATCHATTR_IPV6;
	attrmask = 0;
	attrmask |= DNS_DISPATCHATTR_UDP | DNS_DISPATCHATTR_TCP;
	attrmask |= DNS_DISPATCHATTR_IPV4 | DNS_DISPATCHATTR_IPV6;
	result = dns_dispatch_getudp(ifp->mgr->dispatchmgr, ns_g_socketmgr,
				     ns_g_taskmgr, &ifp->addr,
				     4096, 1000, 32768, 8219, 8237,
				     attrs, attrmask, &ifp->udpdispatch);
	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, ns_g_cpus,
					    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:
	dns_dispatch_detach(&ifp->udpdispatch);
 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;
	}
	result = isc_socket_bind(ifp->tcpsocket, &ifp->addr);
	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;
	}
	result = isc_socket_listen(ifp->tcpsocket, 0);
	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;
	}

	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_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);

	result = ns_interface_listenudp(ifp);
	if (result != ISC_R_SUCCESS)
		goto cleanup_interface;

	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 return 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 (ISC_R_SUCCESS);

 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;
	REQUIRE(NS_INTERFACE_VALID(ifp));

	ns_interface_shutdown(ifp);

	if (ifp->udpdispatch != NULL)
		dns_dispatch_detach(&ifp->udpdispatch);
	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, 10, &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 void
do_ipv4(ns_interfacemgr_t *mgr) {
	isc_interfaceiter_t *iter = NULL;
	isc_result_t result;

	result = isc_interfaceiter_create(mgr->mctx, &iter);
	if (result != ISC_R_SUCCESS)
		return;

	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;

	for (result = isc_interfaceiter_first(iter);
	     result == ISC_R_SUCCESS;
	     result = isc_interfaceiter_next(iter))
	{
		ns_interface_t *ifp;
		isc_interface_t interface;
		ns_listenelt_t *le;
		dns_aclelement_t elt;
		unsigned int prefixlen;

		result = isc_interfaceiter_current(iter, &interface);
		if (result != ISC_R_SUCCESS)
			break;

		if (interface.address.family != AF_INET)
			continue;

		if ((interface.flags & INTERFACE_F_UP) == 0)
			continue;

		result = isc_netaddr_masktoprefixlen(&interface.netmask,
						     &prefixlen);
		if (result != ISC_R_SUCCESS)
			goto ignore_interface;
		elt.type = dns_aclelementtype_ipprefix;
		elt.negative = ISC_FALSE;
		elt.u.ip_prefix.address = interface.address;
		elt.u.ip_prefix.prefixlen = prefixlen;
		/* XXX suppress duplicates */
		result = dns_acl_appendelement(mgr->aclenv.localnets, &elt);
		if (result != ISC_R_SUCCESS)
			goto ignore_interface;
		elt.u.ip_prefix.prefixlen = 32;
		result = dns_acl_appendelement(mgr->aclenv.localhost, &elt);
		if (result != ISC_R_SUCCESS)
			goto ignore_interface;

		for (le = ISC_LIST_HEAD(mgr->listenon4->elts);
		     le != NULL;
		     le = ISC_LIST_NEXT(le, link))
		{
			int match;
			isc_netaddr_t listen_netaddr;
			isc_sockaddr_t listen_sockaddr;

			/*
			 * Construct a socket address for this IP/port
			 * combination.
			 */
			isc_netaddr_fromin(&listen_netaddr,
					   &interface.address.type.in);
			isc_sockaddr_fromnetaddr(&listen_sockaddr,
						 &listen_netaddr,
						 le->port);

			/*
			 * See if the address matches the listen-on statement;
			 * if not, ignore the interface.
			 */
			result = dns_acl_match(&listen_netaddr, NULL,
					       le->acl, &mgr->aclenv,
					       &match, NULL);
			if (match <= 0)
				continue;

			ifp = find_matching_interface(mgr, &listen_sockaddr);
			if (ifp != NULL) {
				ifp->generation = mgr->generation;
			} else {
				char sabuf[ISC_SOCKADDR_FORMATSIZE];
				isc_sockaddr_format(&listen_sockaddr,
						    sabuf, sizeof(sabuf));
				isc_log_write(IFMGR_COMMON_LOGARGS,
					      ISC_LOG_INFO,
					      "listening on IPv4 interface "
					      "%s, %s", interface.name, sabuf);

				result = ns_interface_setup(mgr,
							    &listen_sockaddr,
							    interface.name,
							    &ifp);
				if (result != ISC_R_SUCCESS) {
					isc_log_write(IFMGR_COMMON_LOGARGS,
						 ISC_LOG_ERROR,
						 "creating IPv4 interface %s "
						 "failed; interface ignored",
						 interface.name);
				}
				/* Continue. */
			}

		}
		continue;

	ignore_interface:
		isc_log_write(IFMGR_COMMON_LOGARGS,
			      ISC_LOG_ERROR,
			      "ignoring IPv4 interface %s: %s",
			      interface.name, isc_result_totext(result));
		continue;
	}
	if (result != ISC_R_NOMORE)
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "IPv4: interface iteration failed: %s",
				 isc_result_totext(result));
 cleanup_iter:
	isc_interfaceiter_destroy(&iter);
}

static isc_boolean_t
listenon_is_ip6_none(ns_listenlist_t *p) {
	ns_listenelt_t *elt;
	if (ISC_LIST_EMPTY(p->elts))
		return (ISC_TRUE); /* No listen-on-v6 statements */
	elt = ISC_LIST_HEAD(p->elts);
	if (ISC_LIST_NEXT(elt, link) != NULL)
		return (ISC_FALSE); /* More than one listen-on-v6 stmt */
	if (elt->acl->length == 0)
		return (ISC_TRUE); /* listen-on-v6 { } */
	if (elt->acl->length > 1)
		return (ISC_FALSE);  /* listen-on-v6 { ...; ...; } */
	if (elt->acl->elements[0].negative == ISC_TRUE &&
	    elt->acl->elements[0].type == dns_aclelementtype_any)
		return (ISC_TRUE);  /* listen-on-v6 { none; } */
	return (ISC_FALSE); /* All others */
}

static isc_boolean_t
listenon_is_ip6_any(ns_listenlist_t *p, in_port_t *portp) {
	ns_listenelt_t *elt;
	if (ISC_LIST_EMPTY(p->elts))
		return (ISC_FALSE); /* No listen-on-v6 statements */
	elt = ISC_LIST_HEAD(p->elts);
	if (ISC_LIST_NEXT(elt, link) != NULL)
		return (ISC_FALSE); /* More than one listen-on-v6 stmt */
	if (elt->acl->length != 1)
		return (ISC_FALSE);
	if (elt->acl->elements[0].negative == ISC_FALSE &&
	    elt->acl->elements[0].type == dns_aclelementtype_any) {
		*portp = elt->port;
		return (ISC_TRUE);  /* listen-on-v6 { any; } */
	}
	return (ISC_FALSE); /* All others */
}

static void
do_ipv6(ns_interfacemgr_t *mgr) {
	isc_result_t result;
	ns_interface_t *ifp;
	isc_sockaddr_t listen_addr;
	struct in6_addr in6a;
	in_port_t port;

	if (listenon_is_ip6_none(mgr->listenon6))
		return;

	if (! listenon_is_ip6_any(mgr->listenon6, &port)) {
		isc_log_write(IFMGR_COMMON_LOGARGS,
		      ISC_LOG_ERROR,
		      "bad IPv6 listen-on list: must be 'any' or 'none'");
		return;
	}

	in6a = in6addr_any;
	isc_sockaddr_fromin6(&listen_addr, &in6a, port);

	ifp = find_matching_interface(mgr, &listen_addr);
	if (ifp != NULL) {
		ifp->generation = mgr->generation;
	} else {
		isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO,
			      "listening on IPv6 interfaces, port %u", port);
		result = ns_interface_setup(mgr, &listen_addr, "<any>", &ifp);
		if (result != ISC_R_SUCCESS) {
			isc_log_write(IFMGR_COMMON_LOGARGS,
				      ISC_LOG_ERROR,
				      "listening on IPv6 interfaces failed");
			/* Continue. */
		}
	}
}

void
ns_interfacemgr_scan(ns_interfacemgr_t *mgr, isc_boolean_t verbose) {

	REQUIRE(NS_INTERFACEMGR_VALID(mgr));

	mgr->generation++;	/* Increment the generation count. */

	if (isc_net_probeipv6() == ISC_R_SUCCESS) {
		do_ipv6(mgr);
	} else
		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)
		do_ipv4(mgr);
	else
		isc_log_write(IFMGR_COMMON_LOGARGS,
			      verbose ? ISC_LOG_INFO : ISC_LOG_DEBUG(1),
			      "no IPv4 interfaces found");

	/*
	 * 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.
	 */
	purge_old_interfaces(mgr);

	if (ISC_LIST_EMPTY(mgr->interfaces)) {
		isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_WARNING,
			      "not listening on any interfaces");
		/*
		 * Continue anyway.
		 */
	}
}

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);
}

