/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * REQUESTING state of the client state machine. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "states.h" #include "util.h" #include "packet.h" #include "interface.h" #include "agent.h" static PKT_LIST *select_best(dhcp_smach_t *); static void request_failed(dhcp_smach_t *); static stop_func_t stop_requesting; /* * send_v6_request(): sends a DHCPv6 Request message and switches to REQUESTING * state. This is a separate function because a NoBinding * response can also cause us to do this. * * input: dhcp_smach_t *: the state machine * output: none */ void send_v6_request(dhcp_smach_t *dsmp) { dhcp_pkt_t *dpkt; dhcpv6_ia_na_t d6in; dpkt = init_pkt(dsmp, DHCPV6_MSG_REQUEST); (void) add_pkt_opt(dpkt, DHCPV6_OPT_SERVERID, dsmp->dsm_serverid, dsmp->dsm_serveridlen); /* Add an IA_NA option for our controlling LIF */ d6in.d6in_iaid = htonl(dsmp->dsm_lif->lif_iaid); d6in.d6in_t1 = htonl(0); d6in.d6in_t2 = htonl(0); (void) add_pkt_opt(dpkt, DHCPV6_OPT_IA_NA, (dhcpv6_option_t *)&d6in + 1, sizeof (d6in) - sizeof (dhcpv6_option_t)); /* Add required Option Request option */ (void) add_pkt_prl(dpkt, dsmp); (void) send_pkt_v6(dsmp, dpkt, dsmp->dsm_server, stop_requesting, DHCPV6_REQ_TIMEOUT, DHCPV6_REQ_MAX_RT); /* For DHCPv6, state switch cannot fail */ (void) set_smach_state(dsmp, REQUESTING); } /* * server_unicast_option(): determines the server address to use based on the * DHCPv6 Server Unicast option present in the given * packet. * * input: dhcp_smach_t *: the state machine * PKT_LIST *: received packet (Advertisement or Reply) * output: none */ void server_unicast_option(dhcp_smach_t *dsmp, PKT_LIST *plp) { const dhcpv6_option_t *d6o; uint_t olen; d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_UNICAST, &olen); olen -= sizeof (*d6o); /* LINTED: no consequent */ if (d6o == NULL) { /* No Server Unicast option specified */ } else if (olen != sizeof (dsmp->dsm_server)) { dhcpmsg(MSG_WARNING, "server_unicast_option: %s has Server " "Unicast option with bad length", pkt_type_to_string(pkt_recv_type(plp), B_TRUE)); } else { in6_addr_t addr; (void) memcpy(&addr, d6o + 1, olen); if (IN6_IS_ADDR_UNSPECIFIED(&addr)) { dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " "to unspecified address ignored"); } else if (IN6_IS_ADDR_MULTICAST(&addr)) { dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " "to multicast address ignored"); } else if (IN6_IS_ADDR_V4COMPAT(&addr) || IN6_IS_ADDR_V4MAPPED(&addr)) { dhcpmsg(MSG_WARNING, "server_unicast_option: unicast " "to invalid address ignored"); } else { dsmp->dsm_server = addr; } } } /* * dhcp_requesting(): checks if OFFER packets to come in from DHCP servers. * if so, chooses the best one, sends a REQUEST to the * server and registers an event handler to receive * the ACK/NAK. This may be called by the offer timer or * by any function that wants to check for offers after * canceling that timer. * * input: iu_tq_t *: timer queue; non-NULL if this is a timer callback * void *: the state machine receiving OFFER packets * output: void */ void dhcp_requesting(iu_tq_t *tqp, void *arg) { dhcp_smach_t *dsmp = arg; dhcp_pkt_t *dpkt; PKT_LIST *offer; lease_t lease; boolean_t isv6 = dsmp->dsm_isv6; /* * We assume here that if tqp is set, then this means we're being * called back by the offer wait timer. If so, then drop our hold * on the state machine. Otherwise, cancel the timer if it's running. */ if (tqp != NULL) { dhcpmsg(MSG_VERBOSE, "dhcp_requesting: offer wait timer on v%d %s", isv6 ? 6 : 4, dsmp->dsm_name); dsmp->dsm_offer_timer = -1; if (!verify_smach(dsmp)) return; } else { cancel_offer_timer(dsmp); } /* * select the best OFFER; all others pitched. */ offer = select_best(dsmp); if (offer == NULL) { dhcpmsg(MSG_VERBOSE, "no OFFERs/Advertisements on %s, waiting...", dsmp->dsm_name); /* * no acceptable OFFERs have come in. reschedule * ourself for callback. */ if ((dsmp->dsm_offer_timer = iu_schedule_timer(tq, dsmp->dsm_offer_wait, dhcp_requesting, dsmp)) == -1) { /* * ugh. the best we can do at this point is * revert back to INIT and wait for a user to * restart us. */ dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " "reschedule callback, reverting to INIT state on " "%s", dsmp->dsm_name); stop_pkt_retransmission(dsmp); (void) set_smach_state(dsmp, INIT); dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); } else { hold_smach(dsmp); } return; } /* * With IPv4, the DHCPREQUEST packet we're about to transmit implicitly * declines all other offers we've received. We can no longer use any * cached offers, so we must discard them now. With DHCPv6, though, * we're permitted to hang onto the advertisements (offers) and try * them if the preferred one doesn't pan out. */ if (!isv6) free_pkt_list(&dsmp->dsm_recv_pkt_list); /* stop collecting packets. */ stop_pkt_retransmission(dsmp); /* * For IPv4, check to see whether we got an OFFER or a BOOTP packet. * If we got a BOOTP packet, go to the BOUND state now. */ if (!isv6 && offer->opts[CD_DHCP_TYPE] == NULL) { free_pkt_list(&dsmp->dsm_recv_pkt_list); if (!set_smach_state(dsmp, REQUESTING)) { dhcp_restart(dsmp); return; } if (!dhcp_bound(dsmp, offer)) { dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " "failed for %s", dsmp->dsm_name); dhcp_restart(dsmp); return; } return; } if (isv6) { const char *estr, *msg; const dhcpv6_option_t *d6o; uint_t olen, msglen; /* If there's a Status Code option, print the message */ d6o = dhcpv6_pkt_option(offer, NULL, DHCPV6_OPT_STATUS_CODE, &olen); (void) dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); print_server_msg(dsmp, msg, msglen); /* Copy in the Server ID (guaranteed to be present now) */ if (!save_server_id(dsmp, offer)) goto failure; /* * Determine how to send this message. If the Advertisement * (offer) has the unicast option, then use the address * specified in the option. Otherwise, send via multicast. */ server_unicast_option(dsmp, offer); send_v6_request(dsmp); } else { /* if we got a message from the server, display it. */ if (offer->opts[CD_MESSAGE] != NULL) { print_server_msg(dsmp, (char *)offer->opts[CD_MESSAGE]->value, offer->opts[CD_MESSAGE]->len); } /* * assemble a DHCPREQUEST, with the ciaddr field set to 0, * since we got here from the INIT state. */ dpkt = init_pkt(dsmp, REQUEST); /* * Grab the lease out of the OFFER; we know it's valid because * select_best() already checked. The max dhcp message size * option is set to the interface max, minus the size of the * udp and ip headers. */ (void) memcpy(&lease, offer->opts[CD_LEASE_TIME]->value, sizeof (lease_t)); (void) add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); (void) add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(dsmp->dsm_lif->lif_max - sizeof (struct udpiphdr))); (void) add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, offer->pkt->yiaddr.s_addr); (void) add_pkt_opt(dpkt, CD_SERVER_ID, offer->opts[CD_SERVER_ID]->value, offer->opts[CD_SERVER_ID]->len); if (class_id_len != 0) { (void) add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); } (void) add_pkt_prl(dpkt, dsmp); /* * dsm_reqhost was set for this state machine in * dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a * host name was found */ if (dsmp->dsm_reqhost != NULL) { (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost, strlen(dsmp->dsm_reqhost)); } (void) add_pkt_opt(dpkt, CD_END, NULL, 0); /* * send out the REQUEST, trying retransmissions. either a NAK * or too many REQUEST attempts will revert us to SELECTING. */ if (!set_smach_state(dsmp, REQUESTING)) { dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot switch to " "REQUESTING state; reverting to INIT on %s", dsmp->dsm_name); goto failure; } (void) send_pkt(dsmp, dpkt, htonl(INADDR_BROADCAST), stop_requesting); } /* all done with the offer */ free_pkt_entry(offer); return; failure: dsmp->dsm_dflags |= DHCP_IF_FAILED; (void) set_smach_state(dsmp, INIT); ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); free_pkt_list(&dsmp->dsm_recv_pkt_list); } /* * compute_points_v6(): compute the number of "points" for a given v6 * advertisement. * * input: const PKT_LIST *: packet to inspect * const dhcp_smach_t *: state machine that received the packet * output: int: -1 to discard, -2 to accept immediately, >=0 for preference. */ static int compute_points_v6(const PKT_LIST *pkt, const dhcp_smach_t *dsmp) { char abuf[INET6_ADDRSTRLEN]; int points = 0; const dhcpv6_option_t *d6o, *d6so; uint_t olen, solen; int i; const char *estr, *msg; uint_t msglen; /* * Look through the packet contents. Valid packets must have our * client ID and a server ID, which has already been checked by * dhcp_packet_lif. Bonus points for each option. */ /* One point for having a valid message. */ points++; /* * Per RFC 3315, if the Advertise message says, "yes, we have no * bananas today," then ignore the entire message. (Why it's just * _this_ error and no other is a bit of a mystery, but a standard is a * standard.) */ d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_STATUS_CODE, &olen); if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == DHCPV6_STAT_NOADDRS) { dhcpmsg(MSG_INFO, "discard advertisement from %s on %s: no address status", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&pkt->pktfrom)->sin6_addr, abuf, sizeof (abuf)), dsmp->dsm_name); return (-1); } /* Two points for each batch of offered IP addresses */ d6o = NULL; while ((d6o = dhcpv6_pkt_option(pkt, d6o, DHCPV6_OPT_IA_NA, &olen)) != NULL) { /* * Note that it's possible to have "no bananas" on an * individual IA. We must look for that here. * * RFC 3315 section 17.1.3 does not refer to the status code * embedded in the IA itself. However, the TAHI test suite * checks for this specific case. Because it's extremely * unlikely that any usable server is going to report that it * has no addresses on a network using DHCP for address * assignment, we allow such messages to be dropped. */ d6so = dhcpv6_find_option( (const char *)d6o + sizeof (dhcpv6_ia_na_t), olen - sizeof (dhcpv6_ia_na_t), NULL, DHCPV6_OPT_STATUS_CODE, &solen); if (dhcpv6_status_code(d6so, solen, &estr, &msg, &msglen) == DHCPV6_STAT_NOADDRS) return (-1); points += 2; } /* * Note that we drive on in the case where there are no addresses. The * hope here is that we'll at least get some useful configuration * information. */ /* One point for each requested option */ for (i = 0; i < dsmp->dsm_prllen; i++) { if (dhcpv6_pkt_option(pkt, NULL, dsmp->dsm_prl[i], NULL) != NULL) points++; } /* * Ten points for each point of "preference." Note: the value 255 is * special. It means "stop right now and select this server." */ d6o = dhcpv6_pkt_option(pkt, NULL, DHCPV6_OPT_PREFERENCE, &olen); if (d6o != NULL && olen == sizeof (*d6o) + 1) { int pref = *(const uchar_t *)(d6o + 1); if (pref == 255) return (-2); points += 10 * pref; } return (points); } /* * compute_points_v4(): compute the number of "points" for a given v4 offer. * * input: const PKT_LIST *: packet to inspect * const dhcp_smach_t *: state machine that received the packet * output: int: -1 to discard, >=0 for preference. */ static int compute_points_v4(const PKT_LIST *pkt) { int points = 0; if (pkt->opts[CD_DHCP_TYPE] == NULL) { dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid BOOTP reply"); goto valid_offer; } if (pkt->opts[CD_LEASE_TIME] == NULL) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without lease " "time"); return (-1); } if (pkt->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " "lease time"); return (-1); } if (pkt->opts[CD_SERVER_ID] == NULL) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER without server " "id"); return (-1); } if (pkt->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { dhcpmsg(MSG_WARNING, "compute_points_v4: OFFER with garbled " "server id"); return (-1); } /* valid DHCP OFFER. see if we got our parameters. */ dhcpmsg(MSG_VERBOSE, "compute_points_v4: valid OFFER packet"); points += 30; valid_offer: if (pkt->rfc1048) points += 5; /* * also could be faked, though more difficult because the encapsulation * is hard to encode on a BOOTP server; plus there's not as much real * estate in the packet for options, so it's likely this option would * get dropped. */ if (pkt->opts[CD_VENDOR_SPEC] != NULL) points += 80; if (pkt->opts[CD_SUBNETMASK] != NULL) points++; if (pkt->opts[CD_ROUTER] != NULL) points++; if (pkt->opts[CD_HOSTNAME] != NULL) points += 5; return (points); } /* * select_best(): selects the best offer from a list of IPv4 OFFER packets or * DHCPv6 Advertise packets. * * input: dhcp_smach_t *: state machine with enqueued offers * output: PKT_LIST *: the best packet, or NULL if none are acceptable */ static PKT_LIST * select_best(dhcp_smach_t *dsmp) { PKT_LIST *current = dsmp->dsm_recv_pkt_list; PKT_LIST *next, *best = NULL; int points, best_points = -1; /* * pick out the best offer. point system. * what's important for IPv4? * * 0) DHCP (30 points) * 1) no option overload * 2) encapsulated vendor option (80 points) * 3) non-null sname and siaddr fields * 4) non-null file field * 5) hostname (5 points) * 6) subnetmask (1 point) * 7) router (1 point) */ for (; current != NULL; current = next) { next = current->next; points = current->isv6 ? compute_points_v6(current, dsmp) : compute_points_v4(current); /* * Just discard any unacceptable entries we encounter. */ if (points == -1) { remque(current); free_pkt_entry(current); continue; } dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); /* Special case: stop now and select */ if (points == -2) { best = current; break; } if (points >= best_points) { best_points = points; best = current; } } if (best != NULL) { dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); remque(best); } else { dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); } return (best); } /* * accept_v4_acknak(): determine what to do with a DHCPv4 ACK/NAK based on the * current state. If we're renewing or rebinding, the ACK * must be for the same address and must have a new lease * time. If it's a NAK, then our cache is garbage, and we * must restart. Finally, call dhcp_bound on accepted * ACKs. * * input: dhcp_smach_t *: the state machine to handle the ACK/NAK * PKT_LIST *: the ACK/NAK message * output: void */ static void accept_v4_acknak(dhcp_smach_t *dsmp, PKT_LIST *plp) { /* Account for received and processed messages */ dsmp->dsm_received++; if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { if (dsmp->dsm_state != INFORM_SENT && dsmp->dsm_state != INFORMATION && (plp->opts[CD_LEASE_TIME] == NULL || plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t))) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: ACK packet on " "%s missing mandatory lease option, ignored", dsmp->dsm_name); dsmp->dsm_bad_offers++; free_pkt_entry(plp); return; } if ((dsmp->dsm_state == RENEWING || dsmp->dsm_state == REBINDING) && dsmp->dsm_leases->dl_lifs->lif_addr != plp->pkt->yiaddr.s_addr) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: renewal ACK " "packet has a different IP address (%s), ignored", inet_ntoa(plp->pkt->yiaddr)); dsmp->dsm_bad_offers++; free_pkt_entry(plp); return; } } /* * looks good; cancel the retransmission timer and unregister * the acknak handler. ACK to BOUND, NAK back to SELECTING. */ stop_pkt_retransmission(dsmp); if (*plp->opts[CD_DHCP_TYPE]->value == NAK) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: NAK on interface %s", dsmp->dsm_name); dsmp->dsm_bad_offers++; free_pkt_entry(plp); dhcp_restart(dsmp); /* * remove any bogus cached configuration we might have * around (right now would only happen if we got here * from INIT_REBOOT). */ (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); return; } if (plp->opts[CD_SERVER_ID] == NULL || plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { dhcpmsg(MSG_ERROR, "accept_v4_acknak: ACK with no valid " "server id on %s", dsmp->dsm_name); dsmp->dsm_bad_offers++; free_pkt_entry(plp); dhcp_restart(dsmp); return; } if (plp->opts[CD_MESSAGE] != NULL) { print_server_msg(dsmp, (char *)plp->opts[CD_MESSAGE]->value, plp->opts[CD_MESSAGE]->len); } dhcpmsg(MSG_VERBOSE, "accept_v4_acknak: ACK on %s", dsmp->dsm_name); if (!dhcp_bound(dsmp, plp)) { dhcpmsg(MSG_WARNING, "accept_v4_acknak: dhcp_bound failed " "for %s", dsmp->dsm_name); dhcp_restart(dsmp); } } /* * accept_v6_message(): determine what to do with a DHCPv6 message based on the * current state. * * input: dhcp_smach_t *: the state machine to handle the message * PKT_LIST *: the DHCPv6 message * const char *: type of message (for logging) * uchar_t: type of message (extracted from packet) * output: void */ static void accept_v6_message(dhcp_smach_t *dsmp, PKT_LIST *plp, const char *pname, uchar_t recv_type) { const dhcpv6_option_t *d6o; uint_t olen; const char *estr, *msg; uint_t msglen; int status; /* Account for received and processed messages */ dsmp->dsm_received++; /* We don't yet support Reconfigure at all. */ if (recv_type == DHCPV6_MSG_RECONFIGURE) { dhcpmsg(MSG_VERBOSE, "accept_v6_message: ignored Reconfigure " "on %s", dsmp->dsm_name); free_pkt_entry(plp); return; } /* * All valid DHCPv6 messages must have our Client ID specified. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_CLIENTID, &olen); olen -= sizeof (*d6o); if (d6o == NULL || olen != dsmp->dsm_cidlen || memcmp(d6o + 1, dsmp->dsm_cid, olen) != 0) { dhcpmsg(MSG_VERBOSE, "accept_v6_message: discarded %s on %s: %s Client ID", pname, dsmp->dsm_name, d6o == NULL ? "no" : "wrong"); free_pkt_entry(plp); return; } /* * All valid DHCPv6 messages must have a Server ID specified. * * If this is a Reply and it's not in response to Solicit, Confirm, * Rebind, or Information-Request, then it must also match the Server * ID we're expecting. * * For Reply in the Solicit, Confirm, Rebind, and Information-Request * cases, the Server ID needs to be saved. This is done inside of * dhcp_bound(). */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_SERVERID, &olen); if (d6o == NULL) { dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on %s: no Server ID", pname, dsmp->dsm_name); free_pkt_entry(plp); return; } if (recv_type == DHCPV6_MSG_REPLY && dsmp->dsm_state != SELECTING && dsmp->dsm_state != INIT_REBOOT && dsmp->dsm_state != REBINDING && dsmp->dsm_state != INFORM_SENT) { olen -= sizeof (*d6o); if (olen != dsmp->dsm_serveridlen || memcmp(d6o + 1, dsmp->dsm_serverid, olen) != 0) { dhcpmsg(MSG_DEBUG, "accept_v6_message: discarded %s on " "%s: wrong Server ID", pname, dsmp->dsm_name); free_pkt_entry(plp); return; } } /* * Break out of the switch if the input message needs to be discarded. * Return from the function if the message has been enqueued or * consumed. */ switch (dsmp->dsm_state) { case SELECTING: /* A Reply message signifies a Rapid-Commit. */ if (recv_type == DHCPV6_MSG_REPLY) { if (dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_RAPID_COMMIT, &olen) == NULL) { dhcpmsg(MSG_DEBUG, "accept_v6_message: Reply " "on %s lacks Rapid-Commit; ignoring", dsmp->dsm_name); break; } dhcpmsg(MSG_VERBOSE, "accept_v6_message: rapid-commit Reply on %s", dsmp->dsm_name); cancel_offer_timer(dsmp); goto rapid_commit; } /* Otherwise, we're looking for Advertisements. */ if (recv_type != DHCPV6_MSG_ADVERTISE) break; /* * Special case: if this advertisement has preference 255, then * we must stop right now and select this server. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_PREFERENCE, &olen); if (d6o != NULL && olen == sizeof (*d6o) + 1 && *(const uchar_t *)(d6o + 1) == 255) { pkt_smach_enqueue(dsmp, plp); dhcpmsg(MSG_DEBUG, "accept_v6_message: preference 255;" " immediate Request on %s", dsmp->dsm_name); dhcp_requesting(NULL, dsmp); } else { pkt_smach_enqueue(dsmp, plp); } return; case PRE_BOUND: case BOUND: /* * Not looking for anything in these states. (If we * implemented reconfigure, that might go here.) */ break; case REQUESTING: case INIT_REBOOT: case RENEWING: case REBINDING: case INFORM_SENT: /* * We're looking for Reply messages. */ if (recv_type != DHCPV6_MSG_REPLY) break; dhcpmsg(MSG_VERBOSE, "accept_v6_message: received Reply message on %s", dsmp->dsm_name); rapid_commit: /* * Extract the status code option. If one is present and the * request failed, then try to go to another advertisement in * the list or restart the selection machinery. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, &olen); status = dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen); /* * Check for the UseMulticast status code. If this is present, * and if we were actually using unicast, then drop back and * try again. If we weren't using unicast, then just pretend * we never saw this message -- the peer is confused. (TAHI * does this.) */ if (status == DHCPV6_STAT_USEMCAST) { if (IN6_IS_ADDR_MULTICAST( &dsmp->dsm_send_dest.v6.sin6_addr)) { break; } else { free_pkt_entry(plp); dsmp->dsm_send_dest.v6.sin6_addr = ipv6_all_dhcp_relay_and_servers; retransmit_now(dsmp); return; } } print_server_msg(dsmp, msg, msglen); /* * We treat NoBinding at the top level as "success." Granted, * this doesn't make much sense, but the TAHI test suite does * this. NoBinding really only makes sense in the context of a * specific IA, as it refers to the GUID:IAID binding, so * ignoring it at the top level is safe. */ if (status == DHCPV6_STAT_SUCCESS || status == DHCPV6_STAT_NOBINDING) { if (dhcp_bound(dsmp, plp)) { /* * dhcp_bound will stop retransmission on * success, if that's called for. */ server_unicast_option(dsmp, plp); } else { stop_pkt_retransmission(dsmp); dhcpmsg(MSG_WARNING, "accept_v6_message: " "dhcp_bound failed for %s", dsmp->dsm_name); (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); dhcp_restart(dsmp); } } else { dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", estr); stop_pkt_retransmission(dsmp); free_pkt_entry(plp); if (dsmp->dsm_state == INFORM_SENT) { (void) set_smach_state(dsmp, INIT); ipc_action_finish(dsmp, DHCP_IPC_E_SRVFAILED); } else { (void) remove_hostconf(dsmp->dsm_name, dsmp->dsm_isv6); request_failed(dsmp); } } return; case DECLINING: /* * We're looking for Reply messages. */ if (recv_type != DHCPV6_MSG_REPLY) break; stop_pkt_retransmission(dsmp); /* * Extract the status code option. Note that it's not a * failure if the server reports an error. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, &olen); if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == DHCPV6_STAT_SUCCESS) { print_server_msg(dsmp, msg, msglen); } else { dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", estr); } free_pkt_entry(plp); if (dsmp->dsm_leases == NULL) { dhcpmsg(MSG_VERBOSE, "accept_v6_message: %s has no " "leases left", dsmp->dsm_name); dhcp_restart(dsmp); } else if (dsmp->dsm_lif_wait == 0) { (void) set_smach_state(dsmp, BOUND); } else { (void) set_smach_state(dsmp, PRE_BOUND); } return; case RELEASING: /* * We're looking for Reply messages. */ if (recv_type != DHCPV6_MSG_REPLY) break; stop_pkt_retransmission(dsmp); /* * Extract the status code option. */ d6o = dhcpv6_pkt_option(plp, NULL, DHCPV6_OPT_STATUS_CODE, &olen); if (dhcpv6_status_code(d6o, olen, &estr, &msg, &msglen) == DHCPV6_STAT_SUCCESS) { print_server_msg(dsmp, msg, msglen); } else { dhcpmsg(MSG_WARNING, "accept_v6_message: Reply: %s", estr); } free_pkt_entry(plp); finished_smach(dsmp, DHCP_IPC_SUCCESS); return; } /* * Break from above switch means that the message must be discarded. */ dhcpmsg(MSG_VERBOSE, "accept_v6_message: discarded v6 %s on %s; state %s", pname, dsmp->dsm_name, dhcp_state_to_string(dsmp->dsm_state)); free_pkt_entry(plp); } /* * dhcp_acknak_global(): Processes reception of an ACK or NAK packet on the * global socket -- broadcast packets for IPv4, all * packets for DHCPv6. * * input: iu_eh_t *: unused * int: the global file descriptor the ACK/NAK arrived on * short: unused * iu_event_id_t: unused * void *: unused * output: void */ /* ARGSUSED */ void dhcp_acknak_global(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { PKT_LIST *plp; dhcp_pif_t *pif; uchar_t recv_type; const char *pname; uint_t xid; dhcp_smach_t *dsmp; boolean_t isv6 = (fd == v6_sock_fd); struct sockaddr_in sin; const char *reason; size_t sinlen = sizeof (sin); int sock; plp = recv_pkt(fd, get_max_mtu(isv6), isv6); if (plp == NULL) return; recv_type = pkt_recv_type(plp); pname = pkt_type_to_string(recv_type, isv6); /* * Find the corresponding state machine and pif. * * Note that DHCPv6 Reconfigure would be special: it's not the reply to * any transaction, and thus we would need to search on transaction ID * zero (all state machines) to find the match. However, Reconfigure * is not yet supported. */ xid = pkt_get_xid(plp->pkt, isv6); for (dsmp = lookup_smach_by_xid(xid, NULL, isv6); dsmp != NULL; dsmp = lookup_smach_by_xid(xid, dsmp, isv6)) { pif = dsmp->dsm_lif->lif_pif; if (pif->pif_index == plp->ifindex) break; } if (dsmp == NULL) { dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet" " on ifindex %d: unknown state machine", isv6 ? 6 : 4, pname, plp->ifindex); free_pkt_entry(plp); return; } if (!isv6 && !pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { reason = "not ACK or NAK"; goto drop; } /* * For IPv4, most packets will be handled by dhcp_packet_lif(). The * only exceptions are broadcast packets sent when lif_sock_ip_fd has * bound to something other than INADDR_ANY. */ if (!isv6) { sock = dsmp->dsm_lif->lif_sock_ip_fd; if (getsockname(sock, (struct sockaddr *)&sin, &sinlen) != -1 && sin.sin_addr.s_addr == INADDR_ANY) { reason = "handled by lif_sock_ip_fd"; goto drop; } } /* * We've got a packet; make sure it's acceptable and cancel the REQUEST * retransmissions. */ if (isv6) accept_v6_message(dsmp, plp, pname, recv_type); else accept_v4_acknak(dsmp, plp); return; drop: dhcpmsg(MSG_VERBOSE, "dhcp_acknak_global: ignored v%d %s packet for %s " "received on global socket: %s", isv6 ? 6 : 4, pname, pif->pif_name, reason); free_pkt_entry(plp); } /* * request_failed(): Attempt to request an address has failed. Take an * appropriate action. * * input: dhcp_smach_t *: state machine that has failed * output: void */ static void request_failed(dhcp_smach_t *dsmp) { PKT_LIST *offer; dsmp->dsm_server = ipv6_all_dhcp_relay_and_servers; if ((offer = select_best(dsmp)) != NULL) { insque(offer, &dsmp->dsm_recv_pkt_list); dhcp_requesting(NULL, dsmp); } else { dhcpmsg(MSG_INFO, "no offers left on %s; restarting", dsmp->dsm_name); dhcp_selecting(dsmp); } } /* * dhcp_packet_lif(): Processes reception of an ACK, NAK, or OFFER packet on * a given logical interface for IPv4 (only). * * input: iu_eh_t *: unused * int: the file descriptor the packet arrived on * short: unused * iu_event_id_t: the id of this event callback with the handler * void *: pointer to logical interface receiving message * output: void */ /* ARGSUSED */ void dhcp_packet_lif(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { dhcp_lif_t *lif = arg; PKT_LIST *plp; uchar_t recv_type; const char *pname; uint_t xid; dhcp_smach_t *dsmp; if ((plp = recv_pkt(fd, lif->lif_max, B_FALSE)) == NULL) return; recv_type = pkt_recv_type(plp); pname = pkt_type_to_string(recv_type, B_FALSE); if (!pkt_v4_match(recv_type, DHCP_PACK | DHCP_PNAK | DHCP_PUNTYPED | DHCP_POFFER)) { dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored v4 %s packet " "received via LIF %s", pname, lif->lif_name); free_pkt_entry(plp); return; } /* * Find the corresponding state machine. */ xid = pkt_get_xid(plp->pkt, B_FALSE); for (dsmp = lookup_smach_by_xid(xid, NULL, B_FALSE); dsmp != NULL; dsmp = lookup_smach_by_xid(xid, dsmp, B_FALSE)) { if (dsmp->dsm_lif == lif) break; } if (dsmp == NULL) goto drop; if (pkt_v4_match(recv_type, DHCP_PACK|DHCP_PNAK)) { /* * We've got an ACK/NAK; make sure it's acceptable and cancel * the REQUEST retransmissions. */ accept_v4_acknak(dsmp, plp); } else { if (is_bound_state(dsmp->dsm_state)) goto drop; /* * Must be an OFFER or a BOOTP message: enqueue it for later * processing by select_best(). */ pkt_smach_enqueue(dsmp, plp); } return; drop: dhcpmsg(MSG_VERBOSE, "dhcp_packet_lif: ignored %s packet xid " "%x received via LIF %s; %s", pname, xid, lif->lif_name, dsmp == NULL ? "unknown state machine" : "bound"); free_pkt_entry(plp); } /* * dhcp_restart(): restarts DHCP (from INIT) on a given state machine, but only * if we're leasing addresses. Doesn't restart for information- * only interfaces. * * input: dhcp_smach_t *: the state machine to restart DHCP on * output: void */ void dhcp_restart(dhcp_smach_t *dsmp) { if (dsmp->dsm_state == INFORM_SENT || dsmp->dsm_state == INFORMATION) return; /* * As we're returning to INIT state, we need to discard any leases we * may have, and (for v4) canonize the LIF. There's a bit of tension * between keeping around a possibly still working address, and obeying * the RFCs. A more elaborate design would be to mark the addresses as * DEPRECATED, and then start a removal timer. Such a design would * probably compromise testing. */ deprecate_leases(dsmp); if (!set_start_timer(dsmp)) { dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " "reverting to INIT state on %s", dsmp->dsm_name); (void) set_smach_state(dsmp, INIT); dsmp->dsm_dflags |= DHCP_IF_FAILED; ipc_action_finish(dsmp, DHCP_IPC_E_MEMORY); } else { dhcpmsg(MSG_DEBUG, "dhcp_restart: restarting DHCP on %s", dsmp->dsm_name); } } /* * stop_requesting(): decides when to stop retransmitting REQUESTs * * input: dhcp_smach_t *: the state machine REQUESTs are being sent from * unsigned int: the number of REQUESTs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ static boolean_t stop_requesting(dhcp_smach_t *dsmp, unsigned int n_requests) { uint_t maxreq; maxreq = dsmp->dsm_isv6 ? DHCPV6_REQ_MAX_RC : DHCP_MAX_REQUESTS; if (n_requests >= maxreq) { dhcpmsg(MSG_INFO, "no ACK/NAK/Reply to REQUEST on %s", dsmp->dsm_name); request_failed(dsmp); return (B_TRUE); } else { return (B_FALSE); } }