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 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); 484 (void) add_pkt_prl(dpkt, dsmp); 485 /* 486 * dsm_reqhost was set for this state machine in 487 * dhcp_selecting() if the REQUEST_HOSTNAME option was set and 488 * a host name was found. 489 */ 490 if (dsmp->dsm_reqhost != NULL) { 491 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, 492 strlen(dsmp->dsm_reqhost)); 493 } 494 (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 495 496 IN6_V4MAPPED_TO_IPADDR(&dsmp->dsm_server, server); 497 return (send_pkt(dsmp, dpkt, server, stop_extending)); 498 } 499 } 500 501 /* 502 * stop_extending(): decides when to stop retransmitting v4 REQUEST or v6 503 * Renew/Rebind messages. If we're renewing, then stop if 504 * T2 is soon approaching. 505 * 506 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from 507 * unsigned int: the number of REQUESTs sent so far 508 * output: boolean_t: B_TRUE if retransmissions should stop 509 */ 510 511 /* ARGSUSED */ 512 static boolean_t 513 stop_extending(dhcp_smach_t *dsmp, unsigned int n_requests) 514 { 515 dhcp_lease_t *dlp; 516 517 /* 518 * If we're renewing and rebind time is soon approaching, then don't 519 * schedule 520 */ 521 if (dsmp->dsm_state == RENEWING) { 522 monosec_t t2; 523 524 t2 = 0; 525 for (dlp = dsmp->dsm_leases; dlp != NULL; dlp = dlp->dl_next) { 526 if (dlp->dl_t2.dt_start > t2) 527 t2 = dlp->dl_t2.dt_start; 528 } 529 t2 += dsmp->dsm_curstart_monosec; 530 if (monosec() + TOO_CLOSE >= t2) { 531 dhcpmsg(MSG_DEBUG, "stop_extending: %spast T2 on %s", 532 monosec() > t2 ? "" : "almost ", dsmp->dsm_name); 533 return (B_TRUE); 534 } 535 } 536 537 /* 538 * Note that returning B_TRUE cancels both this transmission and the 539 * one that would occur at dsm_send_timeout, and that for v4 we cut the 540 * time in half for each retransmission. Thus we check here against 541 * half of the minimum. 542 */ 543 if (!dsmp->dsm_isv6 && 544 dsmp->dsm_send_timeout < DHCP_REBIND_MIN * MILLISEC / 2) { 545 dhcpmsg(MSG_DEBUG, "stop_extending: next retry would be in " 546 "%d.%03d; stopping", dsmp->dsm_send_timeout / MILLISEC, 547 dsmp->dsm_send_timeout % MILLISEC); 548 return (B_TRUE); 549 } 550 551 /* Otherwise, w stop only when the next timer (rebind, expire) fires */ 552 return (B_FALSE); 553 } 554