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