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