xref: /netbsd-src/external/mpl/dhcp/dist/common/dispatch.c (revision 9fb66d812c00ebfb445c0b47dea128f32aa6fe96)
1 /*	$NetBSD: dispatch.c,v 1.2 2018/04/07 22:37:29 christos Exp $	*/
2 
3 /* dispatch.c
4 
5    Network input dispatcher... */
6 
7 /*
8  * Copyright (c) 2004-2017 by 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  *   950 Charter Street
25  *   Redwood City, CA 94063
26  *   <info@isc.org>
27  *   https://www.isc.org/
28  *
29  */
30 
31 #include <sys/cdefs.h>
32 __RCSID("$NetBSD: dispatch.c,v 1.2 2018/04/07 22:37:29 christos 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 
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 
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
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
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_timer_detach(&q->isc_timeout);
187 		free_timeouts = q;
188 	} else {
189 		/*
190 		 * Hmm, we should clean up the timer structure but aren't
191 		 * sure about the pointer to the timer block we got so
192 		 * don't try to - may change this to a log_fatal
193 		 */
194 		log_error("Error finding timer structure");
195 	}
196 
197 	isc_event_free(&eventp);
198 	return;
199 }
200 
201 /* maximum value for usec */
202 #define USEC_MAX 1000000
203 
204 void add_timeout (when, where, what, ref, unref)
205 	struct timeval *when;
206 	void (*where) (void *);
207 	void *what;
208 	tvref_t ref;
209 	tvunref_t unref;
210 {
211 	struct timeout *t, *q;
212 	int usereset = 0;
213 	isc_result_t status;
214 	int64_t sec;
215 	int usec;
216 	isc_interval_t interval;
217 	isc_time_t expires;
218 
219 	/* See if this timeout supersedes an existing timeout. */
220 	t = (struct timeout *)0;
221 	for (q = timeouts; q; q = q->next) {
222 		if ((where == NULL || q->func == where) &&
223 		    q->what == what) {
224 			if (t)
225 				t->next = q->next;
226 			else
227 				timeouts = q->next;
228 			usereset = 1;
229 			break;
230 		}
231 		t = q;
232 	}
233 
234 	/* If we didn't supersede a timeout, allocate a timeout
235 	   structure now. */
236 	if (!q) {
237 		if (free_timeouts) {
238 			q = free_timeouts;
239 			free_timeouts = q->next;
240 		} else {
241 			q = ((struct timeout *)
242 			     dmalloc(sizeof(struct timeout), MDL));
243 			if (!q) {
244 				log_fatal("add_timeout: no memory!");
245 			}
246 		}
247 		memset(q, 0, sizeof *q);
248 		q->func = where;
249 		q->ref = ref;
250 		q->unref = unref;
251 		if (q->ref)
252 			(*q->ref)(&q->what, what, MDL);
253 		else
254 			q->what = what;
255 	}
256 
257 	/*
258 	 * The value passed in is a time from an epoch but we need a relative
259 	 * time so we need to do some math to try and recover the period.
260 	 * This is complicated by the fact that not all of the calls cared
261 	 * about the usec value, if it's zero we assume the caller didn't care.
262 	 *
263 	 * The ISC timer library doesn't seem to like negative values
264 	 * and on 64-bit systems, isc_time_nowplusinterval() can generate range
265 	 * errors on values sufficiently larger than 0x7FFFFFFF (TIME_MAX), so
266 	 * we'll limit the interval to:
267 	 *
268 	 * 	0 <= interval <= TIME_MAX - 1
269 	 *
270 	 * We do it before checking the trace option so that both the trace
271 	 * code and * the working code use the same values.
272 	 */
273 
274 	sec  = when->tv_sec - cur_tv.tv_sec;
275 	usec = when->tv_usec - cur_tv.tv_usec;
276 
277 	if ((when->tv_usec != 0) && (usec < 0)) {
278 		sec--;
279 		usec += USEC_MAX;
280 	}
281 
282 	if (sec < 0) {
283 		sec  = 0;
284 		usec = 0;
285 	} else if (sec >= TIME_MAX) {
286 		log_error("Timeout too large "
287 			  "reducing to: %lu (TIME_MAX - 1)",
288 			  (unsigned long)(TIME_MAX - 1));
289 		sec = TIME_MAX - 1;
290 		usec = 0;
291 	} else if (usec < 0) {
292 		usec = 0;
293 	} else if (usec >= USEC_MAX) {
294 		usec = USEC_MAX - 1;
295 	}
296 
297 	/*
298 	 * This is necessary for the tracing code but we put it
299 	 * here in case we want to compare timing information
300 	 * for some reason, like debugging.
301 	 */
302 	q->when.tv_sec  = cur_tv.tv_sec + sec;
303 	q->when.tv_usec = usec;
304 
305 #if defined (TRACING)
306 	if (trace_playback()) {
307 		/*
308 		 * If we are doing playback we need to handle the timers
309 		 * within this code rather than having the isclib handle
310 		 * them for us.  We need to keep the timer list in order
311 		 * to allow us to find the ones to timeout.
312 		 *
313 		 * By using a different timer setup in the playback we may
314 		 * have variations between the orginal and the playback but
315 		 * it's the best we can do for now.
316 		 */
317 
318 		/* Beginning of list? */
319 		if (!timeouts || (timeouts->when.tv_sec > q-> when.tv_sec) ||
320 		    ((timeouts->when.tv_sec == q->when.tv_sec) &&
321 		     (timeouts->when.tv_usec > q->when.tv_usec))) {
322 			q->next = timeouts;
323 			timeouts = q;
324 			return;
325 		}
326 
327 		/* Middle of list? */
328 		for (t = timeouts; t->next; t = t->next) {
329 			if ((t->next->when.tv_sec > q->when.tv_sec) ||
330 			    ((t->next->when.tv_sec == q->when.tv_sec) &&
331 			     (t->next->when.tv_usec > q->when.tv_usec))) {
332 				q->next = t->next;
333 				t->next = q;
334 				return;
335 			}
336 		}
337 
338 		/* End of list. */
339 		t->next = q;
340 		q->next = (struct timeout *)0;
341 		return;
342 	}
343 #endif
344 	/*
345 	 * Don't bother sorting the DHCP list, just add it to the front.
346 	 * Eventually the list should be removed as we migrate the callers
347 	 * to the native ISC timer functions, if it becomes a performance
348 	 * problem before then we may need to order the list.
349 	 */
350 	q->next  = timeouts;
351 	timeouts = q;
352 
353 	isc_interval_set(&interval, sec, usec * 1000);
354 	status = isc_time_nowplusinterval(&expires, &interval);
355 	if (status != ISC_R_SUCCESS) {
356 		/*
357 		 * The system time function isn't happy. Range errors
358 		 * should not be possible with the check logic above.
359 		 */
360 		log_fatal("Unable to set up timer: %s",
361 			  isc_result_totext(status));
362 	}
363 
364 	if (usereset == 0) {
365 		status = isc_timer_create(dhcp_gbl_ctx.timermgr,
366 					  isc_timertype_once, &expires,
367 					  NULL, dhcp_gbl_ctx.task,
368 					  isclib_timer_callback,
369 					  (void *)q, &q->isc_timeout);
370 	} else {
371 		status = isc_timer_reset(q->isc_timeout,
372 					 isc_timertype_once, &expires,
373 					 NULL, 0);
374 	}
375 
376 	/* If it fails log an error and die */
377 	if (status != ISC_R_SUCCESS) {
378 		log_fatal("Unable to add timeout to isclib\n");
379 	}
380 
381 	return;
382 }
383 
384 void cancel_timeout (where, what)
385 	void (*where) (void *);
386 	void *what;
387 {
388 	struct timeout *t, *q;
389 
390 	/* Look for this timeout on the list, and unlink it if we find it. */
391 	t = (struct timeout *)0;
392 	for (q = timeouts; q; q = q -> next) {
393 		if (q->func == where && q->what == what) {
394 			if (t)
395 				t->next = q->next;
396 			else
397 				timeouts = q->next;
398 			break;
399 		}
400 		t = q;
401 	}
402 
403 	/*
404 	 * If we found the timeout, cancel it and put it on the free list.
405 	 * The TRACING stuff is ugly but we don't add a timer when doing
406 	 * playback so we don't want to remove them then either.
407 	 */
408 	if (q) {
409 #if defined (TRACING)
410 		if (!trace_playback()) {
411 #endif
412 			isc_timer_detach(&q->isc_timeout);
413 #if defined (TRACING)
414 		}
415 #endif
416 
417 		if (q->unref)
418 			(*q->unref) (&q->what, MDL);
419 		q->next = free_timeouts;
420 		free_timeouts = q;
421 	}
422 }
423 
424 #if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
425 void cancel_all_timeouts ()
426 {
427 	struct timeout *t, *n;
428 	for (t = timeouts; t; t = n) {
429 		n = t->next;
430 		isc_timer_detach(&t->isc_timeout);
431 		if (t->unref && t->what)
432 			(*t->unref) (&t->what, MDL);
433 		t->next = free_timeouts;
434 		free_timeouts = t;
435 	}
436 }
437 
438 void relinquish_timeouts ()
439 {
440 	struct timeout *t, *n;
441 	for (t = free_timeouts; t; t = n) {
442 		n = t->next;
443 		dfree(t, MDL);
444 	}
445 }
446 #endif
447 
448 void libdhcp_callbacks_register(cb)
449 	libdhcp_callbacks_t *cb;
450 {
451 	memcpy(&libdhcp_callbacks, cb, sizeof(libdhcp_callbacks));
452 	return;
453 }
454