xref: /illumos-gate/usr/src/cmd/cmd-inet/sbin/dhcpagent/renew.c (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/types.h>
29 #include <time.h>
30 #include <netinet/in.h>
31 #include <netinet/dhcp.h>
32 #include <netinet/udp.h>
33 #include <netinet/ip_var.h>
34 #include <netinet/udp_var.h>
35 #include <libinetutil.h>
36 #include <dhcpmsg.h>
37 #include <string.h>
38 
39 #include "packet.h"
40 #include "agent.h"
41 #include "script_handler.h"
42 #include "interface.h"
43 #include "states.h"
44 #include "util.h"
45 
46 /*
47  * Number of seconds to wait for a retry if the user is interacting with the
48  * daemon.
49  */
50 #define	RETRY_DELAY	10
51 
52 /*
53  * If the renew timer fires within this number of seconds of the rebind timer,
54  * then skip renew.  This prevents us from sending back-to-back renew and
55  * rebind messages -- a pointless activity.
56  */
57 #define	TOO_CLOSE	2
58 
59 static boolean_t stop_extending(dhcp_smach_t *, unsigned int);
60 
61 /*
62  * dhcp_renew(): attempts to renew a DHCP lease on expiration of the T1 timer.
63  *
64  *   input: iu_tq_t *: unused
65  *	    void *: the lease to renew (dhcp_lease_t)
66  *  output: void
67  *
68  *   notes: The primary expense involved with DHCP (like most UDP protocols) is
69  *	    with the generation and handling of packets, not the contents of
70  *	    those packets.  Thus, we try to reduce the number of packets that
71  *	    are sent.  It would be nice to just renew all leases here (each one
72  *	    added has trivial added overhead), but the DHCPv6 RFC doesn't
73  *	    explicitly allow that behavior.  Rather than having that argument,
74  *	    we settle for ones that are close in expiry to the one that fired.
75  *	    For v4, we repeatedly reschedule the T1 timer to do the
76  *	    retransmissions.  For v6, we rely on the common timer computation
77  *	    in packet.c.
78  */
79 
80 /* ARGSUSED */
81 void
82 dhcp_renew(iu_tq_t *tqp, void *arg)
83 {
84 	dhcp_lease_t *dlp = arg;
85 	dhcp_smach_t *dsmp = dlp->dl_smach;
86 	uint32_t	t2;
87 
88 	dhcpmsg(MSG_VERBOSE, "dhcp_renew: T1 timer expired on %s",
89 	    dsmp->dsm_name);
90 
91 	dlp->dl_t1.dt_id = -1;
92 
93 	if (dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) {
94 		dhcpmsg(MSG_DEBUG, "dhcp_renew: already renewing");
95 		release_lease(dlp);
96 		return;
97 	}
98 
99 	/*
100 	 * Sanity check: don't send packets if we're past T2, or if we're
101 	 * extremely close.
102 	 */
103 
104 	t2 = dsmp->dsm_curstart_monosec + dlp->dl_t2.dt_start;
105 	if (monosec() + TOO_CLOSE >= t2) {
106 		dhcpmsg(MSG_DEBUG, "dhcp_renew: %spast T2 on %s",
107 		    monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
108 		release_lease(dlp);
109 		return;
110 	}
111 
112 	/*
113 	 * If there isn't an async event pending, or if we can cancel the one
114 	 * that's there, then try to renew by sending an extension request.  If
115 	 * that fails, we'll try again when the next timer fires.
116 	 */
117 	if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
118 	    !dhcp_extending(dsmp)) {
119 		if (monosec() + RETRY_DELAY < t2) {
120 			/*
121 			 * Try again in RETRY_DELAY seconds; user command
122 			 * should be gone.
123 			 */
124 			init_timer(&dlp->dl_t1, RETRY_DELAY);
125 			(void) set_smach_state(dsmp, BOUND);
126 			if (!schedule_lease_timer(dlp, &dlp->dl_t1,
127 			    dhcp_renew)) {
128 				dhcpmsg(MSG_INFO, "dhcp_renew: unable to "
129 				    "reschedule renewal around user command "
130 				    "on %s; will wait for rebind",
131 				    dsmp->dsm_name);
132 			}
133 		} else {
134 			dhcpmsg(MSG_DEBUG, "dhcp_renew: user busy on %s; will "
135 			    "wait for rebind", dsmp->dsm_name);
136 		}
137 	}
138 	release_lease(dlp);
139 }
140 
141 /*
142  * dhcp_rebind(): attempts to renew a DHCP lease from the REBINDING state (T2
143  *		  timer expiry).
144  *
145  *   input: iu_tq_t *: unused
146  *	    void *: the lease to renew
147  *  output: void
148  *   notes: For v4, we repeatedly reschedule the T2 timer to do the
149  *	    retransmissions.  For v6, we rely on the common timer computation
150  *	    in packet.c.
151  */
152 
153 /* ARGSUSED */
154 void
155 dhcp_rebind(iu_tq_t *tqp, void *arg)
156 {
157 	dhcp_lease_t	*dlp = arg;
158 	dhcp_smach_t	*dsmp = dlp->dl_smach;
159 	int		nlifs;
160 	dhcp_lif_t	*lif;
161 	boolean_t	some_valid;
162 	uint32_t	expiremax;
163 	DHCPSTATE	oldstate;
164 
165 	dhcpmsg(MSG_VERBOSE, "dhcp_rebind: T2 timer expired on %s",
166 	    dsmp->dsm_name);
167 
168 	dlp->dl_t2.dt_id = -1;
169 
170 	if ((oldstate = dsmp->dsm_state) == REBINDING) {
171 		dhcpmsg(MSG_DEBUG, "dhcp_renew: already rebinding");
172 		release_lease(dlp);
173 		return;
174 	}
175 
176 	/*
177 	 * Sanity check: don't send packets if we've already expired on all of
178 	 * the addresses.  We compute the maximum expiration time here, because
179 	 * it won't matter for v4 (there's only one lease) and for v6 we need
180 	 * to know when the last lease ages away.
181 	 */
182 
183 	some_valid = B_FALSE;
184 	expiremax = monosec();
185 	lif = dlp->dl_lifs;
186 	for (nlifs = dlp->dl_nlifs; nlifs > 0; nlifs--, lif = lif->lif_next) {
187 		uint32_t expire;
188 
189 		expire = dsmp->dsm_curstart_monosec + lif->lif_expire.dt_start;
190 		if (expire > expiremax) {
191 			expiremax = expire;
192 			some_valid = B_TRUE;
193 		}
194 	}
195 	if (!some_valid) {
196 		dhcpmsg(MSG_DEBUG, "dhcp_rebind: all leases expired on %s",
197 		    dsmp->dsm_name);
198 		release_lease(dlp);
199 		return;
200 	}
201 
202 	/*
203 	 * This is our first venture into the REBINDING state, so reset the
204 	 * server address.  We know the renew timer has already been cancelled
205 	 * (or we wouldn't be here).
206 	 */
207 	if (dsmp->dsm_isv6) {
208 		dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers;
209 	} else {
210 		IN6_IPADDR_TO_V4MAPPED(htonl(INADDR_BROADCAST),
211 		    &dsmp->dsm_server);
212 	}
213 
214 	/* {Bound,Renew}->rebind transitions cannot fail */
215 	(void) set_smach_state(dsmp, REBINDING);
216 
217 	/*
218 	 * If there isn't an async event pending, or if we can cancel the one
219 	 * that's there, then try to rebind by sending an extension request.
220 	 * If that fails, we'll clean up when the lease expires.
221 	 */
222 	if (!async_cancel(dsmp) || !async_start(dsmp, DHCP_EXTEND, B_FALSE) ||
223 	    !dhcp_extending(dsmp)) {
224 		if (monosec() + RETRY_DELAY < expiremax) {
225 			/*
226 			 * Try again in RETRY_DELAY seconds; user command
227 			 * should be gone.
228 			 */
229 			init_timer(&dlp->dl_t2, RETRY_DELAY);
230 			(void) set_smach_state(dsmp, oldstate);
231 			if (!schedule_lease_timer(dlp, &dlp->dl_t2,
232 			    dhcp_rebind)) {
233 				dhcpmsg(MSG_INFO, "dhcp_rebind: unable to "
234 				    "reschedule rebind around user command on "
235 				    "%s; lease may expire", dsmp->dsm_name);
236 			}
237 		} else {
238 			dhcpmsg(MSG_WARNING, "dhcp_rebind: user busy on %s; "
239 			    "will expire", dsmp->dsm_name);
240 		}
241 	}
242 	release_lease(dlp);
243 }
244 
245 /*
246  * dhcp_finish_expire(): finish expiration of a lease after the user script
247  *			 runs.  If this is the last lease, then restart DHCP.
248  *			 The caller has a reference to the LIF, which will be
249  *			 dropped.
250  *
251  *   input: dhcp_smach_t *: the state machine to be restarted
252  *	    void *: logical interface that has expired
253  *  output: int: always 1
254  */
255 
256 static int
257 dhcp_finish_expire(dhcp_smach_t *dsmp, void *arg)
258 {
259 	dhcp_lif_t *lif = arg;
260 	dhcp_lease_t *dlp;
261 
262 	dhcpmsg(MSG_DEBUG, "lease expired on %s; removing", lif->lif_name);
263 
264 	dlp = lif->lif_lease;
265 	unplumb_lif(lif);
266 	if (dlp->dl_nlifs == 0)
267 		remove_lease(dlp);
268 	release_lif(lif);
269 
270 	/* If some valid leases remain, then drive on */
271 	if (dsmp->dsm_leases != NULL) {
272 		dhcpmsg(MSG_DEBUG,
273 		    "dhcp_finish_expire: some leases remain on %s",
274 		    dsmp->dsm_name);
275 		return (1);
276 	}
277 
278 	dhcpmsg(MSG_INFO, "last lease expired on %s -- restarting DHCP",
279 	    dsmp->dsm_name);
280 
281 	/*
282 	 * in the case where the lease is less than DHCP_REBIND_MIN
283 	 * seconds, we will never enter dhcp_renew() and thus the packet
284 	 * counters will not be reset.  in that case, reset them here.
285 	 */
286 
287 	if (dsmp->dsm_state == BOUND) {
288 		dsmp->dsm_bad_offers	= 0;
289 		dsmp->dsm_sent		= 0;
290 		dsmp->dsm_received	= 0;
291 	}
292 
293 	deprecate_leases(dsmp);
294 
295 	/* reset_smach() in dhcp_selecting() will clean up any leftover state */
296 	dhcp_selecting(dsmp);
297 
298 	return (1);
299 }
300 
301 /*
302  * dhcp_deprecate(): deprecates an address on a given logical interface when
303  *		     the preferred lifetime expires.
304  *
305  *   input: iu_tq_t *: unused
306  *	    void *: the logical interface whose lease is expiring
307  *  output: void
308  */
309 
310 /* ARGSUSED */
311 void
312 dhcp_deprecate(iu_tq_t *tqp, void *arg)
313 {
314 	dhcp_lif_t *lif = arg;
315 
316 	set_lif_deprecated(lif);
317 	release_lif(lif);
318 }
319 
320 /*
321  * dhcp_expire(): expires a lease on a given logical interface and, if there
322  *		  are no more leases, restarts DHCP.
323  *
324  *   input: iu_tq_t *: unused
325  *	    void *: the logical interface whose lease has expired
326  *  output: void
327  */
328 
329 /* ARGSUSED */
330 void
331 dhcp_expire(iu_tq_t *tqp, void *arg)
332 {
333 	dhcp_lif_t	*lif = arg;
334 	dhcp_smach_t	*dsmp;
335 	const char	*event;
336 
337 	dhcpmsg(MSG_VERBOSE, "dhcp_expire: lease timer expired on %s",
338 	    lif->lif_name);
339 
340 	lif->lif_expire.dt_id = -1;
341 	if (lif->lif_lease == NULL) {
342 		release_lif(lif);
343 		return;
344 	}
345 
346 	set_lif_deprecated(lif);
347 
348 	dsmp = lif->lif_lease->dl_smach;
349 
350 	if (!async_cancel(dsmp)) {
351 
352 		dhcpmsg(MSG_WARNING,
353 		    "dhcp_expire: cannot cancel current asynchronous command "
354 		    "on %s", dsmp->dsm_name);
355 
356 		/*
357 		 * Try to schedule ourselves for callback.  We're really
358 		 * situation-critical here; there's not much hope for us if
359 		 * this fails.
360 		 */
361 		init_timer(&lif->lif_expire, DHCP_EXPIRE_WAIT);
362 		if (schedule_lif_timer(lif, &lif->lif_expire, dhcp_expire))
363 			return;
364 
365 		dhcpmsg(MSG_CRIT, "dhcp_expire: cannot reschedule dhcp_expire "
366 		    "to get called back, proceeding...");
367 	}
368 
369 	if (!async_start(dsmp, DHCP_START, B_FALSE))
370 		dhcpmsg(MSG_WARNING, "dhcp_expire: cannot start asynchronous "
371 		    "transaction on %s, continuing...", dsmp->dsm_name);
372 
373 	/*
374 	 * Determine if this state machine has any non-expired LIFs left in it.
375 	 * If it doesn't, then this is an "expire" event.  Otherwise, if some
376 	 * valid leases remain, it's a "loss" event.  The SOMEEXP case can
377 	 * occur only with DHCPv6.
378 	 */
379 	if (expired_lif_state(dsmp) == DHCP_EXP_SOMEEXP)
380 		event = EVENT_LOSS6;
381 	else if (dsmp->dsm_isv6)
382 		event = EVENT_EXPIRE6;
383 	else
384 		event = EVENT_EXPIRE;
385 
386 	/*
387 	 * just march on if this fails; at worst someone will be able
388 	 * to async_start() while we're actually busy with our own
389 	 * asynchronous transaction.  better than not having a lease.
390 	 */
391 
392 	(void) script_start(dsmp, event, dhcp_finish_expire, lif, NULL);
393 }
394 
395 /*
396  * dhcp_extending(): sends a REQUEST (IPv4 DHCP) or Rebind/Renew (DHCPv6) to
397  *		     extend a lease on a given state machine
398  *
399  *   input: dhcp_smach_t *: the state machine to send the message from
400  *  output: boolean_t: B_TRUE if the extension request was sent
401  */
402 
403 boolean_t
404 dhcp_extending(dhcp_smach_t *dsmp)
405 {
406 	dhcp_pkt_t		*dpkt;
407 
408 	stop_pkt_retransmission(dsmp);
409 
410 	/*
411 	 * We change state here because this function is also called when
412 	 * adopting a lease and on demand by the user.
413 	 */
414 	if (dsmp->dsm_state == BOUND) {
415 		dsmp->dsm_neg_hrtime	= gethrtime();
416 		dsmp->dsm_bad_offers	= 0;
417 		dsmp->dsm_sent		= 0;
418 		dsmp->dsm_received	= 0;
419 		/* Bound->renew can't fail */
420 		(void) set_smach_state(dsmp, RENEWING);
421 	}
422 
423 	dhcpmsg(MSG_DEBUG, "dhcp_extending: sending request on %s",
424 	    dsmp->dsm_name);
425 
426 	if (dsmp->dsm_isv6) {
427 		dhcp_lease_t *dlp;
428 		dhcp_lif_t *lif;
429 		uint_t nlifs;
430 		uint_t irt, mrt;
431 
432 		/*
433 		 * Start constructing the Renew/Rebind message.  Only Renew has
434 		 * a server ID, as we still think our server might be
435 		 * reachable.
436 		 */
437 		if (dsmp->dsm_state == RENEWING) {
438 			dpkt = init_pkt(dsmp, DHCPV6_MSG_RENEW);
439 			(void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID,
440 			    dsmp->dsm_serverid, dsmp->dsm_serveridlen);
441 			irt = DHCPV6_REN_TIMEOUT;
442 			mrt = DHCPV6_REN_MAX_RT;
443 		} else {
444 			dpkt = init_pkt(dsmp, DHCPV6_MSG_REBIND);
445 			irt = DHCPV6_REB_TIMEOUT;
446 			mrt = DHCPV6_REB_MAX_RT;
447 		}
448 
449 		/*
450 		 * Loop over the leases, and add an IA_NA for each and an
451 		 * IAADDR for each address.
452 		 */
453 		for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
454 			lif = dlp->dl_lifs;
455 			for (nlifs = dlp->dl_nlifs; nlifs > 0;
456 			    nlifs--, lif = lif->lif_next) {
457 				(void) add_pkt_lif(dpkt, lif,
458 				    DHCPV6_STAT_SUCCESS, NULL);
459 			}
460 		}
461 
462 		/* Add required Option Request option */
463 		(void) add_pkt_prl(dpkt, dsmp);
464 
465 		return (send_pkt_v6(dsmp, dpkt, dsmp->dsm_server,
466 		    stop_extending, irt, mrt));
467 	} else {
468 		dhcp_lif_t *lif = dsmp->dsm_lif;
469 		ipaddr_t server;
470 
471 		/* assemble the DHCPREQUEST message. */
472 		dpkt = init_pkt(dsmp, REQUEST);
473 		dpkt->pkt->ciaddr.s_addr = lif->lif_addr;
474 
475 		/*
476 		 * The max dhcp message size option is set to the interface
477 		 * max, minus the size of the udp and ip headers.
478 		 */
479 		(void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE,
480 		    htons(lif->lif_max - sizeof (struct udpiphdr)));
481 		(void) add_pkt_opt32(dpkt, CD_LEASE_TIME, htonl(DHCP_PERM));
482 
483 		if (class_id_len != 0) {
484 			(void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id,
485 			    class_id_len);
486 		}
487 		(void) add_pkt_prl(dpkt, dsmp);
488 		/*
489 		 * dsm_reqhost was set for this state machine in
490 		 * dhcp_selecting() if the REQUEST_HOSTNAME option was set and
491 		 * a host name was found.
492 		 */
493 		if (dsmp->dsm_reqhost != NULL) {
494 			(void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
495 			    strlen(dsmp->dsm_reqhost));
496 		}
497 		(void) add_pkt_opt(dpkt, CD_END, NULL, 0);
498 
499 		IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server);
500 		return (send_pkt(dsmp, dpkt, server, stop_extending));
501 	}
502 }
503 
504 /*
505  * stop_extending(): decides when to stop retransmitting v4 REQUEST or v6
506  *		     Renew/Rebind messages.  If we're renewing, then stop if
507  *		     T2 is soon approaching.
508  *
509  *   input: dhcp_smach_t *: the state machine REQUESTs are being sent from
510  *	    unsigned int: the number of REQUESTs sent so far
511  *  output: boolean_t: B_TRUE if retransmissions should stop
512  */
513 
514 /* ARGSUSED */
515 static boolean_t
516 stop_extending(dhcp_smach_t *dsmp, unsigned int n_requests)
517 {
518 	dhcp_lease_t *dlp;
519 
520 	/*
521 	 * If we're renewing and rebind time is soon approaching, then don't
522 	 * schedule
523 	 */
524 	if (dsmp->dsm_state == RENEWING) {
525 		monosec_t t2;
526 
527 		t2 = 0;
528 		for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) {
529 			if (dlp->dl_t2.dt_start > t2)
530 				t2 = dlp->dl_t2.dt_start;
531 		}
532 		t2 += dsmp->dsm_curstart_monosec;
533 		if (monosec() + TOO_CLOSE >= t2) {
534 			dhcpmsg(MSG_DEBUG, "stop_extending: %spast T2 on %s",
535 			    monosec() > t2 ? "" : "almost ", dsmp->dsm_name);
536 			return (B_TRUE);
537 		}
538 	}
539 
540 	/*
541 	 * Note that returning B_TRUE cancels both this transmission and the
542 	 * one that would occur at dsm_send_timeout, and that for v4 we cut the
543 	 * time in half for each retransmission.  Thus we check here against
544 	 * half of the minimum.
545 	 */
546 	if (!dsmp->dsm_isv6 &&
547 	    dsmp->dsm_send_timeout < DHCP_REBIND_MIN * MILLISEC / 2) {
548 		dhcpmsg(MSG_DEBUG, "stop_extending: next retry would be in "
549 		    "%d.%03d; stopping", dsmp->dsm_send_timeout / MILLISEC,
550 		    dsmp->dsm_send_timeout % MILLISEC);
551 		return (B_TRUE);
552 	}
553 
554 	/* Otherwise, w stop only when the next timer (rebind, expire) fires */
555 	return (B_FALSE);
556 }
557