1/*	$NetBSD: dispatch.c,v 1.5 2023/07/27 10:32:25 tnn Exp $	*/
2
3/* dispatch.c
4
5   Network input dispatcher... */
6
7/*
8 * Copyright (C) 2004-2022 Internet Systems Consortium, Inc. ("ISC")
9 * Copyright (c) 1995-2003 by Internet Software Consortium
10 *
11 * This Source Code Form is subject to the terms of the Mozilla Public
12 * License, v. 2.0. If a copy of the MPL was not distributed with this
13 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
21 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 *
23 *   Internet Systems Consortium, Inc.
24 *   PO Box 360
25 *   Newmarket, NH 03857 USA
26 *   <info@isc.org>
27 *   https://www.isc.org/
28 *
29 */
30
31#include <sys/cdefs.h>
32__RCSID("$NetBSD: dispatch.c,v 1.5 2023/07/27 10:32:25 tnn Exp $");
33
34#include "dhcpd.h"
35
36#include <sys/time.h>
37
38struct timeout *timeouts;
39static struct timeout *free_timeouts;
40
41libdhcp_callbacks_t libdhcp_callbacks;
42
43void set_time(TIME t)
44{
45	/* Do any outstanding timeouts. */
46	if (cur_tv . tv_sec != t) {
47		cur_tv . tv_sec = t;
48		cur_tv . tv_usec = 0;
49		process_outstanding_timeouts ((struct timeval *)0);
50	}
51}
52
53struct timeval *process_outstanding_timeouts (struct timeval *tvp)
54{
55	/* Call any expired timeouts, and then if there's
56	   still a timeout registered, time out the select
57	   call then. */
58      another:
59	if (timeouts) {
60		struct timeout *t;
61		if ((timeouts -> when . tv_sec < cur_tv . tv_sec) ||
62		    ((timeouts -> when . tv_sec == cur_tv . tv_sec) &&
63		     (timeouts -> when . tv_usec <= cur_tv . tv_usec))) {
64			t = timeouts;
65			timeouts = timeouts -> next;
66			(*(t -> func)) (t -> what);
67			if (t -> unref)
68				(*t -> unref) (&t -> what, MDL);
69			t -> next = free_timeouts;
70			free_timeouts = t;
71			goto another;
72		}
73		if (tvp) {
74			tvp -> tv_sec = timeouts -> when . tv_sec;
75			tvp -> tv_usec = timeouts -> when . tv_usec;
76		}
77		return tvp;
78	} else
79		return (struct timeval *)0;
80}
81
82/* Wait for packets to come in using select().   When one does, call
83   receive_packet to receive the packet and possibly strip hardware
84   addressing information from it, and then call through the
85   bootp_packet_handler hook to try to do something with it. */
86
87/*
88 * Use the DHCP timeout list as a place to store DHCP specific
89 * information, but use the ISC timer system to actually dispatch
90 * the events.
91 *
92 * There are several things that the DHCP timer code does that the
93 * ISC code doesn't:
94 * 1) It allows for negative times
95 * 2) The cancel arguments are different.  The DHCP code uses the
96 * function and data to find the proper timer to cancel while the
97 * ISC code uses a pointer to the timer.
98 * 3) The DHCP code includes provision for incrementing and decrementing
99 * a reference counter associated with the data.
100 * The first one is fairly easy to fix but will take some time to go throuh
101 * the callers and update them.  The second is also not all that difficult
102 * in concept - add a pointer to the appropriate structures to hold a pointer
103 * to the timer and use that.  The complications arise in trying to ensure
104 * that all of the corner cases are covered.  The last one is potentially
105 * more painful and requires more investigation.
106 *
107 * The plan is continue with the older DHCP calls and timer list.  The
108 * calls will continue to manipulate the list but will also pass a
109 * timer to the ISC timer code for the actual dispatch.  Later, if desired,
110 * we can go back and modify the underlying calls to use the ISC
111 * timer functions directly without requiring all of the code to change
112 * at the same time.
113 */
114
115void
116dispatch(void)
117{
118	isc_result_t status;
119
120	do {
121		status = isc_app_ctxrun(dhcp_gbl_ctx.actx);
122
123		/*
124		 * isc_app_ctxrun can be stopped by receiving a
125		 * signal. It will return ISC_R_RELOAD in that
126		 * case. That is a normal behavior.
127		 */
128
129		if (status == ISC_R_RELOAD) {
130			/*
131			 * dhcp_set_control_state() will do the job.
132			 * Note its first argument is ignored.
133			 */
134			status = libdhcp_callbacks.dhcp_set_control_state
135					(server_shutdown, server_shutdown);
136			if (status == ISC_R_SUCCESS)
137				status = ISC_R_RELOAD;
138		}
139	} while (status == ISC_R_RELOAD);
140
141	log_fatal ("Dispatch routine failed: %s -- exiting",
142		   isc_result_totext (status));
143}
144
145static void
146isclib_timer_callback(isc_task_t  *taskp,
147		      isc_event_t *eventp)
148{
149	struct timeout *t = (struct timeout *)eventp->ev_arg;
150	struct timeout *q, *r;
151
152	/* Get the current time... */
153	gettimeofday (&cur_tv, (struct timezone *)0);
154
155	/*
156	 * Find the timeout on the dhcp list and remove it.
157	 * As the list isn't ordered we search the entire list
158	 */
159
160	r = NULL;
161	for (q = timeouts; q; q = q->next) {
162		if (q == t) {
163			if (r)
164				r->next = q->next;
165			else
166				timeouts = q->next;
167			break;
168		}
169		r = q;
170	}
171
172	/*
173	 * The timer should always be on the list.  If it is we do
174	 * the work and detach the timer block, if not we log an error.
175	 * In both cases we attempt free the ISC event and continue
176	 * processing.
177	 */
178
179	if (q != NULL) {
180		/* call the callback function */
181		(*(q->func)) (q->what);
182		if (q->unref) {
183			(*q->unref) (&q->what, MDL);
184		}
185		q->next = free_timeouts;
186		isc_event_free(&eventp);
187		isc_timer_destroy(&q->isc_timeout);
188		free_timeouts = q;
189	} else {
190		/*
191		 * Hmm, we should clean up the timer structure but aren't
192		 * sure about the pointer to the timer block we got so
193		 * don't try to - may change this to a log_fatal
194		 */
195		log_error("Error finding timer structure");
196		isc_event_free(&eventp);
197	}
198
199	return;
200}
201
202/* maximum value for usec */
203#define USEC_MAX 1000000
204
205void add_timeout (when, where, what, ref, unref)
206	struct timeval *when;
207	void (*where) (void *);
208	void *what;
209	tvref_t ref;
210	tvunref_t unref;
211{
212	struct timeout *t, *q;
213	int usereset = 0;
214	isc_result_t status;
215	int64_t sec;
216	int usec;
217	isc_interval_t interval;
218	isc_time_t expires;
219
220	/* See if this timeout supersedes an existing timeout. */
221	t = (struct timeout *)0;
222	for (q = timeouts; q; q = q->next) {
223		if ((where == NULL || q->func == where) &&
224		    q->what == what) {
225			if (t)
226				t->next = q->next;
227			else
228				timeouts = q->next;
229			usereset = 1;
230			break;
231		}
232		t = q;
233	}
234
235	/* If we didn't supersede a timeout, allocate a timeout
236	   structure now. */
237	if (!q) {
238		if (free_timeouts) {
239			q = free_timeouts;
240			free_timeouts = q->next;
241		} else {
242			q = ((struct timeout *)
243			     dmalloc(sizeof(struct timeout), MDL));
244			if (!q) {
245				log_fatal("add_timeout: no memory!");
246			}
247		}
248		memset(q, 0, sizeof *q);
249		q->func = where;
250		q->ref = ref;
251		q->unref = unref;
252		if (q->ref)
253			(*q->ref)(&q->what, what, MDL);
254		else
255			q->what = what;
256	}
257
258	/*
259	 * The value passed in is a time from an epoch but we need a relative
260	 * time so we need to do some math to try and recover the period.
261	 * This is complicated by the fact that not all of the calls cared
262	 * about the usec value, if it's zero we assume the caller didn't care.
263	 *
264	 * The ISC timer library doesn't seem to like negative values
265	 * and on 64-bit systems, isc_time_nowplusinterval() can generate range
266	 * errors on values sufficiently larger than 0x7FFFFFFF (TIME_MAX), so
267	 * we'll limit the interval to:
268	 *
269	 * 	0 <= interval <= TIME_MAX - 1
270	 *
271	 * We do it before checking the trace option so that both the trace
272	 * code and * the working code use the same values.
273	 */
274
275	sec  = when->tv_sec - cur_tv.tv_sec;
276	usec = when->tv_usec - cur_tv.tv_usec;
277
278	if ((when->tv_usec != 0) && (usec < 0)) {
279		sec--;
280		usec += USEC_MAX;
281	}
282
283	if (sec < 0) {
284		sec  = 0;
285		usec = 0;
286	} else if (sec >= TIME_MAX) {
287		log_error("Timeout too large "
288			  "reducing to: %lu (TIME_MAX - 1)",
289			  (unsigned long)(TIME_MAX - 1));
290		sec = TIME_MAX - 1;
291		usec = 0;
292	} else if (usec < 0) {
293		usec = 0;
294	} else if (usec >= USEC_MAX) {
295		usec = USEC_MAX - 1;
296	}
297
298	/*
299	 * This is necessary for the tracing code but we put it
300	 * here in case we want to compare timing information
301	 * for some reason, like debugging.
302	 */
303	q->when.tv_sec  = cur_tv.tv_sec + sec;
304	q->when.tv_usec = usec;
305
306#if defined (TRACING)
307	if (trace_playback()) {
308		/*
309		 * If we are doing playback we need to handle the timers
310		 * within this code rather than having the isclib handle
311		 * them for us.  We need to keep the timer list in order
312		 * to allow us to find the ones to timeout.
313		 *
314		 * By using a different timer setup in the playback we may
315		 * have variations between the orginal and the playback but
316		 * it's the best we can do for now.
317		 */
318
319		/* Beginning of list? */
320		if (!timeouts || (timeouts->when.tv_sec > q-> when.tv_sec) ||
321		    ((timeouts->when.tv_sec == q->when.tv_sec) &&
322		     (timeouts->when.tv_usec > q->when.tv_usec))) {
323			q->next = timeouts;
324			timeouts = q;
325			return;
326		}
327
328		/* Middle of list? */
329		for (t = timeouts; t->next; t = t->next) {
330			if ((t->next->when.tv_sec > q->when.tv_sec) ||
331			    ((t->next->when.tv_sec == q->when.tv_sec) &&
332			     (t->next->when.tv_usec > q->when.tv_usec))) {
333				q->next = t->next;
334				t->next = q;
335				return;
336			}
337		}
338
339		/* End of list. */
340		t->next = q;
341		q->next = (struct timeout *)0;
342		return;
343	}
344#endif
345	/*
346	 * Don't bother sorting the DHCP list, just add it to the front.
347	 * Eventually the list should be removed as we migrate the callers
348	 * to the native ISC timer functions, if it becomes a performance
349	 * problem before then we may need to order the list.
350	 */
351	q->next  = timeouts;
352	timeouts = q;
353
354	isc_interval_set(&interval, sec, usec * 1000);
355	status = isc_time_nowplusinterval(&expires, &interval);
356	if (status != ISC_R_SUCCESS) {
357		/*
358		 * The system time function isn't happy. Range errors
359		 * should not be possible with the check logic above.
360		 */
361		log_fatal("Unable to set up timer: %s",
362			  isc_result_totext(status));
363	}
364
365	if (usereset == 0) {
366		status = isc_timer_create(dhcp_gbl_ctx.timermgr,
367					  isc_timertype_once, &expires,
368					  NULL, dhcp_gbl_ctx.task,
369					  isclib_timer_callback,
370					  (void *)q, &q->isc_timeout);
371	} else {
372		status = isc_timer_reset(q->isc_timeout,
373					 isc_timertype_once, &expires,
374					 NULL, 0);
375	}
376
377	/* If it fails log an error and die */
378	if (status != ISC_R_SUCCESS) {
379		log_fatal("Unable to add timeout to isclib\n");
380	}
381
382	return;
383}
384
385void cancel_timeout (where, what)
386	void (*where) (void *);
387	void *what;
388{
389	struct timeout *t, *q;
390
391	/* Look for this timeout on the list, and unlink it if we find it. */
392	t = (struct timeout *)0;
393	for (q = timeouts; q; q = q -> next) {
394		if (q->func == where && q->what == what) {
395			if (t)
396				t->next = q->next;
397			else
398				timeouts = q->next;
399			break;
400		}
401		t = q;
402	}
403
404	/*
405	 * If we found the timeout, cancel it and put it on the free list.
406	 * The TRACING stuff is ugly but we don't add a timer when doing
407	 * playback so we don't want to remove them then either.
408	 */
409	if (q) {
410#if defined (TRACING)
411		if (!trace_playback()) {
412#endif
413			isc_timer_destroy(&q->isc_timeout);
414#if defined (TRACING)
415		}
416#endif
417
418		if (q->unref)
419			(*q->unref) (&q->what, MDL);
420		q->next = free_timeouts;
421		free_timeouts = q;
422	}
423}
424
425#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
426void cancel_all_timeouts ()
427{
428	struct timeout *t, *n;
429	for (t = timeouts; t; t = n) {
430		n = t->next;
431		isc_timer_destroy(&t->isc_timeout);
432		if (t->unref && t->what)
433			(*t->unref) (&t->what, MDL);
434		t->next = free_timeouts;
435		free_timeouts = t;
436	}
437}
438
439void relinquish_timeouts ()
440{
441	struct timeout *t, *n;
442	for (t = free_timeouts; t; t = n) {
443		n = t->next;
444		dfree(t, MDL);
445	}
446}
447#endif
448
449void libdhcp_callbacks_register(cb)
450	libdhcp_callbacks_t *cb;
451{
452	memcpy(&libdhcp_callbacks, cb, sizeof(libdhcp_callbacks));
453	return;
454}
455