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