lwdclient.c revision 0c27b3fe77ac1d5094ba3521e8142d9e7973133f
/*
* Copyright (C) 2000, 2001, 2004, 2005, 2007, 2015, 2016 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/.
*/
/* $Id: lwdclient.c,v 1.22 2007/06/18 23:47:18 tbox Exp $ */
/*! \file */
#include <config.h>
#include <isc/socket.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/util.h>
#include <dns/adb.h>
#include <dns/view.h>
#include <dns/log.h>
#include <named/types.h>
#include <named/log.h>
#include <named/lwresd.h>
#include <named/lwdclient.h>
#define SHUTTINGDOWN(cm) ((cm->flags & NS_LWDCLIENTMGR_FLAGSHUTTINGDOWN) != 0)
static void
lwdclientmgr_shutdown_callback(isc_task_t *task, isc_event_t *ev);
void
ns_lwdclient_log(int level, const char *format, ...) {
va_list args;
va_start(args, format);
isc_log_vwrite(dns_lctx,
DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
ISC_LOG_DEBUG(level), format, args);
va_end(args);
}
isc_result_t
ns_lwdclientmgr_create(ns_lwreslistener_t *listener, unsigned int nclients,
isc_taskmgr_t *taskmgr)
{
ns_lwresd_t *lwresd = listener->manager;
ns_lwdclientmgr_t *cm;
ns_lwdclient_t *client;
unsigned int i;
isc_result_t result;
cm = isc_mem_get(lwresd->mctx, sizeof(ns_lwdclientmgr_t));
if (cm == NULL)
return (ISC_R_NOMEMORY);
result = isc_mutex_init(&cm->lock);
if (result != ISC_R_SUCCESS)
goto freecm;
cm->listener = NULL;
ns_lwreslistener_attach(listener, &cm->listener);
cm->mctx = lwresd->mctx;
cm->sock = NULL;
isc_socket_attach(listener->sock, &cm->sock);
cm->view = lwresd->view;
cm->lwctx = NULL;
cm->task = NULL;
cm->flags = 0;
ISC_LINK_INIT(cm, link);
ISC_LIST_INIT(cm->idle);
ISC_LIST_INIT(cm->running);
result = lwres_context_create(&cm->lwctx, cm->mctx,
ns__lwresd_memalloc, ns__lwresd_memfree,
LWRES_CONTEXT_SERVERMODE);
if (result != ISC_R_SUCCESS)
goto errout;
for (i = 0; i < nclients; i++) {
client = isc_mem_get(lwresd->mctx, sizeof(ns_lwdclient_t));
if (client != NULL) {
ns_lwdclient_log(50, "created client %p, manager %p",
client, cm);
ns_lwdclient_initialize(client, cm);
}
}
/*
* If we could create no clients, clean up and return.
*/
if (ISC_LIST_EMPTY(cm->idle)) {
result = ISC_R_NOMEMORY;
goto errout;
}
result = isc_task_create(taskmgr, 0, &cm->task);
if (result != ISC_R_SUCCESS)
goto errout;
isc_task_setname(cm->task, "lwdclient", NULL);
/*
* This MUST be last, since there is no way to cancel an onshutdown...
*/
result = isc_task_onshutdown(cm->task, lwdclientmgr_shutdown_callback,
cm);
if (result != ISC_R_SUCCESS)
goto errout;
ns_lwreslistener_linkcm(listener, cm);
return (ISC_R_SUCCESS);
errout:
client = ISC_LIST_HEAD(cm->idle);
while (client != NULL) {
ISC_LIST_UNLINK(cm->idle, client, link);
isc_mem_put(lwresd->mctx, client, sizeof(*client));
client = ISC_LIST_HEAD(cm->idle);
}
if (cm->task != NULL)
isc_task_detach(&cm->task);
if (cm->lwctx != NULL)
lwres_context_destroy(&cm->lwctx);
DESTROYLOCK(&cm->lock);
freecm:
isc_mem_put(lwresd->mctx, cm, sizeof(*cm));
return (result);
}
static void
lwdclientmgr_destroy(ns_lwdclientmgr_t *cm) {
ns_lwdclient_t *client;
ns_lwreslistener_t *listener;
LOCK(&cm->lock);
if (!SHUTTINGDOWN(cm)) {
UNLOCK(&cm->lock);
return;
}
/*
* Run through the idle list and free the clients there. Idle
* clients do not have a recv running nor do they have any finds
* or similar running.
*/
client = ISC_LIST_HEAD(cm->idle);
while (client != NULL) {
ns_lwdclient_log(50, "destroying client %p, manager %p",
client, cm);
ISC_LIST_UNLINK(cm->idle, client, link);
isc_mem_put(cm->mctx, client, sizeof(*client));
client = ISC_LIST_HEAD(cm->idle);
}
if (!ISC_LIST_EMPTY(cm->running)) {
UNLOCK(&cm->lock);
return;
}
UNLOCK(&cm->lock);
lwres_context_destroy(&cm->lwctx);
cm->view = NULL;
isc_socket_detach(&cm->sock);
isc_task_detach(&cm->task);
DESTROYLOCK(&cm->lock);
listener = cm->listener;
ns_lwreslistener_unlinkcm(listener, cm);
ns_lwdclient_log(50, "destroying manager %p", cm);
isc_mem_put(cm->mctx, cm, sizeof(*cm));
ns_lwreslistener_detach(&listener);
}
static void
process_request(ns_lwdclient_t *client) {
lwres_buffer_t b;
isc_result_t result;
lwres_buffer_init(&b, client->buffer, client->recvlength);
lwres_buffer_add(&b, client->recvlength);
result = lwres_lwpacket_parseheader(&b, &client->pkt);
if (result != ISC_R_SUCCESS) {
ns_lwdclient_log(50, "invalid packet header received");
goto restart;
}
ns_lwdclient_log(50, "opcode %08x", client->pkt.opcode);
switch (client->pkt.opcode) {
case LWRES_OPCODE_GETADDRSBYNAME:
ns_lwdclient_processgabn(client, &b);
return;
case LWRES_OPCODE_GETNAMEBYADDR:
ns_lwdclient_processgnba(client, &b);
return;
case LWRES_OPCODE_GETRDATABYNAME:
ns_lwdclient_processgrbn(client, &b);
return;
case LWRES_OPCODE_NOOP:
ns_lwdclient_processnoop(client, &b);
return;
default:
ns_lwdclient_log(50, "unknown opcode %08x", client->pkt.opcode);
goto restart;
}
/*
* Drop the packet.
*/
restart:
ns_lwdclient_log(50, "restarting client %p...", client);
ns_lwdclient_stateidle(client);
}
void
ns_lwdclient_recv(isc_task_t *task, isc_event_t *ev) {
isc_result_t result;
ns_lwdclient_t *client = ev->ev_arg;
ns_lwdclientmgr_t *cm = client->clientmgr;
isc_socketevent_t *dev = (isc_socketevent_t *)ev;
INSIST(dev->region.base == client->buffer);
INSIST(NS_LWDCLIENT_ISRECV(client));
NS_LWDCLIENT_SETRECVDONE(client);
LOCK(&cm->lock);
INSIST((cm->flags & NS_LWDCLIENTMGR_FLAGRECVPENDING) != 0);
cm->flags &= ~NS_LWDCLIENTMGR_FLAGRECVPENDING;
UNLOCK(&cm->lock);
ns_lwdclient_log(50,
"event received: task %p, length %u, result %u (%s)",
task, dev->n, dev->result,
isc_result_totext(dev->result));
if (dev->result != ISC_R_SUCCESS) {
isc_event_free(&ev);
dev = NULL;
/*
* Go idle.
*/
ns_lwdclient_stateidle(client);
return;
}
client->recvlength = dev->n;
client->address = dev->address;
if ((dev->attributes & ISC_SOCKEVENTATTR_PKTINFO) != 0) {
client->pktinfo = dev->pktinfo;
client->pktinfo_valid = ISC_TRUE;
} else
client->pktinfo_valid = ISC_FALSE;
isc_event_free(&ev);
dev = NULL;
result = ns_lwdclient_startrecv(cm);
if (result != ISC_R_SUCCESS)
isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_LWRESD, ISC_LOG_ERROR,
"could not start lwres "
"client handler: %s",
isc_result_totext(result));
process_request(client);
}
/*
* This function will start a new recv() on a socket for this client manager.
*/
isc_result_t
ns_lwdclient_startrecv(ns_lwdclientmgr_t *cm) {
ns_lwdclient_t *client;
isc_result_t result;
isc_region_t r;
isc_boolean_t destroy = ISC_FALSE;
LOCK(&cm->lock);
if (SHUTTINGDOWN(cm)) {
destroy = ISC_TRUE;
result = ISC_R_SUCCESS;
goto unlock;
}
/*
* If a recv is already running, don't bother.
*/
if ((cm->flags & NS_LWDCLIENTMGR_FLAGRECVPENDING) != 0) {
result = ISC_R_SUCCESS;
goto unlock;
}
/*
* If we have no idle slots, just return success.
*/
client = ISC_LIST_HEAD(cm->idle);
if (client == NULL) {
result = ISC_R_SUCCESS;
goto unlock;
}
INSIST(NS_LWDCLIENT_ISIDLE(client));
/*
* Set the flag to say there is a recv pending. If isc_socket_recv
* fails we will clear the flag otherwise it will be cleared by
* ns_lwdclient_recv.
*/
cm->flags |= NS_LWDCLIENTMGR_FLAGRECVPENDING;
/*
* Issue the recv. If it fails, return that it did.
*/
r.base = client->buffer;
r.length = LWRES_RECVLENGTH;
result = isc_socket_recv(cm->sock, &r, 0, cm->task, ns_lwdclient_recv,
client);
if (result != ISC_R_SUCCESS) {
cm->flags &= ~NS_LWDCLIENTMGR_FLAGRECVPENDING;
goto unlock;
}
/*
* Remove the client from the idle list, and put it on the running
* list.
*/
NS_LWDCLIENT_SETRECV(client);
ISC_LIST_UNLINK(cm->idle, client, link);
ISC_LIST_APPEND(cm->running, client, link);
unlock:
UNLOCK(&cm->lock);
if (destroy)
lwdclientmgr_destroy(cm);
return (result);
}
static void
lwdclientmgr_shutdown_callback(isc_task_t *task, isc_event_t *ev) {
ns_lwdclientmgr_t *cm = ev->ev_arg;
ns_lwdclient_t *client;
REQUIRE(!SHUTTINGDOWN(cm));
ns_lwdclient_log(50, "got shutdown event, task %p, lwdclientmgr %p",
task, cm);
/*
* run through the idle list and free the clients there. Idle
* clients do not have a recv running nor do they have any finds
* or similar running.
*/
LOCK(&cm->lock);
client = ISC_LIST_HEAD(cm->idle);
while (client != NULL) {
ns_lwdclient_log(50, "destroying client %p, manager %p",
client, cm);
ISC_LIST_UNLINK(cm->idle, client, link);
isc_mem_put(cm->mctx, client, sizeof(*client));
client = ISC_LIST_HEAD(cm->idle);
}
UNLOCK(&cm->lock);
/*
* Cancel any pending I/O.
*/
isc_socket_cancel(cm->sock, task, ISC_SOCKCANCEL_ALL);
/*
* Run through the running client list and kill off any finds
* in progress.
*/
LOCK(&cm->lock);
client = ISC_LIST_HEAD(cm->running);
while (client != NULL) {
if (client->find != client->v4find
&& client->find != client->v6find)
dns_adb_cancelfind(client->find);
if (client->v4find != NULL)
dns_adb_cancelfind(client->v4find);
if (client->v6find != NULL)
dns_adb_cancelfind(client->v6find);
client = ISC_LIST_NEXT(client, link);
}
cm->flags |= NS_LWDCLIENTMGR_FLAGSHUTTINGDOWN;
UNLOCK(&cm->lock);
isc_event_free(&ev);
}
/*
* Do all the crap needed to move a client from the run queue to the idle
* queue.
*/
void
ns_lwdclient_stateidle(ns_lwdclient_t *client) {
ns_lwdclientmgr_t *cm;
isc_result_t result;
cm = client->clientmgr;
INSIST(client->sendbuf == NULL);
INSIST(client->sendlength == 0);
INSIST(client->arg == NULL);
INSIST(client->v4find == NULL);
INSIST(client->v6find == NULL);
LOCK(&cm->lock);
ISC_LIST_UNLINK(cm->running, client, link);
ISC_LIST_PREPEND(cm->idle, client, link);
UNLOCK(&cm->lock);
NS_LWDCLIENT_SETIDLE(client);
result = ns_lwdclient_startrecv(cm);
if (result != ISC_R_SUCCESS)
isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
NS_LOGMODULE_LWRESD, ISC_LOG_ERROR,
"could not start lwres "
"client handler: %s",
isc_result_totext(result));
}
void
ns_lwdclient_send(isc_task_t *task, isc_event_t *ev) {
ns_lwdclient_t *client = ev->ev_arg;
ns_lwdclientmgr_t *cm = client->clientmgr;
isc_socketevent_t *dev = (isc_socketevent_t *)ev;
UNUSED(task);
UNUSED(dev);
INSIST(NS_LWDCLIENT_ISSEND(client));
INSIST(client->sendbuf == dev->region.base);
ns_lwdclient_log(50, "task %p for client %p got send-done event",
task, client);
if (client->sendbuf != client->buffer)
lwres_context_freemem(cm->lwctx, client->sendbuf,
client->sendlength);
client->sendbuf = NULL;
client->sendlength = 0;
ns_lwdclient_stateidle(client);
isc_event_free(&ev);
}
isc_result_t
ns_lwdclient_sendreply(ns_lwdclient_t *client, isc_region_t *r) {
struct in6_pktinfo *pktinfo;
ns_lwdclientmgr_t *cm = client->clientmgr;
if (client->pktinfo_valid)
pktinfo = &client->pktinfo;
else
pktinfo = NULL;
return (isc_socket_sendto(cm->sock, r, cm->task, ns_lwdclient_send,
client, &client->address, pktinfo));
}
void
ns_lwdclient_initialize(ns_lwdclient_t *client, ns_lwdclientmgr_t *cmgr) {
client->clientmgr = cmgr;
ISC_LINK_INIT(client, link);
NS_LWDCLIENT_SETIDLE(client);
client->arg = NULL;
client->recvlength = 0;
client->sendbuf = NULL;
client->sendlength = 0;
client->find = NULL;
client->v4find = NULL;
client->v6find = NULL;
client->find_wanted = 0;
client->options = 0;
client->byaddr = NULL;
client->lookup = NULL;
client->pktinfo_valid = ISC_FALSE;
LOCK(&cmgr->lock);
ISC_LIST_APPEND(cmgr->idle, client, link);
UNLOCK(&cmgr->lock);
}