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