Home | History | Annotate | Line # | Download | only in common
      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 (at) 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 
     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_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 
    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)
    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 
    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 
    449 void libdhcp_callbacks_register(cb)
    450 	libdhcp_callbacks_t *cb;
    451 {
    452 	memcpy(&libdhcp_callbacks, cb, sizeof(libdhcp_callbacks));
    453 	return;
    454 }
    455