Home | History | Annotate | Download | only in dhcpagent
      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