/* * 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 2006 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 /* FLUSHR/FLUSHW */ #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(PKT_LIST **); static stop_func_t stop_requesting; /* * 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 * * input: iu_tq_t *: unused * void *: the interface receiving OFFER packets * output: void */ /* ARGSUSED */ void dhcp_requesting(iu_tq_t *tqp, void *arg) { struct ifslist *ifsp = (struct ifslist *)arg; dhcp_pkt_t *dpkt; PKT_LIST *offer; lease_t lease; ifsp->if_offer_timer = -1; if (check_ifs(ifsp) == 0) { (void) release_ifs(ifsp); return; } /* * select the best OFFER; all others pitched. */ offer = select_best(&ifsp->if_recv_pkt_list); if (offer == NULL) { dhcpmsg(MSG_VERBOSE, "no OFFERs on %s, waiting...", ifsp->if_name); /* * no acceptable OFFERs have come in. reschedule * ourselves for callback. */ if ((ifsp->if_offer_timer = iu_schedule_timer(tq, ifsp->if_offer_wait, dhcp_requesting, ifsp)) == -1) { /* * ugh. the best we can do at this point is * revert back to INIT and wait for a user to * restart us. */ ifsp->if_state = INIT; ifsp->if_dflags |= DHCP_IF_FAILED; stop_pkt_retransmission(ifsp); ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); async_finish(ifsp); dhcpmsg(MSG_WARNING, "dhcp_requesting: cannot " "reschedule callback, reverting to INIT state on " "%s", ifsp->if_name); } else hold_ifs(ifsp); return; } stop_pkt_retransmission(ifsp); /* * stop collecting packets. 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 (iu_unregister_event(eh, ifsp->if_offer_id, NULL) != 0) { (void) release_ifs(ifsp); ifsp->if_offer_id = -1; } if (offer->opts[CD_DHCP_TYPE] == NULL) { ifsp->if_state = REQUESTING; if (dhcp_bound(ifsp, offer) == 0) { dhcpmsg(MSG_WARNING, "dhcp_requesting: dhcp_bound " "failed for %s", ifsp->if_name); dhcp_restart(ifsp); return; } return; } /* * if we got a message from the server, display it. */ if (offer->opts[CD_MESSAGE] != NULL) print_server_msg(ifsp, offer->opts[CD_MESSAGE]); /* * assemble a DHCPREQUEST, with the ciaddr field set to 0, * since we got here from the INIT state. */ dpkt = init_pkt(ifsp, REQUEST); /* * grab the lease out of the OFFER; we know it's valid since * 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)); add_pkt_opt32(dpkt, CD_LEASE_TIME, lease); add_pkt_opt16(dpkt, CD_MAX_DHCP_SIZE, htons(ifsp->if_max - sizeof (struct udpiphdr))); add_pkt_opt32(dpkt, CD_REQUESTED_IP_ADDR, offer->pkt->yiaddr.s_addr); add_pkt_opt(dpkt, CD_SERVER_ID, offer->opts[CD_SERVER_ID]->value, offer->opts[CD_SERVER_ID]->len); add_pkt_opt(dpkt, CD_CLASS_ID, class_id, class_id_len); add_pkt_opt(dpkt, CD_REQUEST_LIST, ifsp->if_prl, ifsp->if_prllen); /* * if_reqhost was set for this interface in dhcp_selecting() * if the DF_REQUEST_HOSTNAME option set and a host name was * found */ if (ifsp->if_reqhost != NULL) { add_pkt_opt(dpkt, CD_HOSTNAME, ifsp->if_reqhost, strlen(ifsp->if_reqhost)); } add_pkt_opt(dpkt, CD_END, NULL, 0); /* all done with the offer */ free_pkt_list(&offer); /* * send out the REQUEST, trying retransmissions. either a NAK * or too many REQUEST attempts will revert us to SELECTING. */ ifsp->if_state = REQUESTING; (void) send_pkt(ifsp, dpkt, htonl(INADDR_BROADCAST), stop_requesting); /* * wait for an ACK or NAK to come back from the server. if * we can't register this event handler, then we won't be able * to see the server's responses. the best we can really do * in that case is drop back to INIT and hope someone notices. */ if (register_acknak(ifsp) == 0) { ifsp->if_state = INIT; ifsp->if_dflags |= DHCP_IF_FAILED; ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); async_finish(ifsp); dhcpmsg(MSG_ERROR, "dhcp_requesting: cannot register to " "collect ACK/NAK packets, reverting to INIT on %s", ifsp->if_name); } } /* * select_best(): selects the best OFFER packet from a list of OFFER packets * * input: PKT_LIST **: a list of packets to select the best from * output: PKT_LIST *: the best packet, or NULL if none are acceptable */ static PKT_LIST * select_best(PKT_LIST **pkts) { PKT_LIST *current, *best = NULL; uint32_t points, best_points = 0; /* * pick out the best offer. point system. * what's important? * * 0) DHCP * 1) no option overload * 2) encapsulated vendor option * 3) non-null sname and siaddr fields * 4) non-null file field * 5) hostname * 6) subnetmask * 7) router */ for (current = *pkts; current != NULL; current = current->next) { points = 0; if (current->opts[CD_DHCP_TYPE] == NULL) { dhcpmsg(MSG_VERBOSE, "valid BOOTP reply"); goto valid_offer; } if (current->opts[CD_LEASE_TIME] == NULL) { dhcpmsg(MSG_WARNING, "select_best: OFFER without " "lease time"); continue; } if (current->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled " "lease time"); continue; } if (current->opts[CD_SERVER_ID] == NULL) { dhcpmsg(MSG_WARNING, "select_best: OFFER without " "server id"); continue; } if (current->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { dhcpmsg(MSG_WARNING, "select_best: OFFER with garbled " "server id"); continue; } /* valid DHCP OFFER. see if we got our parameters. */ dhcpmsg(MSG_VERBOSE, "valid OFFER packet"); points += 30; valid_offer: if (current->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 (current->opts[CD_VENDOR_SPEC] != NULL) points += 80; if (current->opts[CD_SUBNETMASK] != NULL) points++; if (current->opts[CD_ROUTER] != NULL) points++; if (current->opts[CD_HOSTNAME] != NULL) points += 5; dhcpmsg(MSG_DEBUG, "select_best: OFFER had %d points", points); if (points >= best_points) { best_points = points; best = current; } } if (best != NULL) { dhcpmsg(MSG_DEBUG, "select_best: most points: %d", best_points); remove_from_pkt_list(pkts, best); } else dhcpmsg(MSG_DEBUG, "select_best: no valid OFFER/BOOTP reply"); free_pkt_list(pkts); return (best); } /* * dhcp_acknak(): processes reception of an ACK or NAK packet on an interface * * input: iu_eh_t *: unused * int: the file descriptor the ACK/NAK arrived on * short: unused * iu_event_id_t: the id of this event callback with the handler * void *: the interface that received the ACK or NAK * output: void */ /* ARGSUSED */ void dhcp_acknak(iu_eh_t *ehp, int fd, short events, iu_event_id_t id, void *arg) { struct ifslist *ifsp = (struct ifslist *)arg; PKT_LIST *plp; if (check_ifs(ifsp) == 0) { /* unregister_acknak() does our release_ifs() */ (void) unregister_acknak(ifsp); (void) ioctl(fd, I_FLUSH, FLUSHR|FLUSHW); return; } /* * note that check_ifs() did our release_ifs() but we're not * sure we're done yet; call hold_ifs() to reacquire our hold; * if we're done, unregister_acknak() will release_ifs() below. */ hold_ifs(ifsp); if (recv_pkt(ifsp, fd, DHCP_PACK|DHCP_PNAK, B_FALSE) == 0) return; /* * we've got a packet; make sure it's acceptable before * cancelling the REQUEST retransmissions. */ plp = ifsp->if_recv_pkt_list; remove_from_pkt_list(&ifsp->if_recv_pkt_list, plp); if (*plp->opts[CD_DHCP_TYPE]->value == ACK) { if (plp->opts[CD_LEASE_TIME] == NULL || plp->opts[CD_LEASE_TIME]->len != sizeof (lease_t)) { dhcpmsg(MSG_WARNING, "dhcp_acknak: ACK packet on %s " "missing mandatory lease option, ignored", ifsp->if_name); ifsp->if_bad_offers++; free_pkt_list(&plp); return; } if ((ifsp->if_state == RENEWING || ifsp->if_state == REBINDING) && ifsp->if_addr.s_addr != plp->pkt->yiaddr.s_addr) { dhcpmsg(MSG_WARNING, "dhcp_acknak: renewal ACK packet " "has a different IP address (%s), ignored", inet_ntoa(plp->pkt->yiaddr)); ifsp->if_bad_offers++; free_pkt_list(&plp); return; } } /* * looks good; cancel the retransmission timer and unregister * the acknak handler. ACK to BOUND, NAK back to SELECTING. */ stop_pkt_retransmission(ifsp); (void) unregister_acknak(ifsp); if (*(plp->opts[CD_DHCP_TYPE]->value) == NAK) { dhcpmsg(MSG_WARNING, "dhcp_acknak: NAK on interface %s", ifsp->if_name); ifsp->if_bad_offers++; free_pkt_list(&plp); dhcp_restart(ifsp); /* * remove any bogus cached configuration we might have * around (right now would only happen if we got here * from INIT_REBOOT). */ (void) remove_hostconf(ifsp->if_name); return; } if (plp->opts[CD_SERVER_ID] == NULL || plp->opts[CD_SERVER_ID]->len != sizeof (ipaddr_t)) { dhcpmsg(MSG_ERROR, "dhcp_acknak: ACK with no valid server id, " "restarting DHCP on %s", ifsp->if_name); ifsp->if_bad_offers++; free_pkt_list(&plp); dhcp_restart(ifsp); return; } if (plp->opts[CD_MESSAGE] != NULL) print_server_msg(ifsp, plp->opts[CD_MESSAGE]); if (dhcp_bound(ifsp, plp) == 0) { dhcpmsg(MSG_WARNING, "dhcp_acknak: dhcp_bound failed " "for %s", ifsp->if_name); dhcp_restart(ifsp); return; } dhcpmsg(MSG_VERBOSE, "ACK on interface %s", ifsp->if_name); } /* * dhcp_restart(): restarts DHCP (from INIT) on a given interface * * input: struct ifslist *: the interface to restart DHCP on * output: void */ void dhcp_restart(struct ifslist *ifsp) { if (iu_schedule_timer(tq, DHCP_RESTART_WAIT, dhcp_start, ifsp) == -1) { ifsp->if_state = INIT; ifsp->if_dflags |= DHCP_IF_FAILED; ipc_action_finish(ifsp, DHCP_IPC_E_MEMORY); async_finish(ifsp); dhcpmsg(MSG_ERROR, "dhcp_restart: cannot schedule dhcp_start, " "reverting to INIT state on %s", ifsp->if_name); } else hold_ifs(ifsp); } /* * stop_requesting(): decides when to stop retransmitting REQUESTs * * input: struct ifslist *: the interface REQUESTs are being sent on * unsigned int: the number of REQUESTs sent so far * output: boolean_t: B_TRUE if retransmissions should stop */ static boolean_t stop_requesting(struct ifslist *ifsp, unsigned int n_requests) { if (n_requests >= DHCP_MAX_REQUESTS) { (void) unregister_acknak(ifsp); dhcpmsg(MSG_INFO, "no ACK/NAK to REQUESTING REQUEST, " "restarting DHCP on %s", ifsp->if_name); dhcp_selecting(ifsp); return (B_TRUE); } return (B_FALSE); }