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