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*3431Scarlsonj * 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 30*3431Scarlsonj #include <stdlib.h> 31*3431Scarlsonj #include <string.h> 32*3431Scarlsonj #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> 39*3431Scarlsonj #include <arpa/inet.h> 400Sstevel@tonic-gate #include <dhcp_hostconf.h> 41*3431Scarlsonj #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 50*3431Scarlsonj static PKT_LIST *select_best(dhcp_smach_t *); 51*3431Scarlsonj static void request_failed(dhcp_smach_t *); 520Sstevel@tonic-gate static stop_func_t stop_requesting; 530Sstevel@tonic-gate 540Sstevel@tonic-gate /* 55*3431Scarlsonj * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING 56*3431Scarlsonj * state. This is a separate function because a NoBinding 57*3431Scarlsonj * response can also cause us to do this. 58*3431Scarlsonj * 59*3431Scarlsonj * input: dhcp_smach_t *: the state machine 60*3431Scarlsonj * output: none 61*3431Scarlsonj */ 62*3431Scarlsonj 63*3431Scarlsonj void 64*3431Scarlsonj send_v6_request(dhcp_smach_t *dsmp) 65*3431Scarlsonj { 66*3431Scarlsonj dhcp_pkt_t *dpkt; 67*3431Scarlsonj dhcpv6_ia_na_t d6in; 68*3431Scarlsonj 69*3431Scarlsonj dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST); 70*3431Scarlsonj (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, 71*3431Scarlsonj dsmp->dsm_serveridlen); 72*3431Scarlsonj 73*3431Scarlsonj /* Add an IA_NA option for our controlling LIF */ 74*3431Scarlsonj d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); 75*3431Scarlsonj d6in.d6in_t1 = htonl(0); 76*3431Scarlsonj d6in.d6in_t2 = htonl(0); 77*3431Scarlsonj (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, 78*3431Scarlsonj (dhcpv6_option_t *)&d6in + 1, 79*3431Scarlsonj sizeof (d6in) - sizeof (dhcpv6_option_t)); 80*3431Scarlsonj 81*3431Scarlsonj /* Add required Option Request option */ 82*3431Scarlsonj (void) add_pkt_prl(dpkt, dsmp); 83*3431Scarlsonj 84*3431Scarlsonj (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting, 85*3431Scarlsonj DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT); 86*3431Scarlsonj 87*3431Scarlsonj /* For DHCPv6, state switch cannot fail */ 88*3431Scarlsonj (void) set_smach_state(dsmp, REQUESTING); 89*3431Scarlsonj } 90*3431Scarlsonj 91*3431Scarlsonj /* 92*3431Scarlsonj * server_unicast_option(): determines the server address to use based on the 93*3431Scarlsonj * DHCPv6 Server Unicast option present in the given 94*3431Scarlsonj * packet. 95*3431Scarlsonj * 96*3431Scarlsonj * input: dhcp_smach_t *: the state machine 97*3431Scarlsonj * PKT_LIST *: received packet (Advertisement or Reply) 98*3431Scarlsonj * output: none 99*3431Scarlsonj */ 100*3431Scarlsonj 101*3431Scarlsonj void 102*3431Scarlsonj server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp) 103*3431Scarlsonj { 104*3431Scarlsonj const dhcpv6_option_t *d6o; 105*3431Scarlsonj uint_t olen; 106*3431Scarlsonj 107*3431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen); 108*3431Scarlsonj olen -= sizeof (*d6o); 109*3431Scarlsonj /* LINTED: no consequent */ 110*3431Scarlsonj if (d6o == NULL) { 111*3431Scarlsonj /* No Server Unicast option specified */ 112*3431Scarlsonj } else if (olen != sizeof (dsmp->dsm_server)) { 113*3431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server " 114*3431Scarlsonj "Unicast option with bad length", 115*3431Scarlsonj pkt_type_to_string(pkt_recv_type(plp), B_TRUE)); 116*3431Scarlsonj } else { 117*3431Scarlsonj in6_addr_t addr; 118*3431Scarlsonj 119*3431Scarlsonj (void) memcpy(&addr, d6o + 1, olen); 120*3431Scarlsonj if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { 121*3431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 122*3431Scarlsonj "to unspecified address ignored"); 123*3431Scarlsonj } else if (IN6_IS_ADDR_MULTICAST(&addr)) { 124*3431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 125*3431Scarlsonj "to multicast address ignored"); 126*3431Scarlsonj } else if (IN6_IS_ADDR_V4COMPAT(&addr) || 127*3431Scarlsonj IN6_IS_ADDR_V4MAPPED(&addr)) { 128*3431Scarlsonj dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " 129*3431Scarlsonj "to invalid address ignored"); 130*3431Scarlsonj } else { 131*3431Scarlsonj dsmp->dsm_server = addr; 132*3431Scarlsonj } 133*3431Scarlsonj } 134*3431Scarlsonj } 135*3431Scarlsonj 136*3431Scarlsonj /* 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 140*3431Scarlsonj * the ACK/NAK. This may be called by the offer timer or 141*3431Scarlsonj * by any function that wants to check for offers after 142*3431Scarlsonj * canceling that timer. 1430Sstevel@tonic-gate * 144*3431Scarlsonj * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback 145*3431Scarlsonj * 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 { 152*3431Scarlsonj dhcp_smach_t *dsmp = arg; 1530Sstevel@tonic-gate dhcp_pkt_t *dpkt; 1540Sstevel@tonic-gate PKT_LIST *offer; 1550Sstevel@tonic-gate lease_t lease; 156*3431Scarlsonj boolean_t isv6 = dsmp->dsm_isv6; 1572157Sdh155122 158*3431Scarlsonj /* 159*3431Scarlsonj * We assume here that if tqp is set, then this means we're being 160*3431Scarlsonj * called back by the offer wait timer. If so, then drop our hold 161*3431Scarlsonj * on the state machine. Otherwise, cancel the timer if it's running. 162*3431Scarlsonj */ 163*3431Scarlsonj if (tqp != NULL) { 164*3431Scarlsonj dhcpmsg(MSG_VERBOSE, 165*3431Scarlsonj "dhcp_requesting: offer wait timer on v%d %s", 166*3431Scarlsonj isv6 ? 6 : 4, dsmp->dsm_name); 167*3431Scarlsonj dsmp->dsm_offer_timer = -1; 168*3431Scarlsonj if (!verify_smach(dsmp)) 169*3431Scarlsonj return; 170*3431Scarlsonj } else { 171*3431Scarlsonj 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 178*3431Scarlsonj offer = select_best(dsmp); 1790Sstevel@tonic-gate if (offer == NULL) { 1800Sstevel@tonic-gate 181*3431Scarlsonj dhcpmsg(MSG_VERBOSE, 182*3431Scarlsonj "no OFFERs/Advertisements on %s, waiting...", 183*3431Scarlsonj dsmp->dsm_name); 1840Sstevel@tonic-gate 1850Sstevel@tonic-gate /* 1860Sstevel@tonic-gate * no acceptable OFFERs have come in. reschedule 187*3431Scarlsonj * ourself for callback. 1880Sstevel@tonic-gate */ 1890Sstevel@tonic-gate 190*3431Scarlsonj if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, 191*3431Scarlsonj 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 " 201*3431Scarlsonj "%s", dsmp->dsm_name); 2020Sstevel@tonic-gate 203*3431Scarlsonj stop_pkt_retransmission(dsmp); 204*3431Scarlsonj (void) set_smach_state(dsmp, INIT); 205*3431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 206*3431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 207*3431Scarlsonj } else { 208*3431Scarlsonj hold_smach(dsmp); 2090Sstevel@tonic-gate } 2100Sstevel@tonic-gate 2110Sstevel@tonic-gate return; 2120Sstevel@tonic-gate } 2130Sstevel@tonic-gate 2140Sstevel@tonic-gate /* 215*3431Scarlsonj * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly 216*3431Scarlsonj * declines all other offers we've received. We can no longer use any 217*3431Scarlsonj * cached offers, so we must discard them now. With DHCPv6, though, 218*3431Scarlsonj * we're permitted to hang onto the advertisements (offers) and try 219*3431Scarlsonj * them if the preferred one doesn't pan out. 2200Sstevel@tonic-gate */ 221*3431Scarlsonj if (!isv6) 222*3431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 2230Sstevel@tonic-gate 224*3431Scarlsonj /* stop collecting packets. */ 2250Sstevel@tonic-gate 226*3431Scarlsonj stop_pkt_retransmission(dsmp); 2270Sstevel@tonic-gate 2280Sstevel@tonic-gate /* 229*3431Scarlsonj * For IPv4, check to see whether we got an OFFER or a BOOTP packet. 230*3431Scarlsonj * If we got a BOOTP packet, go to the BOUND state now. 2310Sstevel@tonic-gate */ 232*3431Scarlsonj if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { 233*3431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 234*3431Scarlsonj 235*3431Scarlsonj if (!set_smach_state(dsmp, REQUESTING)) { 236*3431Scarlsonj dhcp_restart(dsmp); 237*3431Scarlsonj return; 238*3431Scarlsonj } 239*3431Scarlsonj 240*3431Scarlsonj if (!dhcp_bound(dsmp, offer)) { 241*3431Scarlsonj dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " 242*3431Scarlsonj "failed for %s", dsmp->dsm_name); 243*3431Scarlsonj dhcp_restart(dsmp); 244*3431Scarlsonj return; 245*3431Scarlsonj } 246*3431Scarlsonj 247*3431Scarlsonj return; 2480Sstevel@tonic-gate } 249*3431Scarlsonj 250*3431Scarlsonj if (isv6) { 251*3431Scarlsonj const char *estr, *msg; 252*3431Scarlsonj const dhcpv6_option_t *d6o; 253*3431Scarlsonj uint_t olen, msglen; 254*3431Scarlsonj 255*3431Scarlsonj /* If there's a Status Code option, print the message */ 256*3431Scarlsonj d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, 257*3431Scarlsonj &olen); 258*3431Scarlsonj (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 259*3431Scarlsonj print_server_msg(dsmp, msg, msglen); 260*3431Scarlsonj 261*3431Scarlsonj /* Copy in the Server ID (guaranteed to be present now) */ 262*3431Scarlsonj if (!save_server_id(dsmp, offer)) 263*3431Scarlsonj goto failure; 264*3431Scarlsonj 265*3431Scarlsonj /* 266*3431Scarlsonj * Determine how to send this message. If the Advertisement 267*3431Scarlsonj * (offer) has the unicast option, then use the address 268*3431Scarlsonj * specified in the option. Otherwise, send via multicast. 269*3431Scarlsonj */ 270*3431Scarlsonj server_unicast_option(dsmp, offer); 271*3431Scarlsonj 272*3431Scarlsonj send_v6_request(dsmp); 273*3431Scarlsonj } else { 274*3431Scarlsonj /* if we got a message from the server, display it. */ 275*3431Scarlsonj if (offer->opts[CD_MESSAGE] != NULL) { 276*3431Scarlsonj print_server_msg(dsmp, 277*3431Scarlsonj (char *)offer->opts[CD_MESSAGE]->value, 278*3431Scarlsonj offer->opts[CD_MESSAGE]->len); 279*3431Scarlsonj } 280*3431Scarlsonj 281*3431Scarlsonj /* 282*3431Scarlsonj * assemble a DHCPREQUEST, with the ciaddr field set to 0, 283*3431Scarlsonj * since we got here from the INIT state. 284*3431Scarlsonj */ 285*3431Scarlsonj 286*3431Scarlsonj dpkt = init_pkt(dsmp, REQUEST); 287*3431Scarlsonj 288*3431Scarlsonj /* 289*3431Scarlsonj * Grab the lease out of the OFFER; we know it's valid because 290*3431Scarlsonj * select_best() already checked. The max dhcp message size 291*3431Scarlsonj * option is set to the interface max, minus the size of the 292*3431Scarlsonj * udp and ip headers. 293*3431Scarlsonj */ 294*3431Scarlsonj 295*3431Scarlsonj (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, 296*3431Scarlsonj sizeof (lease_t)); 297*3431Scarlsonj 298*3431Scarlsonj (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); 299*3431Scarlsonj (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, 300*3431Scarlsonj htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); 301*3431Scarlsonj (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, 302*3431Scarlsonj offer->pkt->yiaddr.s_addr); 303*3431Scarlsonj (void) add_pkt_opt(dpkt, CD_SERVER_ID, 304*3431Scarlsonj offer->opts[CD_SERVER_ID]->value, 305*3431Scarlsonj offer->opts[CD_SERVER_ID]->len); 306*3431Scarlsonj 307*3431Scarlsonj (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); 308*3431Scarlsonj (void) add_pkt_prl(dpkt, dsmp); 309*3431Scarlsonj 310*3431Scarlsonj /* 311*3431Scarlsonj * dsm_reqhost was set for this state machine in 312*3431Scarlsonj * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a 313*3431Scarlsonj * host name was found 314*3431Scarlsonj */ 315*3431Scarlsonj if (dsmp->dsm_reqhost != NULL) { 316*3431Scarlsonj (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, 317*3431Scarlsonj strlen(dsmp->dsm_reqhost)); 318*3431Scarlsonj } 319*3431Scarlsonj (void) add_pkt_opt(dpkt, CD_END, NULL, 0); 320*3431Scarlsonj 321*3431Scarlsonj /* 322*3431Scarlsonj * send out the REQUEST, trying retransmissions. either a NAK 323*3431Scarlsonj * or too many REQUEST attempts will revert us to SELECTING. 324*3431Scarlsonj */ 325*3431Scarlsonj 326*3431Scarlsonj if (!set_smach_state(dsmp, REQUESTING)) { 327*3431Scarlsonj dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " 328*3431Scarlsonj "REQUESTING state; reverting to INIT on %s", 329*3431Scarlsonj dsmp->dsm_name); 330*3431Scarlsonj goto failure; 331*3431Scarlsonj } 332*3431Scarlsonj 333*3431Scarlsonj (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), 334*3431Scarlsonj stop_requesting); 335*3431Scarlsonj } 3360Sstevel@tonic-gate 3370Sstevel@tonic-gate /* all done with the offer */ 338*3431Scarlsonj free_pkt_entry(offer); 3390Sstevel@tonic-gate 340*3431Scarlsonj return; 3410Sstevel@tonic-gate 342*3431Scarlsonj failure: 343*3431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 344*3431Scarlsonj (void) set_smach_state(dsmp, INIT); 345*3431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 346*3431Scarlsonj free_pkt_list(&dsmp->dsm_recv_pkt_list); 3470Sstevel@tonic-gate } 3480Sstevel@tonic-gate 3490Sstevel@tonic-gate /* 350*3431Scarlsonj * compute_points_v6(): compute the number of "points" for a given v6 351*3431Scarlsonj * advertisement. 3520Sstevel@tonic-gate * 353*3431Scarlsonj * input: const PKT_LIST *: packet to inspect 354*3431Scarlsonj * const dhcp_smach_t *: state machine that received the packet 355*3431Scarlsonj * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. 356*3431Scarlsonj */ 357*3431Scarlsonj 358*3431Scarlsonj static int 359*3431Scarlsonj compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) 360*3431Scarlsonj { 361*3431Scarlsonj char abuf[INET6_ADDRSTRLEN]; 362*3431Scarlsonj int points = 0; 363*3431Scarlsonj const dhcpv6_option_t *d6o, *d6so; 364*3431Scarlsonj uint_t olen, solen; 365*3431Scarlsonj int i; 366*3431Scarlsonj const char *estr, *msg; 367*3431Scarlsonj uint_t msglen; 368*3431Scarlsonj 369*3431Scarlsonj /* 370*3431Scarlsonj * Look through the packet contents. Valid packets must have our 371*3431Scarlsonj * client ID and a server ID, which has already been checked by 372*3431Scarlsonj * dhcp_acknak_lif. Bonus points for each option. 373*3431Scarlsonj */ 374*3431Scarlsonj 375*3431Scarlsonj /* One point for having a valid message. */ 376*3431Scarlsonj points++; 377*3431Scarlsonj 378*3431Scarlsonj /* 379*3431Scarlsonj * Per RFC 3315, if the Advertise message says, "yes, we have no 380*3431Scarlsonj * bananas today," then ignore the entire message. (Why it's just 381*3431Scarlsonj * _this_ error and no other is a bit of a mystery, but a standard is a 382*3431Scarlsonj * standard.) 383*3431Scarlsonj */ 384*3431Scarlsonj d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); 385*3431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == 386*3431Scarlsonj DHCPV6_STAT_NOADDRS) { 387*3431Scarlsonj dhcpmsg(MSG_INFO, 388*3431Scarlsonj "discard advertisement from %s on %s: no address status", 389*3431Scarlsonj inet_ntop(AF_INET6, 390*3431Scarlsonj &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, 391*3431Scarlsonj abuf, sizeof (abuf)), dsmp->dsm_name); 392*3431Scarlsonj return (-1); 393*3431Scarlsonj } 394*3431Scarlsonj 395*3431Scarlsonj /* Two points for each batch of offered IP addresses */ 396*3431Scarlsonj d6o = NULL; 397*3431Scarlsonj while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, 398*3431Scarlsonj &olen)) != NULL) { 399*3431Scarlsonj 400*3431Scarlsonj /* 401*3431Scarlsonj * Note that it's possible to have "no bananas" on an 402*3431Scarlsonj * individual IA. We must look for that here. 403*3431Scarlsonj * 404*3431Scarlsonj * RFC 3315 section 17.1.3 does not refer to the status code 405*3431Scarlsonj * embedded in the IA itself. However, the TAHI test suite 406*3431Scarlsonj * checks for this specific case. Because it's extremely 407*3431Scarlsonj * unlikely that any usable server is going to report that it 408*3431Scarlsonj * has no addresses on a network using DHCP for address 409*3431Scarlsonj * assignment, we allow such messages to be dropped. 410*3431Scarlsonj */ 411*3431Scarlsonj d6so = dhcpv6_find_option( 412*3431Scarlsonj (const char *)d6o + sizeof (dhcpv6_ia_na_t), 413*3431Scarlsonj olen - sizeof (dhcpv6_ia_na_t), NULL, 414*3431Scarlsonj DHCPV6_OPT_STATUS_CODE, &solen); 415*3431Scarlsonj if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == 416*3431Scarlsonj DHCPV6_STAT_NOADDRS) 417*3431Scarlsonj return (-1); 418*3431Scarlsonj points += 2; 419*3431Scarlsonj } 420*3431Scarlsonj 421*3431Scarlsonj /* 422*3431Scarlsonj * Note that we drive on in the case where there are no addresses. The 423*3431Scarlsonj * hope here is that we'll at least get some useful configuration 424*3431Scarlsonj * information. 425*3431Scarlsonj */ 426*3431Scarlsonj 427*3431Scarlsonj /* One point for each requested option */ 428*3431Scarlsonj for (i = 0; i < dsmp->dsm_prllen; i++) { 429*3431Scarlsonj if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != 430*3431Scarlsonj NULL) 431*3431Scarlsonj points++; 432*3431Scarlsonj } 433*3431Scarlsonj 434*3431Scarlsonj /* 435*3431Scarlsonj * Ten points for each point of "preference." Note: the value 255 is 436*3431Scarlsonj * special. It means "stop right now and select this server." 437*3431Scarlsonj */ 438*3431Scarlsonj d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); 439*3431Scarlsonj if (d6o != NULL && olen == sizeof (*d6o) + 1) { 440*3431Scarlsonj int pref = *(const uchar_t *)(d6o + 1); 441*3431Scarlsonj 442*3431Scarlsonj if (pref == 255) 443*3431Scarlsonj return (-2); 444*3431Scarlsonj points += 10 * pref; 445*3431Scarlsonj } 446*3431Scarlsonj 447*3431Scarlsonj return (points); 448*3431Scarlsonj } 449*3431Scarlsonj 450*3431Scarlsonj /* 451*3431Scarlsonj * compute_points_v4(): compute the number of "points" for a given v4 offer. 452*3431Scarlsonj * 453*3431Scarlsonj * input: const PKT_LIST *: packet to inspect 454*3431Scarlsonj * const dhcp_smach_t *: state machine that received the packet 455*3431Scarlsonj * output: int: -1 to discard, >=0 for preference. 456*3431Scarlsonj */ 457*3431Scarlsonj 458*3431Scarlsonj static int 459*3431Scarlsonj compute_points_v4(const PKT_LIST *pkt) 460*3431Scarlsonj { 461*3431Scarlsonj int points = 0; 462*3431Scarlsonj 463*3431Scarlsonj if (pkt->opts[CD_DHCP_TYPE] == NULL) { 464*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); 465*3431Scarlsonj goto valid_offer; 466*3431Scarlsonj } 467*3431Scarlsonj 468*3431Scarlsonj if (pkt->opts[CD_LEASE_TIME] == NULL) { 469*3431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " 470*3431Scarlsonj "time"); 471*3431Scarlsonj return (-1); 472*3431Scarlsonj } 473*3431Scarlsonj 474*3431Scarlsonj if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 475*3431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 476*3431Scarlsonj "lease time"); 477*3431Scarlsonj return (-1); 478*3431Scarlsonj } 479*3431Scarlsonj 480*3431Scarlsonj if (pkt->opts[CD_SERVER_ID] == NULL) { 481*3431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " 482*3431Scarlsonj "id"); 483*3431Scarlsonj return (-1); 484*3431Scarlsonj } 485*3431Scarlsonj 486*3431Scarlsonj if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 487*3431Scarlsonj dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " 488*3431Scarlsonj "server id"); 489*3431Scarlsonj return (-1); 490*3431Scarlsonj } 491*3431Scarlsonj 492*3431Scarlsonj /* valid DHCP OFFER. see if we got our parameters. */ 493*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); 494*3431Scarlsonj points += 30; 495*3431Scarlsonj 496*3431Scarlsonj valid_offer: 497*3431Scarlsonj if (pkt->rfc1048) 498*3431Scarlsonj points += 5; 499*3431Scarlsonj 500*3431Scarlsonj /* 501*3431Scarlsonj * also could be faked, though more difficult because the encapsulation 502*3431Scarlsonj * is hard to encode on a BOOTP server; plus there's not as much real 503*3431Scarlsonj * estate in the packet for options, so it's likely this option would 504*3431Scarlsonj * get dropped. 505*3431Scarlsonj */ 506*3431Scarlsonj 507*3431Scarlsonj if (pkt->opts[CD_VENDOR_SPEC] != NULL) 508*3431Scarlsonj points += 80; 509*3431Scarlsonj 510*3431Scarlsonj if (pkt->opts[CD_SUBNETMASK] != NULL) 511*3431Scarlsonj points++; 512*3431Scarlsonj 513*3431Scarlsonj if (pkt->opts[CD_ROUTER] != NULL) 514*3431Scarlsonj points++; 515*3431Scarlsonj 516*3431Scarlsonj if (pkt->opts[CD_HOSTNAME] != NULL) 517*3431Scarlsonj points += 5; 518*3431Scarlsonj 519*3431Scarlsonj return (points); 520*3431Scarlsonj } 521*3431Scarlsonj 522*3431Scarlsonj /* 523*3431Scarlsonj * select_best(): selects the best offer from a list of IPv4 OFFER packets or 524*3431Scarlsonj * DHCPv6 Advertise packets. 525*3431Scarlsonj * 526*3431Scarlsonj * input: dhcp_smach_t *: state machine with enqueued offers 5270Sstevel@tonic-gate * output: PKT_LIST *: the best packet, or NULL if none are acceptable 5280Sstevel@tonic-gate */ 5290Sstevel@tonic-gate 5300Sstevel@tonic-gate static PKT_LIST * 531*3431Scarlsonj select_best(dhcp_smach_t *dsmp) 5320Sstevel@tonic-gate { 533*3431Scarlsonj PKT_LIST *current = dsmp->dsm_recv_pkt_list; 534*3431Scarlsonj PKT_LIST *next, *best = NULL; 535*3431Scarlsonj int points, best_points = -1; 5360Sstevel@tonic-gate 5370Sstevel@tonic-gate /* 5380Sstevel@tonic-gate * pick out the best offer. point system. 539*3431Scarlsonj * what's important for IPv4? 5400Sstevel@tonic-gate * 541*3431Scarlsonj * 0) DHCP (30 points) 5420Sstevel@tonic-gate * 1) no option overload 543*3431Scarlsonj * 2) encapsulated vendor option (80 points) 5440Sstevel@tonic-gate * 3) non-null sname and siaddr fields 5450Sstevel@tonic-gate * 4) non-null file field 546*3431Scarlsonj * 5) hostname (5 points) 547*3431Scarlsonj * 6) subnetmask (1 point) 548*3431Scarlsonj * 7) router (1 point) 5490Sstevel@tonic-gate */ 5500Sstevel@tonic-gate 551*3431Scarlsonj for (; current != NULL; current = next) { 552*3431Scarlsonj next = current->next; 5530Sstevel@tonic-gate 554*3431Scarlsonj points = current->isv6 ? 555*3431Scarlsonj compute_points_v6(current, dsmp) : 556*3431Scarlsonj compute_points_v4(current); 5570Sstevel@tonic-gate 558*3431Scarlsonj /* 559*3431Scarlsonj * Just discard any unacceptable entries we encounter. 560*3431Scarlsonj */ 561*3431Scarlsonj if (points == -1) { 562*3431Scarlsonj remque(current); 563*3431Scarlsonj free_pkt_entry(current); 5640Sstevel@tonic-gate continue; 5650Sstevel@tonic-gate } 5660Sstevel@tonic-gate 567*3431Scarlsonj dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); 5680Sstevel@tonic-gate 569*3431Scarlsonj /* Special case: stop now and select */ 570*3431Scarlsonj if (points == -2) { 571*3431Scarlsonj best = current; 572*3431Scarlsonj break; 573*3431Scarlsonj } 5740Sstevel@tonic-gate 5750Sstevel@tonic-gate if (points >= best_points) { 5760Sstevel@tonic-gate best_points = points; 5770Sstevel@tonic-gate best = current; 5780Sstevel@tonic-gate } 5790Sstevel@tonic-gate } 5800Sstevel@tonic-gate 5810Sstevel@tonic-gate if (best != NULL) { 5820Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); 583*3431Scarlsonj remque(best); 584*3431Scarlsonj } else { 5850Sstevel@tonic-gate dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); 586*3431Scarlsonj } 5870Sstevel@tonic-gate 5880Sstevel@tonic-gate return (best); 5890Sstevel@tonic-gate } 5900Sstevel@tonic-gate 5910Sstevel@tonic-gate /* 592*3431Scarlsonj * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the 593*3431Scarlsonj * current state. If we're renewing or rebinding, the ACK 594*3431Scarlsonj * must be for the same address and must have a new lease 595*3431Scarlsonj * time. If it's a NAK, then our cache is garbage, and we 596*3431Scarlsonj * must restart. Finally, call dhcp_bound on accepted 597*3431Scarlsonj * ACKs. 5980Sstevel@tonic-gate * 599*3431Scarlsonj * input: dhcp_smach_t *: the state machine to handle the ACK/NAK 600*3431Scarlsonj * PKT_LIST *: the ACK/NAK message 6010Sstevel@tonic-gate * output: void 6020Sstevel@tonic-gate */ 6030Sstevel@tonic-gate 604*3431Scarlsonj static void 605*3431Scarlsonj accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) 6060Sstevel@tonic-gate { 6070Sstevel@tonic-gate if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { 6080Sstevel@tonic-gate if (plp->opts[CD_LEASE_TIME] == NULL || 6090Sstevel@tonic-gate plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { 610*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " 611*3431Scarlsonj "%s missing mandatory lease option, ignored", 612*3431Scarlsonj dsmp->dsm_name); 613*3431Scarlsonj dsmp->dsm_bad_offers++; 614*3431Scarlsonj free_pkt_entry(plp); 6150Sstevel@tonic-gate return; 6160Sstevel@tonic-gate } 617*3431Scarlsonj if ((dsmp->dsm_state == RENEWING || 618*3431Scarlsonj dsmp->dsm_state == REBINDING) && 619*3431Scarlsonj dsmp->dsm_leases->dl_lifs->lif_addr != 620*3431Scarlsonj plp->pkt->yiaddr.s_addr) { 621*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " 622*3431Scarlsonj "packet has a different IP address (%s), ignored", 623*3431Scarlsonj inet_ntoa(plp->pkt->yiaddr)); 624*3431Scarlsonj dsmp->dsm_bad_offers++; 625*3431Scarlsonj free_pkt_entry(plp); 6260Sstevel@tonic-gate return; 6270Sstevel@tonic-gate } 6280Sstevel@tonic-gate } 6290Sstevel@tonic-gate 6300Sstevel@tonic-gate /* 6310Sstevel@tonic-gate * looks good; cancel the retransmission timer and unregister 6320Sstevel@tonic-gate * the acknak handler. ACK to BOUND, NAK back to SELECTING. 6330Sstevel@tonic-gate */ 6340Sstevel@tonic-gate 635*3431Scarlsonj stop_pkt_retransmission(dsmp); 6360Sstevel@tonic-gate 637*3431Scarlsonj if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { 638*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", 639*3431Scarlsonj dsmp->dsm_name); 640*3431Scarlsonj dsmp->dsm_bad_offers++; 641*3431Scarlsonj free_pkt_entry(plp); 642*3431Scarlsonj dhcp_restart(dsmp); 6430Sstevel@tonic-gate 6440Sstevel@tonic-gate /* 6450Sstevel@tonic-gate * remove any bogus cached configuration we might have 6460Sstevel@tonic-gate * around (right now would only happen if we got here 6470Sstevel@tonic-gate * from INIT_REBOOT). 6480Sstevel@tonic-gate */ 6490Sstevel@tonic-gate 650*3431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); 6510Sstevel@tonic-gate return; 6520Sstevel@tonic-gate } 6530Sstevel@tonic-gate 6540Sstevel@tonic-gate if (plp->opts[CD_SERVER_ID] == NULL || 6550Sstevel@tonic-gate plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { 656*3431Scarlsonj dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " 657*3431Scarlsonj "server id, restarting DHCP on %s", dsmp->dsm_name); 658*3431Scarlsonj dsmp->dsm_bad_offers++; 659*3431Scarlsonj free_pkt_entry(plp); 660*3431Scarlsonj dhcp_restart(dsmp); 661*3431Scarlsonj return; 662*3431Scarlsonj } 663*3431Scarlsonj 664*3431Scarlsonj if (plp->opts[CD_MESSAGE] != NULL) { 665*3431Scarlsonj print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, 666*3431Scarlsonj plp->opts[CD_MESSAGE]->len); 667*3431Scarlsonj } 668*3431Scarlsonj 669*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); 670*3431Scarlsonj if (!dhcp_bound(dsmp, plp)) { 671*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " 672*3431Scarlsonj "for %s", dsmp->dsm_name); 673*3431Scarlsonj dhcp_restart(dsmp); 674*3431Scarlsonj } 675*3431Scarlsonj } 676*3431Scarlsonj 677*3431Scarlsonj /* 678*3431Scarlsonj * accept_v6_message(): determine what to do with a DHCPv6 message based on the 679*3431Scarlsonj * current state. 680*3431Scarlsonj * 681*3431Scarlsonj * input: dhcp_smach_t *: the state machine to handle the message 682*3431Scarlsonj * PKT_LIST *: the DHCPv6 message 683*3431Scarlsonj * const char *: type of message (for logging) 684*3431Scarlsonj * uchar_t: type of message (extracted from packet) 685*3431Scarlsonj * output: void 686*3431Scarlsonj */ 687*3431Scarlsonj 688*3431Scarlsonj static void 689*3431Scarlsonj accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, 690*3431Scarlsonj uchar_t recv_type) 691*3431Scarlsonj { 692*3431Scarlsonj const dhcpv6_option_t *d6o; 693*3431Scarlsonj uint_t olen; 694*3431Scarlsonj const char *estr, *msg; 695*3431Scarlsonj uint_t msglen; 696*3431Scarlsonj int status; 697*3431Scarlsonj 698*3431Scarlsonj /* 699*3431Scarlsonj * All valid DHCPv6 messages must have our Client ID specified. 700*3431Scarlsonj */ 701*3431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); 702*3431Scarlsonj olen -= sizeof (*d6o); 703*3431Scarlsonj if (d6o == NULL || olen != dsmp->dsm_cidlen || 704*3431Scarlsonj memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { 705*3431Scarlsonj dhcpmsg(MSG_VERBOSE, 706*3431Scarlsonj "accept_v6_message: discarded %s on %s: %s Client ID", 707*3431Scarlsonj pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); 708*3431Scarlsonj free_pkt_entry(plp); 7090Sstevel@tonic-gate return; 7100Sstevel@tonic-gate } 7110Sstevel@tonic-gate 712*3431Scarlsonj /* 713*3431Scarlsonj * All valid DHCPv6 messages must have a Server ID specified. 714*3431Scarlsonj * 715*3431Scarlsonj * If this is a Reply and it's not in response to Solicit, Confirm, 716*3431Scarlsonj * Rebind, or Information-Request, then it must also match the Server 717*3431Scarlsonj * ID we're expecting. 718*3431Scarlsonj * 719*3431Scarlsonj * For Reply in the Solicit, Confirm, Rebind, and Information-Request 720*3431Scarlsonj * cases, the Server ID needs to be saved. This is done inside of 721*3431Scarlsonj * dhcp_bound(). 722*3431Scarlsonj */ 723*3431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); 724*3431Scarlsonj if (d6o == NULL) { 725*3431Scarlsonj dhcpmsg(MSG_DEBUG, 726*3431Scarlsonj "accept_v6_message: discarded %s on %s: no Server ID", 727*3431Scarlsonj pname, dsmp->dsm_name); 728*3431Scarlsonj free_pkt_entry(plp); 729*3431Scarlsonj return; 730*3431Scarlsonj } 731*3431Scarlsonj if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && 732*3431Scarlsonj dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && 733*3431Scarlsonj dsmp->dsm_state != INFORM_SENT) { 734*3431Scarlsonj olen -= sizeof (*d6o); 735*3431Scarlsonj if (olen != dsmp->dsm_serveridlen || 736*3431Scarlsonj memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { 737*3431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " 738*3431Scarlsonj "%s: wrong Server ID", pname, dsmp->dsm_name); 739*3431Scarlsonj free_pkt_entry(plp); 740*3431Scarlsonj return; 741*3431Scarlsonj } 742*3431Scarlsonj } 743*3431Scarlsonj 744*3431Scarlsonj /* 745*3431Scarlsonj * Break out of the switch if the input message needs to be discarded. 746*3431Scarlsonj * Return from the function if the message has been enqueued or 747*3431Scarlsonj * consumed. 748*3431Scarlsonj */ 749*3431Scarlsonj switch (dsmp->dsm_state) { 750*3431Scarlsonj case SELECTING: 751*3431Scarlsonj /* A Reply message signifies a Rapid-Commit. */ 752*3431Scarlsonj if (recv_type == DHCPV6_MSG_REPLY) { 753*3431Scarlsonj if (dhcpv6_pkt_option(plp, NULL, 754*3431Scarlsonj DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { 755*3431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " 756*3431Scarlsonj "on %s lacks Rapid-Commit; ignoring", 757*3431Scarlsonj dsmp->dsm_name); 758*3431Scarlsonj break; 759*3431Scarlsonj } 760*3431Scarlsonj dhcpmsg(MSG_VERBOSE, 761*3431Scarlsonj "accept_v6_message: rapid-commit Reply on %s", 762*3431Scarlsonj dsmp->dsm_name); 763*3431Scarlsonj cancel_offer_timer(dsmp); 764*3431Scarlsonj goto rapid_commit; 765*3431Scarlsonj } 766*3431Scarlsonj 767*3431Scarlsonj /* Otherwise, we're looking for Advertisements. */ 768*3431Scarlsonj if (recv_type != DHCPV6_MSG_ADVERTISE) 769*3431Scarlsonj break; 770*3431Scarlsonj 771*3431Scarlsonj /* 772*3431Scarlsonj * Special case: if this advertisement has preference 255, then 773*3431Scarlsonj * we must stop right now and select this server. 774*3431Scarlsonj */ 775*3431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, 776*3431Scarlsonj &olen); 777*3431Scarlsonj if (d6o != NULL && olen == sizeof (*d6o) + 1 && 778*3431Scarlsonj *(const uchar_t *)(d6o + 1) == 255) { 779*3431Scarlsonj pkt_smach_enqueue(dsmp, plp); 780*3431Scarlsonj dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" 781*3431Scarlsonj " immediate Request on %s", dsmp->dsm_name); 782*3431Scarlsonj dhcp_requesting(NULL, dsmp); 783*3431Scarlsonj } else { 784*3431Scarlsonj pkt_smach_enqueue(dsmp, plp); 785*3431Scarlsonj } 786*3431Scarlsonj return; 787*3431Scarlsonj 788*3431Scarlsonj case PRE_BOUND: 789*3431Scarlsonj case BOUND: 790*3431Scarlsonj /* 791*3431Scarlsonj * Not looking for anything in these states. (If we 792*3431Scarlsonj * implemented reconfigure, that might go here.) 793*3431Scarlsonj */ 794*3431Scarlsonj break; 7950Sstevel@tonic-gate 796*3431Scarlsonj case REQUESTING: 797*3431Scarlsonj case INIT_REBOOT: 798*3431Scarlsonj case RENEWING: 799*3431Scarlsonj case REBINDING: 800*3431Scarlsonj case INFORM_SENT: 801*3431Scarlsonj /* 802*3431Scarlsonj * We're looking for Reply messages. 803*3431Scarlsonj */ 804*3431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 805*3431Scarlsonj break; 806*3431Scarlsonj dhcpmsg(MSG_VERBOSE, 807*3431Scarlsonj "accept_v6_message: received Reply message on %s", 808*3431Scarlsonj dsmp->dsm_name); 809*3431Scarlsonj rapid_commit: 810*3431Scarlsonj /* 811*3431Scarlsonj * Extract the status code option. If one is present and the 812*3431Scarlsonj * request failed, then try to go to another advertisement in 813*3431Scarlsonj * the list or restart the selection machinery. 814*3431Scarlsonj */ 815*3431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 816*3431Scarlsonj &olen); 817*3431Scarlsonj status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); 818*3431Scarlsonj /* 819*3431Scarlsonj * Check for the UseMulticast status code. If this is present, 820*3431Scarlsonj * and if we were actually using unicast, then drop back and 821*3431Scarlsonj * try again. If we weren't using unicast, then just pretend 822*3431Scarlsonj * we never saw this message -- the peer is confused. (TAHI 823*3431Scarlsonj * does this.) 824*3431Scarlsonj */ 825*3431Scarlsonj if (status == DHCPV6_STAT_USEMCAST) { 826*3431Scarlsonj if (IN6_IS_ADDR_MULTICAST( 827*3431Scarlsonj &dsmp->dsm_send_dest.v6.sin6_addr)) { 828*3431Scarlsonj break; 829*3431Scarlsonj } else { 830*3431Scarlsonj free_pkt_entry(plp); 831*3431Scarlsonj dsmp->dsm_send_dest.v6.sin6_addr = 832*3431Scarlsonj ipv6_all_dhcp_relay_and_servers; 833*3431Scarlsonj retransmit_now(dsmp); 834*3431Scarlsonj return; 835*3431Scarlsonj } 836*3431Scarlsonj } 837*3431Scarlsonj print_server_msg(dsmp, msg, msglen); 838*3431Scarlsonj /* 839*3431Scarlsonj * We treat NoBinding at the top level as "success." Granted, 840*3431Scarlsonj * this doesn't make much sense, but the TAHI test suite does 841*3431Scarlsonj * this. NoBinding really only makes sense in the context of a 842*3431Scarlsonj * specific IA, as it refers to the GUID:IAID binding, so 843*3431Scarlsonj * ignoring it at the top level is safe. 844*3431Scarlsonj */ 845*3431Scarlsonj if (status == DHCPV6_STAT_SUCCESS || 846*3431Scarlsonj status == DHCPV6_STAT_NOBINDING) { 847*3431Scarlsonj if (dhcp_bound(dsmp, plp)) { 848*3431Scarlsonj /* 849*3431Scarlsonj * dhcp_bound will stop retransmission on 850*3431Scarlsonj * success, if that's called for. 851*3431Scarlsonj */ 852*3431Scarlsonj server_unicast_option(dsmp, plp); 853*3431Scarlsonj } else { 854*3431Scarlsonj stop_pkt_retransmission(dsmp); 855*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: " 856*3431Scarlsonj "dhcp_bound failed for %s", dsmp->dsm_name); 857*3431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, 858*3431Scarlsonj dsmp->dsm_isv6); 859*3431Scarlsonj if (dsmp->dsm_state != INFORM_SENT) 860*3431Scarlsonj dhcp_restart(dsmp); 861*3431Scarlsonj } 862*3431Scarlsonj } else { 863*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 864*3431Scarlsonj estr); 865*3431Scarlsonj stop_pkt_retransmission(dsmp); 866*3431Scarlsonj free_pkt_entry(plp); 867*3431Scarlsonj if (dsmp->dsm_state == INFORM_SENT) { 868*3431Scarlsonj (void) set_smach_state(dsmp, INIT); 869*3431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); 870*3431Scarlsonj } else { 871*3431Scarlsonj (void) remove_hostconf(dsmp->dsm_name, 872*3431Scarlsonj dsmp->dsm_isv6); 873*3431Scarlsonj request_failed(dsmp); 874*3431Scarlsonj } 875*3431Scarlsonj } 876*3431Scarlsonj return; 877*3431Scarlsonj 878*3431Scarlsonj case DECLINING: 879*3431Scarlsonj /* 880*3431Scarlsonj * We're looking for Reply messages. 881*3431Scarlsonj */ 882*3431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 883*3431Scarlsonj break; 884*3431Scarlsonj stop_pkt_retransmission(dsmp); 885*3431Scarlsonj /* 886*3431Scarlsonj * Extract the status code option. Note that it's not a 887*3431Scarlsonj * failure if the server reports an error. 888*3431Scarlsonj */ 889*3431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 890*3431Scarlsonj &olen); 891*3431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, 892*3431Scarlsonj &msglen) == DHCPV6_STAT_SUCCESS) { 893*3431Scarlsonj print_server_msg(dsmp, msg, msglen); 894*3431Scarlsonj } else { 895*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 896*3431Scarlsonj estr); 897*3431Scarlsonj } 898*3431Scarlsonj free_pkt_entry(plp); 899*3431Scarlsonj if (dsmp->dsm_leases == NULL) { 900*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " 901*3431Scarlsonj "leases left; restarting", dsmp->dsm_name); 902*3431Scarlsonj dhcp_restart(dsmp); 903*3431Scarlsonj } else if (dsmp->dsm_lif_wait == 0) { 904*3431Scarlsonj (void) set_smach_state(dsmp, BOUND); 905*3431Scarlsonj } else { 906*3431Scarlsonj (void) set_smach_state(dsmp, PRE_BOUND); 907*3431Scarlsonj } 908*3431Scarlsonj return; 909*3431Scarlsonj 910*3431Scarlsonj case RELEASING: 911*3431Scarlsonj /* 912*3431Scarlsonj * We're looking for Reply messages. 913*3431Scarlsonj */ 914*3431Scarlsonj if (recv_type != DHCPV6_MSG_REPLY) 915*3431Scarlsonj break; 916*3431Scarlsonj stop_pkt_retransmission(dsmp); 917*3431Scarlsonj /* 918*3431Scarlsonj * Extract the status code option. 919*3431Scarlsonj */ 920*3431Scarlsonj d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, 921*3431Scarlsonj &olen); 922*3431Scarlsonj if (dhcpv6_status_code(d6o, olen, &estr, &msg, 923*3431Scarlsonj &msglen) == DHCPV6_STAT_SUCCESS) { 924*3431Scarlsonj print_server_msg(dsmp, msg, msglen); 925*3431Scarlsonj } else { 926*3431Scarlsonj dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", 927*3431Scarlsonj estr); 928*3431Scarlsonj } 929*3431Scarlsonj free_pkt_entry(plp); 930*3431Scarlsonj finished_smach(dsmp, DHCP_IPC_SUCCESS); 9310Sstevel@tonic-gate return; 9320Sstevel@tonic-gate } 9330Sstevel@tonic-gate 934*3431Scarlsonj /* 935*3431Scarlsonj * Break from above switch means that the message must be discarded. 936*3431Scarlsonj */ 937*3431Scarlsonj dhcpmsg(MSG_VERBOSE, 938*3431Scarlsonj "accept_v6_message: discarded v6 %s on %s; state %s", 939*3431Scarlsonj pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); 940*3431Scarlsonj free_pkt_entry(plp); 9410Sstevel@tonic-gate } 9420Sstevel@tonic-gate 9430Sstevel@tonic-gate /* 944*3431Scarlsonj * dhcp_acknak_common(): Processes reception of an ACK or NAK packet on the 945*3431Scarlsonj * global socket -- broadcast packets for IPv4, all 946*3431Scarlsonj * packets for DHCPv6. 9470Sstevel@tonic-gate * 948*3431Scarlsonj * input: iu_eh_t *: unused 949*3431Scarlsonj * int: the global file descriptor the ACK/NAK arrived on 950*3431Scarlsonj * short: unused 951*3431Scarlsonj * iu_event_id_t: unused 952*3431Scarlsonj * void *: unused 953*3431Scarlsonj * output: void 954*3431Scarlsonj */ 955*3431Scarlsonj 956*3431Scarlsonj /* ARGSUSED */ 957*3431Scarlsonj void 958*3431Scarlsonj dhcp_acknak_common(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 959*3431Scarlsonj void *arg) 960*3431Scarlsonj { 961*3431Scarlsonj PKT_LIST *plp; 962*3431Scarlsonj dhcp_pif_t *pif; 963*3431Scarlsonj uchar_t recv_type; 964*3431Scarlsonj const char *pname; 965*3431Scarlsonj uint_t xid; 966*3431Scarlsonj dhcp_smach_t *dsmp; 967*3431Scarlsonj boolean_t isv6 = (fd == v6_sock_fd); 968*3431Scarlsonj 969*3431Scarlsonj if ((plp = recv_pkt(fd, get_max_mtu(isv6), isv6, B_FALSE)) == NULL) 970*3431Scarlsonj return; 971*3431Scarlsonj 972*3431Scarlsonj pif = lookup_pif_by_index(plp->ifindex, isv6); 973*3431Scarlsonj if (pif == NULL) { 974*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored packet " 975*3431Scarlsonj "received on v%d ifIndex %d", isv6 ? 6 : 4, plp->ifindex); 976*3431Scarlsonj free_pkt_entry(plp); 977*3431Scarlsonj return; 978*3431Scarlsonj } 979*3431Scarlsonj 980*3431Scarlsonj recv_type = pkt_recv_type(plp); 981*3431Scarlsonj pname = pkt_type_to_string(recv_type, isv6); 982*3431Scarlsonj if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { 983*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " 984*3431Scarlsonj "received via broadcast on %s", pname, pif->pif_name); 985*3431Scarlsonj free_pkt_entry(plp); 986*3431Scarlsonj return; 987*3431Scarlsonj } 988*3431Scarlsonj 989*3431Scarlsonj if (isv6 && recv_type == DHCPV6_MSG_RECONFIGURE) { 990*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored v6 " 991*3431Scarlsonj "Reconfigure received via %s", pif->pif_name); 992*3431Scarlsonj free_pkt_entry(plp); 993*3431Scarlsonj return; 994*3431Scarlsonj } 995*3431Scarlsonj 996*3431Scarlsonj /* 997*3431Scarlsonj * Find the corresponding state machine not using DLPI. 998*3431Scarlsonj * 999*3431Scarlsonj * Note that DHCPv6 Reconfigure would be special: it's not the reply to 1000*3431Scarlsonj * any transaction, and thus we would need to search on transaction ID 1001*3431Scarlsonj * zero (all state machines) to find the match. However, Reconfigure 1002*3431Scarlsonj * is not yet supported. 1003*3431Scarlsonj */ 1004*3431Scarlsonj xid = pkt_get_xid(plp->pkt, isv6); 1005*3431Scarlsonj for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; 1006*3431Scarlsonj dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { 1007*3431Scarlsonj if (dsmp->dsm_lif->lif_pif == pif) 1008*3431Scarlsonj break; 1009*3431Scarlsonj } 1010*3431Scarlsonj if (dsmp == NULL || dsmp->dsm_using_dlpi) { 1011*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_common: ignored %s packet " 1012*3431Scarlsonj "received via broadcast %s; %s", pname, pif->pif_name, 1013*3431Scarlsonj dsmp == NULL ? "unknown state machine" : "not using DLPI"); 1014*3431Scarlsonj free_pkt_entry(plp); 1015*3431Scarlsonj return; 1016*3431Scarlsonj } 1017*3431Scarlsonj 1018*3431Scarlsonj /* 1019*3431Scarlsonj * We've got a packet; make sure it's acceptable and cancel the REQUEST 1020*3431Scarlsonj * retransmissions. 1021*3431Scarlsonj */ 1022*3431Scarlsonj if (isv6) 1023*3431Scarlsonj accept_v6_message(dsmp, plp, pname, recv_type); 1024*3431Scarlsonj else 1025*3431Scarlsonj accept_v4_acknak(dsmp, plp); 1026*3431Scarlsonj } 1027*3431Scarlsonj 1028*3431Scarlsonj /* 1029*3431Scarlsonj * request_failed(): Attempt to request an address has failed. Take an 1030*3431Scarlsonj * appropriate action. 1031*3431Scarlsonj * 1032*3431Scarlsonj * input: dhcp_smach_t *: state machine that has failed 1033*3431Scarlsonj * output: void 1034*3431Scarlsonj */ 1035*3431Scarlsonj 1036*3431Scarlsonj static void 1037*3431Scarlsonj request_failed(dhcp_smach_t *dsmp) 1038*3431Scarlsonj { 1039*3431Scarlsonj PKT_LIST *offer; 1040*3431Scarlsonj 1041*3431Scarlsonj dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; 1042*3431Scarlsonj if ((offer = select_best(dsmp)) != NULL) { 1043*3431Scarlsonj insque(offer, &dsmp->dsm_recv_pkt_list); 1044*3431Scarlsonj dhcp_requesting(NULL, dsmp); 1045*3431Scarlsonj } else { 1046*3431Scarlsonj dhcpmsg(MSG_INFO, "no offers left on %s; restarting", 1047*3431Scarlsonj dsmp->dsm_name); 1048*3431Scarlsonj dhcp_selecting(dsmp); 1049*3431Scarlsonj } 1050*3431Scarlsonj } 1051*3431Scarlsonj 1052*3431Scarlsonj /* 1053*3431Scarlsonj * dhcp_acknak_lif(): Processes reception of an ACK or NAK packet on a given 1054*3431Scarlsonj * logical interface for IPv4 (only). 1055*3431Scarlsonj * 1056*3431Scarlsonj * input: iu_eh_t *: unused 1057*3431Scarlsonj * int: the global file descriptor the ACK/NAK arrived on 1058*3431Scarlsonj * short: unused 1059*3431Scarlsonj * iu_event_id_t: the id of this event callback with the handler 1060*3431Scarlsonj * void *: pointer to logical interface receiving message 1061*3431Scarlsonj * output: void 1062*3431Scarlsonj */ 1063*3431Scarlsonj 1064*3431Scarlsonj /* ARGSUSED */ 1065*3431Scarlsonj void 1066*3431Scarlsonj dhcp_acknak_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, 1067*3431Scarlsonj void *arg) 1068*3431Scarlsonj { 1069*3431Scarlsonj dhcp_lif_t *lif = arg; 1070*3431Scarlsonj PKT_LIST *plp; 1071*3431Scarlsonj uchar_t recv_type; 1072*3431Scarlsonj const char *pname; 1073*3431Scarlsonj uint_t xid; 1074*3431Scarlsonj dhcp_smach_t *dsmp; 1075*3431Scarlsonj 1076*3431Scarlsonj if ((plp = recv_pkt(fd, lif->lif_max, B_FALSE, B_FALSE)) == NULL) 1077*3431Scarlsonj return; 1078*3431Scarlsonj 1079*3431Scarlsonj recv_type = pkt_recv_type(plp); 1080*3431Scarlsonj pname = pkt_type_to_string(recv_type, B_FALSE); 1081*3431Scarlsonj 1082*3431Scarlsonj if (!pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK)) { 1083*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored v4 %s packet " 1084*3431Scarlsonj "received via LIF %s", pname, lif->lif_name); 1085*3431Scarlsonj free_pkt_entry(plp); 1086*3431Scarlsonj return; 1087*3431Scarlsonj } 1088*3431Scarlsonj 1089*3431Scarlsonj /* 1090*3431Scarlsonj * Find the corresponding state machine not using DLPI. 1091*3431Scarlsonj */ 1092*3431Scarlsonj xid = pkt_get_xid(plp->pkt, B_FALSE); 1093*3431Scarlsonj for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; 1094*3431Scarlsonj dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { 1095*3431Scarlsonj if (dsmp->dsm_lif == lif) 1096*3431Scarlsonj break; 1097*3431Scarlsonj } 1098*3431Scarlsonj if (dsmp == NULL || dsmp->dsm_using_dlpi) { 1099*3431Scarlsonj dhcpmsg(MSG_VERBOSE, "dhcp_acknak_lif: ignored %s packet xid " 1100*3431Scarlsonj "%x received via LIF %s; %s", pname, xid, lif->lif_name, 1101*3431Scarlsonj dsmp == NULL ? "unknown state machine" : "not using DLPI"); 1102*3431Scarlsonj free_pkt_entry(plp); 1103*3431Scarlsonj return; 1104*3431Scarlsonj } 1105*3431Scarlsonj 1106*3431Scarlsonj /* 1107*3431Scarlsonj * We've got a packet; make sure it's acceptable and cancel the REQUEST 1108*3431Scarlsonj * retransmissions. 1109*3431Scarlsonj */ 1110*3431Scarlsonj accept_v4_acknak(dsmp, plp); 1111*3431Scarlsonj } 1112*3431Scarlsonj 1113*3431Scarlsonj /* 1114*3431Scarlsonj * dhcp_restart(): restarts DHCP (from INIT) on a given state machine 1115*3431Scarlsonj * 1116*3431Scarlsonj * input: dhcp_smach_t *: the state machine to restart DHCP on 11170Sstevel@tonic-gate * output: void 11180Sstevel@tonic-gate */ 11190Sstevel@tonic-gate 11202546Scarlsonj void 1121*3431Scarlsonj dhcp_restart(dhcp_smach_t *dsmp) 11220Sstevel@tonic-gate { 1123*3431Scarlsonj /* 1124*3431Scarlsonj * As we're returning to INIT state, we need to discard any leases we 1125*3431Scarlsonj * may have, and (for v4) canonize the LIF. There's a bit of tension 1126*3431Scarlsonj * between keeping around a possibly still working address, and obeying 1127*3431Scarlsonj * the RFCs. A more elaborate design would be to mark the addresses as 1128*3431Scarlsonj * DEPRECATED, and then start a removal timer. Such a design would 1129*3431Scarlsonj * probably compromise testing. 1130*3431Scarlsonj */ 1131*3431Scarlsonj deprecate_leases(dsmp); 11320Sstevel@tonic-gate 1133*3431Scarlsonj if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, dsmp) == -1) { 11342546Scarlsonj dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " 1135*3431Scarlsonj "reverting to INIT state on %s", dsmp->dsm_name); 1136*3431Scarlsonj 1137*3431Scarlsonj (void) set_smach_state(dsmp, INIT); 1138*3431Scarlsonj dsmp->dsm_dflags |= DHCP_IF_FAILED; 1139*3431Scarlsonj ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); 1140*3431Scarlsonj } else { 1141*3431Scarlsonj hold_smach(dsmp); 1142*3431Scarlsonj } 11430Sstevel@tonic-gate } 11440Sstevel@tonic-gate 11450Sstevel@tonic-gate /* 11460Sstevel@tonic-gate * stop_requesting(): decides when to stop retransmitting REQUESTs 11470Sstevel@tonic-gate * 1148*3431Scarlsonj * input: dhcp_smach_t *: the state machine REQUESTs are being sent from 11490Sstevel@tonic-gate * unsigned int: the number of REQUESTs sent so far 11500Sstevel@tonic-gate * output: boolean_t: B_TRUE if retransmissions should stop 11510Sstevel@tonic-gate */ 11520Sstevel@tonic-gate 11530Sstevel@tonic-gate static boolean_t 1154*3431Scarlsonj stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) 11550Sstevel@tonic-gate { 1156*3431Scarlsonj uint_t maxreq; 11570Sstevel@tonic-gate 1158*3431Scarlsonj maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; 1159*3431Scarlsonj if (n_requests >= maxreq) { 11600Sstevel@tonic-gate 1161*3431Scarlsonj dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", 1162*3431Scarlsonj dsmp->dsm_name); 11630Sstevel@tonic-gate 1164*3431Scarlsonj request_failed(dsmp); 11650Sstevel@tonic-gate return (B_TRUE); 1166*3431Scarlsonj } else { 1167*3431Scarlsonj return (B_FALSE); 11680Sstevel@tonic-gate } 11690Sstevel@tonic-gate } 1170