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