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 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>. 25 * 26 * REQUESTING state of the client state machine. 27 */ 28 29 #include <stdlib.h> 30 #include <string.h> 31 #include <search.h> 32 #include <sys/types.h> 33 #include <netinet/in.h> 34 #include <netinet/dhcp.h> 35 #include <netinet/udp.h> 36 #include <netinet/ip_var.h> 37 #include <netinet/udp_var.h> 38 #include <arpa/inet.h> 39 #include <dhcp_hostconf.h> 40 #include <dhcpagent_util.h> 41 #include <dhcpmsg.h> 42 43 #include "states.h" 44 #include "util.h" 45 #include "packet.h" 46 #include "interface.h" 47 #include "agent.h" 48 49 static PKT_LIST *select_best(dhcp_smach_t *); 50 static void request_failed(dhcp_smach_t *); 51 static stop_func_t stop_requesting; 52 53 /* 54 * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING 55 * state. This is a separate function because a NoBinding 56 * response can also cause us to do this. 57 * 58 * input: dhcp_smach_t *: the state machine 59 * output: none 60 */ 61 62 void 63 send_v6_request(dhcp_smach_t *dsmp) 64 { 65 dhcp_pkt_t *dpkt; 66 dhcpv6_ia_na_t d6in; 67 68 dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST); 69 (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, 70 dsmp->dsm_serveridlen); 71 72 /* Add an IA_NA option for our controlling LIF */ 73 d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); 74 d6in.d6in_t1 = htonl(0); 75 d6in.d6in_t2 = htonl(0); 76 (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, 77 (dhcpv6_option_t *)&d6in + 1, 78 sizeof (d6in) - sizeof (dhcpv6_option_t)); 79 80 /* Add required Option Request option */ 81 (void) add_pkt_prl(dpkt, dsmp); 82 83 (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting, 84 DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT); 85 86 /* For DHCPv6, state switch cannot fail */ 87 (void) set_smach_state(dsmp, REQUESTING); 88 } 89 90 /* 91 * server_unicast_option(): determines the server address to use based on the 92 * DHCPv6 Server Unicast option present in the given 93 * packet. 94 * 95 * input: dhcp_smach_t *: the state machine 96 * PKT_LIST *: received packet (Advertisement or Reply) 97 * output: none 98 */ 99 100 void 101 server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp) 102 { 103 const dhcpv6_option_t *d6o; 104 uint_t olen; 105 106 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen); 107 olen -= sizeof (*d6o); 108 /* LINTED: no consequent */ 109 if (d6o == NULL) { 110 /* No Server Unicast option specified */ 111 } else if (olen != sizeof (dsmp->dsm_server)) { 112 dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server " 113 "Unicast option with bad length", 114 pkt_type_to_string(pkt_recv_type(plp), B_TRUE)); 115 } else { 116 in6_addr_t addr; 117 118 (void) memcpy(&addr, d6o + 1, olen); 119 if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { 120 dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 121 "to unspecified address ignored"); 122 } else if (IN6_IS_ADDR_MULTICAST(&addr)) { 123 dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 124 "to multicast address ignored"); 125 } else if (IN6_IS_ADDR_V4COMPAT(&addr) || 126 IN6_IS_ADDR_V4MAPPED(&addr)) { 127 dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 128 "to invalid address ignored"); 129 } else { 130 dsmp->dsm_server = addr; 131 } 132 } 133 } 134 135 /* 136 * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers. 137 * if so, chooses the best one, sends a REQUEST to the 138 * server and registers an event handler to receive 139 * the ACK/NAK. This may be called by the offer timer or 140 * by any function that wants to check for offers after 141 * canceling that timer. 142 * 143 * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback 144 * void *: the state machine receiving OFFER packets 145 * output: void 146 */ 147 148 void 149 dhcp_requesting(iu_tq_t *tqp, void *arg) 150 { 151 dhcp_smach_t *dsmp = arg; 152 dhcp_pkt_t *dpkt; 153 PKT_LIST *offer; 154 lease_t lease; 155 boolean_t isv6 = dsmp->dsm_isv6; 156 157 /* 158 * We assume here that if tqp is set, then this means we're being 159 * called back by the offer wait timer. If so, then drop our hold 160 * on the state machine. Otherwise, cancel the timer if it's running. 161 */ 162 if (tqp != NULL) { 163 dhcpmsg(MSG_VERBOSE, 164 "dhcp_requesting: offer wait timer on v%d %s", 165 isv6 ? 6 : 4, dsmp->dsm_name); 166 dsmp->dsm_offer_timer = -1; 167 if (!verify_smach(dsmp)) 168 return; 169 } else { 170 cancel_offer_timer(dsmp); 171 } 172 173 /* 174 * select the best OFFER; all others pitched. 175 */ 176 177 offer = select_best(dsmp); 178 if (offer == NULL) { 179 180 dhcpmsg(MSG_VERBOSE, 181 "no OFFERs/Advertisements on %s, waiting...", 182 dsmp->dsm_name); 183 184 /* 185 * no acceptable OFFERs have come in. reschedule 186 * ourself for callback. 187 */ 188 189 if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, 190 dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) { 191 192 /* 193 * ugh. the best we can do at this point is 194 * revert back to INIT and wait for a user to 195 * restart us. 196 */ 197 198 dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " 199 "reschedule callback, reverting to INIT state on " 200 "%s", dsmp->dsm_name); 201 202 stop_pkt_retransmission(dsmp); 203 (void) set_smach_state(dsmp, INIT); 204 dsmp->dsm_dflags |= DHCP_IF_FAILED; 205 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 206 } else { 207 hold_smach(dsmp); 208 } 209 210 return; 211 } 212 213 /* 214 * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly 215 * declines all other offers we've received. We can no longer use any 216 * cached offers, so we must discard them now. With DHCPv6, though, 217 * we're permitted to hang onto the advertisements (offers) and try 218 * them if the preferred one doesn't pan out. 219 */ 220 if (!isv6) 221 free_pkt_list(&dsmp->dsm_recv_pkt_list); 222 223 /* stop collecting packets. */ 224 225 stop_pkt_retransmission(dsmp); 226 227 /* 228 * For IPv4, check to see whether we got an OFFER or a BOOTP packet. 229 * If we got a BOOTP packet, go to the BOUND state now. 230 */ 231 if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { 232 free_pkt_list(&dsmp->dsm_recv_pkt_list); 233 234 if (!set_smach_state(dsmp, REQUESTING)) { 235 dhcp_restart(dsmp); 236 return; 237 } 238 239 if (!dhcp_bound(dsmp, offer)) { 240 dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " 241 "failed for %s", dsmp->dsm_name); 242 dhcp_restart(dsmp); 243 return; 244 } 245 246 return; 247 } 248 249 save_domainname(dsmp, offer); 250 251 if (isv6) { 252 const char *estr, *msg; 253 const dhcpv6_option_t *d6o; 254 uint_t olen, msglen; 255 256 /* If there's a Status Code option, print the message */ 257 d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, 258 &olen); 259 (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 260 print_server_msg(dsmp, msg, msglen); 261 262 /* Copy in the Server ID (guaranteed to be present now) */ 263 if (!save_server_id(dsmp, offer)) 264 goto failure; 265 266 /* 267 * Determine how to send this message. If the Advertisement 268 * (offer) has the unicast option, then use the address 269 * specified in the option. Otherwise, send via multicast. 270 */ 271 server_unicast_option(dsmp, offer); 272 273 send_v6_request(dsmp); 274 } else { 275 /* if we got a message from the server, display it. */ 276 if (offer->opts[CD_MESSAGE] != NULL) { 277 print_server_msg(dsmp, 278 (char *)offer->opts[CD_MESSAGE]->value, 279 offer->opts[CD_MESSAGE]->len); 280 } 281 282 /* 283 * assemble a DHCPREQUEST, with the ciaddr field set to 0, 284 * since we got here from the INIT state. 285 */ 286 287 dpkt = init_pkt(dsmp, REQUEST); 288 289 /* 290 * Grab the lease out of the OFFER; we know it's valid because 291 * select_best() already checked. The max dhcp message size 292 * option is set to the interface max, minus the size of the 293 * udp and ip headers. 294 */ 295 296 (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, 297 sizeof (lease_t)); 298 299 (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); 300 (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, 301 htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); 302 (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, 303 offer->pkt->yiaddr.s_addr); 304 (void) add_pkt_opt(dpkt, CD_SERVER_ID, 305 offer->opts[CD_SERVER_ID]->value, 306 offer->opts[CD_SERVER_ID]->len); 307 308 if (class_id_len != 0) { 309 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, 310 class_id_len); 311 } 312 (void) add_pkt_prl(dpkt, dsmp); 313 314 /* 315 * dsm_reqhost was set for this state machine in 316 * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a 317 * host name was found 318 */ 319 if (!dhcp_add_fqdn_opt(dpkt, dsmp) && 320 dsmp->dsm_reqhost != NULL) { 321 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, 322 strlen(dsmp->dsm_reqhost)); 323 } 324 (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 325 326 /* 327 * send out the REQUEST, trying retransmissions. either a NAK 328 * or too many REQUEST attempts will revert us to SELECTING. 329 */ 330 331 if (!set_smach_state(dsmp, REQUESTING)) { 332 dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " 333 "REQUESTING state; reverting to INIT on %s", 334 dsmp->dsm_name); 335 goto failure; 336 } 337 338 (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 339 stop_requesting); 340 } 341 342 /* all done with the offer */ 343 free_pkt_entry(offer); 344 345 return; 346 347 failure: 348 dsmp->dsm_dflags |= DHCP_IF_FAILED; 349 (void) set_smach_state(dsmp, INIT); 350 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 351 free_pkt_list(&dsmp->dsm_recv_pkt_list); 352 } 353 354 /* 355 * compute_points_v6(): compute the number of "points" for a given v6 356 * advertisement. 357 * 358 * input: const PKT_LIST *: packet to inspect 359 * const dhcp_smach_t *: state machine that received the packet 360 * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. 361 */ 362 363 static int 364 compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) 365 { 366 char abuf[INET6_ADDRSTRLEN]; 367 int points = 0; 368 const dhcpv6_option_t *d6o, *d6so; 369 uint_t olen, solen; 370 int i; 371 const char *estr, *msg; 372 uint_t msglen; 373 374 /* 375 * Look through the packet contents. Valid packets must have our 376 * client ID and a server ID, which has already been checked by 377 * dhcp_packet_lif. Bonus points for each option. 378 */ 379 380 /* One point for having a valid message. */ 381 points++; 382 383 /* 384 * Per RFC 3315, if the Advertise message says, "yes, we have no 385 * bananas today," then ignore the entire message. (Why it's just 386 * _this_ error and no other is a bit of a mystery, but a standard is a 387 * standard.) 388 */ 389 d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); 390 if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == 391 DHCPV6_STAT_NOADDRS) { 392 dhcpmsg(MSG_INFO, 393 "discard advertisement from %s on %s: no address status", 394 inet_ntop(AF_INET6, 395 &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, 396 abuf, sizeof (abuf)), dsmp->dsm_name); 397 return (-1); 398 } 399 400 /* Two points for each batch of offered IP addresses */ 401 d6o = NULL; 402 while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, 403 &olen)) != NULL) { 404 405 /* 406 * Note that it's possible to have "no bananas" on an 407 * individual IA. We must look for that here. 408 * 409 * RFC 3315 section 17.1.3 does not refer to the status code 410 * embedded in the IA itself. However, the TAHI test suite 411 * checks for this specific case. Because it's extremely 412 * unlikely that any usable server is going to report that it 413 * has no addresses on a network using DHCP for address 414 * assignment, we allow such messages to be dropped. 415 */ 416 d6so = dhcpv6_find_option( 417 (const char *)d6o + sizeof (dhcpv6_ia_na_t), 418 olen - sizeof (dhcpv6_ia_na_t), NULL, 419 DHCPV6_OPT_STATUS_CODE, &solen); 420 if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == 421 DHCPV6_STAT_NOADDRS) 422 return (-1); 423 points += 2; 424 } 425 426 /* 427 * Note that we drive on in the case where there are no addresses. The 428 * hope here is that we'll at least get some useful configuration 429 * information. 430 */ 431 432 /* One point for each requested option */ 433 for (i = 0; i < dsmp->dsm_prllen; i++) { 434 if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != 435 NULL) 436 points++; 437 } 438 439 /* 440 * Ten points for each point of "preference." Note: the value 255 is 441 * special. It means "stop right now and select this server." 442 */ 443 d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); 444 if (d6o != NULL && olen == sizeof (*d6o) + 1) { 445 int pref = *(const uchar_t *)(d6o + 1); 446 447 if (pref == 255) 448 return (-2); 449 points += 10 * pref; 450 } 451 452 return (points); 453 } 454 455 /* 456 * compute_points_v4(): compute the number of "points" for a given v4 offer. 457 * 458 * input: const PKT_LIST *: packet to inspect 459 * const dhcp_smach_t *: state machine that received the packet 460 * output: int: -1 to discard, >=0 for preference. 461 */ 462 463 static int 464 compute_points_v4(const PKT_LIST *pkt) 465 { 466 int points = 0; 467 468 if (pkt->opts[CD_DHCP_TYPE] == NULL) { 469 dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); 470 goto valid_offer; 471 } 472 473 if (pkt->opts[CD_LEASE_TIME] == NULL) { 474 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " 475 "time"); 476 return (-1); 477 } 478 479 if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 480 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 481 "lease time"); 482 return (-1); 483 } 484 485 if (pkt->opts[CD_SERVER_ID] == NULL) { 486 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " 487 "id"); 488 return (-1); 489 } 490 491 if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 492 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 493 "server id"); 494 return (-1); 495 } 496 497 /* valid DHCP OFFER. see if we got our parameters. */ 498 dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); 499 points += 30; 500 501 valid_offer: 502 if (pkt->rfc1048) 503 points += 5; 504 505 /* 506 * also could be faked, though more difficult because the encapsulation 507 * is hard to encode on a BOOTP server; plus there's not as much real 508 * estate in the packet for options, so it's likely this option would 509 * get dropped. 510 */ 511 512 if (pkt->opts[CD_VENDOR_SPEC] != NULL) 513 points += 80; 514 515 if (pkt->opts[CD_SUBNETMASK] != NULL) 516 points++; 517 518 if (pkt->opts[CD_ROUTER] != NULL) 519 points++; 520 521 if (pkt->opts[CD_HOSTNAME] != NULL) 522 points += 5; 523 524 return (points); 525 } 526 527 /* 528 * select_best(): selects the best offer from a list of IPv4 OFFER packets or 529 * DHCPv6 Advertise packets. 530 * 531 * input: dhcp_smach_t *: state machine with enqueued offers 532 * output: PKT_LIST *: the best packet, or NULL if none are acceptable 533 */ 534 535 static PKT_LIST * 536 select_best(dhcp_smach_t *dsmp) 537 { 538 PKT_LIST *current = dsmp->dsm_recv_pkt_list; 539 PKT_LIST *next, *best = NULL; 540 int points, best_points = -1; 541 542 /* 543 * pick out the best offer. point system. 544 * what's important for IPv4? 545 * 546 * 0) DHCP (30 points) 547 * 1) no option overload 548 * 2) encapsulated vendor option (80 points) 549 * 3) non-null sname and siaddr fields 550 * 4) non-null file field 551 * 5) hostname (5 points) 552 * 6) subnetmask (1 point) 553 * 7) router (1 point) 554 */ 555 556 for (; current != NULL; current = next) { 557 next = current->next; 558 559 points = current->isv6 ? 560 compute_points_v6(current, dsmp) : 561 compute_points_v4(current); 562 563 /* 564 * Just discard any unacceptable entries we encounter. 565 */ 566 if (points == -1) { 567 remque(current); 568 free_pkt_entry(current); 569 continue; 570 } 571 572 dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); 573 574 /* Special case: stop now and select */ 575 if (points == -2) { 576 best = current; 577 break; 578 } 579 580 if (points >= best_points) { 581 best_points = points; 582 best = current; 583 } 584 } 585 586 if (best != NULL) { 587 dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); 588 remque(best); 589 } else { 590 dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); 591 } 592 593 return (best); 594 } 595 596 /* 597 * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the 598 * current state. If we're renewing or rebinding, the ACK 599 * must be for the same address and must have a new lease 600 * time. If it's a NAK, then our cache is garbage, and we 601 * must restart. Finally, call dhcp_bound on accepted 602 * ACKs. 603 * 604 * input: dhcp_smach_t *: the state machine to handle the ACK/NAK 605 * PKT_LIST *: the ACK/NAK message 606 * output: void 607 */ 608 609 static void 610 accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) 611 { 612 /* Account for received and processed messages */ 613 dsmp->dsm_received++; 614 615 if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { 616 if (dsmp->dsm_state != INFORM_SENT && 617 dsmp->dsm_state != INFORMATION && 618 (plp->opts[CD_LEASE_TIME] == NULL || 619 plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { 620 dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " 621 "%s missing mandatory lease option, ignored", 622 dsmp->dsm_name); 623 dsmp->dsm_bad_offers++; 624 free_pkt_entry(plp); 625 return; 626 } 627 if ((dsmp->dsm_state == RENEWING || 628 dsmp->dsm_state == REBINDING) && 629 dsmp->dsm_leases->dl_lifs->lif_addr != 630 plp->pkt->yiaddr.s_addr) { 631 dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " 632 "packet has a different IP address (%s), ignored", 633 inet_ntoa(plp->pkt->yiaddr)); 634 dsmp->dsm_bad_offers++; 635 free_pkt_entry(plp); 636 return; 637 } 638 } 639 640 /* 641 * looks good; cancel the retransmission timer and unregister 642 * the acknak handler. ACK to BOUND, NAK back to SELECTING. 643 */ 644 645 stop_pkt_retransmission(dsmp); 646 647 if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { 648 dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", 649 dsmp->dsm_name); 650 dsmp->dsm_bad_offers++; 651 free_pkt_entry(plp); 652 dhcp_restart(dsmp); 653 654 /* 655 * remove any bogus cached configuration we might have 656 * around (right now would only happen if we got here 657 * from INIT_REBOOT). 658 */ 659 660 (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 661 return; 662 } 663 664 if (plp->opts[CD_SERVER_ID] == NULL || 665 plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 666 dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " 667 "server id on %s", dsmp->dsm_name); 668 dsmp->dsm_bad_offers++; 669 free_pkt_entry(plp); 670 dhcp_restart(dsmp); 671 return; 672 } 673 674 if (plp->opts[CD_MESSAGE] != NULL) { 675 print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, 676 plp->opts[CD_MESSAGE]->len); 677 } 678 679 dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); 680 if (!dhcp_bound(dsmp, plp)) { 681 dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " 682 "for %s", dsmp->dsm_name); 683 dhcp_restart(dsmp); 684 } 685 } 686 687 /* 688 * accept_v6_message(): determine what to do with a DHCPv6 message based on the 689 * current state. 690 * 691 * input: dhcp_smach_t *: the state machine to handle the message 692 * PKT_LIST *: the DHCPv6 message 693 * const char *: type of message (for logging) 694 * uchar_t: type of message (extracted from packet) 695 * output: void 696 */ 697 698 static void 699 accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, 700 uchar_t recv_type) 701 { 702 const dhcpv6_option_t *d6o; 703 uint_t olen; 704 const char *estr, *msg; 705 uint_t msglen; 706 int status; 707 708 /* Account for received and processed messages */ 709 dsmp->dsm_received++; 710 711 /* We don't yet support Reconfigure at all. */ 712 if (recv_type == DHCPV6_MSG_RECONFIGURE) { 713 dhcpmsg(MSG_VERBOSE, "accept_v6_message: ignored Reconfigure " 714 "on %s", dsmp->dsm_name); 715 free_pkt_entry(plp); 716 return; 717 } 718 719 /* 720 * All valid DHCPv6 messages must have our Client ID specified. 721 */ 722 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); 723 olen -= sizeof (*d6o); 724 if (d6o == NULL || olen != dsmp->dsm_cidlen || 725 memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { 726 dhcpmsg(MSG_VERBOSE, 727 "accept_v6_message: discarded %s on %s: %s Client ID", 728 pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); 729 free_pkt_entry(plp); 730 return; 731 } 732 733 /* 734 * All valid DHCPv6 messages must have a Server ID specified. 735 * 736 * If this is a Reply and it's not in response to Solicit, Confirm, 737 * Rebind, or Information-Request, then it must also match the Server 738 * ID we're expecting. 739 * 740 * For Reply in the Solicit, Confirm, Rebind, and Information-Request 741 * cases, the Server ID needs to be saved. This is done inside of 742 * dhcp_bound(). 743 */ 744 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); 745 if (d6o == NULL) { 746 dhcpmsg(MSG_DEBUG, 747 "accept_v6_message: discarded %s on %s: no Server ID", 748 pname, dsmp->dsm_name); 749 free_pkt_entry(plp); 750 return; 751 } 752 if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && 753 dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && 754 dsmp->dsm_state != INFORM_SENT) { 755 olen -= sizeof (*d6o); 756 if (olen != dsmp->dsm_serveridlen || 757 memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { 758 dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " 759 "%s: wrong Server ID", pname, dsmp->dsm_name); 760 free_pkt_entry(plp); 761 return; 762 } 763 } 764 765 /* 766 * Break out of the switch if the input message needs to be discarded. 767 * Return from the function if the message has been enqueued or 768 * consumed. 769 */ 770 switch (dsmp->dsm_state) { 771 case SELECTING: 772 /* A Reply message signifies a Rapid-Commit. */ 773 if (recv_type == DHCPV6_MSG_REPLY) { 774 if (dhcpv6_pkt_option(plp, NULL, 775 DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { 776 dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " 777 "on %s lacks Rapid-Commit; ignoring", 778 dsmp->dsm_name); 779 break; 780 } 781 dhcpmsg(MSG_VERBOSE, 782 "accept_v6_message: rapid-commit Reply on %s", 783 dsmp->dsm_name); 784 cancel_offer_timer(dsmp); 785 goto rapid_commit; 786 } 787 788 /* Otherwise, we're looking for Advertisements. */ 789 if (recv_type != DHCPV6_MSG_ADVERTISE) 790 break; 791 792 /* 793 * Special case: if this advertisement has preference 255, then 794 * we must stop right now and select this server. 795 */ 796 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, 797 &olen); 798 if (d6o != NULL && olen == sizeof (*d6o) + 1 && 799 *(const uchar_t *)(d6o + 1) == 255) { 800 pkt_smach_enqueue(dsmp, plp); 801 dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" 802 " immediate Request on %s", dsmp->dsm_name); 803 dhcp_requesting(NULL, dsmp); 804 } else { 805 pkt_smach_enqueue(dsmp, plp); 806 } 807 return; 808 809 case PRE_BOUND: 810 case BOUND: 811 /* 812 * Not looking for anything in these states. (If we 813 * implemented reconfigure, that might go here.) 814 */ 815 break; 816 817 case REQUESTING: 818 case INIT_REBOOT: 819 case RENEWING: 820 case REBINDING: 821 case INFORM_SENT: 822 /* 823 * We're looking for Reply messages. 824 */ 825 if (recv_type != DHCPV6_MSG_REPLY) 826 break; 827 dhcpmsg(MSG_VERBOSE, 828 "accept_v6_message: received Reply message on %s", 829 dsmp->dsm_name); 830 rapid_commit: 831 /* 832 * Extract the status code option. If one is present and the 833 * request failed, then try to go to another advertisement in 834 * the list or restart the selection machinery. 835 */ 836 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 837 &olen); 838 status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 839 /* 840 * Check for the UseMulticast status code. If this is present, 841 * and if we were actually using unicast, then drop back and 842 * try again. If we weren't using unicast, then just pretend 843 * we never saw this message -- the peer is confused. (TAHI 844 * does this.) 845 */ 846 if (status == DHCPV6_STAT_USEMCAST) { 847 if (IN6_IS_ADDR_MULTICAST( 848 &dsmp->dsm_send_dest.v6.sin6_addr)) { 849 break; 850 } else { 851 free_pkt_entry(plp); 852 dsmp->dsm_send_dest.v6.sin6_addr = 853 ipv6_all_dhcp_relay_and_servers; 854 retransmit_now(dsmp); 855 return; 856 } 857 } 858 print_server_msg(dsmp, msg, msglen); 859 /* 860 * We treat NoBinding at the top level as "success." Granted, 861 * this doesn't make much sense, but the TAHI test suite does 862 * this. NoBinding really only makes sense in the context of a 863 * specific IA, as it refers to the GUID:IAID binding, so 864 * ignoring it at the top level is safe. 865 */ 866 if (status == DHCPV6_STAT_SUCCESS || 867 status == DHCPV6_STAT_NOBINDING) { 868 if (dhcp_bound(dsmp, plp)) { 869 /* 870 * dhcp_bound will stop retransmission on 871 * success, if that's called for. 872 */ 873 server_unicast_option(dsmp, plp); 874 } else { 875 stop_pkt_retransmission(dsmp); 876 dhcpmsg(MSG_WARNING, "accept_v6_message: " 877 "dhcp_bound failed for %s", dsmp->dsm_name); 878 (void) remove_hostconf(dsmp->dsm_name, 879 dsmp->dsm_isv6); 880 dhcp_restart(dsmp); 881 } 882 } else { 883 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 884 estr); 885 stop_pkt_retransmission(dsmp); 886 free_pkt_entry(plp); 887 if (dsmp->dsm_state == INFORM_SENT) { 888 (void) set_smach_state(dsmp, INIT); 889 ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); 890 } else { 891 (void) remove_hostconf(dsmp->dsm_name, 892 dsmp->dsm_isv6); 893 request_failed(dsmp); 894 } 895 } 896 return; 897 898 case DECLINING: 899 /* 900 * We're looking for Reply messages. 901 */ 902 if (recv_type != DHCPV6_MSG_REPLY) 903 break; 904 stop_pkt_retransmission(dsmp); 905 /* 906 * Extract the status code option. Note that it's not a 907 * failure if the server reports an error. 908 */ 909 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 910 &olen); 911 if (dhcpv6_status_code(d6o, olen, &estr, &msg, 912 &msglen) == DHCPV6_STAT_SUCCESS) { 913 print_server_msg(dsmp, msg, msglen); 914 } else { 915 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 916 estr); 917 } 918 free_pkt_entry(plp); 919 if (dsmp->dsm_leases == NULL) { 920 dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " 921 "leases left", dsmp->dsm_name); 922 dhcp_restart(dsmp); 923 } else if (dsmp->dsm_lif_wait == 0) { 924 (void) set_smach_state(dsmp, BOUND); 925 } else { 926 (void) set_smach_state(dsmp, PRE_BOUND); 927 } 928 return; 929 930 case RELEASING: 931 /* 932 * We're looking for Reply messages. 933 */ 934 if (recv_type != DHCPV6_MSG_REPLY) 935 break; 936 stop_pkt_retransmission(dsmp); 937 /* 938 * Extract the status code option. 939 */ 940 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 941 &olen); 942 if (dhcpv6_status_code(d6o, olen, &estr, &msg, 943 &msglen) == DHCPV6_STAT_SUCCESS) { 944 print_server_msg(dsmp, msg, msglen); 945 } else { 946 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 947 estr); 948 } 949 free_pkt_entry(plp); 950 finished_smach(dsmp, DHCP_IPC_SUCCESS); 951 return; 952 } 953 954 /* 955 * Break from above switch means that the message must be discarded. 956 */ 957 dhcpmsg(MSG_VERBOSE, 958 "accept_v6_message: discarded v6 %s on %s; state %s", 959 pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); 960 free_pkt_entry(plp); 961 } 962 963 /* 964 * dhcp_acknak_global(): Processes reception of an ACK or NAK packet on the 965 * global socket -- broadcast packets for IPv4, all 966 * packets for DHCPv6. 967 * 968 * input: iu_eh_t *: unused 969 * int: the global file descriptor the ACK/NAK arrived on 970 * short: unused 971 * iu_event_id_t: unused 972 * void *: unused 973 * output: void 974 */ 975 976 /* ARGSUSED */ 977 void 978 dhcp_acknak_global(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 979 void *arg) 980 { 981 PKT_LIST *plp; 982 dhcp_pif_t *pif; 983 uchar_t recv_type; 984 const char *pname; 985 uint_t xid; 986 dhcp_smach_t *dsmp; 987 boolean_t isv6 = (fd == v6_sock_fd); 988 struct sockaddr_in sin; 989 const char *reason; 990 size_t sinlen = sizeof (sin); 991 int sock; 992 993 plp = recv_pkt(fd, get_max_mtu(isv6), isv6); 994 if (plp == NULL) 995 return; 996 997 recv_type = pkt_recv_type(plp); 998 pname = pkt_type_to_string(recv_type, isv6); 999 1000 /* 1001 * Find the corresponding state machine and pif. 1002 * 1003 * Note that DHCPv6 Reconfigure would be special: it's not the reply to 1004 * any transaction, and thus we would need to search on transaction ID 1005 * zero (all state machines) to find the match. However, Reconfigure 1006 * is not yet supported. 1007 */ 1008 xid = pkt_get_xid(plp->pkt, isv6); 1009 1010 for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; 1011 dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { 1012 pif = dsmp->dsm_lif->lif_pif; 1013 if (pif->pif_index == plp->ifindex || 1014 pif->pif_under_ipmp && pif->pif_grindex == plp->ifindex) 1015 break; 1016 } 1017 1018 if (dsmp == NULL) { 1019 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet" 1020 " on ifindex %d: unknown state machine", isv6 ? 6 : 4, 1021 pname, plp->ifindex); 1022 free_pkt_entry(plp); 1023 return; 1024 } 1025 1026 if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 1027 reason = "not ACK or NAK"; 1028 goto drop; 1029 } 1030 1031 /* 1032 * For IPv4, most packets will be handled by dhcp_packet_lif(). The 1033 * only exceptions are broadcast packets sent when lif_sock_ip_fd has 1034 * bound to something other than INADDR_ANY. 1035 */ 1036 if (!isv6) { 1037 sock = dsmp->dsm_lif->lif_sock_ip_fd; 1038 1039 if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != -1 && 1040 sin.sin_addr.s_addr == INADDR_ANY) { 1041 reason = "handled by lif_sock_ip_fd"; 1042 goto drop; 1043 } 1044 } 1045 1046 /* 1047 * We've got a packet; make sure it's acceptable and cancel the REQUEST 1048 * retransmissions. 1049 */ 1050 if (isv6) 1051 accept_v6_message(dsmp, plp, pname, recv_type); 1052 else 1053 accept_v4_acknak(dsmp, plp); 1054 return; 1055 drop: 1056 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet for %s " 1057 "received on global socket: %s", isv6 ? 6 : 4, pname, pif->pif_name, 1058 reason); 1059 free_pkt_entry(plp); 1060 } 1061 1062 /* 1063 * request_failed(): Attempt to request an address has failed. Take an 1064 * appropriate action. 1065 * 1066 * input: dhcp_smach_t *: state machine that has failed 1067 * output: void 1068 */ 1069 1070 static void 1071 request_failed(dhcp_smach_t *dsmp) 1072 { 1073 PKT_LIST *offer; 1074 1075 dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; 1076 if ((offer = select_best(dsmp)) != NULL) { 1077 insque(offer, &dsmp->dsm_recv_pkt_list); 1078 dhcp_requesting(NULL, dsmp); 1079 } else { 1080 dhcpmsg(MSG_INFO, "no offers left on %s; restarting", 1081 dsmp->dsm_name); 1082 dhcp_selecting(dsmp); 1083 } 1084 } 1085 1086 /* 1087 * dhcp_packet_lif(): Processes reception of an ACK, NAK, or OFFER packet on 1088 * a given logical interface for IPv4 (only). 1089 * 1090 * input: iu_eh_t *: unused 1091 * int: the file descriptor the packet arrived on 1092 * short: unused 1093 * iu_event_id_t: the id of this event callback with the handler 1094 * void *: pointer to logical interface receiving message 1095 * output: void 1096 */ 1097 1098 /* ARGSUSED */ 1099 void 1100 dhcp_packet_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 1101 void *arg) 1102 { 1103 dhcp_lif_t *lif = arg; 1104 PKT_LIST *plp; 1105 uchar_t recv_type; 1106 const char *pname; 1107 uint_t xid; 1108 dhcp_smach_t *dsmp; 1109 1110 if ((plp = recv_pkt(fd, lif->lif_max, B_FALSE)) == NULL) 1111 return; 1112 1113 recv_type = pkt_recv_type(plp); 1114 pname = pkt_type_to_string(recv_type, B_FALSE); 1115 1116 if (!pkt_v4_match(recv_type, 1117 DHCP_PACK | DHCP_PNAK | DHCP_PUNTYPED | DHCP_POFFER)) { 1118 dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored v4 %s packet " 1119 "received via LIF %s", pname, lif->lif_name); 1120 free_pkt_entry(plp); 1121 return; 1122 } 1123 1124 /* 1125 * Find the corresponding state machine. 1126 */ 1127 xid = pkt_get_xid(plp->pkt, B_FALSE); 1128 for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 1129 dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 1130 if (dsmp->dsm_lif == lif) 1131 break; 1132 } 1133 1134 if (dsmp == NULL) 1135 goto drop; 1136 1137 if (pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 1138 /* 1139 * We've got an ACK/NAK; make sure it's acceptable and cancel 1140 * the REQUEST retransmissions. 1141 */ 1142 accept_v4_acknak(dsmp, plp); 1143 } else { 1144 if (is_bound_state(dsmp->dsm_state)) 1145 goto drop; 1146 /* 1147 * Must be an OFFER or a BOOTP message: enqueue it for later 1148 * processing by select_best(). 1149 */ 1150 pkt_smach_enqueue(dsmp, plp); 1151 } 1152 return; 1153 drop: 1154 dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored %s packet xid " 1155 "%x received via LIF %s; %s", pname, xid, lif->lif_name, 1156 dsmp == NULL ? "unknown state machine" : "bound"); 1157 free_pkt_entry(plp); 1158 } 1159 1160 /* 1161 * dhcp_restart(): restarts DHCP (from INIT) on a given state machine, but only 1162 * if we're leasing addresses. Doesn't restart for information- 1163 * only interfaces. 1164 * 1165 * input: dhcp_smach_t *: the state machine to restart DHCP on 1166 * output: void 1167 */ 1168 1169 void 1170 dhcp_restart(dhcp_smach_t *dsmp) 1171 { 1172 if (dsmp->dsm_state == INFORM_SENT || dsmp->dsm_state == INFORMATION) 1173 return; 1174 1175 /* 1176 * As we're returning to INIT state, we need to discard any leases we 1177 * may have, and (for v4) canonize the LIF. There's a bit of tension 1178 * between keeping around a possibly still working address, and obeying 1179 * the RFCs. A more elaborate design would be to mark the addresses as 1180 * DEPRECATED, and then start a removal timer. Such a design would 1181 * probably compromise testing. 1182 */ 1183 deprecate_leases(dsmp); 1184 1185 if (!set_start_timer(dsmp)) { 1186 dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " 1187 "reverting to INIT state on %s", dsmp->dsm_name); 1188 1189 (void) set_smach_state(dsmp, INIT); 1190 dsmp->dsm_dflags |= DHCP_IF_FAILED; 1191 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 1192 } else { 1193 dhcpmsg(MSG_DEBUG, "dhcp_restart: restarting DHCP on %s", 1194 dsmp->dsm_name); 1195 } 1196 } 1197 1198 /* 1199 * stop_requesting(): decides when to stop retransmitting REQUESTs 1200 * 1201 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from 1202 * unsigned int: the number of REQUESTs sent so far 1203 * output: boolean_t: B_TRUE if retransmissions should stop 1204 */ 1205 1206 static boolean_t 1207 stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) 1208 { 1209 uint_t maxreq; 1210 1211 maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; 1212 if (n_requests >= maxreq) { 1213 1214 dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", 1215 dsmp->dsm_name); 1216 1217 request_failed(dsmp); 1218 return (B_TRUE); 1219 } else { 1220 return (B_FALSE); 1221 } 1222 } 1223