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