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