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