/* * Copyright (C) 2004 Internet Systems Consortium, Inc. ("ISC") * Copyright (C) 2000, 2001, 2003 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 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: lwresd.c,v 1.37.2.3 2004/03/09 06:09:19 marka Exp $ */ /* * Main program for the Lightweight Resolver Daemon. * * To paraphrase the old saying about X11, "It's not a lightweight deamon * for resolvers, it's a deamon for lightweight resolvers". */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LWRESD_MAGIC ISC_MAGIC('L', 'W', 'R', 'D') #define VALID_LWRESD(l) ISC_MAGIC_VALID(l, LWRESD_MAGIC) #define LWRESLISTENER_MAGIC ISC_MAGIC('L', 'W', 'R', 'L') #define VALID_LWRESLISTENER(l) ISC_MAGIC_VALID(l, LWRESLISTENER_MAGIC) /* * The total number of clients we can handle will be NTASKS * NRECVS. */ #define NTASKS 2 /* tasks to create to handle lwres queries */ #define NRECVS 2 /* max clients per task */ typedef ISC_LIST(ns_lwreslistener_t) ns_lwreslistenerlist_t; static ns_lwreslistenerlist_t listeners; static isc_mutex_t listeners_lock; static isc_once_t once = ISC_ONCE_INIT; static void initialize_mutex(void) { RUNTIME_CHECK(isc_mutex_init(&listeners_lock) == ISC_R_SUCCESS); } /* * Wrappers around our memory management stuff, for the lwres functions. */ void * ns__lwresd_memalloc(void *arg, size_t size) { return (isc_mem_get(arg, size)); } void ns__lwresd_memfree(void *arg, void *mem, size_t size) { isc_mem_put(arg, mem, size); } #define CHECK(op) \ do { result = (op); \ if (result != ISC_R_SUCCESS) goto cleanup; \ } while (0) static isc_result_t buffer_putstr(isc_buffer_t *b, const char *s) { unsigned int len = strlen(s); if (isc_buffer_availablelength(b) <= len) return (ISC_R_NOSPACE); isc_buffer_putmem(b, (const unsigned char *)s, len); return (ISC_R_SUCCESS); } /* * Convert a resolv.conf file into a config structure. */ isc_result_t ns_lwresd_parseeresolvconf(isc_mem_t *mctx, cfg_parser_t *pctx, cfg_obj_t **configp) { char text[4096]; char str[16]; isc_buffer_t b; lwres_context_t *lwctx = NULL; lwres_conf_t *lwc = NULL; isc_sockaddr_t sa; isc_netaddr_t na; int i; isc_result_t result; lwres_result_t lwresult; lwctx = NULL; lwresult = lwres_context_create(&lwctx, mctx, ns__lwresd_memalloc, ns__lwresd_memfree, LWRES_CONTEXT_SERVERMODE); if (lwresult != LWRES_R_SUCCESS) { result = ISC_R_NOMEMORY; goto cleanup; } lwresult = lwres_conf_parse(lwctx, lwresd_g_resolvconffile); if (lwresult != LWRES_R_SUCCESS) { result = DNS_R_SYNTAX; goto cleanup; } lwc = lwres_conf_get(lwctx); INSIST(lwc != NULL); isc_buffer_init(&b, text, sizeof(text)); CHECK(buffer_putstr(&b, "options {\n")); /* * Build the list of forwarders. */ if (lwc->nsnext > 0) { CHECK(buffer_putstr(&b, "\tforwarders {\n")); for (i = 0 ; i < lwc->nsnext ; i++) { CHECK(lwaddr_sockaddr_fromlwresaddr( &sa, &lwc->nameservers[i], ns_g_port)); isc_netaddr_fromsockaddr(&na, &sa); CHECK(buffer_putstr(&b, "\t\t")); CHECK(isc_netaddr_totext(&na, &b)); CHECK(buffer_putstr(&b, ";\n")); } CHECK(buffer_putstr(&b, "\t};\n")); } /* * Build the sortlist */ if (lwc->sortlistnxt > 0) { CHECK(buffer_putstr(&b, "\tsortlist {\n")); CHECK(buffer_putstr(&b, "\t\t{\n")); CHECK(buffer_putstr(&b, "\t\t\tany;\n")); CHECK(buffer_putstr(&b, "\t\t\t{\n")); for (i = 0 ; i < lwc->sortlistnxt; i++) { lwres_addr_t *lwaddr = &lwc->sortlist[i].addr; lwres_addr_t *lwmask = &lwc->sortlist[i].mask; unsigned int mask; CHECK(lwaddr_sockaddr_fromlwresaddr(&sa, lwmask, 0)); isc_netaddr_fromsockaddr(&na, &sa); result = isc_netaddr_masktoprefixlen(&na, &mask); if (result != ISC_R_SUCCESS) { char addrtext[ISC_NETADDR_FORMATSIZE]; isc_netaddr_format(&na, addrtext, sizeof(addrtext)); isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_ERROR, "processing sortlist: '%s' is " "not a valid netmask", addrtext); goto cleanup; } CHECK(lwaddr_sockaddr_fromlwresaddr(&sa, lwaddr, 0)); isc_netaddr_fromsockaddr(&na, &sa); CHECK(buffer_putstr(&b, "\t\t\t\t")); CHECK(isc_netaddr_totext(&na, &b)); snprintf(str, sizeof(str), "%u", mask); CHECK(buffer_putstr(&b, "/")); CHECK(buffer_putstr(&b, str)); CHECK(buffer_putstr(&b, ";\n")); } CHECK(buffer_putstr(&b, "\t\t\t};\n")); CHECK(buffer_putstr(&b, "\t\t};\n")); CHECK(buffer_putstr(&b, "\t};\n")); } CHECK(buffer_putstr(&b, "};\n\n")); CHECK(buffer_putstr(&b, "lwres {\n")); /* * Build the search path */ if (lwc->searchnxt > 0) { if (lwc->searchnxt > 0) { CHECK(buffer_putstr(&b, "\tsearch {\n")); for (i = 0; i < lwc->searchnxt; i++) { CHECK(buffer_putstr(&b, "\t\t\"")); CHECK(buffer_putstr(&b, lwc->search[i])); CHECK(buffer_putstr(&b, "\";\n")); } CHECK(buffer_putstr(&b, "\t};\n")); } } /* * Build the ndots line */ if (lwc->ndots != 1) { CHECK(buffer_putstr(&b, "\tndots ")); snprintf(str, sizeof(str), "%u", lwc->ndots); CHECK(buffer_putstr(&b, str)); CHECK(buffer_putstr(&b, ";\n")); } /* * Build the listen-on line */ if (lwc->lwnext > 0) { CHECK(buffer_putstr(&b, "\tlisten-on {\n")); for (i = 0 ; i < lwc->lwnext ; i++) { CHECK(lwaddr_sockaddr_fromlwresaddr(&sa, &lwc->lwservers[i], 0)); isc_netaddr_fromsockaddr(&na, &sa); CHECK(buffer_putstr(&b, "\t\t")); CHECK(isc_netaddr_totext(&na, &b)); CHECK(buffer_putstr(&b, ";\n")); } CHECK(buffer_putstr(&b, "\t};\n")); } CHECK(buffer_putstr(&b, "};\n")); #if 0 printf("%.*s\n", (int)isc_buffer_usedlength(&b), (char *)isc_buffer_base(&b)); #endif lwres_conf_clear(lwctx); lwres_context_destroy(&lwctx); return (cfg_parse_buffer(pctx, &b, &cfg_type_namedconf, configp)); cleanup: if (lwctx != NULL) { lwres_conf_clear(lwctx); lwres_context_destroy(&lwctx); } return (result); } /* * Handle lwresd manager objects */ isc_result_t ns_lwdmanager_create(isc_mem_t *mctx, cfg_obj_t *lwres, ns_lwresd_t **lwresdp) { ns_lwresd_t *lwresd; const char *vname; dns_rdataclass_t vclass; cfg_obj_t *obj, *viewobj, *searchobj; cfg_listelt_t *element; isc_result_t result; INSIST(lwresdp != NULL && *lwresdp == NULL); lwresd = isc_mem_get(mctx, sizeof(ns_lwresd_t)); if (lwresd == NULL) return (ISC_R_NOMEMORY); lwresd->mctx = NULL; isc_mem_attach(mctx, &lwresd->mctx); lwresd->view = NULL; lwresd->search = NULL; lwresd->refs = 1; obj = NULL; (void)cfg_map_get(lwres, "ndots", &obj); if (obj != NULL) lwresd->ndots = cfg_obj_asuint32(obj); else lwresd->ndots = 1; RUNTIME_CHECK(isc_mutex_init(&lwresd->lock) == ISC_R_SUCCESS); lwresd->shutting_down = ISC_FALSE; viewobj = NULL; (void)cfg_map_get(lwres, "view", &viewobj); if (viewobj != NULL) { vname = cfg_obj_asstring(cfg_tuple_get(viewobj, "name")); obj = cfg_tuple_get(viewobj, "class"); result = ns_config_getclass(obj, dns_rdataclass_in, &vclass); if (result != ISC_R_SUCCESS) goto fail; } else { vname = "_default"; vclass = dns_rdataclass_in; } result = dns_viewlist_find(&ns_g_server->viewlist, vname, vclass, &lwresd->view); if (result != ISC_R_SUCCESS) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "couldn't find view %s", vname); goto fail; } searchobj = NULL; cfg_map_get(lwres, "search", &searchobj); if (searchobj != NULL) { lwresd->search = NULL; result = ns_lwsearchlist_create(lwresd->mctx, &lwresd->search); if (result != ISC_R_SUCCESS) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "couldn't create searchlist"); goto fail; } for (element = cfg_list_first(searchobj); element != NULL; element = cfg_list_next(element)) { cfg_obj_t *search; char *searchstr; isc_buffer_t namebuf; dns_fixedname_t fname; dns_name_t *name; search = cfg_listelt_value(element); searchstr = cfg_obj_asstring(search); dns_fixedname_init(&fname); name = dns_fixedname_name(&fname); isc_buffer_init(&namebuf, searchstr, strlen(searchstr)); isc_buffer_add(&namebuf, strlen(searchstr)); result = dns_name_fromtext(name, &namebuf, dns_rootname, ISC_FALSE, NULL); if (result != ISC_R_SUCCESS) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "invalid name %s in searchlist", searchstr); continue; } result = ns_lwsearchlist_append(lwresd->search, name); if (result != ISC_R_SUCCESS) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "couldn't update searchlist"); goto fail; } } } lwresd->magic = LWRESD_MAGIC; *lwresdp = lwresd; return (ISC_R_SUCCESS); fail: if (lwresd->view != NULL) dns_view_detach(&lwresd->view); if (lwresd->search != NULL) ns_lwsearchlist_detach(&lwresd->search); if (lwresd->mctx != NULL) isc_mem_detach(&lwresd->mctx); return (result); } void ns_lwdmanager_attach(ns_lwresd_t *source, ns_lwresd_t **targetp) { INSIST(VALID_LWRESD(source)); INSIST(targetp != NULL && *targetp == NULL); LOCK(&source->lock); source->refs++; UNLOCK(&source->lock); *targetp = source; } void ns_lwdmanager_detach(ns_lwresd_t **lwresdp) { ns_lwresd_t *lwresd; isc_mem_t *mctx; isc_boolean_t done = ISC_FALSE; INSIST(lwresdp != NULL && *lwresdp != NULL); INSIST(VALID_LWRESD(*lwresdp)); lwresd = *lwresdp; *lwresdp = NULL; LOCK(&lwresd->lock); INSIST(lwresd->refs > 0); lwresd->refs--; if (lwresd->refs == 0) done = ISC_TRUE; UNLOCK(&lwresd->lock); if (!done) return; dns_view_detach(&lwresd->view); if (lwresd->search != NULL) ns_lwsearchlist_detach(&lwresd->search); mctx = lwresd->mctx; lwresd->magic = 0; isc_mem_put(mctx, lwresd, sizeof(*lwresd)); isc_mem_detach(&mctx); } /* * Handle listener objects */ void ns_lwreslistener_attach(ns_lwreslistener_t *source, ns_lwreslistener_t **targetp) { INSIST(VALID_LWRESLISTENER(source)); INSIST(targetp != NULL && *targetp == NULL); LOCK(&source->lock); source->refs++; UNLOCK(&source->lock); *targetp = source; } void ns_lwreslistener_detach(ns_lwreslistener_t **listenerp) { ns_lwreslistener_t *listener; isc_mem_t *mctx; isc_boolean_t done = ISC_FALSE; INSIST(listenerp != NULL && *listenerp != NULL); INSIST(VALID_LWRESLISTENER(*listenerp)); listener = *listenerp; LOCK(&listener->lock); INSIST(listener->refs > 0); listener->refs--; if (listener->refs == 0) done = ISC_TRUE; UNLOCK(&listener->lock); if (!done) return; if (listener->manager != NULL) ns_lwdmanager_detach(&listener->manager); if (listener->sock != NULL) isc_socket_detach(&listener->sock); listener->magic = 0; mctx = listener->mctx; isc_mem_put(mctx, listener, sizeof(*listener)); isc_mem_detach(&mctx); listenerp = NULL; } static isc_result_t listener_create(isc_mem_t *mctx, ns_lwresd_t *lwresd, ns_lwreslistener_t **listenerp) { ns_lwreslistener_t *listener; REQUIRE(listenerp != NULL && *listenerp == NULL); listener = isc_mem_get(mctx, sizeof(ns_lwreslistener_t)); if (listener == NULL) return (ISC_R_NOMEMORY); RUNTIME_CHECK(isc_mutex_init(&listener->lock) == ISC_R_SUCCESS); listener->magic = LWRESLISTENER_MAGIC; listener->refs = 1; listener->sock = NULL; listener->manager = NULL; ns_lwdmanager_attach(lwresd, &listener->manager); listener->mctx = NULL; isc_mem_attach(mctx, &listener->mctx); ISC_LINK_INIT(listener, link); ISC_LIST_INIT(listener->cmgrs); *listenerp = listener; return (ISC_R_SUCCESS); } static isc_result_t listener_bind(ns_lwreslistener_t *listener, isc_sockaddr_t *address) { isc_socket_t *sock = NULL; isc_result_t result = ISC_R_SUCCESS; int pf; pf = isc_sockaddr_pf(address); if ((pf == AF_INET && isc_net_probeipv4() != ISC_R_SUCCESS) || (pf == AF_INET6 && isc_net_probeipv6() != ISC_R_SUCCESS)) return (ISC_R_FAMILYNOSUPPORT); listener->address = *address; if (isc_sockaddr_getport(&listener->address) == 0) { in_port_t port; port = lwresd_g_listenport; if (port == 0) port = LWRES_UDP_PORT; isc_sockaddr_setport(&listener->address, port); } sock = NULL; result = isc_socket_create(ns_g_socketmgr, pf, isc_sockettype_udp, &sock); if (result != ISC_R_SUCCESS) { isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "failed to create lwres socket: %s", isc_result_totext(result)); return (result); } result = isc_socket_bind(sock, &listener->address); if (result != ISC_R_SUCCESS) { char socktext[ISC_SOCKADDR_FORMATSIZE]; isc_sockaddr_format(&listener->address, socktext, sizeof(socktext)); isc_socket_detach(&sock); isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "failed to add lwres socket: %s: %s", socktext, isc_result_totext(result)); return (result); } listener->sock = sock; return (ISC_R_SUCCESS); } static void listener_copysock(ns_lwreslistener_t *oldlistener, ns_lwreslistener_t *newlistener) { newlistener->address = oldlistener->address; isc_socket_attach(oldlistener->sock, &newlistener->sock); } static isc_result_t listener_startclients(ns_lwreslistener_t *listener) { ns_lwdclientmgr_t *cm; unsigned int i; isc_result_t result; /* * Create the client managers. */ result = ISC_R_SUCCESS; for (i = 0 ; i < NTASKS && result == ISC_R_SUCCESS; i++) result = ns_lwdclientmgr_create(listener, NRECVS, ns_g_taskmgr); /* * Ensure that we have created at least one. */ if (ISC_LIST_EMPTY(listener->cmgrs)) return (result); /* * Walk the list of clients and start each one up. */ LOCK(&listener->lock); cm = ISC_LIST_HEAD(listener->cmgrs); while (cm != NULL) { ns_lwdclient_startrecv(cm); cm = ISC_LIST_NEXT(cm, link); } UNLOCK(&listener->lock); return (ISC_R_SUCCESS); } static void listener_shutdown(ns_lwreslistener_t *listener) { ns_lwdclientmgr_t *cm; cm = ISC_LIST_HEAD(listener->cmgrs); while (cm != NULL) { isc_task_shutdown(cm->task); cm = ISC_LIST_NEXT(cm, link); } } static isc_result_t find_listener(isc_sockaddr_t *address, ns_lwreslistener_t **listenerp) { ns_lwreslistener_t *listener; INSIST(listenerp != NULL && *listenerp == NULL); for (listener = ISC_LIST_HEAD(listeners); listener != NULL; listener = ISC_LIST_NEXT(listener, link)) { if (!isc_sockaddr_equal(address, &listener->address)) continue; *listenerp = listener; return (ISC_R_SUCCESS); } return (ISC_R_NOTFOUND); } void ns_lwreslistener_unlinkcm(ns_lwreslistener_t *listener, ns_lwdclientmgr_t *cm) { REQUIRE(VALID_LWRESLISTENER(listener)); LOCK(&listener->lock); ISC_LIST_UNLINK(listener->cmgrs, cm, link); UNLOCK(&listener->lock); } void ns_lwreslistener_linkcm(ns_lwreslistener_t *listener, ns_lwdclientmgr_t *cm) { REQUIRE(VALID_LWRESLISTENER(listener)); /* * This does no locking, since it's called early enough that locking * isn't needed. */ ISC_LIST_APPEND(listener->cmgrs, cm, link); } static isc_result_t configure_listener(isc_sockaddr_t *address, ns_lwresd_t *lwresd, isc_mem_t *mctx, ns_lwreslistenerlist_t *newlisteners) { ns_lwreslistener_t *listener, *oldlistener = NULL; char socktext[ISC_SOCKADDR_FORMATSIZE]; isc_result_t result; (void)find_listener(address, &oldlistener); listener = NULL; result = listener_create(mctx, lwresd, &listener); if (result != ISC_R_SUCCESS) { isc_sockaddr_format(address, socktext, sizeof(socktext)); isc_log_write(ns_g_lctx, ISC_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "lwres failed to configure %s: %s", socktext, isc_result_totext(result)); return (result); } /* * If there's already a listener, don't rebind the socket. */ if (oldlistener == NULL) { result = listener_bind(listener, address); if (result != ISC_R_SUCCESS) { ns_lwreslistener_detach(&listener); return (ISC_R_SUCCESS); } } else listener_copysock(oldlistener, listener); result = listener_startclients(listener); if (result != ISC_R_SUCCESS) { isc_sockaddr_format(address, socktext, sizeof(socktext)); isc_log_write(ns_g_lctx, ISC_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_WARNING, "lwres: failed to start %s: %s", socktext, isc_result_totext(result)); ns_lwreslistener_detach(&listener); return (ISC_R_SUCCESS); } if (oldlistener != NULL) { /* * Remove the old listener from the old list and shut it down. */ ISC_LIST_UNLINK(listeners, oldlistener, link); listener_shutdown(oldlistener); ns_lwreslistener_detach(&oldlistener); } else { isc_sockaddr_format(address, socktext, sizeof(socktext)); isc_log_write(ns_g_lctx, ISC_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_NOTICE, "lwres listening on %s", socktext); } ISC_LIST_APPEND(*newlisteners, listener, link); return (result); } isc_result_t ns_lwresd_configure(isc_mem_t *mctx, cfg_obj_t *config) { cfg_obj_t *lwreslist = NULL; cfg_obj_t *lwres = NULL; cfg_obj_t *listenerslist = NULL; cfg_listelt_t *element = NULL; ns_lwreslistener_t *listener; ns_lwreslistenerlist_t newlisteners; isc_result_t result; char socktext[ISC_SOCKADDR_FORMATSIZE]; isc_sockaddr_t *addrs = NULL; ns_lwresd_t *lwresd = NULL; isc_uint32_t count = 0; REQUIRE(mctx != NULL); REQUIRE(config != NULL); RUNTIME_CHECK(isc_once_do(&once, initialize_mutex) == ISC_R_SUCCESS); ISC_LIST_INIT(newlisteners); result = cfg_map_get(config, "lwres", &lwreslist); if (result != ISC_R_SUCCESS) return (ISC_R_SUCCESS); LOCK(&listeners_lock); /* * Run through the new lwres address list, noting sockets that * are already being listened on and moving them to the new list. * * Identifying duplicates addr/port combinations is left to either * the underlying config code, or to the bind attempt getting an * address-in-use error. */ for (element = cfg_list_first(lwreslist); element != NULL; element = cfg_list_next(element)) { in_port_t port; lwres = cfg_listelt_value(element); CHECK(ns_lwdmanager_create(mctx, lwres, &lwresd)); port = lwresd_g_listenport; if (port == 0) port = LWRES_UDP_PORT; listenerslist = NULL; cfg_map_get(lwres, "listen-on", &listenerslist); if (listenerslist == NULL) { struct in_addr localhost; isc_sockaddr_t address; localhost.s_addr = htonl(INADDR_LOOPBACK); isc_sockaddr_fromin(&address, &localhost, port); CHECK(configure_listener(&address, lwresd, mctx, &newlisteners)); } else { isc_uint32_t i; CHECK(ns_config_getiplist(config, listenerslist, port, mctx, &addrs, &count)); for (i = 0; i < count; i++) CHECK(configure_listener(&addrs[i], lwresd, mctx, &newlisteners)); ns_config_putiplist(mctx, &addrs, count); } ns_lwdmanager_detach(&lwresd); } /* * Shutdown everything on the listeners list, and remove them from * the list. Then put all of the new listeners on it. */ while (!ISC_LIST_EMPTY(listeners)) { listener = ISC_LIST_HEAD(listeners); ISC_LIST_UNLINK(listeners, listener, link); isc_sockaddr_format(&listener->address, socktext, sizeof(socktext)); listener_shutdown(listener); ns_lwreslistener_detach(&listener); isc_log_write(ns_g_lctx, ISC_LOGCATEGORY_GENERAL, NS_LOGMODULE_LWRESD, ISC_LOG_NOTICE, "lwres no longer listening on %s", socktext); } cleanup: ISC_LIST_APPENDLIST(listeners, newlisteners, link); if (addrs != NULL) ns_config_putiplist(mctx, &addrs, count); if (lwresd != NULL) ns_lwdmanager_detach(&lwresd); UNLOCK(&listeners_lock); return (result); } void ns_lwresd_shutdown(void) { ns_lwreslistener_t *listener; RUNTIME_CHECK(isc_once_do(&once, initialize_mutex) == ISC_R_SUCCESS); while (!ISC_LIST_EMPTY(listeners)) { listener = ISC_LIST_HEAD(listeners); ISC_LIST_UNLINK(listeners, listener, link); ns_lwreslistener_detach(&listener); } }