10Sstevel@tonic-gate /* 20Sstevel@tonic-gate * CDDL HEADER START 30Sstevel@tonic-gate * 40Sstevel@tonic-gate * The contents of this file are subject to the terms of the 52157Sdh155122 * Common Development and Distribution License (the "License"). 62157Sdh155122 * You may not use this file except in compliance with the License. 70Sstevel@tonic-gate * 80Sstevel@tonic-gate * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 90Sstevel@tonic-gate * or http://www.opensolaris.org/os/licensing. 100Sstevel@tonic-gate * See the License for the specific language governing permissions 110Sstevel@tonic-gate * and limitations under the License. 120Sstevel@tonic-gate * 130Sstevel@tonic-gate * When distributing Covered Code, include this CDDL HEADER in each 140Sstevel@tonic-gate * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 150Sstevel@tonic-gate * If applicable, add the following below this CDDL HEADER, with the 160Sstevel@tonic-gate * fields enclosed by brackets "[]" replaced with your own identifying 170Sstevel@tonic-gate * information: Portions Copyright [yyyy] [name of copyright owner] 180Sstevel@tonic-gate * 190Sstevel@tonic-gate * CDDL HEADER END 200Sstevel@tonic-gate */ 210Sstevel@tonic-gate /* 22*8485SPeter.Memishian@Sun.COM * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 230Sstevel@tonic-gate * Use is subject to license terms. 240Sstevel@tonic-gate * 250Sstevel@tonic-gate * REQUESTING state of the client state machine. 260Sstevel@tonic-gate */ 270Sstevel@tonic-gate 283431Scarlsonj #include <stdlib.h> 293431Scarlsonj #include <string.h> 303431Scarlsonj #include <search.h> 310Sstevel@tonic-gate #include <sys/types.h> 320Sstevel@tonic-gate #include <netinet/in.h> 330Sstevel@tonic-gate #include <netinet/dhcp.h> 340Sstevel@tonic-gate #include <netinet/udp.h> 350Sstevel@tonic-gate #include <netinet/ip_var.h> 360Sstevel@tonic-gate #include <netinet/udp_var.h> 373431Scarlsonj #include <arpa/inet.h> 380Sstevel@tonic-gate #include <dhcp_hostconf.h> 393431Scarlsonj #include <dhcpagent_util.h> 400Sstevel@tonic-gate #include <dhcpmsg.h> 410Sstevel@tonic-gate 420Sstevel@tonic-gate #include "states.h" 430Sstevel@tonic-gate #include "util.h" 440Sstevel@tonic-gate #include "packet.h" 450Sstevel@tonic-gate #include "interface.h" 460Sstevel@tonic-gate #include "agent.h" 470Sstevel@tonic-gate 483431Scarlsonj static PKT_LIST *select_best(dhcp_smach_t *); 493431Scarlsonj static void request_failed(dhcp_smach_t *); 500Sstevel@tonic-gate static stop_func_t stop_requesting; 510Sstevel@tonic-gate 520Sstevel@tonic-gate /* 533431Scarlsonj * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING 543431Scarlsonj * state. This is a separate function because a NoBinding 553431Scarlsonj * response can also cause us to do this. 563431Scarlsonj * 573431Scarlsonj * input: dhcp_smach_t *: the state machine 583431Scarlsonj * output: none 593431Scarlsonj */ 603431Scarlsonj 613431Scarlsonj void 623431Scarlsonj send_v6_request(dhcp_smach_t *dsmp) 633431Scarlsonj { 643431Scarlsonj dhcp_pkt_t *dpkt; 653431Scarlsonj dhcpv6_ia_na_t d6in; 663431Scarlsonj 673431Scarlsonj dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST); 683431Scarlsonj (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, 693431Scarlsonj dsmp->dsm_serveridlen); 703431Scarlsonj 713431Scarlsonj /* Add an IA_NA option for our controlling LIF */ 723431Scarlsonj d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); 733431Scarlsonj d6in.d6in_t1 = htonl(0); 743431Scarlsonj d6in.d6in_t2 = htonl(0); 753431Scarlsonj (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, 763431Scarlsonj (dhcpv6_option_t *)&d6in + 1, 773431Scarlsonj sizeof (d6in) - sizeof (dhcpv6_option_t)); 783431Scarlsonj 793431Scarlsonj /* Add required Option Request option */ 803431Scarlsonj (void) add_pkt_prl(dpkt, dsmp); 813431Scarlsonj 823431Scarlsonj (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting, 833431Scarlsonj DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT); 843431Scarlsonj 853431Scarlsonj /* For DHCPv6, state switch cannot fail */ 863431Scarlsonj (void) set_smach_state(dsmp, REQUESTING); 873431Scarlsonj } 883431Scarlsonj 893431Scarlsonj /* 903431Scarlsonj * server_unicast_option(): determines the server address to use based on the 913431Scarlsonj * DHCPv6 Server Unicast option present in the given 923431Scarlsonj * packet. 933431Scarlsonj * 943431Scarlsonj * input: dhcp_smach_t *: the state machine 953431Scarlsonj * PKT_LIST *: received packet (Advertisement or Reply) 963431Scarlsonj * output: none 973431Scarlsonj */ 983431Scarlsonj 993431Scarlsonj void 1003431Scarlsonj server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp) 1013431Scarlsonj { 1023431Scarlsonj const dhcpv6_option_t *d6o; 1033431Scarlsonj uint_t olen; 1043431Scarlsonj 1053431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen); 1063431Scarlsonj olen -= sizeof (*d6o); 1073431Scarlsonj /* LINTED: no consequent */ 1083431Scarlsonj if (d6o == NULL) { 1093431Scarlsonj /* No Server Unicast option specified */ 1103431Scarlsonj } else if (olen != sizeof (dsmp->dsm_server)) { 1113431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server " 1123431Scarlsonj "Unicast option with bad length", 1133431Scarlsonj pkt_type_to_string(pkt_recv_type(plp), B_TRUE)); 1143431Scarlsonj } else { 1153431Scarlsonj in6_addr_t addr; 1163431Scarlsonj 1173431Scarlsonj (void) memcpy(&addr, d6o + 1, olen); 1183431Scarlsonj if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { 1193431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 1203431Scarlsonj "to unspecified address ignored"); 1213431Scarlsonj } else if (IN6_IS_ADDR_MULTICAST(&addr)) { 1223431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 1233431Scarlsonj "to multicast address ignored"); 1243431Scarlsonj } else if (IN6_IS_ADDR_V4COMPAT(&addr) || 1253431Scarlsonj IN6_IS_ADDR_V4MAPPED(&addr)) { 1263431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 1273431Scarlsonj "to invalid address ignored"); 1283431Scarlsonj } else { 1293431Scarlsonj dsmp->dsm_server = addr; 1303431Scarlsonj } 1313431Scarlsonj } 1323431Scarlsonj } 1333431Scarlsonj 1343431Scarlsonj /* 1350Sstevel@tonic-gate * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers. 1360Sstevel@tonic-gate * if so, chooses the best one, sends a REQUEST to the 1370Sstevel@tonic-gate * server and registers an event handler to receive 1383431Scarlsonj * the ACK/NAK. This may be called by the offer timer or 1393431Scarlsonj * by any function that wants to check for offers after 1403431Scarlsonj * canceling that timer. 1410Sstevel@tonic-gate * 1423431Scarlsonj * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback 1433431Scarlsonj * void *: the state machine receiving OFFER packets 1440Sstevel@tonic-gate * output: void 1450Sstevel@tonic-gate */ 1460Sstevel@tonic-gate 1470Sstevel@tonic-gate void 1480Sstevel@tonic-gate dhcp_requesting(iu_tq_t *tqp, void *arg) 1490Sstevel@tonic-gate { 1503431Scarlsonj dhcp_smach_t *dsmp = arg; 1510Sstevel@tonic-gate dhcp_pkt_t *dpkt; 1520Sstevel@tonic-gate PKT_LIST *offer; 1530Sstevel@tonic-gate lease_t lease; 1543431Scarlsonj boolean_t isv6 = dsmp->dsm_isv6; 1552157Sdh155122 1563431Scarlsonj /* 1573431Scarlsonj * We assume here that if tqp is set, then this means we're being 1583431Scarlsonj * called back by the offer wait timer. If so, then drop our hold 1593431Scarlsonj * on the state machine. Otherwise, cancel the timer if it's running. 1603431Scarlsonj */ 1613431Scarlsonj if (tqp != NULL) { 1623431Scarlsonj dhcpmsg(MSG_VERBOSE, 1633431Scarlsonj "dhcp_requesting: offer wait timer on v%d %s", 1643431Scarlsonj isv6 ? 6 : 4, dsmp->dsm_name); 1653431Scarlsonj dsmp->dsm_offer_timer = -1; 1663431Scarlsonj if (!verify_smach(dsmp)) 1673431Scarlsonj return; 1683431Scarlsonj } else { 1693431Scarlsonj cancel_offer_timer(dsmp); 1700Sstevel@tonic-gate } 1710Sstevel@tonic-gate 1720Sstevel@tonic-gate /* 1730Sstevel@tonic-gate * select the best OFFER; all others pitched. 1740Sstevel@tonic-gate */ 1750Sstevel@tonic-gate 1763431Scarlsonj offer = select_best(dsmp); 1770Sstevel@tonic-gate if (offer == NULL) { 1780Sstevel@tonic-gate 1793431Scarlsonj dhcpmsg(MSG_VERBOSE, 1803431Scarlsonj "no OFFERs/Advertisements on %s, waiting...", 1813431Scarlsonj dsmp->dsm_name); 1820Sstevel@tonic-gate 1830Sstevel@tonic-gate /* 1840Sstevel@tonic-gate * no acceptable OFFERs have come in. reschedule 1853431Scarlsonj * ourself for callback. 1860Sstevel@tonic-gate */ 1870Sstevel@tonic-gate 1883431Scarlsonj if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, 1893431Scarlsonj dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) { 1900Sstevel@tonic-gate 1910Sstevel@tonic-gate /* 1920Sstevel@tonic-gate * ugh. the best we can do at this point is 1930Sstevel@tonic-gate * revert back to INIT and wait for a user to 1940Sstevel@tonic-gate * restart us. 1950Sstevel@tonic-gate */ 1960Sstevel@tonic-gate 1970Sstevel@tonic-gate dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " 1980Sstevel@tonic-gate "reschedule callback, reverting to INIT state on " 1993431Scarlsonj "%s", dsmp->dsm_name); 2000Sstevel@tonic-gate 2013431Scarlsonj stop_pkt_retransmission(dsmp); 2023431Scarlsonj (void) set_smach_state(dsmp, INIT); 2033431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 2043431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 2053431Scarlsonj } else { 2063431Scarlsonj hold_smach(dsmp); 2070Sstevel@tonic-gate } 2080Sstevel@tonic-gate 2090Sstevel@tonic-gate return; 2100Sstevel@tonic-gate } 2110Sstevel@tonic-gate 2120Sstevel@tonic-gate /* 2133431Scarlsonj * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly 2143431Scarlsonj * declines all other offers we've received. We can no longer use any 2153431Scarlsonj * cached offers, so we must discard them now. With DHCPv6, though, 2163431Scarlsonj * we're permitted to hang onto the advertisements (offers) and try 2173431Scarlsonj * them if the preferred one doesn't pan out. 2180Sstevel@tonic-gate */ 2193431Scarlsonj if (!isv6) 2203431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 2210Sstevel@tonic-gate 2223431Scarlsonj /* stop collecting packets. */ 2230Sstevel@tonic-gate 2243431Scarlsonj stop_pkt_retransmission(dsmp); 2250Sstevel@tonic-gate 2260Sstevel@tonic-gate /* 2273431Scarlsonj * For IPv4, check to see whether we got an OFFER or a BOOTP packet. 2283431Scarlsonj * If we got a BOOTP packet, go to the BOUND state now. 2290Sstevel@tonic-gate */ 2303431Scarlsonj if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { 2313431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 2323431Scarlsonj 2333431Scarlsonj if (!set_smach_state(dsmp, REQUESTING)) { 2343431Scarlsonj dhcp_restart(dsmp); 2353431Scarlsonj return; 2363431Scarlsonj } 2373431Scarlsonj 2383431Scarlsonj if (!dhcp_bound(dsmp, offer)) { 2393431Scarlsonj dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " 2403431Scarlsonj "failed for %s", dsmp->dsm_name); 2413431Scarlsonj dhcp_restart(dsmp); 2423431Scarlsonj return; 2433431Scarlsonj } 2443431Scarlsonj 2453431Scarlsonj return; 2460Sstevel@tonic-gate } 2473431Scarlsonj 2483431Scarlsonj if (isv6) { 2493431Scarlsonj const char *estr, *msg; 2503431Scarlsonj const dhcpv6_option_t *d6o; 2513431Scarlsonj uint_t olen, msglen; 2523431Scarlsonj 2533431Scarlsonj /* If there's a Status Code option, print the message */ 2543431Scarlsonj d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, 2553431Scarlsonj &olen); 2563431Scarlsonj (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 2573431Scarlsonj print_server_msg(dsmp, msg, msglen); 2583431Scarlsonj 2593431Scarlsonj /* Copy in the Server ID (guaranteed to be present now) */ 2603431Scarlsonj if (!save_server_id(dsmp, offer)) 2613431Scarlsonj goto failure; 2623431Scarlsonj 2633431Scarlsonj /* 2643431Scarlsonj * Determine how to send this message. If the Advertisement 2653431Scarlsonj * (offer) has the unicast option, then use the address 2663431Scarlsonj * specified in the option. Otherwise, send via multicast. 2673431Scarlsonj */ 2683431Scarlsonj server_unicast_option(dsmp, offer); 2693431Scarlsonj 2703431Scarlsonj send_v6_request(dsmp); 2713431Scarlsonj } else { 2723431Scarlsonj /* if we got a message from the server, display it. */ 2733431Scarlsonj if (offer->opts[CD_MESSAGE] != NULL) { 2743431Scarlsonj print_server_msg(dsmp, 2753431Scarlsonj (char *)offer->opts[CD_MESSAGE]->value, 2763431Scarlsonj offer->opts[CD_MESSAGE]->len); 2773431Scarlsonj } 2783431Scarlsonj 2793431Scarlsonj /* 2803431Scarlsonj * assemble a DHCPREQUEST, with the ciaddr field set to 0, 2813431Scarlsonj * since we got here from the INIT state. 2823431Scarlsonj */ 2833431Scarlsonj 2843431Scarlsonj dpkt = init_pkt(dsmp, REQUEST); 2853431Scarlsonj 2863431Scarlsonj /* 2873431Scarlsonj * Grab the lease out of the OFFER; we know it's valid because 2883431Scarlsonj * select_best() already checked. The max dhcp message size 2893431Scarlsonj * option is set to the interface max, minus the size of the 2903431Scarlsonj * udp and ip headers. 2913431Scarlsonj */ 2923431Scarlsonj 2933431Scarlsonj (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, 2943431Scarlsonj sizeof (lease_t)); 2953431Scarlsonj 2963431Scarlsonj (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); 2973431Scarlsonj (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, 2983431Scarlsonj htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); 2993431Scarlsonj (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, 3003431Scarlsonj offer->pkt->yiaddr.s_addr); 3013431Scarlsonj (void) add_pkt_opt(dpkt, CD_SERVER_ID, 3023431Scarlsonj offer->opts[CD_SERVER_ID]->value, 3033431Scarlsonj offer->opts[CD_SERVER_ID]->len); 3043431Scarlsonj 3053448Sdh155122 if (class_id_len != 0) { 3063448Sdh155122 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, 3073448Sdh155122 class_id_len); 3083448Sdh155122 } 3093431Scarlsonj (void) add_pkt_prl(dpkt, dsmp); 3103431Scarlsonj 3113431Scarlsonj /* 3123431Scarlsonj * dsm_reqhost was set for this state machine in 3133431Scarlsonj * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a 3143431Scarlsonj * host name was found 3153431Scarlsonj */ 3163431Scarlsonj if (dsmp->dsm_reqhost != NULL) { 3173431Scarlsonj (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, 3183431Scarlsonj strlen(dsmp->dsm_reqhost)); 3193431Scarlsonj } 3203431Scarlsonj (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 3213431Scarlsonj 3223431Scarlsonj /* 3233431Scarlsonj * send out the REQUEST, trying retransmissions. either a NAK 3243431Scarlsonj * or too many REQUEST attempts will revert us to SELECTING. 3253431Scarlsonj */ 3263431Scarlsonj 3273431Scarlsonj if (!set_smach_state(dsmp, REQUESTING)) { 3283431Scarlsonj dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " 3293431Scarlsonj "REQUESTING state; reverting to INIT on %s", 3303431Scarlsonj dsmp->dsm_name); 3313431Scarlsonj goto failure; 3323431Scarlsonj } 3333431Scarlsonj 3343431Scarlsonj (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 3353431Scarlsonj stop_requesting); 3363431Scarlsonj } 3370Sstevel@tonic-gate 3380Sstevel@tonic-gate /* all done with the offer */ 3393431Scarlsonj free_pkt_entry(offer); 3400Sstevel@tonic-gate 3413431Scarlsonj return; 3420Sstevel@tonic-gate 3433431Scarlsonj failure: 3443431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 3453431Scarlsonj (void) set_smach_state(dsmp, INIT); 3463431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 3473431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 3480Sstevel@tonic-gate } 3490Sstevel@tonic-gate 3500Sstevel@tonic-gate /* 3513431Scarlsonj * compute_points_v6(): compute the number of "points" for a given v6 3523431Scarlsonj * advertisement. 3530Sstevel@tonic-gate * 3543431Scarlsonj * input: const PKT_LIST *: packet to inspect 3553431Scarlsonj * const dhcp_smach_t *: state machine that received the packet 3563431Scarlsonj * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. 3573431Scarlsonj */ 3583431Scarlsonj 3593431Scarlsonj static int 3603431Scarlsonj compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) 3613431Scarlsonj { 3623431Scarlsonj char abuf[INET6_ADDRSTRLEN]; 3633431Scarlsonj int points = 0; 3643431Scarlsonj const dhcpv6_option_t *d6o, *d6so; 3653431Scarlsonj uint_t olen, solen; 3663431Scarlsonj int i; 3673431Scarlsonj const char *estr, *msg; 3683431Scarlsonj uint_t msglen; 3693431Scarlsonj 3703431Scarlsonj /* 3713431Scarlsonj * Look through the packet contents. Valid packets must have our 3723431Scarlsonj * client ID and a server ID, which has already been checked by 3735381Smeem * dhcp_packet_lif. Bonus points for each option. 3743431Scarlsonj */ 3753431Scarlsonj 3763431Scarlsonj /* One point for having a valid message. */ 3773431Scarlsonj points++; 3783431Scarlsonj 3793431Scarlsonj /* 3803431Scarlsonj * Per RFC 3315, if the Advertise message says, "yes, we have no 3813431Scarlsonj * bananas today," then ignore the entire message. (Why it's just 3823431Scarlsonj * _this_ error and no other is a bit of a mystery, but a standard is a 3833431Scarlsonj * standard.) 3843431Scarlsonj */ 3853431Scarlsonj d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); 3863431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == 3873431Scarlsonj DHCPV6_STAT_NOADDRS) { 3883431Scarlsonj dhcpmsg(MSG_INFO, 3893431Scarlsonj "discard advertisement from %s on %s: no address status", 3903431Scarlsonj inet_ntop(AF_INET6, 3913431Scarlsonj &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, 3923431Scarlsonj abuf, sizeof (abuf)), dsmp->dsm_name); 3933431Scarlsonj return (-1); 3943431Scarlsonj } 3953431Scarlsonj 3963431Scarlsonj /* Two points for each batch of offered IP addresses */ 3973431Scarlsonj d6o = NULL; 3983431Scarlsonj while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, 3993431Scarlsonj &olen)) != NULL) { 4003431Scarlsonj 4013431Scarlsonj /* 4023431Scarlsonj * Note that it's possible to have "no bananas" on an 4033431Scarlsonj * individual IA. We must look for that here. 4043431Scarlsonj * 4053431Scarlsonj * RFC 3315 section 17.1.3 does not refer to the status code 4063431Scarlsonj * embedded in the IA itself. However, the TAHI test suite 4073431Scarlsonj * checks for this specific case. Because it's extremely 4083431Scarlsonj * unlikely that any usable server is going to report that it 4093431Scarlsonj * has no addresses on a network using DHCP for address 4103431Scarlsonj * assignment, we allow such messages to be dropped. 4113431Scarlsonj */ 4123431Scarlsonj d6so = dhcpv6_find_option( 4133431Scarlsonj (const char *)d6o + sizeof (dhcpv6_ia_na_t), 4143431Scarlsonj olen - sizeof (dhcpv6_ia_na_t), NULL, 4153431Scarlsonj DHCPV6_OPT_STATUS_CODE, &solen); 4163431Scarlsonj if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == 4173431Scarlsonj DHCPV6_STAT_NOADDRS) 4183431Scarlsonj return (-1); 4193431Scarlsonj points += 2; 4203431Scarlsonj } 4213431Scarlsonj 4223431Scarlsonj /* 4233431Scarlsonj * Note that we drive on in the case where there are no addresses. The 4243431Scarlsonj * hope here is that we'll at least get some useful configuration 4253431Scarlsonj * information. 4263431Scarlsonj */ 4273431Scarlsonj 4283431Scarlsonj /* One point for each requested option */ 4293431Scarlsonj for (i = 0; i < dsmp->dsm_prllen; i++) { 4303431Scarlsonj if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != 4313431Scarlsonj NULL) 4323431Scarlsonj points++; 4333431Scarlsonj } 4343431Scarlsonj 4353431Scarlsonj /* 4363431Scarlsonj * Ten points for each point of "preference." Note: the value 255 is 4373431Scarlsonj * special. It means "stop right now and select this server." 4383431Scarlsonj */ 4393431Scarlsonj d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); 4403431Scarlsonj if (d6o != NULL && olen == sizeof (*d6o) + 1) { 4413431Scarlsonj int pref = *(const uchar_t *)(d6o + 1); 4423431Scarlsonj 4433431Scarlsonj if (pref == 255) 4443431Scarlsonj return (-2); 4453431Scarlsonj points += 10 * pref; 4463431Scarlsonj } 4473431Scarlsonj 4483431Scarlsonj return (points); 4493431Scarlsonj } 4503431Scarlsonj 4513431Scarlsonj /* 4523431Scarlsonj * compute_points_v4(): compute the number of "points" for a given v4 offer. 4533431Scarlsonj * 4543431Scarlsonj * input: const PKT_LIST *: packet to inspect 4553431Scarlsonj * const dhcp_smach_t *: state machine that received the packet 4563431Scarlsonj * output: int: -1 to discard, >=0 for preference. 4573431Scarlsonj */ 4583431Scarlsonj 4593431Scarlsonj static int 4603431Scarlsonj compute_points_v4(const PKT_LIST *pkt) 4613431Scarlsonj { 4623431Scarlsonj int points = 0; 4633431Scarlsonj 4643431Scarlsonj if (pkt->opts[CD_DHCP_TYPE] == NULL) { 4653431Scarlsonj dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); 4663431Scarlsonj goto valid_offer; 4673431Scarlsonj } 4683431Scarlsonj 4693431Scarlsonj if (pkt->opts[CD_LEASE_TIME] == NULL) { 4703431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " 4713431Scarlsonj "time"); 4723431Scarlsonj return (-1); 4733431Scarlsonj } 4743431Scarlsonj 4753431Scarlsonj if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 4763431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 4773431Scarlsonj "lease time"); 4783431Scarlsonj return (-1); 4793431Scarlsonj } 4803431Scarlsonj 4813431Scarlsonj if (pkt->opts[CD_SERVER_ID] == NULL) { 4823431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " 4833431Scarlsonj "id"); 4843431Scarlsonj return (-1); 4853431Scarlsonj } 4863431Scarlsonj 4873431Scarlsonj if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 4883431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 4893431Scarlsonj "server id"); 4903431Scarlsonj return (-1); 4913431Scarlsonj } 4923431Scarlsonj 4933431Scarlsonj /* valid DHCP OFFER. see if we got our parameters. */ 4943431Scarlsonj dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); 4953431Scarlsonj points += 30; 4963431Scarlsonj 4973431Scarlsonj valid_offer: 4983431Scarlsonj if (pkt->rfc1048) 4993431Scarlsonj points += 5; 5003431Scarlsonj 5013431Scarlsonj /* 5023431Scarlsonj * also could be faked, though more difficult because the encapsulation 5033431Scarlsonj * is hard to encode on a BOOTP server; plus there's not as much real 5043431Scarlsonj * estate in the packet for options, so it's likely this option would 5053431Scarlsonj * get dropped. 5063431Scarlsonj */ 5073431Scarlsonj 5083431Scarlsonj if (pkt->opts[CD_VENDOR_SPEC] != NULL) 5093431Scarlsonj points += 80; 5103431Scarlsonj 5113431Scarlsonj if (pkt->opts[CD_SUBNETMASK] != NULL) 5123431Scarlsonj points++; 5133431Scarlsonj 5143431Scarlsonj if (pkt->opts[CD_ROUTER] != NULL) 5153431Scarlsonj points++; 5163431Scarlsonj 5173431Scarlsonj if (pkt->opts[CD_HOSTNAME] != NULL) 5183431Scarlsonj points += 5; 5193431Scarlsonj 5203431Scarlsonj return (points); 5213431Scarlsonj } 5223431Scarlsonj 5233431Scarlsonj /* 5243431Scarlsonj * select_best(): selects the best offer from a list of IPv4 OFFER packets or 5253431Scarlsonj * DHCPv6 Advertise packets. 5263431Scarlsonj * 5273431Scarlsonj * input: dhcp_smach_t *: state machine with enqueued offers 5280Sstevel@tonic-gate * output: PKT_LIST *: the best packet, or NULL if none are acceptable 5290Sstevel@tonic-gate */ 5300Sstevel@tonic-gate 5310Sstevel@tonic-gate static PKT_LIST * 5323431Scarlsonj select_best(dhcp_smach_t *dsmp) 5330Sstevel@tonic-gate { 5343431Scarlsonj PKT_LIST *current = dsmp->dsm_recv_pkt_list; 5353431Scarlsonj PKT_LIST *next, *best = NULL; 5363431Scarlsonj int points, best_points = -1; 5370Sstevel@tonic-gate 5380Sstevel@tonic-gate /* 5390Sstevel@tonic-gate * pick out the best offer. point system. 5403431Scarlsonj * what's important for IPv4? 5410Sstevel@tonic-gate * 5423431Scarlsonj * 0) DHCP (30 points) 5430Sstevel@tonic-gate * 1) no option overload 5443431Scarlsonj * 2) encapsulated vendor option (80 points) 5450Sstevel@tonic-gate * 3) non-null sname and siaddr fields 5460Sstevel@tonic-gate * 4) non-null file field 5473431Scarlsonj * 5) hostname (5 points) 5483431Scarlsonj * 6) subnetmask (1 point) 5493431Scarlsonj * 7) router (1 point) 5500Sstevel@tonic-gate */ 5510Sstevel@tonic-gate 5523431Scarlsonj for (; current != NULL; current = next) { 5533431Scarlsonj next = current->next; 5540Sstevel@tonic-gate 5553431Scarlsonj points = current->isv6 ? 5563431Scarlsonj compute_points_v6(current, dsmp) : 5573431Scarlsonj compute_points_v4(current); 5580Sstevel@tonic-gate 5593431Scarlsonj /* 5603431Scarlsonj * Just discard any unacceptable entries we encounter. 5613431Scarlsonj */ 5623431Scarlsonj if (points == -1) { 5633431Scarlsonj remque(current); 5643431Scarlsonj free_pkt_entry(current); 5650Sstevel@tonic-gate continue; 5660Sstevel@tonic-gate } 5670Sstevel@tonic-gate 5683431Scarlsonj dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); 5690Sstevel@tonic-gate 5703431Scarlsonj /* Special case: stop now and select */ 5713431Scarlsonj if (points == -2) { 5723431Scarlsonj best = current; 5733431Scarlsonj break; 5743431Scarlsonj } 5750Sstevel@tonic-gate 5760Sstevel@tonic-gate if (points >= best_points) { 5770Sstevel@tonic-gate best_points = points; 5780Sstevel@tonic-gate best = current; 5790Sstevel@tonic-gate } 5800Sstevel@tonic-gate } 5810Sstevel@tonic-gate 5820Sstevel@tonic-gate if (best != NULL) { 5830Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); 5843431Scarlsonj remque(best); 5853431Scarlsonj } else { 5860Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); 5873431Scarlsonj } 5880Sstevel@tonic-gate 5890Sstevel@tonic-gate return (best); 5900Sstevel@tonic-gate } 5910Sstevel@tonic-gate 5920Sstevel@tonic-gate /* 5933431Scarlsonj * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the 5943431Scarlsonj * current state. If we're renewing or rebinding, the ACK 5953431Scarlsonj * must be for the same address and must have a new lease 5963431Scarlsonj * time. If it's a NAK, then our cache is garbage, and we 5973431Scarlsonj * must restart. Finally, call dhcp_bound on accepted 5983431Scarlsonj * ACKs. 5990Sstevel@tonic-gate * 6003431Scarlsonj * input: dhcp_smach_t *: the state machine to handle the ACK/NAK 6013431Scarlsonj * PKT_LIST *: the ACK/NAK message 6020Sstevel@tonic-gate * output: void 6030Sstevel@tonic-gate */ 6040Sstevel@tonic-gate 6053431Scarlsonj static void 6063431Scarlsonj accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) 6070Sstevel@tonic-gate { 6084516Scarlsonj /* Account for received and processed messages */ 6094516Scarlsonj dsmp->dsm_received++; 6104516Scarlsonj 6110Sstevel@tonic-gate if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { 6124106Scarlsonj if (dsmp->dsm_state != INFORM_SENT && 6134106Scarlsonj dsmp->dsm_state != INFORMATION && 6144106Scarlsonj (plp->opts[CD_LEASE_TIME] == NULL || 6154106Scarlsonj plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { 6163431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " 6173431Scarlsonj "%s missing mandatory lease option, ignored", 6183431Scarlsonj dsmp->dsm_name); 6193431Scarlsonj dsmp->dsm_bad_offers++; 6203431Scarlsonj free_pkt_entry(plp); 6210Sstevel@tonic-gate return; 6220Sstevel@tonic-gate } 6233431Scarlsonj if ((dsmp->dsm_state == RENEWING || 6243431Scarlsonj dsmp->dsm_state == REBINDING) && 6253431Scarlsonj dsmp->dsm_leases->dl_lifs->lif_addr != 6263431Scarlsonj plp->pkt->yiaddr.s_addr) { 6273431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " 6283431Scarlsonj "packet has a different IP address (%s), ignored", 6293431Scarlsonj inet_ntoa(plp->pkt->yiaddr)); 6303431Scarlsonj dsmp->dsm_bad_offers++; 6313431Scarlsonj free_pkt_entry(plp); 6320Sstevel@tonic-gate return; 6330Sstevel@tonic-gate } 6340Sstevel@tonic-gate } 6350Sstevel@tonic-gate 6360Sstevel@tonic-gate /* 6370Sstevel@tonic-gate * looks good; cancel the retransmission timer and unregister 6380Sstevel@tonic-gate * the acknak handler. ACK to BOUND, NAK back to SELECTING. 6390Sstevel@tonic-gate */ 6400Sstevel@tonic-gate 6413431Scarlsonj stop_pkt_retransmission(dsmp); 6420Sstevel@tonic-gate 6433431Scarlsonj if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { 6443431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", 6453431Scarlsonj dsmp->dsm_name); 6463431Scarlsonj dsmp->dsm_bad_offers++; 6473431Scarlsonj free_pkt_entry(plp); 6483431Scarlsonj dhcp_restart(dsmp); 6490Sstevel@tonic-gate 6500Sstevel@tonic-gate /* 6510Sstevel@tonic-gate * remove any bogus cached configuration we might have 6520Sstevel@tonic-gate * around (right now would only happen if we got here 6530Sstevel@tonic-gate * from INIT_REBOOT). 6540Sstevel@tonic-gate */ 6550Sstevel@tonic-gate 6563431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 6570Sstevel@tonic-gate return; 6580Sstevel@tonic-gate } 6590Sstevel@tonic-gate 6600Sstevel@tonic-gate if (plp->opts[CD_SERVER_ID] == NULL || 6610Sstevel@tonic-gate plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 6623431Scarlsonj dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " 6634516Scarlsonj "server id on %s", dsmp->dsm_name); 6643431Scarlsonj dsmp->dsm_bad_offers++; 6653431Scarlsonj free_pkt_entry(plp); 6663431Scarlsonj dhcp_restart(dsmp); 6673431Scarlsonj return; 6683431Scarlsonj } 6693431Scarlsonj 6703431Scarlsonj if (plp->opts[CD_MESSAGE] != NULL) { 6713431Scarlsonj print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, 6723431Scarlsonj plp->opts[CD_MESSAGE]->len); 6733431Scarlsonj } 6743431Scarlsonj 6753431Scarlsonj dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); 6763431Scarlsonj if (!dhcp_bound(dsmp, plp)) { 6773431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " 6783431Scarlsonj "for %s", dsmp->dsm_name); 6793431Scarlsonj dhcp_restart(dsmp); 6803431Scarlsonj } 6813431Scarlsonj } 6823431Scarlsonj 6833431Scarlsonj /* 6843431Scarlsonj * accept_v6_message(): determine what to do with a DHCPv6 message based on the 6853431Scarlsonj * current state. 6863431Scarlsonj * 6873431Scarlsonj * input: dhcp_smach_t *: the state machine to handle the message 6883431Scarlsonj * PKT_LIST *: the DHCPv6 message 6893431Scarlsonj * const char *: type of message (for logging) 6903431Scarlsonj * uchar_t: type of message (extracted from packet) 6913431Scarlsonj * output: void 6923431Scarlsonj */ 6933431Scarlsonj 6943431Scarlsonj static void 6953431Scarlsonj accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, 6963431Scarlsonj uchar_t recv_type) 6973431Scarlsonj { 6983431Scarlsonj const dhcpv6_option_t *d6o; 6993431Scarlsonj uint_t olen; 7003431Scarlsonj const char *estr, *msg; 7013431Scarlsonj uint_t msglen; 7023431Scarlsonj int status; 7033431Scarlsonj 7044516Scarlsonj /* Account for received and processed messages */ 7054516Scarlsonj dsmp->dsm_received++; 7064516Scarlsonj 7074516Scarlsonj /* We don't yet support Reconfigure at all. */ 7084516Scarlsonj if (recv_type == DHCPV6_MSG_RECONFIGURE) { 7094516Scarlsonj dhcpmsg(MSG_VERBOSE, "accept_v6_message: ignored Reconfigure " 7104516Scarlsonj "on %s", dsmp->dsm_name); 7114516Scarlsonj free_pkt_entry(plp); 7124516Scarlsonj return; 7134516Scarlsonj } 7144516Scarlsonj 7153431Scarlsonj /* 7163431Scarlsonj * All valid DHCPv6 messages must have our Client ID specified. 7173431Scarlsonj */ 7183431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); 7193431Scarlsonj olen -= sizeof (*d6o); 7203431Scarlsonj if (d6o == NULL || olen != dsmp->dsm_cidlen || 7213431Scarlsonj memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { 7223431Scarlsonj dhcpmsg(MSG_VERBOSE, 7233431Scarlsonj "accept_v6_message: discarded %s on %s: %s Client ID", 7243431Scarlsonj pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); 7253431Scarlsonj free_pkt_entry(plp); 7260Sstevel@tonic-gate return; 7270Sstevel@tonic-gate } 7280Sstevel@tonic-gate 7293431Scarlsonj /* 7303431Scarlsonj * All valid DHCPv6 messages must have a Server ID specified. 7313431Scarlsonj * 7323431Scarlsonj * If this is a Reply and it's not in response to Solicit, Confirm, 7333431Scarlsonj * Rebind, or Information-Request, then it must also match the Server 7343431Scarlsonj * ID we're expecting. 7353431Scarlsonj * 7363431Scarlsonj * For Reply in the Solicit, Confirm, Rebind, and Information-Request 7373431Scarlsonj * cases, the Server ID needs to be saved. This is done inside of 7383431Scarlsonj * dhcp_bound(). 7393431Scarlsonj */ 7403431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); 7413431Scarlsonj if (d6o == NULL) { 7423431Scarlsonj dhcpmsg(MSG_DEBUG, 7433431Scarlsonj "accept_v6_message: discarded %s on %s: no Server ID", 7443431Scarlsonj pname, dsmp->dsm_name); 7453431Scarlsonj free_pkt_entry(plp); 7463431Scarlsonj return; 7473431Scarlsonj } 7483431Scarlsonj if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && 7493431Scarlsonj dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && 7503431Scarlsonj dsmp->dsm_state != INFORM_SENT) { 7513431Scarlsonj olen -= sizeof (*d6o); 7523431Scarlsonj if (olen != dsmp->dsm_serveridlen || 7533431Scarlsonj memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { 7543431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " 7553431Scarlsonj "%s: wrong Server ID", pname, dsmp->dsm_name); 7563431Scarlsonj free_pkt_entry(plp); 7573431Scarlsonj return; 7583431Scarlsonj } 7593431Scarlsonj } 7603431Scarlsonj 7613431Scarlsonj /* 7623431Scarlsonj * Break out of the switch if the input message needs to be discarded. 7633431Scarlsonj * Return from the function if the message has been enqueued or 7643431Scarlsonj * consumed. 7653431Scarlsonj */ 7663431Scarlsonj switch (dsmp->dsm_state) { 7673431Scarlsonj case SELECTING: 7683431Scarlsonj /* A Reply message signifies a Rapid-Commit. */ 7693431Scarlsonj if (recv_type == DHCPV6_MSG_REPLY) { 7703431Scarlsonj if (dhcpv6_pkt_option(plp, NULL, 7713431Scarlsonj DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { 7723431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " 7733431Scarlsonj "on %s lacks Rapid-Commit; ignoring", 7743431Scarlsonj dsmp->dsm_name); 7753431Scarlsonj break; 7763431Scarlsonj } 7773431Scarlsonj dhcpmsg(MSG_VERBOSE, 7783431Scarlsonj "accept_v6_message: rapid-commit Reply on %s", 7793431Scarlsonj dsmp->dsm_name); 7803431Scarlsonj cancel_offer_timer(dsmp); 7813431Scarlsonj goto rapid_commit; 7823431Scarlsonj } 7833431Scarlsonj 7843431Scarlsonj /* Otherwise, we're looking for Advertisements. */ 7853431Scarlsonj if (recv_type != DHCPV6_MSG_ADVERTISE) 7863431Scarlsonj break; 7873431Scarlsonj 7883431Scarlsonj /* 7893431Scarlsonj * Special case: if this advertisement has preference 255, then 7903431Scarlsonj * we must stop right now and select this server. 7913431Scarlsonj */ 7923431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, 7933431Scarlsonj &olen); 7943431Scarlsonj if (d6o != NULL && olen == sizeof (*d6o) + 1 && 7953431Scarlsonj *(const uchar_t *)(d6o + 1) == 255) { 7963431Scarlsonj pkt_smach_enqueue(dsmp, plp); 7973431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" 7983431Scarlsonj " immediate Request on %s", dsmp->dsm_name); 7993431Scarlsonj dhcp_requesting(NULL, dsmp); 8003431Scarlsonj } else { 8013431Scarlsonj pkt_smach_enqueue(dsmp, plp); 8023431Scarlsonj } 8033431Scarlsonj return; 8043431Scarlsonj 8053431Scarlsonj case PRE_BOUND: 8063431Scarlsonj case BOUND: 8073431Scarlsonj /* 8083431Scarlsonj * Not looking for anything in these states. (If we 8093431Scarlsonj * implemented reconfigure, that might go here.) 8103431Scarlsonj */ 8113431Scarlsonj break; 8120Sstevel@tonic-gate 8133431Scarlsonj case REQUESTING: 8143431Scarlsonj case INIT_REBOOT: 8153431Scarlsonj case RENEWING: 8163431Scarlsonj case REBINDING: 8173431Scarlsonj case INFORM_SENT: 8183431Scarlsonj /* 8193431Scarlsonj * We're looking for Reply messages. 8203431Scarlsonj */ 8213431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 8223431Scarlsonj break; 8233431Scarlsonj dhcpmsg(MSG_VERBOSE, 8243431Scarlsonj "accept_v6_message: received Reply message on %s", 8253431Scarlsonj dsmp->dsm_name); 8263431Scarlsonj rapid_commit: 8273431Scarlsonj /* 8283431Scarlsonj * Extract the status code option. If one is present and the 8293431Scarlsonj * request failed, then try to go to another advertisement in 8303431Scarlsonj * the list or restart the selection machinery. 8313431Scarlsonj */ 8323431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 8333431Scarlsonj &olen); 8343431Scarlsonj status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 8353431Scarlsonj /* 8363431Scarlsonj * Check for the UseMulticast status code. If this is present, 8373431Scarlsonj * and if we were actually using unicast, then drop back and 8383431Scarlsonj * try again. If we weren't using unicast, then just pretend 8393431Scarlsonj * we never saw this message -- the peer is confused. (TAHI 8403431Scarlsonj * does this.) 8413431Scarlsonj */ 8423431Scarlsonj if (status == DHCPV6_STAT_USEMCAST) { 8433431Scarlsonj if (IN6_IS_ADDR_MULTICAST( 8443431Scarlsonj &dsmp->dsm_send_dest.v6.sin6_addr)) { 8453431Scarlsonj break; 8463431Scarlsonj } else { 8473431Scarlsonj free_pkt_entry(plp); 8483431Scarlsonj dsmp->dsm_send_dest.v6.sin6_addr = 8493431Scarlsonj ipv6_all_dhcp_relay_and_servers; 8503431Scarlsonj retransmit_now(dsmp); 8513431Scarlsonj return; 8523431Scarlsonj } 8533431Scarlsonj } 8543431Scarlsonj print_server_msg(dsmp, msg, msglen); 8553431Scarlsonj /* 8563431Scarlsonj * We treat NoBinding at the top level as "success." Granted, 8573431Scarlsonj * this doesn't make much sense, but the TAHI test suite does 8583431Scarlsonj * this. NoBinding really only makes sense in the context of a 8593431Scarlsonj * specific IA, as it refers to the GUID:IAID binding, so 8603431Scarlsonj * ignoring it at the top level is safe. 8613431Scarlsonj */ 8623431Scarlsonj if (status == DHCPV6_STAT_SUCCESS || 8633431Scarlsonj status == DHCPV6_STAT_NOBINDING) { 8643431Scarlsonj if (dhcp_bound(dsmp, plp)) { 8653431Scarlsonj /* 8663431Scarlsonj * dhcp_bound will stop retransmission on 8673431Scarlsonj * success, if that's called for. 8683431Scarlsonj */ 8693431Scarlsonj server_unicast_option(dsmp, plp); 8703431Scarlsonj } else { 8713431Scarlsonj stop_pkt_retransmission(dsmp); 8723431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: " 8733431Scarlsonj "dhcp_bound failed for %s", dsmp->dsm_name); 8743431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, 8753431Scarlsonj dsmp->dsm_isv6); 8764516Scarlsonj dhcp_restart(dsmp); 8773431Scarlsonj } 8783431Scarlsonj } else { 8793431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 8803431Scarlsonj estr); 8813431Scarlsonj stop_pkt_retransmission(dsmp); 8823431Scarlsonj free_pkt_entry(plp); 8833431Scarlsonj if (dsmp->dsm_state == INFORM_SENT) { 8843431Scarlsonj (void) set_smach_state(dsmp, INIT); 8853431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); 8863431Scarlsonj } else { 8873431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, 8883431Scarlsonj dsmp->dsm_isv6); 8893431Scarlsonj request_failed(dsmp); 8903431Scarlsonj } 8913431Scarlsonj } 8923431Scarlsonj return; 8933431Scarlsonj 8943431Scarlsonj case DECLINING: 8953431Scarlsonj /* 8963431Scarlsonj * We're looking for Reply messages. 8973431Scarlsonj */ 8983431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 8993431Scarlsonj break; 9003431Scarlsonj stop_pkt_retransmission(dsmp); 9013431Scarlsonj /* 9023431Scarlsonj * Extract the status code option. Note that it's not a 9033431Scarlsonj * failure if the server reports an error. 9043431Scarlsonj */ 9053431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 9063431Scarlsonj &olen); 9073431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, 9083431Scarlsonj &msglen) == DHCPV6_STAT_SUCCESS) { 9093431Scarlsonj print_server_msg(dsmp, msg, msglen); 9103431Scarlsonj } else { 9113431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 9123431Scarlsonj estr); 9133431Scarlsonj } 9143431Scarlsonj free_pkt_entry(plp); 9153431Scarlsonj if (dsmp->dsm_leases == NULL) { 9163431Scarlsonj dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " 9174516Scarlsonj "leases left", dsmp->dsm_name); 9183431Scarlsonj dhcp_restart(dsmp); 9193431Scarlsonj } else if (dsmp->dsm_lif_wait == 0) { 9203431Scarlsonj (void) set_smach_state(dsmp, BOUND); 9213431Scarlsonj } else { 9223431Scarlsonj (void) set_smach_state(dsmp, PRE_BOUND); 9233431Scarlsonj } 9243431Scarlsonj return; 9253431Scarlsonj 9263431Scarlsonj case RELEASING: 9273431Scarlsonj /* 9283431Scarlsonj * We're looking for Reply messages. 9293431Scarlsonj */ 9303431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 9313431Scarlsonj break; 9323431Scarlsonj stop_pkt_retransmission(dsmp); 9333431Scarlsonj /* 9343431Scarlsonj * Extract the status code option. 9353431Scarlsonj */ 9363431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 9373431Scarlsonj &olen); 9383431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, 9393431Scarlsonj &msglen) == DHCPV6_STAT_SUCCESS) { 9403431Scarlsonj print_server_msg(dsmp, msg, msglen); 9413431Scarlsonj } else { 9423431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 9433431Scarlsonj estr); 9443431Scarlsonj } 9453431Scarlsonj free_pkt_entry(plp); 9463431Scarlsonj finished_smach(dsmp, DHCP_IPC_SUCCESS); 9470Sstevel@tonic-gate return; 9480Sstevel@tonic-gate } 9490Sstevel@tonic-gate 9503431Scarlsonj /* 9513431Scarlsonj * Break from above switch means that the message must be discarded. 9523431Scarlsonj */ 9533431Scarlsonj dhcpmsg(MSG_VERBOSE, 9543431Scarlsonj "accept_v6_message: discarded v6 %s on %s; state %s", 9553431Scarlsonj pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); 9563431Scarlsonj free_pkt_entry(plp); 9570Sstevel@tonic-gate } 9580Sstevel@tonic-gate 9590Sstevel@tonic-gate /* 9605381Smeem * dhcp_acknak_global(): Processes reception of an ACK or NAK packet on the 9613431Scarlsonj * global socket -- broadcast packets for IPv4, all 9623431Scarlsonj * packets for DHCPv6. 9630Sstevel@tonic-gate * 9643431Scarlsonj * input: iu_eh_t *: unused 9653431Scarlsonj * int: the global file descriptor the ACK/NAK arrived on 9663431Scarlsonj * short: unused 9673431Scarlsonj * iu_event_id_t: unused 9683431Scarlsonj * void *: unused 9693431Scarlsonj * output: void 9703431Scarlsonj */ 9713431Scarlsonj 9723431Scarlsonj /* ARGSUSED */ 9733431Scarlsonj void 9745381Smeem dhcp_acknak_global(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 9753431Scarlsonj void *arg) 9763431Scarlsonj { 9773431Scarlsonj PKT_LIST *plp; 9783431Scarlsonj dhcp_pif_t *pif; 9793431Scarlsonj uchar_t recv_type; 9803431Scarlsonj const char *pname; 9813431Scarlsonj uint_t xid; 9823431Scarlsonj dhcp_smach_t *dsmp; 9833431Scarlsonj boolean_t isv6 = (fd == v6_sock_fd); 9845381Smeem struct sockaddr_in sin; 9855381Smeem const char *reason; 9865381Smeem size_t sinlen = sizeof (sin); 9875381Smeem int sock; 9883431Scarlsonj 9895381Smeem plp = recv_pkt(fd, get_max_mtu(isv6), isv6); 9904456Sss150715 if (plp == NULL) 9913431Scarlsonj return; 9923431Scarlsonj 9933431Scarlsonj recv_type = pkt_recv_type(plp); 9943431Scarlsonj pname = pkt_type_to_string(recv_type, isv6); 9953431Scarlsonj 9963431Scarlsonj /* 9975978Smeem * Find the corresponding state machine and pif. 9983431Scarlsonj * 9993431Scarlsonj * Note that DHCPv6 Reconfigure would be special: it's not the reply to 10003431Scarlsonj * any transaction, and thus we would need to search on transaction ID 10015381Smeem * zero (all state machines) to find the match. However, Reconfigure 10023431Scarlsonj * is not yet supported. 10033431Scarlsonj */ 10043431Scarlsonj xid = pkt_get_xid(plp->pkt, isv6); 10055381Smeem 10063431Scarlsonj for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; 10073431Scarlsonj dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { 10085978Smeem pif = dsmp->dsm_lif->lif_pif; 1009*8485SPeter.Memishian@Sun.COM if (pif->pif_index == plp->ifindex || 1010*8485SPeter.Memishian@Sun.COM pif->pif_under_ipmp && pif->pif_grindex == plp->ifindex) 10113431Scarlsonj break; 10123431Scarlsonj } 10135381Smeem 10145381Smeem if (dsmp == NULL) { 10155978Smeem dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet" 10165978Smeem " on ifindex %d: unknown state machine", isv6 ? 6 : 4, 10175978Smeem pname, plp->ifindex); 10185978Smeem free_pkt_entry(plp); 10195978Smeem return; 10205978Smeem } 10215978Smeem 10225978Smeem if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 10235978Smeem reason = "not ACK or NAK"; 10245381Smeem goto drop; 10255381Smeem } 10265381Smeem 10275381Smeem /* 10285381Smeem * For IPv4, most packets will be handled by dhcp_packet_lif(). The 10295381Smeem * only exceptions are broadcast packets sent when lif_sock_ip_fd has 10305381Smeem * bound to something other than INADDR_ANY. 10315381Smeem */ 10325381Smeem if (!isv6) { 10335381Smeem sock = dsmp->dsm_lif->lif_sock_ip_fd; 10345381Smeem 10355381Smeem if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != -1 && 10365381Smeem sin.sin_addr.s_addr == INADDR_ANY) { 10375381Smeem reason = "handled by lif_sock_ip_fd"; 10385381Smeem goto drop; 10395381Smeem } 10403431Scarlsonj } 10413431Scarlsonj 10423431Scarlsonj /* 10433431Scarlsonj * We've got a packet; make sure it's acceptable and cancel the REQUEST 10443431Scarlsonj * retransmissions. 10453431Scarlsonj */ 10463431Scarlsonj if (isv6) 10473431Scarlsonj accept_v6_message(dsmp, plp, pname, recv_type); 10483431Scarlsonj else 10493431Scarlsonj accept_v4_acknak(dsmp, plp); 10505381Smeem return; 10515381Smeem drop: 10525381Smeem dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet for %s " 10535381Smeem "received on global socket: %s", isv6 ? 6 : 4, pname, pif->pif_name, 10545381Smeem reason); 10555381Smeem free_pkt_entry(plp); 10563431Scarlsonj } 10573431Scarlsonj 10583431Scarlsonj /* 10593431Scarlsonj * request_failed(): Attempt to request an address has failed. Take an 10603431Scarlsonj * appropriate action. 10613431Scarlsonj * 10623431Scarlsonj * input: dhcp_smach_t *: state machine that has failed 10633431Scarlsonj * output: void 10643431Scarlsonj */ 10653431Scarlsonj 10663431Scarlsonj static void 10673431Scarlsonj request_failed(dhcp_smach_t *dsmp) 10683431Scarlsonj { 10693431Scarlsonj PKT_LIST *offer; 10703431Scarlsonj 10713431Scarlsonj dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; 10723431Scarlsonj if ((offer = select_best(dsmp)) != NULL) { 10733431Scarlsonj insque(offer, &dsmp->dsm_recv_pkt_list); 10743431Scarlsonj dhcp_requesting(NULL, dsmp); 10753431Scarlsonj } else { 10763431Scarlsonj dhcpmsg(MSG_INFO, "no offers left on %s; restarting", 10773431Scarlsonj dsmp->dsm_name); 10783431Scarlsonj dhcp_selecting(dsmp); 10793431Scarlsonj } 10803431Scarlsonj } 10813431Scarlsonj 10823431Scarlsonj /* 10835381Smeem * dhcp_packet_lif(): Processes reception of an ACK, NAK, or OFFER packet on 10845381Smeem * a given logical interface for IPv4 (only). 10853431Scarlsonj * 10863431Scarlsonj * input: iu_eh_t *: unused 10875381Smeem * int: the file descriptor the packet arrived on 10883431Scarlsonj * short: unused 10893431Scarlsonj * iu_event_id_t: the id of this event callback with the handler 10903431Scarlsonj * void *: pointer to logical interface receiving message 10913431Scarlsonj * output: void 10923431Scarlsonj */ 10933431Scarlsonj 10943431Scarlsonj /* ARGSUSED */ 10953431Scarlsonj void 10965381Smeem dhcp_packet_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 10973431Scarlsonj void *arg) 10983431Scarlsonj { 10993431Scarlsonj dhcp_lif_t *lif = arg; 11003431Scarlsonj PKT_LIST *plp; 11013431Scarlsonj uchar_t recv_type; 11023431Scarlsonj const char *pname; 11033431Scarlsonj uint_t xid; 11043431Scarlsonj dhcp_smach_t *dsmp; 11053431Scarlsonj 11065381Smeem if ((plp = recv_pkt(fd, lif->lif_max, B_FALSE)) == NULL) 11073431Scarlsonj return; 11083431Scarlsonj 11093431Scarlsonj recv_type = pkt_recv_type(plp); 11103431Scarlsonj pname = pkt_type_to_string(recv_type, B_FALSE); 11113431Scarlsonj 11125381Smeem if (!pkt_v4_match(recv_type, 11135381Smeem DHCP_PACK | DHCP_PNAK | DHCP_PUNTYPED | DHCP_POFFER)) { 11145381Smeem dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored v4 %s packet " 11153431Scarlsonj "received via LIF %s", pname, lif->lif_name); 11163431Scarlsonj free_pkt_entry(plp); 11173431Scarlsonj return; 11183431Scarlsonj } 11193431Scarlsonj 11203431Scarlsonj /* 11215381Smeem * Find the corresponding state machine. 11223431Scarlsonj */ 11233431Scarlsonj xid = pkt_get_xid(plp->pkt, B_FALSE); 11243431Scarlsonj for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 11253431Scarlsonj dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 11263431Scarlsonj if (dsmp->dsm_lif == lif) 11273431Scarlsonj break; 11283431Scarlsonj } 11295381Smeem 11305381Smeem if (dsmp == NULL) 11315381Smeem goto drop; 11325381Smeem 11335381Smeem if (pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 11345381Smeem /* 11355381Smeem * We've got an ACK/NAK; make sure it's acceptable and cancel 11365381Smeem * the REQUEST retransmissions. 11375381Smeem */ 11385381Smeem accept_v4_acknak(dsmp, plp); 11395381Smeem } else { 11405381Smeem if (is_bound_state(dsmp->dsm_state)) 11415381Smeem goto drop; 11425381Smeem /* 11435381Smeem * Must be an OFFER or a BOOTP message: enqueue it for later 11445381Smeem * processing by select_best(). 11455381Smeem */ 11465381Smeem pkt_smach_enqueue(dsmp, plp); 11473431Scarlsonj } 11485381Smeem return; 11495381Smeem drop: 11505381Smeem dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored %s packet xid " 11515381Smeem "%x received via LIF %s; %s", pname, xid, lif->lif_name, 11525381Smeem dsmp == NULL ? "unknown state machine" : "bound"); 11535381Smeem free_pkt_entry(plp); 11543431Scarlsonj } 11553431Scarlsonj 11563431Scarlsonj /* 11574516Scarlsonj * dhcp_restart(): restarts DHCP (from INIT) on a given state machine, but only 11584516Scarlsonj * if we're leasing addresses. Doesn't restart for information- 11594516Scarlsonj * only interfaces. 11603431Scarlsonj * 11613431Scarlsonj * input: dhcp_smach_t *: the state machine to restart DHCP on 11620Sstevel@tonic-gate * output: void 11630Sstevel@tonic-gate */ 11640Sstevel@tonic-gate 11652546Scarlsonj void 11663431Scarlsonj dhcp_restart(dhcp_smach_t *dsmp) 11670Sstevel@tonic-gate { 11684516Scarlsonj if (dsmp->dsm_state == INFORM_SENT || dsmp->dsm_state == INFORMATION) 11694516Scarlsonj return; 11704516Scarlsonj 11713431Scarlsonj /* 11723431Scarlsonj * As we're returning to INIT state, we need to discard any leases we 11733431Scarlsonj * may have, and (for v4) canonize the LIF. There's a bit of tension 11743431Scarlsonj * between keeping around a possibly still working address, and obeying 11753431Scarlsonj * the RFCs. A more elaborate design would be to mark the addresses as 11763431Scarlsonj * DEPRECATED, and then start a removal timer. Such a design would 11773431Scarlsonj * probably compromise testing. 11783431Scarlsonj */ 11793431Scarlsonj deprecate_leases(dsmp); 11800Sstevel@tonic-gate 11814106Scarlsonj if (!set_start_timer(dsmp)) { 11822546Scarlsonj dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " 11833431Scarlsonj "reverting to INIT state on %s", dsmp->dsm_name); 11843431Scarlsonj 11853431Scarlsonj (void) set_smach_state(dsmp, INIT); 11863431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 11873431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 11884516Scarlsonj } else { 11894516Scarlsonj dhcpmsg(MSG_DEBUG, "dhcp_restart: restarting DHCP on %s", 11904516Scarlsonj dsmp->dsm_name); 11913431Scarlsonj } 11920Sstevel@tonic-gate } 11930Sstevel@tonic-gate 11940Sstevel@tonic-gate /* 11950Sstevel@tonic-gate * stop_requesting(): decides when to stop retransmitting REQUESTs 11960Sstevel@tonic-gate * 11973431Scarlsonj * input: dhcp_smach_t *: the state machine REQUESTs are being sent from 11980Sstevel@tonic-gate * unsigned int: the number of REQUESTs sent so far 11990Sstevel@tonic-gate * output: boolean_t: B_TRUE if retransmissions should stop 12000Sstevel@tonic-gate */ 12010Sstevel@tonic-gate 12020Sstevel@tonic-gate static boolean_t 12033431Scarlsonj stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) 12040Sstevel@tonic-gate { 12053431Scarlsonj uint_t maxreq; 12060Sstevel@tonic-gate 12073431Scarlsonj maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; 12083431Scarlsonj if (n_requests >= maxreq) { 12090Sstevel@tonic-gate 12103431Scarlsonj dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", 12113431Scarlsonj dsmp->dsm_name); 12120Sstevel@tonic-gate 12133431Scarlsonj request_failed(dsmp); 12140Sstevel@tonic-gate return (B_TRUE); 12153431Scarlsonj } else { 12163431Scarlsonj return (B_FALSE); 12170Sstevel@tonic-gate } 12180Sstevel@tonic-gate } 1219