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 (plp->opts[CD_LEASE_TIME] == NULL || 612 plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 613 dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " 614 "%s missing mandatory lease option, ignored", 615 dsmp->dsm_name); 616 dsmp->dsm_bad_offers++; 617 free_pkt_entry(plp); 618 return; 619 } 620 if ((dsmp->dsm_state == RENEWING || 621 dsmp->dsm_state == REBINDING) && 622 dsmp->dsm_leases->dl_lifs->lif_addr != 623 plp->pkt->yiaddr.s_addr) { 624 dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " 625 "packet has a different IP address (%s), ignored", 626 inet_ntoa(plp->pkt->yiaddr)); 627 dsmp->dsm_bad_offers++; 628 free_pkt_entry(plp); 629 return; 630 } 631 } 632 633 /* 634 * looks good; cancel the retransmission timer and unregister 635 * the acknak handler. ACK to BOUND, NAK back to SELECTING. 636 */ 637 638 stop_pkt_retransmission(dsmp); 639 640 if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { 641 dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", 642 dsmp->dsm_name); 643 dsmp->dsm_bad_offers++; 644 free_pkt_entry(plp); 645 dhcp_restart(dsmp); 646 647 /* 648 * remove any bogus cached configuration we might have 649 * around (right now would only happen if we got here 650 * from INIT_REBOOT). 651 */ 652 653 (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 654 return; 655 } 656 657 if (plp->opts[CD_SERVER_ID] == NULL || 658 plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 659 dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " 660 "server id, restarting DHCP on %s", dsmp->dsm_name); 661 dsmp->dsm_bad_offers++; 662 free_pkt_entry(plp); 663 dhcp_restart(dsmp); 664 return; 665 } 666 667 if (plp->opts[CD_MESSAGE] != NULL) { 668 print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, 669 plp->opts[CD_MESSAGE]->len); 670 } 671 672 dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); 673 if (!dhcp_bound(dsmp, plp)) { 674 dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " 675 "for %s", dsmp->dsm_name); 676 dhcp_restart(dsmp); 677 } 678 } 679 680 /* 681 * accept_v6_message(): determine what to do with a DHCPv6 message based on the 682 * current state. 683 * 684 * input: dhcp_smach_t *: the state machine to handle the message 685 * PKT_LIST *: the DHCPv6 message 686 * const char *: type of message (for logging) 687 * uchar_t: type of message (extracted from packet) 688 * output: void 689 */ 690 691 static void 692 accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, 693 uchar_t recv_type) 694 { 695 const dhcpv6_option_t *d6o; 696 uint_t olen; 697 const char *estr, *msg; 698 uint_t msglen; 699 int status; 700 701 /* 702 * All valid DHCPv6 messages must have our Client ID specified. 703 */ 704 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); 705 olen -= sizeof (*d6o); 706 if (d6o == NULL || olen != dsmp->dsm_cidlen || 707 memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { 708 dhcpmsg(MSG_VERBOSE, 709 "accept_v6_message: discarded %s on %s: %s Client ID", 710 pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); 711 free_pkt_entry(plp); 712 return; 713 } 714 715 /* 716 * All valid DHCPv6 messages must have a Server ID specified. 717 * 718 * If this is a Reply and it's not in response to Solicit, Confirm, 719 * Rebind, or Information-Request, then it must also match the Server 720 * ID we're expecting. 721 * 722 * For Reply in the Solicit, Confirm, Rebind, and Information-Request 723 * cases, the Server ID needs to be saved. This is done inside of 724 * dhcp_bound(). 725 */ 726 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); 727 if (d6o == NULL) { 728 dhcpmsg(MSG_DEBUG, 729 "accept_v6_message: discarded %s on %s: no Server ID", 730 pname, dsmp->dsm_name); 731 free_pkt_entry(plp); 732 return; 733 } 734 if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && 735 dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && 736 dsmp->dsm_state != INFORM_SENT) { 737 olen -= sizeof (*d6o); 738 if (olen != dsmp->dsm_serveridlen || 739 memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { 740 dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " 741 "%s: wrong Server ID", pname, dsmp->dsm_name); 742 free_pkt_entry(plp); 743 return; 744 } 745 } 746 747 /* 748 * Break out of the switch if the input message needs to be discarded. 749 * Return from the function if the message has been enqueued or 750 * consumed. 751 */ 752 switch (dsmp->dsm_state) { 753 case SELECTING: 754 /* A Reply message signifies a Rapid-Commit. */ 755 if (recv_type == DHCPV6_MSG_REPLY) { 756 if (dhcpv6_pkt_option(plp, NULL, 757 DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { 758 dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " 759 "on %s lacks Rapid-Commit; ignoring", 760 dsmp->dsm_name); 761 break; 762 } 763 dhcpmsg(MSG_VERBOSE, 764 "accept_v6_message: rapid-commit Reply on %s", 765 dsmp->dsm_name); 766 cancel_offer_timer(dsmp); 767 goto rapid_commit; 768 } 769 770 /* Otherwise, we're looking for Advertisements. */ 771 if (recv_type != DHCPV6_MSG_ADVERTISE) 772 break; 773 774 /* 775 * Special case: if this advertisement has preference 255, then 776 * we must stop right now and select this server. 777 */ 778 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, 779 &olen); 780 if (d6o != NULL && olen == sizeof (*d6o) + 1 && 781 *(const uchar_t *)(d6o + 1) == 255) { 782 pkt_smach_enqueue(dsmp, plp); 783 dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" 784 " immediate Request on %s", dsmp->dsm_name); 785 dhcp_requesting(NULL, dsmp); 786 } else { 787 pkt_smach_enqueue(dsmp, plp); 788 } 789 return; 790 791 case PRE_BOUND: 792 case BOUND: 793 /* 794 * Not looking for anything in these states. (If we 795 * implemented reconfigure, that might go here.) 796 */ 797 break; 798 799 case REQUESTING: 800 case INIT_REBOOT: 801 case RENEWING: 802 case REBINDING: 803 case INFORM_SENT: 804 /* 805 * We're looking for Reply messages. 806 */ 807 if (recv_type != DHCPV6_MSG_REPLY) 808 break; 809 dhcpmsg(MSG_VERBOSE, 810 "accept_v6_message: received Reply message on %s", 811 dsmp->dsm_name); 812 rapid_commit: 813 /* 814 * Extract the status code option. If one is present and the 815 * request failed, then try to go to another advertisement in 816 * the list or restart the selection machinery. 817 */ 818 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 819 &olen); 820 status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 821 /* 822 * Check for the UseMulticast status code. If this is present, 823 * and if we were actually using unicast, then drop back and 824 * try again. If we weren't using unicast, then just pretend 825 * we never saw this message -- the peer is confused. (TAHI 826 * does this.) 827 */ 828 if (status == DHCPV6_STAT_USEMCAST) { 829 if (IN6_IS_ADDR_MULTICAST( 830 &dsmp->dsm_send_dest.v6.sin6_addr)) { 831 break; 832 } else { 833 free_pkt_entry(plp); 834 dsmp->dsm_send_dest.v6.sin6_addr = 835 ipv6_all_dhcp_relay_and_servers; 836 retransmit_now(dsmp); 837 return; 838 } 839 } 840 print_server_msg(dsmp, msg, msglen); 841 /* 842 * We treat NoBinding at the top level as "success." Granted, 843 * this doesn't make much sense, but the TAHI test suite does 844 * this. NoBinding really only makes sense in the context of a 845 * specific IA, as it refers to the GUID:IAID binding, so 846 * ignoring it at the top level is safe. 847 */ 848 if (status == DHCPV6_STAT_SUCCESS || 849 status == DHCPV6_STAT_NOBINDING) { 850 if (dhcp_bound(dsmp, plp)) { 851 /* 852 * dhcp_bound will stop retransmission on 853 * success, if that's called for. 854 */ 855 server_unicast_option(dsmp, plp); 856 } else { 857 stop_pkt_retransmission(dsmp); 858 dhcpmsg(MSG_WARNING, "accept_v6_message: " 859 "dhcp_bound failed for %s", dsmp->dsm_name); 860 (void) remove_hostconf(dsmp->dsm_name, 861 dsmp->dsm_isv6); 862 if (dsmp->dsm_state != INFORM_SENT) 863 dhcp_restart(dsmp); 864 } 865 } else { 866 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 867 estr); 868 stop_pkt_retransmission(dsmp); 869 free_pkt_entry(plp); 870 if (dsmp->dsm_state == INFORM_SENT) { 871 (void) set_smach_state(dsmp, INIT); 872 ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); 873 } else { 874 (void) remove_hostconf(dsmp->dsm_name, 875 dsmp->dsm_isv6); 876 request_failed(dsmp); 877 } 878 } 879 return; 880 881 case DECLINING: 882 /* 883 * We're looking for Reply messages. 884 */ 885 if (recv_type != DHCPV6_MSG_REPLY) 886 break; 887 stop_pkt_retransmission(dsmp); 888 /* 889 * Extract the status code option. Note that it's not a 890 * failure if the server reports an error. 891 */ 892 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 893 &olen); 894 if (dhcpv6_status_code(d6o, olen, &estr, &msg, 895 &msglen) == DHCPV6_STAT_SUCCESS) { 896 print_server_msg(dsmp, msg, msglen); 897 } else { 898 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 899 estr); 900 } 901 free_pkt_entry(plp); 902 if (dsmp->dsm_leases == NULL) { 903 dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " 904 "leases left; restarting", dsmp->dsm_name); 905 dhcp_restart(dsmp); 906 } else if (dsmp->dsm_lif_wait == 0) { 907 (void) set_smach_state(dsmp, BOUND); 908 } else { 909 (void) set_smach_state(dsmp, PRE_BOUND); 910 } 911 return; 912 913 case RELEASING: 914 /* 915 * We're looking for Reply messages. 916 */ 917 if (recv_type != DHCPV6_MSG_REPLY) 918 break; 919 stop_pkt_retransmission(dsmp); 920 /* 921 * Extract the status code option. 922 */ 923 d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 924 &olen); 925 if (dhcpv6_status_code(d6o, olen, &estr, &msg, 926 &msglen) == DHCPV6_STAT_SUCCESS) { 927 print_server_msg(dsmp, msg, msglen); 928 } else { 929 dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 930 estr); 931 } 932 free_pkt_entry(plp); 933 finished_smach(dsmp, DHCP_IPC_SUCCESS); 934 return; 935 } 936 937 /* 938 * Break from above switch means that the message must be discarded. 939 */ 940 dhcpmsg(MSG_VERBOSE, 941 "accept_v6_message: discarded v6 %s on %s; state %s", 942 pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); 943 free_pkt_entry(plp); 944 } 945 946 /* 947 * dhcp_acknak_common(): Processes reception of an ACK or NAK packet on the 948 * global socket -- broadcast packets for IPv4, all 949 * packets for DHCPv6. 950 * 951 * input: iu_eh_t *: unused 952 * int: the global file descriptor the ACK/NAK arrived on 953 * short: unused 954 * iu_event_id_t: unused 955 * void *: unused 956 * output: void 957 */ 958 959 /* ARGSUSED */ 960 void 961 dhcp_acknak_common(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 962 void *arg) 963 { 964 PKT_LIST *plp; 965 dhcp_pif_t *pif; 966 uchar_t recv_type; 967 const char *pname; 968 uint_t xid; 969 dhcp_smach_t *dsmp; 970 boolean_t isv6 = (fd == v6_sock_fd); 971 972 if ((plp = recv_pkt(fd, get_max_mtu(isv6), isv6, B_FALSE)) == NULL) 973 return; 974 975 pif = lookup_pif_by_index(plp->ifindex, isv6); 976 if (pif == NULL) { 977 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored packet " 978 "received on v%d ifIndex %d", isv6 ? 6 : 4, plp->ifindex); 979 free_pkt_entry(plp); 980 return; 981 } 982 983 recv_type = pkt_recv_type(plp); 984 pname = pkt_type_to_string(recv_type, isv6); 985 if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 986 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " 987 "received via broadcast on %s", pname, pif->pif_name); 988 free_pkt_entry(plp); 989 return; 990 } 991 992 if (isv6 && recv_type == DHCPV6_MSG_RECONFIGURE) { 993 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored v6 " 994 "Reconfigure received via %s", pif->pif_name); 995 free_pkt_entry(plp); 996 return; 997 } 998 999 /* 1000 * Find the corresponding state machine not using DLPI. 1001 * 1002 * Note that DHCPv6 Reconfigure would be special: it's not the reply to 1003 * any transaction, and thus we would need to search on transaction ID 1004 * zero (all state machines) to find the match. However, Reconfigure 1005 * is not yet supported. 1006 */ 1007 xid = pkt_get_xid(plp->pkt, isv6); 1008 for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; 1009 dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { 1010 if (dsmp->dsm_lif->lif_pif == pif) 1011 break; 1012 } 1013 if (dsmp == NULL || dsmp->dsm_using_dlpi) { 1014 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " 1015 "received via broadcast %s; %s", pname, pif->pif_name, 1016 dsmp == NULL ? "unknown state machine" : "not using DLPI"); 1017 free_pkt_entry(plp); 1018 return; 1019 } 1020 1021 /* 1022 * We've got a packet; make sure it's acceptable and cancel the REQUEST 1023 * retransmissions. 1024 */ 1025 if (isv6) 1026 accept_v6_message(dsmp, plp, pname, recv_type); 1027 else 1028 accept_v4_acknak(dsmp, plp); 1029 } 1030 1031 /* 1032 * request_failed(): Attempt to request an address has failed. Take an 1033 * appropriate action. 1034 * 1035 * input: dhcp_smach_t *: state machine that has failed 1036 * output: void 1037 */ 1038 1039 static void 1040 request_failed(dhcp_smach_t *dsmp) 1041 { 1042 PKT_LIST *offer; 1043 1044 dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; 1045 if ((offer = select_best(dsmp)) != NULL) { 1046 insque(offer, &dsmp->dsm_recv_pkt_list); 1047 dhcp_requesting(NULL, dsmp); 1048 } else { 1049 dhcpmsg(MSG_INFO, "no offers left on %s; restarting", 1050 dsmp->dsm_name); 1051 dhcp_selecting(dsmp); 1052 } 1053 } 1054 1055 /* 1056 * dhcp_acknak_lif(): Processes reception of an ACK or NAK packet on a given 1057 * logical interface for IPv4 (only). 1058 * 1059 * input: iu_eh_t *: unused 1060 * int: the global file descriptor the ACK/NAK arrived on 1061 * short: unused 1062 * iu_event_id_t: the id of this event callback with the handler 1063 * void *: pointer to logical interface receiving message 1064 * output: void 1065 */ 1066 1067 /* ARGSUSED */ 1068 void 1069 dhcp_acknak_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 1070 void *arg) 1071 { 1072 dhcp_lif_t *lif = arg; 1073 PKT_LIST *plp; 1074 uchar_t recv_type; 1075 const char *pname; 1076 uint_t xid; 1077 dhcp_smach_t *dsmp; 1078 1079 if ((plp = recv_pkt(fd, lif->lif_max, B_FALSE, B_FALSE)) == NULL) 1080 return; 1081 1082 recv_type = pkt_recv_type(plp); 1083 pname = pkt_type_to_string(recv_type, B_FALSE); 1084 1085 if (!pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK)) { 1086 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored v4 %s packet " 1087 "received via LIF %s", pname, lif->lif_name); 1088 free_pkt_entry(plp); 1089 return; 1090 } 1091 1092 /* 1093 * Find the corresponding state machine not using DLPI. 1094 */ 1095 xid = pkt_get_xid(plp->pkt, B_FALSE); 1096 for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 1097 dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 1098 if (dsmp->dsm_lif == lif) 1099 break; 1100 } 1101 if (dsmp == NULL || dsmp->dsm_using_dlpi) { 1102 dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored %s packet xid " 1103 "%x received via LIF %s; %s", pname, xid, lif->lif_name, 1104 dsmp == NULL ? "unknown state machine" : "not using DLPI"); 1105 free_pkt_entry(plp); 1106 return; 1107 } 1108 1109 /* 1110 * We've got a packet; make sure it's acceptable and cancel the REQUEST 1111 * retransmissions. 1112 */ 1113 accept_v4_acknak(dsmp, plp); 1114 } 1115 1116 /* 1117 * dhcp_restart(): restarts DHCP (from INIT) on a given state machine 1118 * 1119 * input: dhcp_smach_t *: the state machine to restart DHCP on 1120 * output: void 1121 */ 1122 1123 void 1124 dhcp_restart(dhcp_smach_t *dsmp) 1125 { 1126 /* 1127 * As we're returning to INIT state, we need to discard any leases we 1128 * may have, and (for v4) canonize the LIF. There's a bit of tension 1129 * between keeping around a possibly still working address, and obeying 1130 * the RFCs. A more elaborate design would be to mark the addresses as 1131 * DEPRECATED, and then start a removal timer. Such a design would 1132 * probably compromise testing. 1133 */ 1134 deprecate_leases(dsmp); 1135 1136 if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, dsmp) == -1) { 1137 dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " 1138 "reverting to INIT state on %s", dsmp->dsm_name); 1139 1140 (void) set_smach_state(dsmp, INIT); 1141 dsmp->dsm_dflags |= DHCP_IF_FAILED; 1142 ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 1143 } else { 1144 hold_smach(dsmp); 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