summaryrefslogtreecommitdiff
path: root/common/dispatch.c
blob: 0207ad3cdedf53253a715adadfdca747b2b2382d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
/* dispatch.c

   Network input dispatcher... */

/*
 * Copyright (c) 2004-2017 by Internet Systems Consortium, Inc. ("ISC")
 * Copyright (c) 1995-2003 by Internet Software Consortium
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 *   Internet Systems Consortium, Inc.
 *   950 Charter Street
 *   Redwood City, CA 94063
 *   <info@isc.org>
 *   https://www.isc.org/
 *
 */

#include "dhcpd.h"

#include <sys/time.h>

struct timeout *timeouts;
static struct timeout *free_timeouts;

void set_time(TIME t)
{
	/* Do any outstanding timeouts. */
	if (cur_tv . tv_sec != t) {
		cur_tv . tv_sec = t;
		cur_tv . tv_usec = 0;
		process_outstanding_timeouts ((struct timeval *)0);
	}
}

struct timeval *process_outstanding_timeouts (struct timeval *tvp)
{
	/* Call any expired timeouts, and then if there's
	   still a timeout registered, time out the select
	   call then. */
      another:
	if (timeouts) {
		struct timeout *t;
		if ((timeouts -> when . tv_sec < cur_tv . tv_sec) ||
		    ((timeouts -> when . tv_sec == cur_tv . tv_sec) &&
		     (timeouts -> when . tv_usec <= cur_tv . tv_usec))) {
			t = timeouts;
			timeouts = timeouts -> next;
			(*(t -> func)) (t -> what);
			if (t -> unref)
				(*t -> unref) (&t -> what, MDL);
			t -> next = free_timeouts;
			free_timeouts = t;
			goto another;
		}
		if (tvp) {
			tvp -> tv_sec = timeouts -> when . tv_sec;
			tvp -> tv_usec = timeouts -> when . tv_usec;
		}
		return tvp;
	} else
		return (struct timeval *)0;
}

/* Wait for packets to come in using select().   When one does, call
   receive_packet to receive the packet and possibly strip hardware
   addressing information from it, and then call through the
   bootp_packet_handler hook to try to do something with it. */

/*
 * Use the DHCP timeout list as a place to store DHCP specific
 * information, but use the ISC timer system to actually dispatch
 * the events.
 *
 * There are several things that the DHCP timer code does that the
 * ISC code doesn't:
 * 1) It allows for negative times
 * 2) The cancel arguments are different.  The DHCP code uses the
 * function and data to find the proper timer to cancel while the
 * ISC code uses a pointer to the timer.
 * 3) The DHCP code includes provision for incrementing and decrementing
 * a reference counter associated with the data.
 * The first one is fairly easy to fix but will take some time to go throuh
 * the callers and update them.  The second is also not all that difficult
 * in concept - add a pointer to the appropriate structures to hold a pointer
 * to the timer and use that.  The complications arise in trying to ensure
 * that all of the corner cases are covered.  The last one is potentially
 * more painful and requires more investigation.
 * 
 * The plan is continue with the older DHCP calls and timer list.  The
 * calls will continue to manipulate the list but will also pass a
 * timer to the ISC timer code for the actual dispatch.  Later, if desired,
 * we can go back and modify the underlying calls to use the ISC
 * timer functions directly without requiring all of the code to change
 * at the same time.
 */

void
dispatch(void)
{
	isc_result_t status;

	do {
		status = isc_app_ctxrun(dhcp_gbl_ctx.actx);

		/*
		 * isc_app_ctxrun can be stopped by receiving a
		 * signal. It will return ISC_R_RELOAD in that
		 * case. That is a normal behavior.
		 */

		if (status == ISC_R_RELOAD) {
			/*
			 * dhcp_set_control_state() will do the job.
			 * Note its first argument is ignored.
			 */
			status = dhcp_set_control_state(server_shutdown,
							server_shutdown);
			if (status == ISC_R_SUCCESS)
				status = ISC_R_RELOAD;
		}
	} while (status == ISC_R_RELOAD);

	log_fatal ("Dispatch routine failed: %s -- exiting",
		   isc_result_totext (status));
}

