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