request.c revision f7d61273153286ce87ee33b9834053a95b15fc39
/*
* 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
* 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 <stdlib.h>
#include <string.h>
#include <search.h>
#include <dhcp_hostconf.h>
#include <dhcpagent_util.h>
#include <dhcpmsg.h>
#include "states.h"
#include "util.h"
#include "packet.h"
#include "interface.h"
#include "agent.h"
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
{
/* Add an IA_NA option for our controlling LIF */
sizeof (d6in) - sizeof (dhcpv6_option_t));
/* Add required Option Request option */
/* For DHCPv6, state switch cannot fail */
}
/*
* 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
{
const dhcpv6_option_t *d6o;
/* LINTED: no consequent */
/* No Server Unicast option specified */
"Unicast option with bad length",
} else {
if (IN6_IS_ADDR_UNSPECIFIED(&addr)) {
"to unspecified address ignored");
} else if (IN6_IS_ADDR_MULTICAST(&addr)) {
"to multicast address ignored");
} else if (IN6_IS_ADDR_V4COMPAT(&addr) ||
IN6_IS_ADDR_V4MAPPED(&addr)) {
"to invalid address ignored");
} else {
}
}
}
/*
* 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
* 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
{
/*
* 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.
*/
"dhcp_requesting: offer wait timer on v%d %s",
if (!verify_smach(dsmp))
return;
} else {
}
/*
* select the best OFFER; all others pitched.
*/
"no OFFERs/Advertisements on %s, waiting...",
/*
* no acceptable OFFERs have come in. reschedule
* ourself for callback.
*/
/*
* ugh. the best we can do at this point is
* revert back to INIT and wait for a user to
* restart us.
*/
"reschedule callback, reverting to INIT state on "
} else {
}
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)
/* stop collecting packets. */
/*
* 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.
*/
return;
}
return;
}
return;
}
if (isv6) {
const dhcpv6_option_t *d6o;
/* If there's a Status Code option, print the message */
&olen);
/* Copy in the Server ID (guaranteed to be present now) */
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.
*/
} else {
/* if we got a message from the server, display it. */
}
/*
* assemble a DHCPREQUEST, with the ciaddr field set to 0,
* since we got here from the INIT state.
*/
/*
* 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.
*/
sizeof (lease_t));
if (class_id_len != 0) {
}
/*
* dsm_reqhost was set for this state machine in
* dhcp_selecting() if the DF_REQUEST_HOSTNAME option set and a
* host name was found
*/
}
/*
* send out the REQUEST, trying retransmissions. either a NAK
* or too many REQUEST attempts will revert us to SELECTING.
*/
"REQUESTING state; reverting to INIT on %s",
goto failure;
}
}
/* all done with the offer */
return;
}
/*
* 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
{
char abuf[INET6_ADDRSTRLEN];
int points = 0;
int i;
/*
* 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.)
*/
"discard advertisement from %s on %s: no address status",
return (-1);
}
/* Two points for each batch of offered IP addresses */
/*
* 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.
*/
(const char *)d6o + sizeof (dhcpv6_ia_na_t),
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++) {
NULL)
points++;
}
/*
* Ten points for each point of "preference." Note: the value 255 is
* special. It means "stop right now and select this server."
*/
if (pref == 255)
return (-2);
}
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
{
int points = 0;
goto valid_offer;
}
"time");
return (-1);
}
"lease time");
return (-1);
}
"id");
return (-1);
}
"server id");
return (-1);
}
/* valid DHCP OFFER. see if we got our parameters. */
points += 30;
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.
*/
points += 80;
points++;
points++;
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 *
{
/*
* 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)
*/
/*
* Just discard any unacceptable entries we encounter.
*/
if (points == -1) {
continue;
}
/* Special case: stop now and select */
if (points == -2) {
break;
}
if (points >= best_points) {
}
}
} else {
}
return (best);
}
/*
* 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.
*
* output: void
*/
static void
{
/* Account for received and processed messages */
dsmp->dsm_received++;
"%s missing mandatory lease option, ignored",
dsmp->dsm_bad_offers++;
return;
}
"packet has a different IP address (%s), ignored",
dsmp->dsm_bad_offers++;
return;
}
}
/*
* looks good; cancel the retransmission timer and unregister
* the acknak handler. ACK to BOUND, NAK back to SELECTING.
*/
dsmp->dsm_bad_offers++;
/*
* remove any bogus cached configuration we might have
* around (right now would only happen if we got here
* from INIT_REBOOT).
*/
return;
}
dsmp->dsm_bad_offers++;
return;
}
}
}
}
/*
* 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
{
const dhcpv6_option_t *d6o;
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) {
return;
}
/*
* All valid DHCPv6 messages must have our Client ID specified.
*/
"accept_v6_message: discarded %s on %s: %s Client ID",
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().
*/
"accept_v6_message: discarded %s on %s: no Server ID",
return;
}
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.
*/
case SELECTING:
/* A Reply message signifies a Rapid-Commit. */
if (recv_type == DHCPV6_MSG_REPLY) {
"on %s lacks Rapid-Commit; ignoring",
break;
}
"accept_v6_message: rapid-commit Reply on %s",
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.
*/
&olen);
} else {
}
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;
"accept_v6_message: received Reply message on %s",
/*
* 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.
*/
&olen);
/*
* 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) {
break;
} else {
return;
}
}
/*
* 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) {
/*
* dhcp_bound will stop retransmission on
* success, if that's called for.
*/
} else {
}
} else {
estr);
} else {
}
}
return;
case DECLINING:
/*
* We're looking for Reply messages.
*/
if (recv_type != DHCPV6_MSG_REPLY)
break;
/*
* Extract the status code option. Note that it's not a
* failure if the server reports an error.
*/
&olen);
&msglen) == DHCPV6_STAT_SUCCESS) {
} else {
estr);
}
} else if (dsmp->dsm_lif_wait == 0) {
} else {
}
return;
case RELEASING:
/*
* We're looking for Reply messages.
*/
if (recv_type != DHCPV6_MSG_REPLY)
break;
/*
* Extract the status code option.
*/
&olen);
&msglen) == DHCPV6_STAT_SUCCESS) {
} else {
estr);
}
return;
}
/*
* Break from above switch means that the message must be discarded.
*/
"accept_v6_message: discarded v6 %s on %s; state %s",
}
/*
* 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
* short: unused
* iu_event_id_t: unused
* void *: unused
* output: void
*/
/* ARGSUSED */
void
void *arg)
{
const char *pname;
struct sockaddr_in sin;
const char *reason;
int sock;
return;
/*
* 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.
*/
break;
}
return;
}
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) {
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)
else
return;
drop:
reason);
}
/*
* 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
{
} else {
}
}
/*
* 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
void *arg)
{
const char *pname;
return;
if (!pkt_v4_match(recv_type,
return;
}
/*
* Find the corresponding state machine.
*/
break;
}
goto drop;
/*
* the REQUEST retransmissions.
*/
} else {
goto drop;
/*
* Must be an OFFER or a BOOTP message: enqueue it for later
* processing by select_best().
*/
}
return;
drop:
}
/*
* 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
{
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.
*/
if (!set_start_timer(dsmp)) {
} else {
}
}
/*
* 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
{
if (n_requests >= maxreq) {
return (B_TRUE);
} else {
return (B_FALSE);
}
}