void
isclib_timer_callback(isc_task_t  *taskp,
		      isc_event_t *eventp)
{
	struct timeout *t = (struct timeout *)eventp->ev_arg;
	struct timeout *q, *r;

	/* Get the current time... */
	gettimeofday (&cur_tv, (struct timezone *)0);

	/*
	 * Find the timeout on the dhcp list and remove it.
	 * As the list isn't ordered we search the entire list
	 */

	r = NULL;
	for (q = timeouts; q; q = q->next) {
		if (q == t) {
			if (r)
				r->next = q->next;
			else
				timeouts = q->next;
			break;
		}
		r = q;
	}

	/*
	 * The timer should always be on the list.  If it is we do
	 * the work and detach the timer block, if not we log an error.
	 * In both cases we attempt free the ISC event and continue
	 * processing.
	 */

	if (q != NULL) {
		/* call the callback function */
		(*(q->func)) (q->what);
		if (q->unref) {
			(*q->unref) (&q->what, MDL);
		}
		q->next = free_timeouts;
		isc_timer_detach(&q->isc_timeout);
		free_timeouts = q;
	} else {
		/*
		 * Hmm, we should clean up the timer structure but aren't
		 * sure about the pointer to the timer block we got so
		 * don't try to - may change this to a log_fatal
		 */
		log_error("Error finding timer structure");
	}

	isc_event_free(&eventp);
	return;
}

/* maximum value for usec */
#define USEC_MAX 1000000

void add_timeout (when, where, what, ref, unref)
	struct timeval *when;
	void (*where) (void *);
	void *what;
	tvref_t ref;
	tvunref_t unref;
{
	struct timeout *t, *q;
	int usereset = 0;
	isc_result_t status;
	int64_t sec;
	int usec;
	isc_interval_t interval;
	isc_time_t expires;

	/* See if this timeout supersedes an existing timeout. */
	t = (struct timeout *)0;
	for (q = timeouts; q; q = q->next) {
		if ((where == NULL || q->func == where) &&
		    q->what == what) {
			if (t)
				t->next = q->next;
			else
				timeouts = q->next;
			usereset = 1;
			break;
		}
		t = q;
	}

	/* If we didn't supersede a timeout, allocate a timeout
	   structure now. */
	if (!q) {
		if (free_timeouts) {
			q = free_timeouts;
			free_timeouts = q->next;
		} else {
			q = ((struct timeout *)
			     dmalloc(sizeof(struct timeout), MDL));
			if (!q) {
				log_fatal("add_timeout: no memory!");
			}
		}
		memset(q, 0, sizeof *q);
		q->func = where;
		q->ref = ref;
		q->unref = unref;
		if (q->ref)
			(*q->ref)(&q->what, what, MDL);
		else
			q->what = what;
	}

	/*
	 * The value passed in is a time from an epoch but we need a relative
	 * time so we need to do some math to try and recover the period.
	 * This is complicated by the fact that not all of the calls cared
	 * about the usec value, if it's zero we assume the caller didn't care.
	 *
	 * The ISC timer library doesn't seem to like negative values
	 * and on 64-bit systems, isc_time_nowplusinterval() can generate range
	 * errors on values sufficiently larger than 0x7FFFFFFF (TIME_MAX), so
	 * we'll limit the interval to:
	 *
	 * 	0 <= interval <= TIME_MAX - 1
	 *
	 * We do it before checking the trace option so that both the trace
	 * code and * the working code use the same values.
	 */

	sec  = when->tv_sec - cur_tv.tv_sec;
	usec = when->tv_usec - cur_tv.tv_usec;
	
	if ((when->tv_usec != 0) && (usec < 0)) {
		sec--;
		usec += USEC_MAX;
	}

	if (sec < 0) {
		sec  = 0;
		usec = 0;
	} else if (sec >= TIME_MAX) {
		log_error("Timeout too large "
			  "reducing to: %lu (TIME_MAX - 1)",
			  (unsigned long)(TIME_MAX - 1));
		sec = TIME_MAX - 1;
		usec = 0;
	} else if (usec < 0) {
		usec = 0;
	} else if (usec >= USEC_MAX) {
		usec = USEC_MAX - 1;
	}

	/* 
	 * This is necessary for the tracing code but we put it
	 * here in case we want to compare timing information
	 * for some reason, like debugging.
	 */
	q->when.tv_sec  = cur_tv.tv_sec + sec;
	q->when.tv_usec = usec;

#if defined (TRACING)
	if (trace_playback()) {
		/*
		 * If we are doing playback we need to handle the timers
		 * within this code rather than having the isclib handle
		 * them for us.  We need to keep the timer list in order
		 * to allow us to find the ones to timeout.
		 *
		 * By using a different timer setup in the playback we may
		 * have variations between the orginal and the playback but
		 * it's the best we can do for now.
		 */

		/* Beginning of list? */
		if (!timeouts || (timeouts->when.tv_sec > q-> when.tv_sec) ||
		    ((timeouts->when.tv_sec == q->when.tv_sec) &&
		     (timeouts->when.tv_usec > q->when.tv_usec))) {
			q->next = timeouts;
			timeouts = q;
			return;
		}

		/* Middle of list? */
		for (t = timeouts; t->next; t = t->next) {
			if ((t->next->when.tv_sec > q->when.tv_sec) ||
			    ((t->next->when.tv_sec == q->when.tv_sec) &&
			     (t->next->when.tv_usec > q->when.tv_usec))) {
				q->next = t->next;
				t->next = q;
				return;
			}
		}

		/* End of list. */
		t->next = q;
		q->next = (struct timeout *)0;
		return;
	}
#endif
	/*
	 * Don't bother sorting the DHCP list, just add it to the front.
	 * Eventually the list should be removed as we migrate the callers
	 * to the native ISC timer functions, if it becomes a performance
	 * problem before then we may need to order the list.
	 */
	q->next  = timeouts;
	timeouts = q;

	isc_interval_set(&interval, sec, usec * 1000);
	status = isc_time_nowplusinterval(&expires, &interval);
	if (status != ISC_R_SUCCESS) {
		/*
		 * The system time function isn't happy. Range errors
		 * should not be possible with the check logic above.
		 */
		log_fatal("Unable to set up timer: %s",
			  isc_result_totext(status));
	}

	if (usereset == 0) {
		status = isc_timer_create(dhcp_gbl_ctx.timermgr,
					  isc_timertype_once, &expires,
					  NULL, dhcp_gbl_ctx.task,
					  isclib_timer_callback,
					  (void *)q, &q->isc_timeout);
	} else {
		status = isc_timer_reset(q->isc_timeout,
					 isc_timertype_once, &expires,
					 NULL, 0);
	}

	/* If it fails log an error and die */
	if (status != ISC_R_SUCCESS) {
		log_fatal("Unable to add timeout to isclib\n");
	}

	return;
}

