bound.c revision 10e672b7a3135068856cad9c6564d7d0a38fd869
/*
* 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 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* BOUND state of the DHCP client state machine.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <search.h>
#include <sys/sysmacros.h>
#include <dhcp_hostconf.h>
#include <dhcpagent_util.h>
#include <dhcpmsg.h>
#include "states.h"
#include "packet.h"
#include "util.h"
#include "agent.h"
#include "interface.h"
#include "script_handler.h"
/*
* Possible outcomes for IPv6 binding attempt.
*/
enum v6_bind_result {
v6Restart, /* report failure and restart state machine */
v6Resent, /* new Request message has been sent */
v6Done /* successful binding */
};
/*
* bound_event_cb(): callback for script_start on the event EVENT_BOUND
*
* input: dhcp_smach_t *: the state machine configured
* void *: unused
* output: int: always 1
*/
/* ARGSUSED1 */
static int
{
else
return (1);
}
/*
* dhcp_bound(): configures an state machine and interfaces using information
* Before starting, the requested address is verified by
* Duplicate Address Detection to make sure it's not in use.
*
* input: dhcp_smach_t *: the state machine to move to bound
* output: boolean_t: B_TRUE on success, B_FALSE on failure
*/
{
enum v6_bind_result v6b;
/* If ack we're replacing is not the original, then free it */
/* Save the first ack as the original */
}
switch (oldstate) {
case ADOPTING:
/* Note that adoption occurs only for IPv4 DHCP. */
/* Ignore BOOTP */
return (B_FALSE);
/*
* if we're adopting a lease, the lease timers
* only provide an upper bound since we don't know
* from what time they are relative to. assume we
* have a lease time of at most DHCP_ADOPT_LEASE_MAX.
*/
sizeof (lease_t));
sizeof (lease_t));
/*
* we have no idea when the REQUEST that generated
* this ACK was sent, but for diagnostic purposes
* we'll assume its close to the current time.
*/
} else {
if (!configure_v4_lease(dsmp))
return (B_FALSE);
if (!configure_v4_timers(dsmp))
return (B_FALSE);
}
break;
case SELECTING:
case REQUESTING:
case INIT_REBOOT:
} else {
if (!configure_v4_lease(dsmp))
return (B_FALSE);
if (!configure_v4_timers(dsmp))
return (B_FALSE);
return (B_FALSE);
}
/* Stop sending requests now */
/*
* If we didn't end up with any usable leases, then we have a
* problem.
*/
"dhcp_bound: no address lease established");
return (B_FALSE);
}
/*
* If this is a Rapid-Commit (selecting state) or if we're
* dealing with a reboot (init-reboot), then we will have a new
* server ID to save.
*/
"dhcp_bound: unable to save server ID on %s",
return (B_FALSE);
}
/*
* We will continue configuring the interfaces via
* dhcp_bound_complete, once kernel DAD completes. If no new
* leases were created (which can happen on an init-reboot used
* for link-up confirmation), then go straight to bound state.
*/
return (B_FALSE);
if (dsmp->dsm_lif_wait == 0)
break;
case PRE_BOUND:
case BOUND:
case INFORMATION:
/* This is just a duplicate ack; silently ignore it */
return (B_TRUE);
case RENEWING:
case REBINDING:
} else {
if (!configure_v4_timers(dsmp))
return (B_FALSE);
return (B_FALSE);
}
/*
* If some or all of the leases were torn down by the server,
* then handle that as an expiry. When the script is done
* running for the LOSS6 event, we'll end up back here.
*/
}
return (B_FALSE);
}
return (B_FALSE);
}
/*
* (Should just never happen, but RFC 3315 section 18.1.8
* requires it, and TAHI tests for it.)
*/
break;
}
"allow retransmit");
return (B_TRUE);
}
return (B_FALSE);
/* Stop sending requests now */
break;
case INFORM_SENT:
return (B_FALSE);
}
return (B_FALSE);
/* Stop sending requests now */
break;
default:
/* something is really bizarre... */
"dhcp_bound: called in unexpected state: %s",
return (B_FALSE);
}
/*
* remove any stale hostconf file that might be lying around for
* this state machine. (in general, it's harmless, since we'll write a
* fresh one when we exit anyway, but just to reduce confusion..)
*/
return (B_TRUE);
}
/*
* dhcp_bound_complete(): complete interface configuration after DAD
*
* input: dhcp_smach_t *: the state machine now ready
* output: none
*/
void
{
int i;
/*
* Do bound state entry processing only if running IPv4. There's no
* need for this with DHCPv6 because link-locals are used for I/O and
* because DHCPv6 isn't entangled with routing.
*/
NULL);
return;
}
/*
* Add each provided router; we'll clean them up when the
* state machine goes away or when our lease expires.
*
* Note that we do not handle default routers on IPv4 logicals;
* see README for details.
*/
if (router_list != NULL &&
"default router list, ignoring default routers");
dsmp->dsm_nrouters = 0;
}
for (i = 0; i < dsmp->dsm_nrouters; i++) {
sizeof (ipaddr_t));
&dsmp->dsm_routers[i])) {
"add default router %s on %s", inet_ntoa(
continue;
}
}
}
"dhcp_bound_complete: cannot set bound state on %s",
return;
}
/*
* We're now committed to this binding, so if it came from BOOTP, set
* the flag.
*/
/*
* If the previous state was ADOPTING, event loop has not been started
* at this time; so don't run the EVENT_BOUND script.
*/
NULL);
}
}
/*
* We use up to plus or minus 2% jitter in the time. This is a
* small value, but the timers involved are typically long. A
* common T1 value is one day, and the fuzz is up to 28.8 minutes;
* plenty of time to make sure that individual clients don't renew
* all at the same time.
*
* input: uint32_t: the number of seconds until lease expiration
* double: the approximate percentage of that time to return
* output: double: a number approximating (sec * pct)
*/
static double
{
}
/*
* get_pkt_times(): pulls the lease times out of a v4 DHCP packet and stores
* them as host byte-order relative times in the passed in
* parameters.
*
* input: PKT_LIST *: the packet to pull the packet times from
* lease_t *: where to store the relative lease time in hbo
* lease_t *: where to store the relative t1 time in hbo
* lease_t *: where to store the relative t2 time in hbo
* output: void
*/
static void
{
"get_pkt_times: BOOTP response; infinite lease");
return;
}
"get_pkt_times: no lease option provided");
return;
}
}
return;
}
}
}
}
/*
* configure_v4_timers(): configures the lease timers on a v4 state machine
*
* input: dhcp_smach_t *: the state machine to configure
* output: boolean_t: B_TRUE on success, B_FALSE on failure
*/
static boolean_t
{
/* v4 has just one lease per state machine, and one LIF */
/*
* If it's DHCP, but there's no valid lease time, then complain,
* decline the lease and return error.
*/
return (B_FALSE);
}
/* Stop the T1 and T2 timers */
/* Stop the LEASE timer */
/*
* type has already been verified as ACK. if type is not set,
* then we got a BOOTP packet. we now fetch the t1, t2, and
* lease options out of the packet into variables. they are
* returned as relative host-byte-ordered times.
*/
/*
* if the current lease is mysteriously close to the new
* lease, warn the user. unless there's less than a minute
* left, round to the closest minute.
*/
const char *noext = "configure_v4_timers: lease renewed but "
"time not extended";
int msg_level;
else
} else if (minleft == 1) {
} else if (minleft > 120) {
} else {
}
}
"configure_v4_timers: %s acquired permanent lease",
return (B_TRUE);
}
/*
* according to RFC2131, there is no minimum lease time, but don't
*/
goto failure;
if (lease < DHCP_REBIND_MIN) {
return (B_TRUE);
}
goto failure;
goto failure;
return (B_TRUE);
"configure_v4_timers: cannot schedule lease timers");
return (B_FALSE);
}
/*
* configure_v6_leases(): configures the IPv6 leases on a state machine from
* the current DHCPv6 ACK. We need to scan the ACK,
* create a lease for each IA_NA, and a new LIF for each
* IAADDR.
*
* input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack)
* output: enum v6_bind_result: restart, resend, or done
*/
static enum v6_bind_result
{
"configure_v6_leases: garbled IA_NA");
continue;
}
/*
* Check the IAID. It should be for our controlling LIF. If a
* single state machine needs to use multiple IAIDs, then this
* will need to change.
*/
continue;
}
/*
* See notes below; there's only one IA_NA and a single IAID
* for now.
*/
/*
* Note that some bug-ridden servers will try to give us
* multiple IA_NA options for a single IAID. We ignore
* duplicates.
*/
if (got_iana) {
"extra IA_NA ignored");
continue;
}
continue;
}
/*
* There may be a status code here. Process if present.
*/
if (scode != DHCPV6_STAT_SUCCESS) {
"configure_v6_leases: IA_NA: %s: %.*s",
}
/*
* Other errors are possible here. According to RFC 3315
* section 18.1.8, we ignore the entire IA if it gives the "no
* addresses" status code. We may try another server if we
* like -- we instead opt to allow the addresses to expire and
* then try a new server.
*
* If the status code is "no binding," then we must go back and
* redo the Request. Surprisingly, it doesn't matter if it's
* any other code.
*/
if (scode == DHCPV6_STAT_NOADDRS) {
"no-addrs status in IA_NA");
continue;
}
if (scode == DHCPV6_STAT_NOBINDING) {
return (v6Resent);
}
/*
* Find or create the lease structure. This part is simple,
* because we support only IA_NA and a single IAID. This means
* there's only one lease structure. The design supports
* multiple lease structures so that IA_TA and IA_PD can be
* added later.
*/
"allocate memory for lease");
return (v6Restart);
}
/*
* Iterate over the IAADDR options contained within this IA_NA.
*/
"configure_v6_leases: garbled IAADDR");
continue;
}
/* RFC 3315 required validity check */
"configure_v6_leases: ignored IAADDR with "
"preferred lifetime %u > valid %u",
continue;
}
/*
* RFC 3315 allows a status code to be buried inside
* the IAADDR option. Look for it, and process if
* present. Process in a manner similar to that for
* the IA itself; TAHI checks for this. Real servers
* likely won't do this.
*/
&msglen);
if (scode == DHCPV6_STAT_NOADDRS) {
"ignoring no-addrs status in IAADDR");
continue;
}
if (scode == DHCPV6_STAT_NOBINDING) {
return (v6Resent);
}
if (scode != DHCPV6_STAT_SUCCESS) {
"configure_v6_leases: IAADDR: %s", estr);
}
/*
* Locate the existing LIF within the lease associated
* with this address, if any.
*/
&lif->lif_v6addr))
break;
}
/*
* If the server has set the lifetime to zero, then
* delete the LIF. Otherwise, set the new LIF expiry
* time, adding the LIF if necessary.
*/
if (d6ia.d6ia_vallife == 0) {
/* If it was found, then it's expired */
if (nlifs != 0) {
"configure_v6_leases: lif %s has "
}
continue;
}
/* If it wasn't found, then create it now. */
if (nlifs == 0) {
continue;
} else {
}
dsmp->dsm_lif_wait++;
} else {
/* If it was found, cancel timer */
if (d6ia.d6ia_preflife != 0 &&
!clear_lif_deprecated(lif)) {
continue;
}
}
/* Set the new expiry timers */
/*
* If the preferred lifetime is over now, then the LIF
* is deprecated. If it's the same as the expiry time,
* then we don't need a separate timer for it.
*/
if (d6ia.d6ia_preflife == 0) {
dhcp_deprecate)) {
continue;
}
dhcp_expire)) {
continue;
}
}
"configure_v6_leases: no IAADDRs found in IA_NA");
continue;
}
/* Default values from RFC 3315: 0.5 and 0.8 */
}
} else {
}
}
if (!got_iana) {
"configure_v6_leases: no usable IA_NA option found");
}
return (v6Done);
}
/*
* configure_v4_lease(): configures the IPv4 lease on a state machine from
* the current DHCP ACK. There's only one lease and LIF
* per state machine in IPv4.
*
* input: dhcp_smach_t *: the machine to configure (with a valid dsm_ack)
* output: boolean_t: B_TRUE on success, B_FALSE on failure
*/
static boolean_t
{
struct sockaddr_in *sin;
/*
* if we're using DHCP, then we'll have a valid CD_SERVER_ID
* (we checked in dhcp_acknak()); set it now so that
* dsmp->dsm_server is valid in case we need to send_decline().
* note that we use comparisons against opts[CD_DHCP_TYPE]
* since we haven't set DHCP_IF_BOOTP yet (we don't do that
* until we're sure we want the offered address.)
*/
sizeof (inaddr));
}
/*
* There needs to be exactly one lease for IPv4, and that lease
* controls the main LIF for the state machine. If it doesn't exist
* yet, then create it now.
*/
"memory for lease");
return (B_FALSE);
}
/* The lease holds a reference on the LIF */
}
if ((addrhbo & IN_CLASSA_NET) == 0 ||
"configure_v4_lease: got invalid IP address %s for %s",
return (B_FALSE);
}
/*
* bring the interface online. note that there is no optimal
* order here: it is considered bad taste (and in > solaris 7,
* likely illegal) to bring an interface up before it has an
* ip address. however, due to an apparent bug in sun fddi
* 5.0, fddi will not obtain a network routing entry unless
* the interface is brought up before it has an ip address.
* we take the lesser of the two evils; if fddi customers have
* problems, they can get a newer fddi distribution which
* fixes the problem.
*/
sizeof (inaddr));
} else {
"subnet mask length is %d instead of %d, ignoring",
} else {
"netmask specified for %s, making best guess",
}
/*
* no legitimate IP subnet mask specified.. use best
* guess. recall that lif_addr is in network order, so
* imagine it's 0x11223344: then when it is read into
* a register on x86, it becomes 0x44332211, so we
* must ntohl() it to convert it to 0x11223344 in
*/
else /* must be class c */
}
return (B_FALSE);
}
return (B_FALSE);
}
dsmp->dsm_lif_wait++;
sizeof (inaddr));
} else {
"broadcast address length is %d instead of %d, "
sizeof (inaddr));
} else {
"broadcast specified for %s, making best guess",
}
/*
* no legitimate IP broadcast specified. compute it
* from the IP address and netmask.
*/
}
/*
* the kernel will set the broadcast address for us as part of
* bringing the interface up. since experience has shown that dhcp
* servers sometimes provide a bogus broadcast address, we let the
* kernel set it so that it's guaranteed to be correct.
*
* also, note any inconsistencies and save the broadcast address the
* kernel set so that we can watch for changes to it.
*/
return (B_FALSE);
}
}
"configure_v4_lease: using broadcast address %s on %s",
return (B_TRUE);
}
/*
* save_server_id(): save off the new DHCPv6 Server ID
*
* input: dhcp_smach_t *: the state machine to use
* PKT_LIST *: the packet with the Reply message
* output: boolean_t: B_TRUE on success, B_FALSE on failure
*/
{
const dhcpv6_option_t *d6o;
return (B_FALSE);
return (B_FALSE);
} else {
return (B_TRUE);
}
}