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