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_pif->pif_mtu - 302 sizeof (struct udpiphdr))); 303 (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, 304 offer->pkt->yiaddr.s_addr); 305 (void) add_pkt_opt(dpkt, CD_SERVER_ID, 306 offer->opts[CD_SERVER_ID]->value, 307 offer->opts[CD_SERVER_ID]->len); 308 309 if (class_id_len != 0) { 310 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, 311 class_id_len); 312 } 313 (void) add_pkt_prl(dpkt, dsmp); 314 315 /* 316 * dsm_reqhost was set for this state machine in 317 * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a 318 * host name was found 319 */ 320 if (!dhcp_add_fqdn_opt(dpkt, dsmp) && 321 dsmp->dsm_reqhost != NULL) { 322 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, 323 strlen(dsmp->dsm_reqhost)); 324 } 325 (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 326 327 /* 328 * send out the REQUEST, trying retransmissions. either a NAK 329 * or too many REQUEST attempts will revert us to SELECTING. 330 */ 331 332 if (!set_smach_state(dsmp, REQUESTING)) { 333 dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " 334 "REQUESTING state; reverting to INIT on %s", 335 dsmp->dsm_name); 336 goto failure; 337 } 338 339 (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 340 stop_requesting); 341 } 342 343 /* all done with the offer */ 344 free_pkt_entry(offer); 345 346 return; 347 348 failure: 349 dsmp->dsm_dflags |= DHCP_IF_FAILED; 350 (void) set_smach_state(dsmp, INIT); 351 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 352 free_pkt_list(&dsmp->dsm_recv_pkt_list); 353 } 354 355 /* 356 * compute_points_v6(): compute the number of "points" for a given v6 357 * advertisement. 358 * 359 * input: const PKT_LIST *: packet to inspect 360 * const dhcp_smach_t *: state machine that received the packet 361 * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. 362 */ 363 364 static int 365 compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) 366 { 367 char abuf[INET6_ADDRSTRLEN]; 368 int points = 0; 369 const dhcpv6_option_t *d6o, *d6so; 370 uint_t olen, solen; 371 int i; 372 const char *estr, *msg; 373 uint_t msglen; 374 375 /* 376 * Look through the packet contents. Valid packets must have our 377 * client ID and a server ID, which has already been checked by 378 * dhcp_packet_lif. Bonus points for each option. 379 */ 380 381 /* One point for having a valid message. */ 382 points++; 383 384 /* 385 * Per RFC 3315, if the Advertise message says, "yes, we have no 386 * bananas today," then ignore the entire message. (Why it's just 387 * _this_ error and no other is a bit of a mystery, but a standard is a 388 * standard.) 389 */ 390 d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); 391 if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == 392 DHCPV6_STAT_NOADDRS) { 393 dhcpmsg(MSG_INFO, 394 "discard advertisement from %s on %s: no address status", 395 inet_ntop(AF_INET6, 396 &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, 397 abuf, sizeof (abuf)), dsmp->dsm_name); 398 return (-1); 399 } 400 401 /* Two points for each batch of offered IP addresses */ 402 d6o = NULL; 403 while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, 404 &olen)) != NULL) { 405 406 /* 407 * Note that it's possible to have "no bananas" on an 408 * individual IA. We must look for that here. 409 * 410 * RFC 3315 section 17.1.3 does not refer to the status code 411 * embedded in the IA itself. However, the TAHI test suite 412 * checks for this specific case. Because it's extremely 413 * unlikely that any usable server is going to report that it 414 * has no addresses on a network using DHCP for address 415 * assignment, we allow such messages to be dropped. 416 */ 417 d6so = dhcpv6_find_option( 418 (const char *)d6o + sizeof (dhcpv6_ia_na_t), 419 olen - sizeof (dhcpv6_ia_na_t), NULL, 420 DHCPV6_OPT_STATUS_CODE, &solen); 421 if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == 422 DHCPV6_STAT_NOADDRS) 423 return (-1); 424 points += 2; 425 } 426 427 /* 428 * Note that we drive on in the case where there are no addresses. The 429 * hope here is that we'll at least get some useful configuration 430 * information. 431 */ 432 433 /* One point for each requested option */ 434 for (i = 0; i < dsmp->dsm_prllen; i++) { 435 if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != 436 NULL) 437 points++; 438 } 439 440 /* 441 * Ten points for each point of "preference." Note: the value 255 is 442 * special. It means "stop right now and select this server." 443 */ 444 d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); 445 if (d6o != NULL && olen == sizeof (*d6o) + 1) { 446 int pref = *(const uchar_t *)(d6o + 1); 447 448 if (pref == 255) 449 return (-2); 450 points += 10 * pref; 451 } 452 453 return (points); 454 } 455 456 /* 457 * compute_points_v4(): compute the number of "points" for a given v4 offer. 458 * 459 * input: const PKT_LIST *: packet to inspect 460 * const dhcp_smach_t *: state machine that received the packet 461 * output: int: -1 to discard, >=0 for preference. 462 */ 463 464 static int 465 compute_points_v4(const PKT_LIST *pkt) 466 { 467 int points = 0; 468 469 if (pkt->opts[CD_DHCP_TYPE] == NULL) { 470 dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); 471 goto valid_offer; 472 } 473 474 if (pkt->opts[CD_LEASE_TIME] == NULL) { 475 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " 476 "time"); 477 return (-1); 478 } 479 480 if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 481 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 482 "lease time"); 483 return (-1); 484 } 485 486 if (pkt->opts[CD_SERVER_ID] == NULL) { 487 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " 488 "id"); 489 return (-1); 490 } 491 492 if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 493 dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 494 "server id"); 495 return (-1); 496 } 497 498 /* valid DHCP OFFER. see if we got our parameters. */ 499 dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); 500 points += 30; 501 502 valid_offer: 503 if (pkt->rfc1048) 504 points += 5; 505 506 /* 507 * also could be faked, though more difficult because the encapsulation 508 * is hard to encode on a BOOTP server; plus there's not as much real 509 * estate in the packet for options, so it's likely this option would 510 * get dropped. 511 */ 512 513 if (pkt->opts[CD_VENDOR_SPEC] != NULL) 514 points += 80; 515 516 if (pkt->opts[CD_SUBNETMASK] != NULL) 517 points++; 518 519 if (pkt->opts[CD_ROUTER] != NULL) 520 points++; 521 522 if (pkt->opts[CD_HOSTNAME] != NULL) 523 points += 5; 524 525 return (points); 526 } 527 528 /* 529 * select_best(): selects the best offer from a list of IPv4 OFFER packets or 530 * DHCPv6 Advertise packets. 531 * 532 * input: dhcp_smach_t *: state machine with enqueued offers 533 * output: PKT_LIST *: the best packet, or NULL if none are acceptable 534 */ 535 536 static PKT_LIST * 537 select_best(dhcp_smach_t *dsmp) 538 { 539 PKT_LIST *current = dsmp->dsm_recv_pkt_list; 540 PKT_LIST *next, *best = NULL; 541 int points, best_points = -1; 542 543 /* 544 * pick out the best offer. point system. 545 * what's important for IPv4? 546 * 547 * 0) DHCP (30 points) 548 * 1) no option overload 549 * 2) encapsulated vendor option (80 points) 550 * 3) non-null sname and siaddr fields 551 * 4) non-null file field 552 * 5) hostname (5 points) 553 * 6) subnetmask (1 point) 554 * 7) router (1 point) 555 */ 556 557 for (; current != NULL; current = next) { 558 next = current->next; 559 560 points = current->isv6 ? 561 compute_points_v6(current, dsmp) : 562 compute_points_v4(current); 563 564 /* 565 * Just discard any unacceptable entries we encounter. 566 */ 567 if (points == -1) { 568 remque(current); 569 free_pkt_entry(current); 570 continue; 571 } 572 573 dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); 574 575 /* Special case: stop now and select */ 576 if (points == -2) { 577 best = current; 578 break; 579 } 580 581 if (points >= best_points) { 582 best_points = points; 583 best = current; 584 } 585 } 586 587 if (best != NULL) { 588 dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); 589 remque(best); 590 } else { 591 dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); 592 } 593 594 return (best); 595 } 596 597 /* 598 * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the 599 * current state. If we're renewing or rebinding, the ACK 600 * must be for the same address and must have a new lease 601 * time. If it's a NAK, then our cache is garbage, and we 602 * must restart. Finally, call dhcp_bound on accepted 603 * ACKs. 604 * 605 * input: dhcp_smach_t *: the state machine to handle the ACK/NAK 606 * PKT_LIST *: the ACK/NAK message 607 * output: void 608 */ 609 610 static void 611 accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) 612 { 613 /* Account for received and processed messages */ 614 dsmp->dsm_received++; 615 616 if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { 617 if (dsmp->dsm_state != INFORM_SENT && 618 dsmp->dsm_state != INFORMATION && 619 (plp->opts[CD_LEASE_TIME] == NULL || 620 plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { 621 dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " 622 "%s missing mandatory lease option, ignored", 623 dsmp->dsm_name); 624 dsmp->dsm_bad_offers++; 625 free_pkt_entry(plp); 626 return; 627 } 628 if ((dsmp->dsm_state == RENEWING || 629 dsmp->dsm_state == REBINDING) && 630 dsmp->dsm_leases->dl_lifs->lif_addr != 631 plp->pkt->yiaddr.s_addr) { 632 dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " 633 "packet has a different IP address (%s), ignored", 634 inet_ntoa(plp->pkt->yiaddr)); 635 dsmp->dsm_bad_offers++; 636 free_pkt_entry(plp); 637 return; 638 } 639 } 640 641 /* 642 * looks good; cancel the retransmission timer and unregister 643 * the acknak handler. ACK to BOUND, NAK back to SELECTING. 644 */ 645 646 stop_pkt_retransmission(dsmp); 647 648 if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { 649 dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", 650 dsmp->dsm_name); 651 dsmp->dsm_bad_offers++; 652 free_pkt_entry(plp); 653 dhcp_restart(dsmp); 654 655 /* 656 * remove any bogus cached configuration we might have 657 * around (right now would only happen if we got here 658 * from INIT_REBOOT). 659 */ 660 661 (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 662 return; 663 } 664 665 if (plp->opts[CD_SERVER_ID] == NULL || 666 plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 667 dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " 668 "server id on %s", dsmp->dsm_name); 669 dsmp->dsm_bad_offers++; 670 free_pkt_entry(plp); 671 dhcp_restart(dsmp); 672 return; 673 } 674 675 if (plp->opts[CD_MESSAGE] != NULL) { 676 print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, 677 plp->opts[CD_MESSAGE]->len); 678 } 679 680 dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); 681 if (!dhcp_bound(dsmp, plp)) { 682 dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " 683 "for %s", dsmp->dsm_name); 684 dhcp_restart(dsmp); 685 } 686 } 687 688 /* 689 * accept_v6_message(): determine what to do with a DHCPv6 message based on the 690 * current state. 691 * 692 * input: dhcp_smach_t *: the state machine to handle the message 693 * PKT_LIST *: the DHCPv6 message 694 * const char *: type of message (for logging) 695 * uchar_t: type of message (extracted from packet) 696 * output: void 697 */ 698 699 static void 700 accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, 701 uchar_t recv_type) 702 { 703 const dhcpv6_option_t *d6o; 704 uint_t olen; 705 const char *estr, *msg; 706 uint_t msglen; 707 int status; 708 709 /* Account for received and processed messages */ 710 dsmp->dsm_received++; 711 712 /* We don't yet support Reconfigure at all. */ 713 if (recv_type == DHCPV6_MSG_RECONFIGURE) { 714 dhcpmsg(MSG_VERBOSE, "accept_v6_message: ignored Reconfigure " 715 "on %s", dsmp->dsm_name); 716 free_pkt_entry(plp); 717 return; 718 } 719 720 /* 721 * All valid DHCPv6 messages must have our Client ID specified. 722 */ 723 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); 724 olen -= sizeof (*d6o); 725 if (d6o == NULL || olen != dsmp->dsm_cidlen || 726 memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { 727 dhcpmsg(MSG_VERBOSE, 728 "accept_v6_message: discarded %s on %s: %s Client ID", 729 pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); 730 free_pkt_entry(plp); 731 return; 732 } 733 734 /* 735 * All valid DHCPv6 messages must have a Server ID specified. 736 * 737 * If this is a Reply and it's not in response to Solicit, Confirm, 738 * Rebind, or Information-Request, then it must also match the Server 739 * ID we're expecting. 740 * 741 * For Reply in the Solicit, Confirm, Rebind, and Information-Request 742 * cases, the Server ID needs to be saved. This is done inside of 743 * dhcp_bound(). 744 */ 745 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); 746 if (d6o == NULL) { 747 dhcpmsg(MSG_DEBUG, 748 "accept_v6_message: discarded %s on %s: no Server ID", 749 pname, dsmp->dsm_name); 750 free_pkt_entry(plp); 751 return; 752 } 753 if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && 754 dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && 755 dsmp->dsm_state != INFORM_SENT) { 756 olen -= sizeof (*d6o); 757 if (olen != dsmp->dsm_serveridlen || 758 memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { 759 dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " 760 "%s: wrong Server ID", pname, dsmp->dsm_name); 761 free_pkt_entry(plp); 762 return; 763 } 764 } 765 766 /* 767 * Break out of the switch if the input message needs to be discarded. 768 * Return from the function if the message has been enqueued or 769 * consumed. 770 */ 771 switch (dsmp->dsm_state) { 772 case SELECTING: 773 /* A Reply message signifies a Rapid-Commit. */ 774 if (recv_type == DHCPV6_MSG_REPLY) { 775 if (dhcpv6_pkt_option(plp, NULL, 776 DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { 777 dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " 778 "on %s lacks Rapid-Commit; ignoring", 779 dsmp->dsm_name); 780 break; 781 } 782 dhcpmsg(MSG_VERBOSE, 783 "accept_v6_message: rapid-commit Reply on %s", 784 dsmp->dsm_name); 785 cancel_offer_timer(dsmp); 786 goto rapid_commit; 787 } 788 789 /* Otherwise, we're looking for Advertisements. */ 790 if (recv_type != DHCPV6_MSG_ADVERTISE) 791 break; 792 793 /* 794 * Special case: if this advertisement has preference 255, then 795 * we must stop right now and select this server. 796 */ 797 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, 798 &olen); 799 if (d6o != NULL && olen == sizeof (*d6o) + 1 && 800 *(const uchar_t *)(d6o + 1) == 255) { 801 pkt_smach_enqueue(dsmp, plp); 802 dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" 803 " immediate Request on %s", dsmp->dsm_name); 804 dhcp_requesting(NULL, dsmp); 805 } else { 806 pkt_smach_enqueue(dsmp, plp); 807 } 808 return; 809 810 case PRE_BOUND: 811 case BOUND: 812 /* 813 * Not looking for anything in these states. (If we 814 * implemented reconfigure, that might go here.) 815 */ 816 break; 817 818 case REQUESTING: 819 case INIT_REBOOT: 820 case RENEWING: 821 case REBINDING: 822 case INFORM_SENT: 823 /* 824 * We're looking for Reply messages. 825 */ 826 if (recv_type != DHCPV6_MSG_REPLY) 827 break; 828 dhcpmsg(MSG_VERBOSE, 829 "accept_v6_message: received Reply message on %s", 830 dsmp->dsm_name); 831 rapid_commit: 832 /* 833 * Extract the status code option. If one is present and the 834 * request failed, then try to go to another advertisement in 835 * the list or restart the selection machinery. 836 */ 837 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 838 &olen); 839 status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 840 /* 841 * Check for the UseMulticast status code. If this is present, 842 * and if we were actually using unicast, then drop back and 843 * try again. If we weren't using unicast, then just pretend 844 * we never saw this message -- the peer is confused. (TAHI 845 * does this.) 846 */ 847 if (status == DHCPV6_STAT_USEMCAST) { 848 if (IN6_IS_ADDR_MULTICAST( 849 &dsmp->dsm_send_dest.v6.sin6_addr)) { 850 break; 851 } else { 852 free_pkt_entry(plp); 853 dsmp->dsm_send_dest.v6.sin6_addr = 854 ipv6_all_dhcp_relay_and_servers; 855 retransmit_now(dsmp); 856 return; 857 } 858 } 859 print_server_msg(dsmp, msg, msglen); 860 /* 861 * We treat NoBinding at the top level as "success." Granted, 862 * this doesn't make much sense, but the TAHI test suite does 863 * this. NoBinding really only makes sense in the context of a 864 * specific IA, as it refers to the GUID:IAID binding, so 865 * ignoring it at the top level is safe. 866 */ 867 if (status == DHCPV6_STAT_SUCCESS || 868 status == DHCPV6_STAT_NOBINDING) { 869 if (dhcp_bound(dsmp, plp)) { 870 /* 871 * dhcp_bound will stop retransmission on 872 * success, if that's called for. 873 */ 874 server_unicast_option(dsmp, plp); 875 } else { 876 stop_pkt_retransmission(dsmp); 877 dhcpmsg(MSG_WARNING, "accept_v6_message: " 878 "dhcp_bound failed for %s", dsmp->dsm_name); 879 (void) remove_hostconf(dsmp->dsm_name, 880 dsmp->dsm_isv6); 881 dhcp_restart(dsmp); 882 } 883 } else { 884 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 885 estr); 886 stop_pkt_retransmission(dsmp); 887 free_pkt_entry(plp); 888 if (dsmp->dsm_state == INFORM_SENT) { 889 (void) set_smach_state(dsmp, INIT); 890 ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); 891 } else { 892 (void) remove_hostconf(dsmp->dsm_name, 893 dsmp->dsm_isv6); 894 request_failed(dsmp); 895 } 896 } 897 return; 898 899 case DECLINING: 900 /* 901 * We're looking for Reply messages. 902 */ 903 if (recv_type != DHCPV6_MSG_REPLY) 904 break; 905 stop_pkt_retransmission(dsmp); 906 /* 907 * Extract the status code option. Note that it's not a 908 * failure if the server reports an error. 909 */ 910 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 911 &olen); 912 if (dhcpv6_status_code(d6o, olen, &estr, &msg, 913 &msglen) == DHCPV6_STAT_SUCCESS) { 914 print_server_msg(dsmp, msg, msglen); 915 } else { 916 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 917 estr); 918 } 919 free_pkt_entry(plp); 920 if (dsmp->dsm_leases == NULL) { 921 dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " 922 "leases left", dsmp->dsm_name); 923 dhcp_restart(dsmp); 924 } else if (dsmp->dsm_lif_wait == 0) { 925 (void) set_smach_state(dsmp, BOUND); 926 } else { 927 (void) set_smach_state(dsmp, PRE_BOUND); 928 } 929 return; 930 931 case RELEASING: 932 /* 933 * We're looking for Reply messages. 934 */ 935 if (recv_type != DHCPV6_MSG_REPLY) 936 break; 937 stop_pkt_retransmission(dsmp); 938 /* 939 * Extract the status code option. 940 */ 941 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 942 &olen); 943 if (dhcpv6_status_code(d6o, olen, &estr, &msg, 944 &msglen) == DHCPV6_STAT_SUCCESS) { 945 print_server_msg(dsmp, msg, msglen); 946 } else { 947 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 948 estr); 949 } 950 free_pkt_entry(plp); 951 finished_smach(dsmp, DHCP_IPC_SUCCESS); 952 return; 953 } 954 955 /* 956 * Break from above switch means that the message must be discarded. 957 */ 958 dhcpmsg(MSG_VERBOSE, 959 "accept_v6_message: discarded v6 %s on %s; state %s", 960 pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); 961 free_pkt_entry(plp); 962 } 963 964 /* 965 * dhcp_acknak_global(): Processes reception of an ACK or NAK packet on the 966 * global socket -- broadcast packets for IPv4, all 967 * packets for DHCPv6. 968 * 969 * input: iu_eh_t *: unused 970 * int: the global file descriptor the ACK/NAK arrived on 971 * short: unused 972 * iu_event_id_t: unused 973 * void *: unused 974 * output: void 975 */ 976 977 /* ARGSUSED */ 978 void 979 dhcp_acknak_global(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 980 void *arg) 981 { 982 PKT_LIST *plp; 983 dhcp_pif_t *pif; 984 uchar_t recv_type; 985 const char *pname; 986 uint_t xid; 987 dhcp_smach_t *dsmp; 988 boolean_t isv6 = (fd == v6_sock_fd); 989 struct sockaddr_in sin; 990 const char *reason; 991 size_t sinlen = sizeof (sin); 992 int sock; 993 994 plp = recv_pkt(fd, get_max_mtu(isv6), isv6); 995 if (plp == NULL) 996 return; 997 998 recv_type = pkt_recv_type(plp); 999 pname = pkt_type_to_string(recv_type, isv6); 1000 1001 /* 1002 * Find the corresponding state machine and pif. 1003 * 1004 * Note that DHCPv6 Reconfigure would be special: it's not the reply to 1005 * any transaction, and thus we would need to search on transaction ID 1006 * zero (all state machines) to find the match. However, Reconfigure 1007 * is not yet supported. 1008 */ 1009 xid = pkt_get_xid(plp->pkt, isv6); 1010 1011 for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; 1012 dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { 1013 pif = dsmp->dsm_lif->lif_pif; 1014 if (pif->pif_index == plp->ifindex || 1015 pif->pif_under_ipmp && pif->pif_grindex == plp->ifindex) 1016 break; 1017 } 1018 1019 if (dsmp == NULL) { 1020 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet" 1021 " on ifindex %d: unknown state machine", isv6 ? 6 : 4, 1022 pname, plp->ifindex); 1023 free_pkt_entry(plp); 1024 return; 1025 } 1026 1027 if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 1028 reason = "not ACK or NAK"; 1029 goto drop; 1030 } 1031 1032 /* 1033 * For IPv4, most packets will be handled by dhcp_packet_lif(). The 1034 * only exceptions are broadcast packets sent when lif_sock_ip_fd has 1035 * bound to something other than INADDR_ANY. 1036 */ 1037 if (!isv6) { 1038 sock = dsmp->dsm_lif->lif_sock_ip_fd; 1039 1040 if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != -1 && 1041 sin.sin_addr.s_addr == INADDR_ANY) { 1042 reason = "handled by lif_sock_ip_fd"; 1043 goto drop; 1044 } 1045 } 1046 1047 /* 1048 * We've got a packet; make sure it's acceptable and cancel the REQUEST 1049 * retransmissions. 1050 */ 1051 if (isv6) 1052 accept_v6_message(dsmp, plp, pname, recv_type); 1053 else 1054 accept_v4_acknak(dsmp, plp); 1055 return; 1056 drop: 1057 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet for %s " 1058 "received on global socket: %s", isv6 ? 6 : 4, pname, pif->pif_name, 1059 reason); 1060 free_pkt_entry(plp); 1061 } 1062 1063 /* 1064 * request_failed(): Attempt to request an address has failed. Take an 1065 * appropriate action. 1066 * 1067 * input: dhcp_smach_t *: state machine that has failed 1068 * output: void 1069 */ 1070 1071 static void 1072 request_failed(dhcp_smach_t *dsmp) 1073 { 1074 PKT_LIST *offer; 1075 1076 dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; 1077 if ((offer = select_best(dsmp)) != NULL) { 1078 insque(offer, &dsmp->dsm_recv_pkt_list); 1079 dhcp_requesting(NULL, dsmp); 1080 } else { 1081 dhcpmsg(MSG_INFO, "no offers left on %s; restarting", 1082 dsmp->dsm_name); 1083 dhcp_selecting(dsmp); 1084 } 1085 } 1086 1087 /* 1088 * dhcp_packet_lif(): Processes reception of an ACK, NAK, or OFFER packet on 1089 * a given logical interface for IPv4 (only). 1090 * 1091 * input: iu_eh_t *: unused 1092 * int: the file descriptor the packet arrived on 1093 * short: unused 1094 * iu_event_id_t: the id of this event callback with the handler 1095 * void *: pointer to logical interface receiving message 1096 * output: void 1097 */ 1098 1099 /* ARGSUSED */ 1100 void 1101 dhcp_packet_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 1102 void *arg) 1103 { 1104 dhcp_lif_t *lif = arg; 1105 PKT_LIST *plp; 1106 uchar_t recv_type; 1107 const char *pname; 1108 uint_t xid; 1109 dhcp_smach_t *dsmp; 1110 1111 if ((plp = recv_pkt(fd, lif->lif_pif->pif_mtu, B_FALSE)) == NULL) 1112 return; 1113 1114 recv_type = pkt_recv_type(plp); 1115 pname = pkt_type_to_string(recv_type, B_FALSE); 1116 1117 if (!pkt_v4_match(recv_type, 1118 DHCP_PACK | DHCP_PNAK | DHCP_PUNTYPED | DHCP_POFFER)) { 1119 dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored v4 %s packet " 1120 "received via LIF %s", pname, lif->lif_name); 1121 free_pkt_entry(plp); 1122 return; 1123 } 1124 1125 /* 1126 * Find the corresponding state machine. 1127 */ 1128 xid = pkt_get_xid(plp->pkt, B_FALSE); 1129 for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 1130 dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 1131 if (dsmp->dsm_lif == lif) 1132 break; 1133 } 1134 1135 if (dsmp == NULL) 1136 goto drop; 1137 1138 if (pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 1139 /* 1140 * We've got an ACK/NAK; make sure it's acceptable and cancel 1141 * the REQUEST retransmissions. 1142 */ 1143 accept_v4_acknak(dsmp, plp); 1144 } else { 1145 if (is_bound_state(dsmp->dsm_state)) 1146 goto drop; 1147 /* 1148 * Must be an OFFER or a BOOTP message: enqueue it for later 1149 * processing by select_best(). 1150 */ 1151 pkt_smach_enqueue(dsmp, plp); 1152 } 1153 return; 1154 drop: 1155 dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored %s packet xid " 1156 "%x received via LIF %s; %s", pname, xid, lif->lif_name, 1157 dsmp == NULL ? "unknown state machine" : "bound"); 1158 free_pkt_entry(plp); 1159 } 1160 1161 /* 1162 * dhcp_restart(): restarts DHCP (from INIT) on a given state machine, but only 1163 * if we're leasing addresses. Doesn't restart for information- 1164 * only interfaces. 1165 * 1166 * input: dhcp_smach_t *: the state machine to restart DHCP on 1167 * output: void 1168 */ 1169 1170 void 1171 dhcp_restart(dhcp_smach_t *dsmp) 1172 { 1173 if (dsmp->dsm_state == INFORM_SENT || dsmp->dsm_state == INFORMATION) 1174 return; 1175 1176 /* 1177 * As we're returning to INIT state, we need to discard any leases we 1178 * may have, and (for v4) canonize the LIF. There's a bit of tension 1179 * between keeping around a possibly still working address, and obeying 1180 * the RFCs. A more elaborate design would be to mark the addresses as 1181 * DEPRECATED, and then start a removal timer. Such a design would 1182 * probably compromise testing. 1183 */ 1184 deprecate_leases(dsmp); 1185 1186 if (!set_start_timer(dsmp)) { 1187 dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " 1188 "reverting to INIT state on %s", dsmp->dsm_name); 1189 1190 (void) set_smach_state(dsmp, INIT); 1191 dsmp->dsm_dflags |= DHCP_IF_FAILED; 1192 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 1193 } else { 1194 dhcpmsg(MSG_DEBUG, "dhcp_restart: restarting DHCP on %s", 1195 dsmp->dsm_name); 1196 } 1197 } 1198 1199 /* 1200 * stop_requesting(): decides when to stop retransmitting REQUESTs 1201 * 1202 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from 1203 * unsigned int: the number of REQUESTs sent so far 1204 * output: boolean_t: B_TRUE if retransmissions should stop 1205 */ 1206 1207 static boolean_t 1208 stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) 1209 { 1210 uint_t maxreq; 1211 1212 maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; 1213 if (n_requests >= maxreq) { 1214 1215 dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", 1216 dsmp->dsm_name); 1217 1218 request_failed(dsmp); 1219 return (B_TRUE); 1220 } else { 1221 return (B_FALSE); 1222 } 1223 } 1224