void cancel_timeout (where, what)
	void (*where) (void *);
	void *what;
{
	struct timeout *t, *q;

	/* Look for this timeout on the list, and unlink it if we find it. */
	t = (struct timeout *)0;
	for (q = timeouts; q; q = q -> next) {
		if (q->func == where && q->what == what) {
			if (t)
				t->next = q->next;
			else
				timeouts = q->next;
			break;
		}
		t = q;
	}

	/*
	 * If we found the timeout, cancel it and put it on the free list.
	 * The TRACING stuff is ugly but we don't add a timer when doing
	 * playback so we don't want to remove them then either.
	 */
	if (q) {
#if defined (TRACING)
		if (!trace_playback()) {
#endif
			isc_timer_detach(&q->isc_timeout);
#if defined (TRACING)
		}
#endif

		if (q->unref)
			(*q->unref) (&q->what, MDL);
		q->next = free_timeouts;
		free_timeouts = q;
	}
}

#if defined (DEBUG_MEMORY_LEAKAGE_ON_EXIT)
void cancel_all_timeouts ()
{
	struct timeout *t, *n;
	for (t = timeouts; t; t = n) {
		n = t->next;
		isc_timer_detach(&t->isc_timeout);
		if (t->unref && t->what)
			(*t->unref) (&t->what, MDL);
		t->next = free_timeouts;
		free_timeouts = t;
	}
}

void relinquish_timeouts ()
{
	struct timeout *t, *n;
	for (t = free_timeouts; t; t = n) {
		n = t->next;
		dfree(t, MDL);
	}
}
#endif