lwdclient.c revision 222395
1/*
2 * Copyright (C) 2004, 2005, 2007  Internet Systems Consortium, Inc. ("ISC")
3 * Copyright (C) 2000, 2001  Internet Software Consortium.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
10 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
12 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 * PERFORMANCE OF THIS SOFTWARE.
16 */
17
18/* $Id: lwdclient.c,v 1.22 2007-06-18 23:47:18 tbox Exp $ */
19
20/*! \file */
21
22#include <config.h>
23
24#include <isc/socket.h>
25#include <isc/string.h>
26#include <isc/task.h>
27#include <isc/util.h>
28
29#include <dns/adb.h>
30#include <dns/view.h>
31#include <dns/log.h>
32
33#include <named/types.h>
34#include <named/log.h>
35#include <named/lwresd.h>
36#include <named/lwdclient.h>
37
38#define SHUTTINGDOWN(cm) ((cm->flags & NS_LWDCLIENTMGR_FLAGSHUTTINGDOWN) != 0)
39
40static void
41lwdclientmgr_shutdown_callback(isc_task_t *task, isc_event_t *ev);
42
43void
44ns_lwdclient_log(int level, const char *format, ...) {
45	va_list args;
46
47	va_start(args, format);
48	isc_log_vwrite(dns_lctx,
49		       DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_ADB,
50		       ISC_LOG_DEBUG(level), format, args);
51	va_end(args);
52}
53
54isc_result_t
55ns_lwdclientmgr_create(ns_lwreslistener_t *listener, unsigned int nclients,
56		    isc_taskmgr_t *taskmgr)
57{
58	ns_lwresd_t *lwresd = listener->manager;
59	ns_lwdclientmgr_t *cm;
60	ns_lwdclient_t *client;
61	unsigned int i;
62	isc_result_t result = ISC_R_FAILURE;
63
64	cm = isc_mem_get(lwresd->mctx, sizeof(ns_lwdclientmgr_t));
65	if (cm == NULL)
66		return (ISC_R_NOMEMORY);
67
68	cm->listener = NULL;
69	ns_lwreslistener_attach(listener, &cm->listener);
70	cm->mctx = lwresd->mctx;
71	cm->sock = NULL;
72	isc_socket_attach(listener->sock, &cm->sock);
73	cm->view = lwresd->view;
74	cm->lwctx = NULL;
75	cm->task = NULL;
76	cm->flags = 0;
77	ISC_LINK_INIT(cm, link);
78	ISC_LIST_INIT(cm->idle);
79	ISC_LIST_INIT(cm->running);
80
81	if (lwres_context_create(&cm->lwctx, cm->mctx,
82				 ns__lwresd_memalloc, ns__lwresd_memfree,
83				 LWRES_CONTEXT_SERVERMODE)
84	    != ISC_R_SUCCESS)
85		goto errout;
86
87	for (i = 0; i < nclients; i++) {
88		client = isc_mem_get(lwresd->mctx, sizeof(ns_lwdclient_t));
89		if (client != NULL) {
90			ns_lwdclient_log(50, "created client %p, manager %p",
91					 client, cm);
92			ns_lwdclient_initialize(client, cm);
93		}
94	}
95
96	/*
97	 * If we could create no clients, clean up and return.
98	 */
99	if (ISC_LIST_EMPTY(cm->idle))
100		goto errout;
101
102	result = isc_task_create(taskmgr, 0, &cm->task);
103	if (result != ISC_R_SUCCESS)
104		goto errout;
105	isc_task_setname(cm->task, "lwdclient", NULL);
106
107	/*
108	 * This MUST be last, since there is no way to cancel an onshutdown...
109	 */
110	result = isc_task_onshutdown(cm->task, lwdclientmgr_shutdown_callback,
111				     cm);
112	if (result != ISC_R_SUCCESS)
113		goto errout;
114
115	ns_lwreslistener_linkcm(listener, cm);
116
117	return (ISC_R_SUCCESS);
118
119 errout:
120	client = ISC_LIST_HEAD(cm->idle);
121	while (client != NULL) {
122		ISC_LIST_UNLINK(cm->idle, client, link);
123		isc_mem_put(lwresd->mctx, client, sizeof(*client));
124		client = ISC_LIST_HEAD(cm->idle);
125	}
126
127	if (cm->task != NULL)
128		isc_task_detach(&cm->task);
129
130	if (cm->lwctx != NULL)
131		lwres_context_destroy(&cm->lwctx);
132
133	isc_mem_put(lwresd->mctx, cm, sizeof(*cm));
134	return (result);
135}
136
137static void
138lwdclientmgr_destroy(ns_lwdclientmgr_t *cm) {
139	ns_lwdclient_t *client;
140	ns_lwreslistener_t *listener;
141
142	if (!SHUTTINGDOWN(cm))
143		return;
144
145	/*
146	 * run through the idle list and free the clients there.  Idle
147	 * clients do not have a recv running nor do they have any finds
148	 * or similar running.
149	 */
150	client = ISC_LIST_HEAD(cm->idle);
151	while (client != NULL) {
152		ns_lwdclient_log(50, "destroying client %p, manager %p",
153				 client, cm);
154		ISC_LIST_UNLINK(cm->idle, client, link);
155		isc_mem_put(cm->mctx, client, sizeof(*client));
156		client = ISC_LIST_HEAD(cm->idle);
157	}
158
159	if (!ISC_LIST_EMPTY(cm->running))
160		return;
161
162	lwres_context_destroy(&cm->lwctx);
163	cm->view = NULL;
164	isc_socket_detach(&cm->sock);
165	isc_task_detach(&cm->task);
166
167	listener = cm->listener;
168	ns_lwreslistener_unlinkcm(listener, cm);
169	ns_lwdclient_log(50, "destroying manager %p", cm);
170	isc_mem_put(cm->mctx, cm, sizeof(*cm));
171	ns_lwreslistener_detach(&listener);
172}
173
174static void
175process_request(ns_lwdclient_t *client) {
176	lwres_buffer_t b;
177	isc_result_t result;
178
179	lwres_buffer_init(&b, client->buffer, client->recvlength);
180	lwres_buffer_add(&b, client->recvlength);
181
182	result = lwres_lwpacket_parseheader(&b, &client->pkt);
183	if (result != ISC_R_SUCCESS) {
184		ns_lwdclient_log(50, "invalid packet header received");
185		goto restart;
186	}
187
188	ns_lwdclient_log(50, "opcode %08x", client->pkt.opcode);
189
190	switch (client->pkt.opcode) {
191	case LWRES_OPCODE_GETADDRSBYNAME:
192		ns_lwdclient_processgabn(client, &b);
193		return;
194	case LWRES_OPCODE_GETNAMEBYADDR:
195		ns_lwdclient_processgnba(client, &b);
196		return;
197	case LWRES_OPCODE_GETRDATABYNAME:
198		ns_lwdclient_processgrbn(client, &b);
199		return;
200	case LWRES_OPCODE_NOOP:
201		ns_lwdclient_processnoop(client, &b);
202		return;
203	default:
204		ns_lwdclient_log(50, "unknown opcode %08x", client->pkt.opcode);
205		goto restart;
206	}
207
208	/*
209	 * Drop the packet.
210	 */
211 restart:
212	ns_lwdclient_log(50, "restarting client %p...", client);
213	ns_lwdclient_stateidle(client);
214}
215
216void
217ns_lwdclient_recv(isc_task_t *task, isc_event_t *ev) {
218	isc_result_t result;
219	ns_lwdclient_t *client = ev->ev_arg;
220	ns_lwdclientmgr_t *cm = client->clientmgr;
221	isc_socketevent_t *dev = (isc_socketevent_t *)ev;
222
223	INSIST(dev->region.base == client->buffer);
224	INSIST(NS_LWDCLIENT_ISRECV(client));
225
226	NS_LWDCLIENT_SETRECVDONE(client);
227
228	INSIST((cm->flags & NS_LWDCLIENTMGR_FLAGRECVPENDING) != 0);
229	cm->flags &= ~NS_LWDCLIENTMGR_FLAGRECVPENDING;
230
231	ns_lwdclient_log(50,
232			 "event received: task %p, length %u, result %u (%s)",
233			 task, dev->n, dev->result,
234			 isc_result_totext(dev->result));
235
236	if (dev->result != ISC_R_SUCCESS) {
237		isc_event_free(&ev);
238		dev = NULL;
239
240		/*
241		 * Go idle.
242		 */
243		ns_lwdclient_stateidle(client);
244
245		return;
246	}
247
248	client->recvlength = dev->n;
249	client->address = dev->address;
250	if ((dev->attributes & ISC_SOCKEVENTATTR_PKTINFO) != 0) {
251		client->pktinfo = dev->pktinfo;
252		client->pktinfo_valid = ISC_TRUE;
253	} else
254		client->pktinfo_valid = ISC_FALSE;
255	isc_event_free(&ev);
256	dev = NULL;
257
258	result = ns_lwdclient_startrecv(cm);
259	if (result != ISC_R_SUCCESS)
260		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
261			      NS_LOGMODULE_LWRESD, ISC_LOG_ERROR,
262			      "could not start lwres "
263			      "client handler: %s",
264			      isc_result_totext(result));
265
266	process_request(client);
267}
268
269/*
270 * This function will start a new recv() on a socket for this client manager.
271 */
272isc_result_t
273ns_lwdclient_startrecv(ns_lwdclientmgr_t *cm) {
274	ns_lwdclient_t *client;
275	isc_result_t result;
276	isc_region_t r;
277
278	if (SHUTTINGDOWN(cm)) {
279		lwdclientmgr_destroy(cm);
280		return (ISC_R_SUCCESS);
281	}
282
283	/*
284	 * If a recv is already running, don't bother.
285	 */
286	if ((cm->flags & NS_LWDCLIENTMGR_FLAGRECVPENDING) != 0)
287		return (ISC_R_SUCCESS);
288
289	/*
290	 * If we have no idle slots, just return success.
291	 */
292	client = ISC_LIST_HEAD(cm->idle);
293	if (client == NULL)
294		return (ISC_R_SUCCESS);
295	INSIST(NS_LWDCLIENT_ISIDLE(client));
296
297	/*
298	 * Issue the recv.  If it fails, return that it did.
299	 */
300	r.base = client->buffer;
301	r.length = LWRES_RECVLENGTH;
302	result = isc_socket_recv(cm->sock, &r, 0, cm->task, ns_lwdclient_recv,
303				 client);
304	if (result != ISC_R_SUCCESS)
305		return (result);
306
307	/*
308	 * Set the flag to say we've issued a recv() call.
309	 */
310	cm->flags |= NS_LWDCLIENTMGR_FLAGRECVPENDING;
311
312	/*
313	 * Remove the client from the idle list, and put it on the running
314	 * list.
315	 */
316	NS_LWDCLIENT_SETRECV(client);
317	ISC_LIST_UNLINK(cm->idle, client, link);
318	ISC_LIST_APPEND(cm->running, client, link);
319
320	return (ISC_R_SUCCESS);
321}
322
323static void
324lwdclientmgr_shutdown_callback(isc_task_t *task, isc_event_t *ev) {
325	ns_lwdclientmgr_t *cm = ev->ev_arg;
326	ns_lwdclient_t *client;
327
328	REQUIRE(!SHUTTINGDOWN(cm));
329
330	ns_lwdclient_log(50, "got shutdown event, task %p, lwdclientmgr %p",
331			 task, cm);
332
333	/*
334	 * run through the idle list and free the clients there.  Idle
335	 * clients do not have a recv running nor do they have any finds
336	 * or similar running.
337	 */
338	client = ISC_LIST_HEAD(cm->idle);
339	while (client != NULL) {
340		ns_lwdclient_log(50, "destroying client %p, manager %p",
341				 client, cm);
342		ISC_LIST_UNLINK(cm->idle, client, link);
343		isc_mem_put(cm->mctx, client, sizeof(*client));
344		client = ISC_LIST_HEAD(cm->idle);
345	}
346
347	/*
348	 * Cancel any pending I/O.
349	 */
350	isc_socket_cancel(cm->sock, task, ISC_SOCKCANCEL_ALL);
351
352	/*
353	 * Run through the running client list and kill off any finds
354	 * in progress.
355	 */
356	client = ISC_LIST_HEAD(cm->running);
357	while (client != NULL) {
358		if (client->find != client->v4find
359		    && client->find != client->v6find)
360			dns_adb_cancelfind(client->find);
361		if (client->v4find != NULL)
362			dns_adb_cancelfind(client->v4find);
363		if (client->v6find != NULL)
364			dns_adb_cancelfind(client->v6find);
365		client = ISC_LIST_NEXT(client, link);
366	}
367
368	cm->flags |= NS_LWDCLIENTMGR_FLAGSHUTTINGDOWN;
369
370	isc_event_free(&ev);
371}
372
373/*
374 * Do all the crap needed to move a client from the run queue to the idle
375 * queue.
376 */
377void
378ns_lwdclient_stateidle(ns_lwdclient_t *client) {
379	ns_lwdclientmgr_t *cm;
380	isc_result_t result;
381
382	cm = client->clientmgr;
383
384	INSIST(client->sendbuf == NULL);
385	INSIST(client->sendlength == 0);
386	INSIST(client->arg == NULL);
387	INSIST(client->v4find == NULL);
388	INSIST(client->v6find == NULL);
389
390	ISC_LIST_UNLINK(cm->running, client, link);
391	ISC_LIST_PREPEND(cm->idle, client, link);
392
393	NS_LWDCLIENT_SETIDLE(client);
394
395	result = ns_lwdclient_startrecv(cm);
396	if (result != ISC_R_SUCCESS)
397		isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL,
398			      NS_LOGMODULE_LWRESD, ISC_LOG_ERROR,
399			      "could not start lwres "
400			      "client handler: %s",
401			      isc_result_totext(result));
402}
403
404void
405ns_lwdclient_send(isc_task_t *task, isc_event_t *ev) {
406	ns_lwdclient_t *client = ev->ev_arg;
407	ns_lwdclientmgr_t *cm = client->clientmgr;
408	isc_socketevent_t *dev = (isc_socketevent_t *)ev;
409
410	UNUSED(task);
411	UNUSED(dev);
412
413	INSIST(NS_LWDCLIENT_ISSEND(client));
414	INSIST(client->sendbuf == dev->region.base);
415
416	ns_lwdclient_log(50, "task %p for client %p got send-done event",
417			 task, client);
418
419	if (client->sendbuf != client->buffer)
420		lwres_context_freemem(cm->lwctx, client->sendbuf,
421				      client->sendlength);
422	client->sendbuf = NULL;
423	client->sendlength = 0;
424
425	ns_lwdclient_stateidle(client);
426
427	isc_event_free(&ev);
428}
429
430isc_result_t
431ns_lwdclient_sendreply(ns_lwdclient_t *client, isc_region_t *r) {
432	struct in6_pktinfo *pktinfo;
433	ns_lwdclientmgr_t *cm = client->clientmgr;
434
435	if (client->pktinfo_valid)
436		pktinfo = &client->pktinfo;
437	else
438		pktinfo = NULL;
439	return (isc_socket_sendto(cm->sock, r, cm->task, ns_lwdclient_send,
440				  client, &client->address, pktinfo));
441}
442
443void
444ns_lwdclient_initialize(ns_lwdclient_t *client, ns_lwdclientmgr_t *cmgr) {
445	client->clientmgr = cmgr;
446	ISC_LINK_INIT(client, link);
447	NS_LWDCLIENT_SETIDLE(client);
448	client->arg = NULL;
449
450	client->recvlength = 0;
451
452	client->sendbuf = NULL;
453	client->sendlength = 0;
454
455	client->find = NULL;
456	client->v4find = NULL;
457	client->v6find = NULL;
458	client->find_wanted = 0;
459
460	client->options = 0;
461	client->byaddr = NULL;
462
463	client->lookup = NULL;
464
465	client->pktinfo_valid = ISC_FALSE;
466
467	ISC_LIST_APPEND(cmgr->idle, client, link);
468}
469