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
send_v6_request(dhcp_smach_t * dsmp)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
server_unicast_option(dhcp_smach_t * dsmp,PKT_LIST * plp)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
dhcp_requesting(iu_tq_t * tqp,void * arg)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
compute_points_v6(const PKT_LIST * pkt,const dhcp_smach_t * dsmp)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
compute_points_v4(const PKT_LIST * pkt)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 *
select_best(dhcp_smach_t * dsmp)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
accept_v4_acknak(dhcp_smach_t * dsmp,PKT_LIST * plp)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
accept_v6_message(dhcp_smach_t * dsmp,PKT_LIST * plp,const char * pname,uchar_t recv_type)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
dhcp_acknak_global(iu_eh_t * ehp,int fd,short events,iu_event_id_t id,void * arg)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
request_failed(dhcp_smach_t * dsmp)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
dhcp_packet_lif(iu_eh_t * ehp,int fd,short events,iu_event_id_t id,void * arg)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
dhcp_restart(dhcp_smach_t * dsmp)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
stop_requesting(dhcp_smach_t * dsmp,unsigned int n_requests)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