dhcpv4.c revision fa9e4066f08beec538e775443c5be79dd423fcab
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
* Standalone dhcp client.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/bootconf.h>
#include <sys/isa_defs.h>
#include <netinet/in_systm.h>
#include <netinet/inetutil.h>
#include <dhcp_impl.h>
#include <net/if_types.h>
#include <sys/platnames.h>
#include <socket_inet.h>
#include "ipv4.h"
#include "mac.h"
#include <sys/bootdebug.h>
#include "dhcpv4.h"
static char *s_n = "INIT";
static int dhcp_buf_size;
static uint8_t dhcp_clientid_len = 0;
static int pkt_counter;
#define PROM_BOOT_CACHED "bootp-response"
#ifndef __i386
extern char *bootp_response; /* bootprop.c */
#else
char *bootp_response; /* i386 has *real* bsetprop */
#endif /* __i386 */
extern int pagesize;
/*
* Do whatever reset actions/initialization actions are generic for every
*
* Returns: the updated options ptr.
*/
static uint8_t *
{
if (xid == 0)
else
xid++;
}
/*
* Parameter request list.
*/
static void
{
/*
* This parameter request list is used in the normal 4-packet
* DHCPDISCOVER/OFFER/REQUEST/ACK exchange; it must not contain
* CD_REQUESTED_IP_ADDR or CD_LEASE_TIME.
*/
CD_REQUEST_LIST, /* parameter request list option number */
4, /* number of options requested */
};
}
}
/*
* Set hardware specific fields
*/
static void
{
int adjust_len, len, i;
} else {
/*
* The mac address does not fit in the chaddr
* field, thus it can not be sent to the server,
* thus server can not unicast the reply. Per
* RFC 2131 4.4.1, client can set this bit in
*/
}
if (dhcp_classid[0] == '\0') {
/*
* Classids based on mfg name: Commas (,) are
* converted to periods (.), and spaces ( ) are removed.
*/
dhcp_classid[0] = CD_CLASS_ID;
} else {
len = 0;
cbuf[0] = '\0';
}
adjust_len = 0;
if (*tp == ',') {
*dp++ = '.';
} else if (*tp == ' ') {
adjust_len++;
} else {
}
}
len -= adjust_len;
} else
prom_panic("Not enough space for class id");
#ifdef DHCP_DEBUG
#endif /* DHCP_DEBUG */
}
}
}
static void
flush_list(void)
{
}
pkt_counter = 0;
}
static void
{
return;
} else {
}
pkt_counter--;
if (flag) {
}
}
/*
* Collects BOOTP responses. Length has to be right, it has to be
* a BOOTP reply pkt, with the same XID and HW address as ours. Adds
* them to the pkt list.
*
* collection of replies should stop. Used in inet() calls.
*/
static int
bootp_collect(int len)
{
return (1);
}
/* Add a packet to the pkt list */
return (1); /* got enough packets already */
return (1);
}
} else {
}
pkt_counter++;
}
return (0);
}
/*
* Checks if BOOTP exchange(s) were successful. Returns 1 if they
* were, 0 otherwise. Used in inet() calls.
*/
static int
bootp_success(void)
{
/* remember the secs - we may need them later */
return (1);
}
return (0);
}
/*
* This function accesses the network. Opens a connection, and binds to
* it if a client binding doesn't already exist. If 'tries' is 0, then
* attempts are made to get a valid response. If 'tol' is not zero,
* then this function will wait for 'tol' milliseconds for more than one
* response to a transmit.
*
* Returns 0 for success, errno otherwise.
*/
static int
{
int sd;
return (errno);
}
flags = 0;
dprintf("%s: Cannot bind to port %d, errno: %d\n",
(void) socket_close(sd);
return (errno);
}
}
do {
dprintf("%s: sendto failed with errno: %d\n",
(void) socket_close(sd);
return (errno);
}
if (!tries)
break; /* don't bother to check for reply */
now = prom_gettime();
if (timeout == 0)
timeout = 4000;
else {
timeout <<= 1;
if (timeout > 64000)
timeout = 64000;
}
do {
NULL)) < 0) {
if (errno == EWOULDBLOCK)
continue; /* DONT WAIT */
(void) socket_close(sd);
flush_list();
return (errno);
}
if (bootp_collect(len))
break; /* Stop collecting */
if (tol != 0) {
if (wait_time < prom_gettime())
break; /* collection timeout */
}
} while (prom_gettime() < init_timeout);
if (bootp_success()) {
break; /* got the goods */
}
(void) socket_close(sd);
return (done ? 0 : DHCP_NO_DATA);
}
/*
* Print the message from the server.
*/
static void
{
if (len > DHCP_MAX_OPT_SIZE)
}
/*
* This function scans the list of OFFERS, and returns the "best" offer.
* The criteria used for determining this is:
*
* The best:
* DHCP OFFER (not BOOTP), same client_id as ours, same class_id,
* Longest lease, all the options we need.
*
* Not quite as good:
* DHCP OFFER, no class_id, short lease, only some of the options we need.
*
* We're really reach'in
* BOOTP reply.
*
* DON'T select an offer from a server that gave us a configuration we
* couldn't use. Take this server off the "bad" list when this is done.
* Next time, we could potentially retry this server's configuration.
*
* NOTE: perhaps this bad server should have a counter associated with it.
*/
static PKT_LIST *
select_best(void)
{
int err = 0;
/* Pass one. Scan for options, set appropriate opt field. */
/* Garbled Options. Nuke this pkt. */
switch (err) {
case DHCP_WRONG_MSG_TYPE:
printf("%s: Unexpected DHCP message.\n",
s_n);
break;
case DHCP_GARBLED_MSG_TYPE:
"%s: Garbled DHCP message type.\n",
s_n);
break;
case DHCP_BAD_OPT_OVLD:
printf("%s: Bad option overload.\n",
s_n);
break;
}
}
continue;
}
}
/*
* Pass two. 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
*/
dprintf("%s: Unexpected DHCP message."
" Expected OFFER message.\n", s_n);
continue;
}
dprintf("%s: DHCP OFFER message without lease "
"time parameter.\n", s_n);
continue;
} else {
dprintf("%s: Lease expiration time is "
"garbled.\n", s_n);
continue;
}
}
dprintf("%s: DHCP OFFER message without server "
"id parameter.\n", s_n);
continue;
} else {
dprintf("%s: Server identifier "
"parameter is garbled.\n", s_n);
continue;
}
}
/* Valid DHCP OFFER. See if we got our parameters. */
/*
* 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.
*/
} else
/*
* RFC1048 BOOTP?
*/
sizeof (magic)) == 0) {
/*
* Prefer options that have diskless boot significance
*/
}
}
#ifdef DHCP_DEBUG
#endif /* DHCP_DEBUG */
if (!best)
else {
}
}
if (best) {
#ifdef DHCP_DEBUG
#endif /* DHCP_DEBUG */
} else {
dprintf("%s: No valid BOOTP reply or DHCP OFFER was found.\n",
s_n);
}
flush_list(); /* toss the remaining list */
return (best);
}
/*
* Send a decline message to the generator of the DHCPACK.
*/
static void
{
int pkt_size;
} else
}
*opt++ = CD_SERVER_ID;
/*
* Use the given yiaddr in our ciaddr field so server can identify us.
*/
*opt++ = CD_MESSAGE;
timeout = 0; /* reset timeout */
}
/*
* Implementings SELECTING state of DHCP client state machine.
*/
static int
dhcp_selecting(void)
{
int pkt_size;
*opt++ = CD_MAX_DHCP_SIZE;
sizeof (struct udphdr));
*opt++ = CD_LEASE_TIME;
/*
* If a clientid was set using dhcp_set_client_id(), add this
* to the options.
*/
if (dhcp_clientid_len > 0) {
*opt++ = CD_CLIENT_ID;
*opt++ = dhcp_clientid_len;
opt += dhcp_clientid_len;
}
timeout = 0; /* reset timeout */
}
/*
* implements the REQUESTING state of the DHCP client state machine.
*/
static int
dhcp_requesting(void)
{
/* here we scan the list of OFFERS, select the best one. */
dprintf("%s: No valid BOOTP reply or DHCP OFFER was found.\n",
s_n);
return (1);
}
/*
* Check to see if we got an OFFER pkt(s). If not, then We only got
* a response from a BOOTP server. We'll go to the bound state and
* try to use that configuration.
*/
/* Someone responded! go back to SELECTING state. */
return (1);
}
return (0);
}
/*
* 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 DISCOVER state. Keep secs field the same for relay
* agents. We start with the DHCPOFFER packet we got, and the
* options contained in it to make a requested option list.
*/
/* Time from Successful DISCOVER message. */
sizeof (struct udphdr));
*opt++ = CD_MAX_DHCP_SIZE;
*opt++ = CD_SERVER_ID;
/* our offered lease minus boot time */
*opt++ = CD_LEASE_TIME;
*opt++ = 4;
sizeof (t_time));
/* our offered IP address, as required. */
*opt++ = CD_REQUESTED_IP_ADDR;
/*
* If a clientid was set using dhcp_set_client_id(), add this
* to the options.
*/
if (dhcp_clientid_len > 0) {
*opt++ = CD_CLIENT_ID;
*opt++ = dhcp_clientid_len;
opt += dhcp_clientid_len;
}
/* Done with the OFFER pkt. */
/*
* We make 4 attempts here. We wait for 2 seconds to accumulate
* requests, just in case a BOOTP server is too fast!
*/
timeout = 0; /* reset timeout */
return (err);
continue; /* garbled options */
case ACK:
break;
case NAK:
printf("%s: rejected by DHCP server: %s\n",
break;
default:
break;
}
}
flush_list();
/* arp our new address, just to make sure */
/*
* No response. Check if lease is ok.
*/
dhcp_decline("Missing or corrupted lease time",
state_pl);
err++;
}
} else {
dhcp_decline("IP Address already being used!",
state_pl);
err++;
}
if (err) {
} else
return (0); /* passes the tests! */
}
return (1);
}
/*
* Implements BOUND state of DHCP client state machine.
*/
static int
dhcp_bound(void)
{
#ifdef DHCP_DEBUG
if (dhcp_classid[0] != 0) {
}
#endif /* DHCP_DEBUG */
/* First, set the bare essentials. (IP layer parameters) */
else {
}
if (boothowto & RB_VERBOSE)
/*
* Ensure that the Boot NFS READ size, if given, is an int16_t.
*/
}
/*
* Set subnetmask. Nice, but not required.
*/
else {
sizeof (struct in_addr));
}
}
/*
* Set default IP TTL. Nice, but not required.
*/
else {
}
}
/*
* Set default router. Not required, although we'll know soon
* enough...
*/
} else {
mac_get_hdr_len())) == NULL) {
return (-1);
}
}
if (fnd)
break;
"%s: Warning: Router %s is unreachable.\n",
}
/*
* If fnd is 0, we didn't find a working router. We'll
* still try to use first default router. If we got
* a bad router address (like not on the same net),
* we're hosed anyway.
*/
if (!fnd) {
"%s: Warning: Using default router: %s\n",
}
/* ipv4_route expects network order IP addresses */
&defr);
}
}
/*
* Set hostname. Not required.
*/
if (i > MAXHOSTNAMELEN)
i = MAXHOSTNAMELEN;
hostname[i] = '\0';
(void) sethostname(hostname, i);
if (boothowto & RB_VERBOSE)
}
/*
* We don't care about the lease time.... We can't enforce it anyway.
*/
return (0);
}
/*
* Convert the DHCPACK into a pure ASCII boot property for use by the kernel
* later in the boot process.
*/
static void
{
#if defined(__i386)
#endif /* __i386 */
return;
return;
&buflen) != 0) {
}
#if defined(__i386)
/* Use bsetprop to create the bootp-response property */
BOOT_SUCCESS) {
}
#endif /* __i386 */
}
/*
* Examines /chosen node for "bootp-response" property. If it exists, this
* property is the DHCPACK cached there by the PROM's DHCP implementation.
*
* If cache_present is B_TRUE, we simply return B_TRUE if the property exists
* w/o decoding it. If cache_present is B_FALSE, we decode the packet and
* use it as our state packet for the jump to BOUND mode. Note that it's good
* enough that the cache exists w/o validation for determining if that's what
* the user intended.
*
* We'll short-circuit the DHCP exchange by accepting this packet. We build a
* PKT_LIST structure, and copy the bootp-response property value into a
* PKT buffer we allocated. We then scan the PKT for options, and then
* set state_pl to point to it.
*
* Returns B_TRUE if a packet was cached (and was processed correctly), false
* otherwise. The caller needs to make the state change from SELECTING to
* BOUND upon a B_TRUE return from this function.
*/
int
{
int len;
#if defined(__i386)
char *ack;
int pxe_ack_cache(char **);
return (B_FALSE);
#else
char *prop = PROM_BOOT_CACHED;
return (B_FALSE);
#endif /* __i386 */
if (cache_present)
return (B_TRUE);
return (B_FALSE);
}
#if defined(__i386)
#else
#endif /* __i386 */
/* garbled packet */
return (B_FALSE);
}
return (B_TRUE);
}
/*
* Perform DHCP to acquire what we used to get using rarp/bootparams.
* Returns 0 for success, nonzero otherwise.
*
* DHCP client state machine.
*/
int
dhcp(void)
{
int err = 0;
dhcp_buf_size = mac_get_mtu();
if (dhcp_snd_bufp != NULL)
if (dhcp_rcv_bufp != NULL)
return (-1);
}
switch (dhcp_state) {
case INIT:
s_n = "INIT";
if (prom_cached_reply(B_FALSE)) {
dprintf("%s: Using PROM cached BOOT reply...\n",
s_n);
dhcp_state = BOUND;
} else {
}
break;
case SELECTING:
s_n = "SELECTING";
err = dhcp_selecting();
switch (err) {
case 0:
break;
case DHCP_NO_DATA:
"%s: No DHCP response after %d tries.\n",
s_n, DHCP_RETRIES);
break;
default:
/* major network problems */
dprintf("%s: Network transaction failed: %d\n",
break;
}
break;
case REQUESTING:
s_n = "REQUESTING";
err = dhcp_requesting();
switch (err) {
case 0:
dhcp_state = BOUND;
break;
case DHCP_NO_DATA:
err = 0;
(void) sleep(10);
break;
default:
/* major network problems */
dprintf("%s: Network transaction failed: %d\n",
break;
}
break;
case BOUND:
/*
* We just "give up" if bound state fails.
*/
s_n = "BOUND";
if ((err = dhcp_bound()) == 0) {
}
break;
case CONFIGURED:
s_n = "CONFIGURED";
break;
}
}
return (err);
}
/*
* Returns a copy of the DHCP-supplied value of the parameter requested
* by code.
*/
{
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* Sets the clientid option.
*/
void
{
}