xref: /netbsd/external/mpl/dhcp/dist/common/dispatch.c (revision 2fb1a3e7)
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 
38 struct timeout *timeouts;
39 static struct timeout *free_timeouts;
40 
41 libdhcp_callbacks_t libdhcp_callbacks;
42 
set_time(TIME t)43 void 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 
process_outstanding_timeouts(struct timeval * tvp)53 struct 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 
115 void
dispatch(void)116 dispatch(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 
145 static void
isclib_timer_callback(isc_task_t * taskp,isc_event_t * eventp)146 isclib_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 
add_timeout(when,where,what,ref,unref)205 void 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 
385 void 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)
cancel_all_timeouts()426 void 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 
relinquish_timeouts()439 void 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 
libdhcp_callbacks_register(cb)449 void libdhcp_callbacks_register(cb)
450 	libdhcp_callbacks_t *cb;
451 {
452 	memcpy(&libdhcp_callbacks, cb, sizeof(libdhcp_callbacks));
453 	return;
454 }
455