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 /* 223431Scarlsonj * Copyright 2007 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 280Sstevel@tonic-gate #pragma ident "%Z%%M% %I% %E% SMI" 290Sstevel@tonic-gate 303431Scarlsonj #include <stdlib.h> 313431Scarlsonj #include <string.h> 323431Scarlsonj #include <search.h> 330Sstevel@tonic-gate #include <sys/types.h> 340Sstevel@tonic-gate #include <netinet/in.h> 350Sstevel@tonic-gate #include <netinet/dhcp.h> 360Sstevel@tonic-gate #include <netinet/udp.h> 370Sstevel@tonic-gate #include <netinet/ip_var.h> 380Sstevel@tonic-gate #include <netinet/udp_var.h> 393431Scarlsonj #include <arpa/inet.h> 400Sstevel@tonic-gate #include <dhcp_hostconf.h> 413431Scarlsonj #include <dhcpagent_util.h> 420Sstevel@tonic-gate #include <dhcpmsg.h> 430Sstevel@tonic-gate 440Sstevel@tonic-gate #include "states.h" 450Sstevel@tonic-gate #include "util.h" 460Sstevel@tonic-gate #include "packet.h" 470Sstevel@tonic-gate #include "interface.h" 480Sstevel@tonic-gate #include "agent.h" 490Sstevel@tonic-gate 503431Scarlsonj static PKT_LIST *select_best(dhcp_smach_t *); 513431Scarlsonj static void request_failed(dhcp_smach_t *); 520Sstevel@tonic-gate static stop_func_t stop_requesting; 530Sstevel@tonic-gate 540Sstevel@tonic-gate /* 553431Scarlsonj * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING 563431Scarlsonj * state. This is a separate function because a NoBinding 573431Scarlsonj * response can also cause us to do this. 583431Scarlsonj * 593431Scarlsonj * input: dhcp_smach_t *: the state machine 603431Scarlsonj * output: none 613431Scarlsonj */ 623431Scarlsonj 633431Scarlsonj void 643431Scarlsonj send_v6_request(dhcp_smach_t *dsmp) 653431Scarlsonj { 663431Scarlsonj dhcp_pkt_t *dpkt; 673431Scarlsonj dhcpv6_ia_na_t d6in; 683431Scarlsonj 693431Scarlsonj dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST); 703431Scarlsonj (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, 713431Scarlsonj dsmp->dsm_serveridlen); 723431Scarlsonj 733431Scarlsonj /* Add an IA_NA option for our controlling LIF */ 743431Scarlsonj d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); 753431Scarlsonj d6in.d6in_t1 = htonl(0); 763431Scarlsonj d6in.d6in_t2 = htonl(0); 773431Scarlsonj (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, 783431Scarlsonj (dhcpv6_option_t *)&d6in + 1, 793431Scarlsonj sizeof (d6in) - sizeof (dhcpv6_option_t)); 803431Scarlsonj 813431Scarlsonj /* Add required Option Request option */ 823431Scarlsonj (void) add_pkt_prl(dpkt, dsmp); 833431Scarlsonj 843431Scarlsonj (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting, 853431Scarlsonj DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT); 863431Scarlsonj 873431Scarlsonj /* For DHCPv6, state switch cannot fail */ 883431Scarlsonj (void) set_smach_state(dsmp, REQUESTING); 893431Scarlsonj } 903431Scarlsonj 913431Scarlsonj /* 923431Scarlsonj * server_unicast_option(): determines the server address to use based on the 933431Scarlsonj * DHCPv6 Server Unicast option present in the given 943431Scarlsonj * packet. 953431Scarlsonj * 963431Scarlsonj * input: dhcp_smach_t *: the state machine 973431Scarlsonj * PKT_LIST *: received packet (Advertisement or Reply) 983431Scarlsonj * output: none 993431Scarlsonj */ 1003431Scarlsonj 1013431Scarlsonj void 1023431Scarlsonj server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp) 1033431Scarlsonj { 1043431Scarlsonj const dhcpv6_option_t *d6o; 1053431Scarlsonj uint_t olen; 1063431Scarlsonj 1073431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen); 1083431Scarlsonj olen -= sizeof (*d6o); 1093431Scarlsonj /* LINTED: no consequent */ 1103431Scarlsonj if (d6o == NULL) { 1113431Scarlsonj /* No Server Unicast option specified */ 1123431Scarlsonj } else if (olen != sizeof (dsmp->dsm_server)) { 1133431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server " 1143431Scarlsonj "Unicast option with bad length", 1153431Scarlsonj pkt_type_to_string(pkt_recv_type(plp), B_TRUE)); 1163431Scarlsonj } else { 1173431Scarlsonj in6_addr_t addr; 1183431Scarlsonj 1193431Scarlsonj (void) memcpy(&addr, d6o + 1, olen); 1203431Scarlsonj if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { 1213431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 1223431Scarlsonj "to unspecified address ignored"); 1233431Scarlsonj } else if (IN6_IS_ADDR_MULTICAST(&addr)) { 1243431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 1253431Scarlsonj "to multicast address ignored"); 1263431Scarlsonj } else if (IN6_IS_ADDR_V4COMPAT(&addr) || 1273431Scarlsonj IN6_IS_ADDR_V4MAPPED(&addr)) { 1283431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 1293431Scarlsonj "to invalid address ignored"); 1303431Scarlsonj } else { 1313431Scarlsonj dsmp->dsm_server = addr; 1323431Scarlsonj } 1333431Scarlsonj } 1343431Scarlsonj } 1353431Scarlsonj 1363431Scarlsonj /* 1370Sstevel@tonic-gate * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers. 1380Sstevel@tonic-gate * if so, chooses the best one, sends a REQUEST to the 1390Sstevel@tonic-gate * server and registers an event handler to receive 1403431Scarlsonj * the ACK/NAK. This may be called by the offer timer or 1413431Scarlsonj * by any function that wants to check for offers after 1423431Scarlsonj * canceling that timer. 1430Sstevel@tonic-gate * 1443431Scarlsonj * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback 1453431Scarlsonj * void *: the state machine receiving OFFER packets 1460Sstevel@tonic-gate * output: void 1470Sstevel@tonic-gate */ 1480Sstevel@tonic-gate 1490Sstevel@tonic-gate void 1500Sstevel@tonic-gate dhcp_requesting(iu_tq_t *tqp, void *arg) 1510Sstevel@tonic-gate { 1523431Scarlsonj dhcp_smach_t *dsmp = arg; 1530Sstevel@tonic-gate dhcp_pkt_t *dpkt; 1540Sstevel@tonic-gate PKT_LIST *offer; 1550Sstevel@tonic-gate lease_t lease; 1563431Scarlsonj boolean_t isv6 = dsmp->dsm_isv6; 1572157Sdh155122 1583431Scarlsonj /* 1593431Scarlsonj * We assume here that if tqp is set, then this means we're being 1603431Scarlsonj * called back by the offer wait timer. If so, then drop our hold 1613431Scarlsonj * on the state machine. Otherwise, cancel the timer if it's running. 1623431Scarlsonj */ 1633431Scarlsonj if (tqp != NULL) { 1643431Scarlsonj dhcpmsg(MSG_VERBOSE, 1653431Scarlsonj "dhcp_requesting: offer wait timer on v%d %s", 1663431Scarlsonj isv6 ? 6 : 4, dsmp->dsm_name); 1673431Scarlsonj dsmp->dsm_offer_timer = -1; 1683431Scarlsonj if (!verify_smach(dsmp)) 1693431Scarlsonj return; 1703431Scarlsonj } else { 1713431Scarlsonj cancel_offer_timer(dsmp); 1720Sstevel@tonic-gate } 1730Sstevel@tonic-gate 1740Sstevel@tonic-gate /* 1750Sstevel@tonic-gate * select the best OFFER; all others pitched. 1760Sstevel@tonic-gate */ 1770Sstevel@tonic-gate 1783431Scarlsonj offer = select_best(dsmp); 1790Sstevel@tonic-gate if (offer == NULL) { 1800Sstevel@tonic-gate 1813431Scarlsonj dhcpmsg(MSG_VERBOSE, 1823431Scarlsonj "no OFFERs/Advertisements on %s, waiting...", 1833431Scarlsonj dsmp->dsm_name); 1840Sstevel@tonic-gate 1850Sstevel@tonic-gate /* 1860Sstevel@tonic-gate * no acceptable OFFERs have come in. reschedule 1873431Scarlsonj * ourself for callback. 1880Sstevel@tonic-gate */ 1890Sstevel@tonic-gate 1903431Scarlsonj if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, 1913431Scarlsonj dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) { 1920Sstevel@tonic-gate 1930Sstevel@tonic-gate /* 1940Sstevel@tonic-gate * ugh. the best we can do at this point is 1950Sstevel@tonic-gate * revert back to INIT and wait for a user to 1960Sstevel@tonic-gate * restart us. 1970Sstevel@tonic-gate */ 1980Sstevel@tonic-gate 1990Sstevel@tonic-gate dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " 2000Sstevel@tonic-gate "reschedule callback, reverting to INIT state on " 2013431Scarlsonj "%s", dsmp->dsm_name); 2020Sstevel@tonic-gate 2033431Scarlsonj stop_pkt_retransmission(dsmp); 2043431Scarlsonj (void) set_smach_state(dsmp, INIT); 2053431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 2063431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 2073431Scarlsonj } else { 2083431Scarlsonj hold_smach(dsmp); 2090Sstevel@tonic-gate } 2100Sstevel@tonic-gate 2110Sstevel@tonic-gate return; 2120Sstevel@tonic-gate } 2130Sstevel@tonic-gate 2140Sstevel@tonic-gate /* 2153431Scarlsonj * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly 2163431Scarlsonj * declines all other offers we've received. We can no longer use any 2173431Scarlsonj * cached offers, so we must discard them now. With DHCPv6, though, 2183431Scarlsonj * we're permitted to hang onto the advertisements (offers) and try 2193431Scarlsonj * them if the preferred one doesn't pan out. 2200Sstevel@tonic-gate */ 2213431Scarlsonj if (!isv6) 2223431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 2230Sstevel@tonic-gate 2243431Scarlsonj /* stop collecting packets. */ 2250Sstevel@tonic-gate 2263431Scarlsonj stop_pkt_retransmission(dsmp); 2270Sstevel@tonic-gate 2280Sstevel@tonic-gate /* 2293431Scarlsonj * For IPv4, check to see whether we got an OFFER or a BOOTP packet. 2303431Scarlsonj * If we got a BOOTP packet, go to the BOUND state now. 2310Sstevel@tonic-gate */ 2323431Scarlsonj if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { 2333431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 2343431Scarlsonj 2353431Scarlsonj if (!set_smach_state(dsmp, REQUESTING)) { 2363431Scarlsonj dhcp_restart(dsmp); 2373431Scarlsonj return; 2383431Scarlsonj } 2393431Scarlsonj 2403431Scarlsonj if (!dhcp_bound(dsmp, offer)) { 2413431Scarlsonj dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " 2423431Scarlsonj "failed for %s", dsmp->dsm_name); 2433431Scarlsonj dhcp_restart(dsmp); 2443431Scarlsonj return; 2453431Scarlsonj } 2463431Scarlsonj 2473431Scarlsonj return; 2480Sstevel@tonic-gate } 2493431Scarlsonj 2503431Scarlsonj if (isv6) { 2513431Scarlsonj const char *estr, *msg; 2523431Scarlsonj const dhcpv6_option_t *d6o; 2533431Scarlsonj uint_t olen, msglen; 2543431Scarlsonj 2553431Scarlsonj /* If there's a Status Code option, print the message */ 2563431Scarlsonj d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, 2573431Scarlsonj &olen); 2583431Scarlsonj (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 2593431Scarlsonj print_server_msg(dsmp, msg, msglen); 2603431Scarlsonj 2613431Scarlsonj /* Copy in the Server ID (guaranteed to be present now) */ 2623431Scarlsonj if (!save_server_id(dsmp, offer)) 2633431Scarlsonj goto failure; 2643431Scarlsonj 2653431Scarlsonj /* 2663431Scarlsonj * Determine how to send this message. If the Advertisement 2673431Scarlsonj * (offer) has the unicast option, then use the address 2683431Scarlsonj * specified in the option. Otherwise, send via multicast. 2693431Scarlsonj */ 2703431Scarlsonj server_unicast_option(dsmp, offer); 2713431Scarlsonj 2723431Scarlsonj send_v6_request(dsmp); 2733431Scarlsonj } else { 2743431Scarlsonj /* if we got a message from the server, display it. */ 2753431Scarlsonj if (offer->opts[CD_MESSAGE] != NULL) { 2763431Scarlsonj print_server_msg(dsmp, 2773431Scarlsonj (char *)offer->opts[CD_MESSAGE]->value, 2783431Scarlsonj offer->opts[CD_MESSAGE]->len); 2793431Scarlsonj } 2803431Scarlsonj 2813431Scarlsonj /* 2823431Scarlsonj * assemble a DHCPREQUEST, with the ciaddr field set to 0, 2833431Scarlsonj * since we got here from the INIT state. 2843431Scarlsonj */ 2853431Scarlsonj 2863431Scarlsonj dpkt = init_pkt(dsmp, REQUEST); 2873431Scarlsonj 2883431Scarlsonj /* 2893431Scarlsonj * Grab the lease out of the OFFER; we know it's valid because 2903431Scarlsonj * select_best() already checked. The max dhcp message size 2913431Scarlsonj * option is set to the interface max, minus the size of the 2923431Scarlsonj * udp and ip headers. 2933431Scarlsonj */ 2943431Scarlsonj 2953431Scarlsonj (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, 2963431Scarlsonj sizeof (lease_t)); 2973431Scarlsonj 2983431Scarlsonj (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); 2993431Scarlsonj (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, 3003431Scarlsonj htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); 3013431Scarlsonj (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, 3023431Scarlsonj offer->pkt->yiaddr.s_addr); 3033431Scarlsonj (void) add_pkt_opt(dpkt, CD_SERVER_ID, 3043431Scarlsonj offer->opts[CD_SERVER_ID]->value, 3053431Scarlsonj offer->opts[CD_SERVER_ID]->len); 3063431Scarlsonj 3073448Sdh155122 if (class_id_len != 0) { 3083448Sdh155122 (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, 3093448Sdh155122 class_id_len); 3103448Sdh155122 } 3113431Scarlsonj (void) add_pkt_prl(dpkt, dsmp); 3123431Scarlsonj 3133431Scarlsonj /* 3143431Scarlsonj * dsm_reqhost was set for this state machine in 3153431Scarlsonj * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a 3163431Scarlsonj * host name was found 3173431Scarlsonj */ 3183431Scarlsonj if (dsmp->dsm_reqhost != NULL) { 3193431Scarlsonj (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, 3203431Scarlsonj strlen(dsmp->dsm_reqhost)); 3213431Scarlsonj } 3223431Scarlsonj (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 3233431Scarlsonj 3243431Scarlsonj /* 3253431Scarlsonj * send out the REQUEST, trying retransmissions. either a NAK 3263431Scarlsonj * or too many REQUEST attempts will revert us to SELECTING. 3273431Scarlsonj */ 3283431Scarlsonj 3293431Scarlsonj if (!set_smach_state(dsmp, REQUESTING)) { 3303431Scarlsonj dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " 3313431Scarlsonj "REQUESTING state; reverting to INIT on %s", 3323431Scarlsonj dsmp->dsm_name); 3333431Scarlsonj goto failure; 3343431Scarlsonj } 3353431Scarlsonj 3363431Scarlsonj (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 3373431Scarlsonj stop_requesting); 3383431Scarlsonj } 3390Sstevel@tonic-gate 3400Sstevel@tonic-gate /* all done with the offer */ 3413431Scarlsonj free_pkt_entry(offer); 3420Sstevel@tonic-gate 3433431Scarlsonj return; 3440Sstevel@tonic-gate 3453431Scarlsonj failure: 3463431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 3473431Scarlsonj (void) set_smach_state(dsmp, INIT); 3483431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 3493431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 3500Sstevel@tonic-gate } 3510Sstevel@tonic-gate 3520Sstevel@tonic-gate /* 3533431Scarlsonj * compute_points_v6(): compute the number of "points" for a given v6 3543431Scarlsonj * advertisement. 3550Sstevel@tonic-gate * 3563431Scarlsonj * input: const PKT_LIST *: packet to inspect 3573431Scarlsonj * const dhcp_smach_t *: state machine that received the packet 3583431Scarlsonj * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. 3593431Scarlsonj */ 3603431Scarlsonj 3613431Scarlsonj static int 3623431Scarlsonj compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) 3633431Scarlsonj { 3643431Scarlsonj char abuf[INET6_ADDRSTRLEN]; 3653431Scarlsonj int points = 0; 3663431Scarlsonj const dhcpv6_option_t *d6o, *d6so; 3673431Scarlsonj uint_t olen, solen; 3683431Scarlsonj int i; 3693431Scarlsonj const char *estr, *msg; 3703431Scarlsonj uint_t msglen; 3713431Scarlsonj 3723431Scarlsonj /* 3733431Scarlsonj * Look through the packet contents. Valid packets must have our 3743431Scarlsonj * client ID and a server ID, which has already been checked by 3753431Scarlsonj * dhcp_acknak_lif. Bonus points for each option. 3763431Scarlsonj */ 3773431Scarlsonj 3783431Scarlsonj /* One point for having a valid message. */ 3793431Scarlsonj points++; 3803431Scarlsonj 3813431Scarlsonj /* 3823431Scarlsonj * Per RFC 3315, if the Advertise message says, "yes, we have no 3833431Scarlsonj * bananas today," then ignore the entire message. (Why it's just 3843431Scarlsonj * _this_ error and no other is a bit of a mystery, but a standard is a 3853431Scarlsonj * standard.) 3863431Scarlsonj */ 3873431Scarlsonj d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); 3883431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == 3893431Scarlsonj DHCPV6_STAT_NOADDRS) { 3903431Scarlsonj dhcpmsg(MSG_INFO, 3913431Scarlsonj "discard advertisement from %s on %s: no address status", 3923431Scarlsonj inet_ntop(AF_INET6, 3933431Scarlsonj &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, 3943431Scarlsonj abuf, sizeof (abuf)), dsmp->dsm_name); 3953431Scarlsonj return (-1); 3963431Scarlsonj } 3973431Scarlsonj 3983431Scarlsonj /* Two points for each batch of offered IP addresses */ 3993431Scarlsonj d6o = NULL; 4003431Scarlsonj while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, 4013431Scarlsonj &olen)) != NULL) { 4023431Scarlsonj 4033431Scarlsonj /* 4043431Scarlsonj * Note that it's possible to have "no bananas" on an 4053431Scarlsonj * individual IA. We must look for that here. 4063431Scarlsonj * 4073431Scarlsonj * RFC 3315 section 17.1.3 does not refer to the status code 4083431Scarlsonj * embedded in the IA itself. However, the TAHI test suite 4093431Scarlsonj * checks for this specific case. Because it's extremely 4103431Scarlsonj * unlikely that any usable server is going to report that it 4113431Scarlsonj * has no addresses on a network using DHCP for address 4123431Scarlsonj * assignment, we allow such messages to be dropped. 4133431Scarlsonj */ 4143431Scarlsonj d6so = dhcpv6_find_option( 4153431Scarlsonj (const char *)d6o + sizeof (dhcpv6_ia_na_t), 4163431Scarlsonj olen - sizeof (dhcpv6_ia_na_t), NULL, 4173431Scarlsonj DHCPV6_OPT_STATUS_CODE, &solen); 4183431Scarlsonj if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == 4193431Scarlsonj DHCPV6_STAT_NOADDRS) 4203431Scarlsonj return (-1); 4213431Scarlsonj points += 2; 4223431Scarlsonj } 4233431Scarlsonj 4243431Scarlsonj /* 4253431Scarlsonj * Note that we drive on in the case where there are no addresses. The 4263431Scarlsonj * hope here is that we'll at least get some useful configuration 4273431Scarlsonj * information. 4283431Scarlsonj */ 4293431Scarlsonj 4303431Scarlsonj /* One point for each requested option */ 4313431Scarlsonj for (i = 0; i < dsmp->dsm_prllen; i++) { 4323431Scarlsonj if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != 4333431Scarlsonj NULL) 4343431Scarlsonj points++; 4353431Scarlsonj } 4363431Scarlsonj 4373431Scarlsonj /* 4383431Scarlsonj * Ten points for each point of "preference." Note: the value 255 is 4393431Scarlsonj * special. It means "stop right now and select this server." 4403431Scarlsonj */ 4413431Scarlsonj d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); 4423431Scarlsonj if (d6o != NULL && olen == sizeof (*d6o) + 1) { 4433431Scarlsonj int pref = *(const uchar_t *)(d6o + 1); 4443431Scarlsonj 4453431Scarlsonj if (pref == 255) 4463431Scarlsonj return (-2); 4473431Scarlsonj points += 10 * pref; 4483431Scarlsonj } 4493431Scarlsonj 4503431Scarlsonj return (points); 4513431Scarlsonj } 4523431Scarlsonj 4533431Scarlsonj /* 4543431Scarlsonj * compute_points_v4(): compute the number of "points" for a given v4 offer. 4553431Scarlsonj * 4563431Scarlsonj * input: const PKT_LIST *: packet to inspect 4573431Scarlsonj * const dhcp_smach_t *: state machine that received the packet 4583431Scarlsonj * output: int: -1 to discard, >=0 for preference. 4593431Scarlsonj */ 4603431Scarlsonj 4613431Scarlsonj static int 4623431Scarlsonj compute_points_v4(const PKT_LIST *pkt) 4633431Scarlsonj { 4643431Scarlsonj int points = 0; 4653431Scarlsonj 4663431Scarlsonj if (pkt->opts[CD_DHCP_TYPE] == NULL) { 4673431Scarlsonj dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); 4683431Scarlsonj goto valid_offer; 4693431Scarlsonj } 4703431Scarlsonj 4713431Scarlsonj if (pkt->opts[CD_LEASE_TIME] == NULL) { 4723431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " 4733431Scarlsonj "time"); 4743431Scarlsonj return (-1); 4753431Scarlsonj } 4763431Scarlsonj 4773431Scarlsonj if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 4783431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 4793431Scarlsonj "lease time"); 4803431Scarlsonj return (-1); 4813431Scarlsonj } 4823431Scarlsonj 4833431Scarlsonj if (pkt->opts[CD_SERVER_ID] == NULL) { 4843431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " 4853431Scarlsonj "id"); 4863431Scarlsonj return (-1); 4873431Scarlsonj } 4883431Scarlsonj 4893431Scarlsonj if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 4903431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 4913431Scarlsonj "server id"); 4923431Scarlsonj return (-1); 4933431Scarlsonj } 4943431Scarlsonj 4953431Scarlsonj /* valid DHCP OFFER. see if we got our parameters. */ 4963431Scarlsonj dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); 4973431Scarlsonj points += 30; 4983431Scarlsonj 4993431Scarlsonj valid_offer: 5003431Scarlsonj if (pkt->rfc1048) 5013431Scarlsonj points += 5; 5023431Scarlsonj 5033431Scarlsonj /* 5043431Scarlsonj * also could be faked, though more difficult because the encapsulation 5053431Scarlsonj * is hard to encode on a BOOTP server; plus there's not as much real 5063431Scarlsonj * estate in the packet for options, so it's likely this option would 5073431Scarlsonj * get dropped. 5083431Scarlsonj */ 5093431Scarlsonj 5103431Scarlsonj if (pkt->opts[CD_VENDOR_SPEC] != NULL) 5113431Scarlsonj points += 80; 5123431Scarlsonj 5133431Scarlsonj if (pkt->opts[CD_SUBNETMASK] != NULL) 5143431Scarlsonj points++; 5153431Scarlsonj 5163431Scarlsonj if (pkt->opts[CD_ROUTER] != NULL) 5173431Scarlsonj points++; 5183431Scarlsonj 5193431Scarlsonj if (pkt->opts[CD_HOSTNAME] != NULL) 5203431Scarlsonj points += 5; 5213431Scarlsonj 5223431Scarlsonj return (points); 5233431Scarlsonj } 5243431Scarlsonj 5253431Scarlsonj /* 5263431Scarlsonj * select_best(): selects the best offer from a list of IPv4 OFFER packets or 5273431Scarlsonj * DHCPv6 Advertise packets. 5283431Scarlsonj * 5293431Scarlsonj * input: dhcp_smach_t *: state machine with enqueued offers 5300Sstevel@tonic-gate * output: PKT_LIST *: the best packet, or NULL if none are acceptable 5310Sstevel@tonic-gate */ 5320Sstevel@tonic-gate 5330Sstevel@tonic-gate static PKT_LIST * 5343431Scarlsonj select_best(dhcp_smach_t *dsmp) 5350Sstevel@tonic-gate { 5363431Scarlsonj PKT_LIST *current = dsmp->dsm_recv_pkt_list; 5373431Scarlsonj PKT_LIST *next, *best = NULL; 5383431Scarlsonj int points, best_points = -1; 5390Sstevel@tonic-gate 5400Sstevel@tonic-gate /* 5410Sstevel@tonic-gate * pick out the best offer. point system. 5423431Scarlsonj * what's important for IPv4? 5430Sstevel@tonic-gate * 5443431Scarlsonj * 0) DHCP (30 points) 5450Sstevel@tonic-gate * 1) no option overload 5463431Scarlsonj * 2) encapsulated vendor option (80 points) 5470Sstevel@tonic-gate * 3) non-null sname and siaddr fields 5480Sstevel@tonic-gate * 4) non-null file field 5493431Scarlsonj * 5) hostname (5 points) 5503431Scarlsonj * 6) subnetmask (1 point) 5513431Scarlsonj * 7) router (1 point) 5520Sstevel@tonic-gate */ 5530Sstevel@tonic-gate 5543431Scarlsonj for (; current != NULL; current = next) { 5553431Scarlsonj next = current->next; 5560Sstevel@tonic-gate 5573431Scarlsonj points = current->isv6 ? 5583431Scarlsonj compute_points_v6(current, dsmp) : 5593431Scarlsonj compute_points_v4(current); 5600Sstevel@tonic-gate 5613431Scarlsonj /* 5623431Scarlsonj * Just discard any unacceptable entries we encounter. 5633431Scarlsonj */ 5643431Scarlsonj if (points == -1) { 5653431Scarlsonj remque(current); 5663431Scarlsonj free_pkt_entry(current); 5670Sstevel@tonic-gate continue; 5680Sstevel@tonic-gate } 5690Sstevel@tonic-gate 5703431Scarlsonj dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); 5710Sstevel@tonic-gate 5723431Scarlsonj /* Special case: stop now and select */ 5733431Scarlsonj if (points == -2) { 5743431Scarlsonj best = current; 5753431Scarlsonj break; 5763431Scarlsonj } 5770Sstevel@tonic-gate 5780Sstevel@tonic-gate if (points >= best_points) { 5790Sstevel@tonic-gate best_points = points; 5800Sstevel@tonic-gate best = current; 5810Sstevel@tonic-gate } 5820Sstevel@tonic-gate } 5830Sstevel@tonic-gate 5840Sstevel@tonic-gate if (best != NULL) { 5850Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); 5863431Scarlsonj remque(best); 5873431Scarlsonj } else { 5880Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); 5893431Scarlsonj } 5900Sstevel@tonic-gate 5910Sstevel@tonic-gate return (best); 5920Sstevel@tonic-gate } 5930Sstevel@tonic-gate 5940Sstevel@tonic-gate /* 5953431Scarlsonj * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the 5963431Scarlsonj * current state. If we're renewing or rebinding, the ACK 5973431Scarlsonj * must be for the same address and must have a new lease 5983431Scarlsonj * time. If it's a NAK, then our cache is garbage, and we 5993431Scarlsonj * must restart. Finally, call dhcp_bound on accepted 6003431Scarlsonj * ACKs. 6010Sstevel@tonic-gate * 6023431Scarlsonj * input: dhcp_smach_t *: the state machine to handle the ACK/NAK 6033431Scarlsonj * PKT_LIST *: the ACK/NAK message 6040Sstevel@tonic-gate * output: void 6050Sstevel@tonic-gate */ 6060Sstevel@tonic-gate 6073431Scarlsonj static void 6083431Scarlsonj accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) 6090Sstevel@tonic-gate { 6100Sstevel@tonic-gate if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { 611*4106Scarlsonj if (dsmp->dsm_state != INFORM_SENT && 612*4106Scarlsonj dsmp->dsm_state != INFORMATION && 613*4106Scarlsonj (plp->opts[CD_LEASE_TIME] == NULL || 614*4106Scarlsonj plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { 6153431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " 6163431Scarlsonj "%s missing mandatory lease option, ignored", 6173431Scarlsonj dsmp->dsm_name); 6183431Scarlsonj dsmp->dsm_bad_offers++; 6193431Scarlsonj free_pkt_entry(plp); 6200Sstevel@tonic-gate return; 6210Sstevel@tonic-gate } 6223431Scarlsonj if ((dsmp->dsm_state == RENEWING || 6233431Scarlsonj dsmp->dsm_state == REBINDING) && 6243431Scarlsonj dsmp->dsm_leases->dl_lifs->lif_addr != 6253431Scarlsonj plp->pkt->yiaddr.s_addr) { 6263431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " 6273431Scarlsonj "packet has a different IP address (%s), ignored", 6283431Scarlsonj inet_ntoa(plp->pkt->yiaddr)); 6293431Scarlsonj dsmp->dsm_bad_offers++; 6303431Scarlsonj free_pkt_entry(plp); 6310Sstevel@tonic-gate return; 6320Sstevel@tonic-gate } 6330Sstevel@tonic-gate } 6340Sstevel@tonic-gate 6350Sstevel@tonic-gate /* 6360Sstevel@tonic-gate * looks good; cancel the retransmission timer and unregister 6370Sstevel@tonic-gate * the acknak handler. ACK to BOUND, NAK back to SELECTING. 6380Sstevel@tonic-gate */ 6390Sstevel@tonic-gate 6403431Scarlsonj stop_pkt_retransmission(dsmp); 6410Sstevel@tonic-gate 6423431Scarlsonj if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { 6433431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", 6443431Scarlsonj dsmp->dsm_name); 6453431Scarlsonj dsmp->dsm_bad_offers++; 6463431Scarlsonj free_pkt_entry(plp); 6473431Scarlsonj dhcp_restart(dsmp); 6480Sstevel@tonic-gate 6490Sstevel@tonic-gate /* 6500Sstevel@tonic-gate * remove any bogus cached configuration we might have 6510Sstevel@tonic-gate * around (right now would only happen if we got here 6520Sstevel@tonic-gate * from INIT_REBOOT). 6530Sstevel@tonic-gate */ 6540Sstevel@tonic-gate 6553431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 6560Sstevel@tonic-gate return; 6570Sstevel@tonic-gate } 6580Sstevel@tonic-gate 6590Sstevel@tonic-gate if (plp->opts[CD_SERVER_ID] == NULL || 6600Sstevel@tonic-gate plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 6613431Scarlsonj dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " 6623431Scarlsonj "server id, restarting DHCP on %s", dsmp->dsm_name); 6633431Scarlsonj dsmp->dsm_bad_offers++; 6643431Scarlsonj free_pkt_entry(plp); 6653431Scarlsonj dhcp_restart(dsmp); 6663431Scarlsonj return; 6673431Scarlsonj } 6683431Scarlsonj 6693431Scarlsonj if (plp->opts[CD_MESSAGE] != NULL) { 6703431Scarlsonj print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, 6713431Scarlsonj plp->opts[CD_MESSAGE]->len); 6723431Scarlsonj } 6733431Scarlsonj 6743431Scarlsonj dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); 6753431Scarlsonj if (!dhcp_bound(dsmp, plp)) { 6763431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " 6773431Scarlsonj "for %s", dsmp->dsm_name); 6783431Scarlsonj dhcp_restart(dsmp); 6793431Scarlsonj } 6803431Scarlsonj } 6813431Scarlsonj 6823431Scarlsonj /* 6833431Scarlsonj * accept_v6_message(): determine what to do with a DHCPv6 message based on the 6843431Scarlsonj * current state. 6853431Scarlsonj * 6863431Scarlsonj * input: dhcp_smach_t *: the state machine to handle the message 6873431Scarlsonj * PKT_LIST *: the DHCPv6 message 6883431Scarlsonj * const char *: type of message (for logging) 6893431Scarlsonj * uchar_t: type of message (extracted from packet) 6903431Scarlsonj * output: void 6913431Scarlsonj */ 6923431Scarlsonj 6933431Scarlsonj static void 6943431Scarlsonj accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, 6953431Scarlsonj uchar_t recv_type) 6963431Scarlsonj { 6973431Scarlsonj const dhcpv6_option_t *d6o; 6983431Scarlsonj uint_t olen; 6993431Scarlsonj const char *estr, *msg; 7003431Scarlsonj uint_t msglen; 7013431Scarlsonj int status; 7023431Scarlsonj 7033431Scarlsonj /* 7043431Scarlsonj * All valid DHCPv6 messages must have our Client ID specified. 7053431Scarlsonj */ 7063431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); 7073431Scarlsonj olen -= sizeof (*d6o); 7083431Scarlsonj if (d6o == NULL || olen != dsmp->dsm_cidlen || 7093431Scarlsonj memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { 7103431Scarlsonj dhcpmsg(MSG_VERBOSE, 7113431Scarlsonj "accept_v6_message: discarded %s on %s: %s Client ID", 7123431Scarlsonj pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); 7133431Scarlsonj free_pkt_entry(plp); 7140Sstevel@tonic-gate return; 7150Sstevel@tonic-gate } 7160Sstevel@tonic-gate 7173431Scarlsonj /* 7183431Scarlsonj * All valid DHCPv6 messages must have a Server ID specified. 7193431Scarlsonj * 7203431Scarlsonj * If this is a Reply and it's not in response to Solicit, Confirm, 7213431Scarlsonj * Rebind, or Information-Request, then it must also match the Server 7223431Scarlsonj * ID we're expecting. 7233431Scarlsonj * 7243431Scarlsonj * For Reply in the Solicit, Confirm, Rebind, and Information-Request 7253431Scarlsonj * cases, the Server ID needs to be saved. This is done inside of 7263431Scarlsonj * dhcp_bound(). 7273431Scarlsonj */ 7283431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); 7293431Scarlsonj if (d6o == NULL) { 7303431Scarlsonj dhcpmsg(MSG_DEBUG, 7313431Scarlsonj "accept_v6_message: discarded %s on %s: no Server ID", 7323431Scarlsonj pname, dsmp->dsm_name); 7333431Scarlsonj free_pkt_entry(plp); 7343431Scarlsonj return; 7353431Scarlsonj } 7363431Scarlsonj if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && 7373431Scarlsonj dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && 7383431Scarlsonj dsmp->dsm_state != INFORM_SENT) { 7393431Scarlsonj olen -= sizeof (*d6o); 7403431Scarlsonj if (olen != dsmp->dsm_serveridlen || 7413431Scarlsonj memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { 7423431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " 7433431Scarlsonj "%s: wrong Server ID", pname, dsmp->dsm_name); 7443431Scarlsonj free_pkt_entry(plp); 7453431Scarlsonj return; 7463431Scarlsonj } 7473431Scarlsonj } 7483431Scarlsonj 7493431Scarlsonj /* 7503431Scarlsonj * Break out of the switch if the input message needs to be discarded. 7513431Scarlsonj * Return from the function if the message has been enqueued or 7523431Scarlsonj * consumed. 7533431Scarlsonj */ 7543431Scarlsonj switch (dsmp->dsm_state) { 7553431Scarlsonj case SELECTING: 7563431Scarlsonj /* A Reply message signifies a Rapid-Commit. */ 7573431Scarlsonj if (recv_type == DHCPV6_MSG_REPLY) { 7583431Scarlsonj if (dhcpv6_pkt_option(plp, NULL, 7593431Scarlsonj DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { 7603431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " 7613431Scarlsonj "on %s lacks Rapid-Commit; ignoring", 7623431Scarlsonj dsmp->dsm_name); 7633431Scarlsonj break; 7643431Scarlsonj } 7653431Scarlsonj dhcpmsg(MSG_VERBOSE, 7663431Scarlsonj "accept_v6_message: rapid-commit Reply on %s", 7673431Scarlsonj dsmp->dsm_name); 7683431Scarlsonj cancel_offer_timer(dsmp); 7693431Scarlsonj goto rapid_commit; 7703431Scarlsonj } 7713431Scarlsonj 7723431Scarlsonj /* Otherwise, we're looking for Advertisements. */ 7733431Scarlsonj if (recv_type != DHCPV6_MSG_ADVERTISE) 7743431Scarlsonj break; 7753431Scarlsonj 7763431Scarlsonj /* 7773431Scarlsonj * Special case: if this advertisement has preference 255, then 7783431Scarlsonj * we must stop right now and select this server. 7793431Scarlsonj */ 7803431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, 7813431Scarlsonj &olen); 7823431Scarlsonj if (d6o != NULL && olen == sizeof (*d6o) + 1 && 7833431Scarlsonj *(const uchar_t *)(d6o + 1) == 255) { 7843431Scarlsonj pkt_smach_enqueue(dsmp, plp); 7853431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" 7863431Scarlsonj " immediate Request on %s", dsmp->dsm_name); 7873431Scarlsonj dhcp_requesting(NULL, dsmp); 7883431Scarlsonj } else { 7893431Scarlsonj pkt_smach_enqueue(dsmp, plp); 7903431Scarlsonj } 7913431Scarlsonj return; 7923431Scarlsonj 7933431Scarlsonj case PRE_BOUND: 7943431Scarlsonj case BOUND: 7953431Scarlsonj /* 7963431Scarlsonj * Not looking for anything in these states. (If we 7973431Scarlsonj * implemented reconfigure, that might go here.) 7983431Scarlsonj */ 7993431Scarlsonj break; 8000Sstevel@tonic-gate 8013431Scarlsonj case REQUESTING: 8023431Scarlsonj case INIT_REBOOT: 8033431Scarlsonj case RENEWING: 8043431Scarlsonj case REBINDING: 8053431Scarlsonj case INFORM_SENT: 8063431Scarlsonj /* 8073431Scarlsonj * We're looking for Reply messages. 8083431Scarlsonj */ 8093431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 8103431Scarlsonj break; 8113431Scarlsonj dhcpmsg(MSG_VERBOSE, 8123431Scarlsonj "accept_v6_message: received Reply message on %s", 8133431Scarlsonj dsmp->dsm_name); 8143431Scarlsonj rapid_commit: 8153431Scarlsonj /* 8163431Scarlsonj * Extract the status code option. If one is present and the 8173431Scarlsonj * request failed, then try to go to another advertisement in 8183431Scarlsonj * the list or restart the selection machinery. 8193431Scarlsonj */ 8203431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 8213431Scarlsonj &olen); 8223431Scarlsonj status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 8233431Scarlsonj /* 8243431Scarlsonj * Check for the UseMulticast status code. If this is present, 8253431Scarlsonj * and if we were actually using unicast, then drop back and 8263431Scarlsonj * try again. If we weren't using unicast, then just pretend 8273431Scarlsonj * we never saw this message -- the peer is confused. (TAHI 8283431Scarlsonj * does this.) 8293431Scarlsonj */ 8303431Scarlsonj if (status == DHCPV6_STAT_USEMCAST) { 8313431Scarlsonj if (IN6_IS_ADDR_MULTICAST( 8323431Scarlsonj &dsmp->dsm_send_dest.v6.sin6_addr)) { 8333431Scarlsonj break; 8343431Scarlsonj } else { 8353431Scarlsonj free_pkt_entry(plp); 8363431Scarlsonj dsmp->dsm_send_dest.v6.sin6_addr = 8373431Scarlsonj ipv6_all_dhcp_relay_and_servers; 8383431Scarlsonj retransmit_now(dsmp); 8393431Scarlsonj return; 8403431Scarlsonj } 8413431Scarlsonj } 8423431Scarlsonj print_server_msg(dsmp, msg, msglen); 8433431Scarlsonj /* 8443431Scarlsonj * We treat NoBinding at the top level as "success." Granted, 8453431Scarlsonj * this doesn't make much sense, but the TAHI test suite does 8463431Scarlsonj * this. NoBinding really only makes sense in the context of a 8473431Scarlsonj * specific IA, as it refers to the GUID:IAID binding, so 8483431Scarlsonj * ignoring it at the top level is safe. 8493431Scarlsonj */ 8503431Scarlsonj if (status == DHCPV6_STAT_SUCCESS || 8513431Scarlsonj status == DHCPV6_STAT_NOBINDING) { 8523431Scarlsonj if (dhcp_bound(dsmp, plp)) { 8533431Scarlsonj /* 8543431Scarlsonj * dhcp_bound will stop retransmission on 8553431Scarlsonj * success, if that's called for. 8563431Scarlsonj */ 8573431Scarlsonj server_unicast_option(dsmp, plp); 8583431Scarlsonj } else { 8593431Scarlsonj stop_pkt_retransmission(dsmp); 8603431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: " 8613431Scarlsonj "dhcp_bound failed for %s", dsmp->dsm_name); 8623431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, 8633431Scarlsonj dsmp->dsm_isv6); 8643431Scarlsonj if (dsmp->dsm_state != INFORM_SENT) 8653431Scarlsonj dhcp_restart(dsmp); 8663431Scarlsonj } 8673431Scarlsonj } else { 8683431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 8693431Scarlsonj estr); 8703431Scarlsonj stop_pkt_retransmission(dsmp); 8713431Scarlsonj free_pkt_entry(plp); 8723431Scarlsonj if (dsmp->dsm_state == INFORM_SENT) { 8733431Scarlsonj (void) set_smach_state(dsmp, INIT); 8743431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); 8753431Scarlsonj } else { 8763431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, 8773431Scarlsonj dsmp->dsm_isv6); 8783431Scarlsonj request_failed(dsmp); 8793431Scarlsonj } 8803431Scarlsonj } 8813431Scarlsonj return; 8823431Scarlsonj 8833431Scarlsonj case DECLINING: 8843431Scarlsonj /* 8853431Scarlsonj * We're looking for Reply messages. 8863431Scarlsonj */ 8873431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 8883431Scarlsonj break; 8893431Scarlsonj stop_pkt_retransmission(dsmp); 8903431Scarlsonj /* 8913431Scarlsonj * Extract the status code option. Note that it's not a 8923431Scarlsonj * failure if the server reports an error. 8933431Scarlsonj */ 8943431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 8953431Scarlsonj &olen); 8963431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, 8973431Scarlsonj &msglen) == DHCPV6_STAT_SUCCESS) { 8983431Scarlsonj print_server_msg(dsmp, msg, msglen); 8993431Scarlsonj } else { 9003431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 9013431Scarlsonj estr); 9023431Scarlsonj } 9033431Scarlsonj free_pkt_entry(plp); 9043431Scarlsonj if (dsmp->dsm_leases == NULL) { 9053431Scarlsonj dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " 9063431Scarlsonj "leases left; restarting", dsmp->dsm_name); 9073431Scarlsonj dhcp_restart(dsmp); 9083431Scarlsonj } else if (dsmp->dsm_lif_wait == 0) { 9093431Scarlsonj (void) set_smach_state(dsmp, BOUND); 9103431Scarlsonj } else { 9113431Scarlsonj (void) set_smach_state(dsmp, PRE_BOUND); 9123431Scarlsonj } 9133431Scarlsonj return; 9143431Scarlsonj 9153431Scarlsonj case RELEASING: 9163431Scarlsonj /* 9173431Scarlsonj * We're looking for Reply messages. 9183431Scarlsonj */ 9193431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 9203431Scarlsonj break; 9213431Scarlsonj stop_pkt_retransmission(dsmp); 9223431Scarlsonj /* 9233431Scarlsonj * Extract the status code option. 9243431Scarlsonj */ 9253431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 9263431Scarlsonj &olen); 9273431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, 9283431Scarlsonj &msglen) == DHCPV6_STAT_SUCCESS) { 9293431Scarlsonj print_server_msg(dsmp, msg, msglen); 9303431Scarlsonj } else { 9313431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 9323431Scarlsonj estr); 9333431Scarlsonj } 9343431Scarlsonj free_pkt_entry(plp); 9353431Scarlsonj finished_smach(dsmp, DHCP_IPC_SUCCESS); 9360Sstevel@tonic-gate return; 9370Sstevel@tonic-gate } 9380Sstevel@tonic-gate 9393431Scarlsonj /* 9403431Scarlsonj * Break from above switch means that the message must be discarded. 9413431Scarlsonj */ 9423431Scarlsonj dhcpmsg(MSG_VERBOSE, 9433431Scarlsonj "accept_v6_message: discarded v6 %s on %s; state %s", 9443431Scarlsonj pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); 9453431Scarlsonj free_pkt_entry(plp); 9460Sstevel@tonic-gate } 9470Sstevel@tonic-gate 9480Sstevel@tonic-gate /* 9493431Scarlsonj * dhcp_acknak_common(): Processes reception of an ACK or NAK packet on the 9503431Scarlsonj * global socket -- broadcast packets for IPv4, all 9513431Scarlsonj * packets for DHCPv6. 9520Sstevel@tonic-gate * 9533431Scarlsonj * input: iu_eh_t *: unused 9543431Scarlsonj * int: the global file descriptor the ACK/NAK arrived on 9553431Scarlsonj * short: unused 9563431Scarlsonj * iu_event_id_t: unused 9573431Scarlsonj * void *: unused 9583431Scarlsonj * output: void 9593431Scarlsonj */ 9603431Scarlsonj 9613431Scarlsonj /* ARGSUSED */ 9623431Scarlsonj void 9633431Scarlsonj dhcp_acknak_common(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 9643431Scarlsonj void *arg) 9653431Scarlsonj { 9663431Scarlsonj PKT_LIST *plp; 9673431Scarlsonj dhcp_pif_t *pif; 9683431Scarlsonj uchar_t recv_type; 9693431Scarlsonj const char *pname; 9703431Scarlsonj uint_t xid; 9713431Scarlsonj dhcp_smach_t *dsmp; 9723431Scarlsonj boolean_t isv6 = (fd == v6_sock_fd); 9733431Scarlsonj 9743431Scarlsonj if ((plp = recv_pkt(fd, get_max_mtu(isv6), isv6, B_FALSE)) == NULL) 9753431Scarlsonj return; 9763431Scarlsonj 9773431Scarlsonj pif = lookup_pif_by_index(plp->ifindex, isv6); 9783431Scarlsonj if (pif == NULL) { 9793431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored packet " 9803431Scarlsonj "received on v%d ifIndex %d", isv6 ? 6 : 4, plp->ifindex); 9813431Scarlsonj free_pkt_entry(plp); 9823431Scarlsonj return; 9833431Scarlsonj } 9843431Scarlsonj 9853431Scarlsonj recv_type = pkt_recv_type(plp); 9863431Scarlsonj pname = pkt_type_to_string(recv_type, isv6); 9873431Scarlsonj if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 9883431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " 9893431Scarlsonj "received via broadcast on %s", pname, pif->pif_name); 9903431Scarlsonj free_pkt_entry(plp); 9913431Scarlsonj return; 9923431Scarlsonj } 9933431Scarlsonj 9943431Scarlsonj if (isv6 && recv_type == DHCPV6_MSG_RECONFIGURE) { 9953431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored v6 " 9963431Scarlsonj "Reconfigure received via %s", pif->pif_name); 9973431Scarlsonj free_pkt_entry(plp); 9983431Scarlsonj return; 9993431Scarlsonj } 10003431Scarlsonj 10013431Scarlsonj /* 10023431Scarlsonj * Find the corresponding state machine not using DLPI. 10033431Scarlsonj * 10043431Scarlsonj * Note that DHCPv6 Reconfigure would be special: it's not the reply to 10053431Scarlsonj * any transaction, and thus we would need to search on transaction ID 10063431Scarlsonj * zero (all state machines) to find the match. However, Reconfigure 10073431Scarlsonj * is not yet supported. 10083431Scarlsonj */ 10093431Scarlsonj xid = pkt_get_xid(plp->pkt, isv6); 10103431Scarlsonj for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; 10113431Scarlsonj dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { 10123431Scarlsonj if (dsmp->dsm_lif->lif_pif == pif) 10133431Scarlsonj break; 10143431Scarlsonj } 10153431Scarlsonj if (dsmp == NULL || dsmp->dsm_using_dlpi) { 10163431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " 10173431Scarlsonj "received via broadcast %s; %s", pname, pif->pif_name, 10183431Scarlsonj dsmp == NULL ? "unknown state machine" : "not using DLPI"); 10193431Scarlsonj free_pkt_entry(plp); 10203431Scarlsonj return; 10213431Scarlsonj } 10223431Scarlsonj 10233431Scarlsonj /* 10243431Scarlsonj * We've got a packet; make sure it's acceptable and cancel the REQUEST 10253431Scarlsonj * retransmissions. 10263431Scarlsonj */ 10273431Scarlsonj if (isv6) 10283431Scarlsonj accept_v6_message(dsmp, plp, pname, recv_type); 10293431Scarlsonj else 10303431Scarlsonj accept_v4_acknak(dsmp, plp); 10313431Scarlsonj } 10323431Scarlsonj 10333431Scarlsonj /* 10343431Scarlsonj * request_failed(): Attempt to request an address has failed. Take an 10353431Scarlsonj * appropriate action. 10363431Scarlsonj * 10373431Scarlsonj * input: dhcp_smach_t *: state machine that has failed 10383431Scarlsonj * output: void 10393431Scarlsonj */ 10403431Scarlsonj 10413431Scarlsonj static void 10423431Scarlsonj request_failed(dhcp_smach_t *dsmp) 10433431Scarlsonj { 10443431Scarlsonj PKT_LIST *offer; 10453431Scarlsonj 10463431Scarlsonj dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; 10473431Scarlsonj if ((offer = select_best(dsmp)) != NULL) { 10483431Scarlsonj insque(offer, &dsmp->dsm_recv_pkt_list); 10493431Scarlsonj dhcp_requesting(NULL, dsmp); 10503431Scarlsonj } else { 10513431Scarlsonj dhcpmsg(MSG_INFO, "no offers left on %s; restarting", 10523431Scarlsonj dsmp->dsm_name); 10533431Scarlsonj dhcp_selecting(dsmp); 10543431Scarlsonj } 10553431Scarlsonj } 10563431Scarlsonj 10573431Scarlsonj /* 10583431Scarlsonj * dhcp_acknak_lif(): Processes reception of an ACK or NAK packet on a given 10593431Scarlsonj * logical interface for IPv4 (only). 10603431Scarlsonj * 10613431Scarlsonj * input: iu_eh_t *: unused 10623431Scarlsonj * int: the global file descriptor the ACK/NAK arrived on 10633431Scarlsonj * short: unused 10643431Scarlsonj * iu_event_id_t: the id of this event callback with the handler 10653431Scarlsonj * void *: pointer to logical interface receiving message 10663431Scarlsonj * output: void 10673431Scarlsonj */ 10683431Scarlsonj 10693431Scarlsonj /* ARGSUSED */ 10703431Scarlsonj void 10713431Scarlsonj dhcp_acknak_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 10723431Scarlsonj void *arg) 10733431Scarlsonj { 10743431Scarlsonj dhcp_lif_t *lif = arg; 10753431Scarlsonj PKT_LIST *plp; 10763431Scarlsonj uchar_t recv_type; 10773431Scarlsonj const char *pname; 10783431Scarlsonj uint_t xid; 10793431Scarlsonj dhcp_smach_t *dsmp; 10803431Scarlsonj 10813431Scarlsonj if ((plp = recv_pkt(fd, lif->lif_max, B_FALSE, B_FALSE)) == NULL) 10823431Scarlsonj return; 10833431Scarlsonj 10843431Scarlsonj recv_type = pkt_recv_type(plp); 10853431Scarlsonj pname = pkt_type_to_string(recv_type, B_FALSE); 10863431Scarlsonj 10873431Scarlsonj if (!pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK)) { 10883431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored v4 %s packet " 10893431Scarlsonj "received via LIF %s", pname, lif->lif_name); 10903431Scarlsonj free_pkt_entry(plp); 10913431Scarlsonj return; 10923431Scarlsonj } 10933431Scarlsonj 10943431Scarlsonj /* 10953431Scarlsonj * Find the corresponding state machine not using DLPI. 10963431Scarlsonj */ 10973431Scarlsonj xid = pkt_get_xid(plp->pkt, B_FALSE); 10983431Scarlsonj for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 10993431Scarlsonj dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 11003431Scarlsonj if (dsmp->dsm_lif == lif) 11013431Scarlsonj break; 11023431Scarlsonj } 11033431Scarlsonj if (dsmp == NULL || dsmp->dsm_using_dlpi) { 11043431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored %s packet xid " 11053431Scarlsonj "%x received via LIF %s; %s", pname, xid, lif->lif_name, 11063431Scarlsonj dsmp == NULL ? "unknown state machine" : "not using DLPI"); 11073431Scarlsonj free_pkt_entry(plp); 11083431Scarlsonj return; 11093431Scarlsonj } 11103431Scarlsonj 11113431Scarlsonj /* 11123431Scarlsonj * We've got a packet; make sure it's acceptable and cancel the REQUEST 11133431Scarlsonj * retransmissions. 11143431Scarlsonj */ 11153431Scarlsonj accept_v4_acknak(dsmp, plp); 11163431Scarlsonj } 11173431Scarlsonj 11183431Scarlsonj /* 11193431Scarlsonj * dhcp_restart(): restarts DHCP (from INIT) on a given state machine 11203431Scarlsonj * 11213431Scarlsonj * input: dhcp_smach_t *: the state machine to restart DHCP on 11220Sstevel@tonic-gate * output: void 11230Sstevel@tonic-gate */ 11240Sstevel@tonic-gate 11252546Scarlsonj void 11263431Scarlsonj dhcp_restart(dhcp_smach_t *dsmp) 11270Sstevel@tonic-gate { 11283431Scarlsonj /* 11293431Scarlsonj * As we're returning to INIT state, we need to discard any leases we 11303431Scarlsonj * may have, and (for v4) canonize the LIF. There's a bit of tension 11313431Scarlsonj * between keeping around a possibly still working address, and obeying 11323431Scarlsonj * the RFCs. A more elaborate design would be to mark the addresses as 11333431Scarlsonj * DEPRECATED, and then start a removal timer. Such a design would 11343431Scarlsonj * probably compromise testing. 11353431Scarlsonj */ 11363431Scarlsonj deprecate_leases(dsmp); 11370Sstevel@tonic-gate 1138*4106Scarlsonj if (!set_start_timer(dsmp)) { 11392546Scarlsonj dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " 11403431Scarlsonj "reverting to INIT state on %s", dsmp->dsm_name); 11413431Scarlsonj 11423431Scarlsonj (void) set_smach_state(dsmp, INIT); 11433431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 11443431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 11453431Scarlsonj } 11460Sstevel@tonic-gate } 11470Sstevel@tonic-gate 11480Sstevel@tonic-gate /* 11490Sstevel@tonic-gate * stop_requesting(): decides when to stop retransmitting REQUESTs 11500Sstevel@tonic-gate * 11513431Scarlsonj * input: dhcp_smach_t *: the state machine REQUESTs are being sent from 11520Sstevel@tonic-gate * unsigned int: the number of REQUESTs sent so far 11530Sstevel@tonic-gate * output: boolean_t: B_TRUE if retransmissions should stop 11540Sstevel@tonic-gate */ 11550Sstevel@tonic-gate 11560Sstevel@tonic-gate static boolean_t 11573431Scarlsonj stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) 11580Sstevel@tonic-gate { 11593431Scarlsonj uint_t maxreq; 11600Sstevel@tonic-gate 11613431Scarlsonj maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; 11623431Scarlsonj if (n_requests >= maxreq) { 11630Sstevel@tonic-gate 11643431Scarlsonj dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", 11653431Scarlsonj dsmp->dsm_name); 11660Sstevel@tonic-gate 11673431Scarlsonj request_failed(dsmp); 11680Sstevel@tonic-gate return (B_TRUE); 11693431Scarlsonj } else { 11703431Scarlsonj return (B_FALSE); 11710Sstevel@tonic-gate } 11720Sstevel@tonic-gate } 1173