OpenSolaris_b135/stand/lib/inet/dhcpv4.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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.
 *
 * Standalone dhcp client.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/salib.h>
#include <sys/bootconf.h>
#include <sys/bootcmn.h>
#include <sys/socket.h>
#include <sys/isa_defs.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/inetutil.h>
#include <netinet/dhcp.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <dhcp_impl.h>
#include <net/if_types.h>
#include <sys/promif.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 enum DHCPSTATE 	dhcp_state = INIT;

static PKT		*dhcp_snd_bufp, *dhcp_rcv_bufp;
static int		dhcp_buf_size;

static const uint8_t	magic[] = BOOTMAGIC;	/* RFC1048 */
static uint8_t		opt_discover[]  = { CD_DHCP_TYPE, 1, DISCOVER };
static uint8_t		opt_request[]   = { CD_DHCP_TYPE, 1, REQUEST };
static uint8_t		opt_decline[]   = { CD_DHCP_TYPE, 1, DECLINE };

static uint8_t		dhcp_classid[DHCP_MAX_OPT_SIZE + 3];
static uint8_t		dhcp_clientid[DHCP_MAX_CID_LEN];
static uint8_t		dhcp_clientid_len = 0;

static uint32_t		dhcp_start_time;	/* start time (msecs */
static time_t		dhcp_secs;
static uint32_t		timeout;	/* timeout in milliseconds */

static int		pkt_counter;
PKT_LIST		*list_tl, *list_hd;
PKT_LIST		*state_pl = NULL;

#define	PROM_BOOT_CACHED	"bootp-response"
extern char	*bootp_response;	/* bootprop.c */
extern int	pagesize;

/*
 * Do whatever reset actions/initialization actions are generic for every
 * DHCP/bootp message. Set the message type.
 *
 * Returns: the updated options ptr.
 */
static uint8_t *
init_msg(PKT *pkt, uint8_t *pkttype)
{
	static uint32_t xid;

	bzero(pkt, dhcp_buf_size);
	bcopy(magic, pkt->cookie, sizeof (pkt->cookie));
	pkt->op = BOOTREQUEST;
	if (xid == 0)
		bcopy(mac_get_addr_buf()+2, &xid, 4);
	else
		xid++;
	pkt->xid = xid;
	bcopy(pkttype, pkt->options, 3);
	return ((uint8_t *)(pkt->options + 3));
}

/*
 *  Parameter request list.
 */
static void
parameter_request_list(uint8_t **opt)
{
	/*
	 * 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.
	 */
	static uint8_t	prlist[] = {
	    CD_REQUEST_LIST,	/* parameter request list option number */
	    4,			/* number of options requested */
	    CD_SUBNETMASK,
	    CD_ROUTER,
	    CD_HOSTNAME,
	    CD_VENDOR_SPEC
	    };
	if (opt && *opt) {
		bcopy(prlist, *opt, sizeof (prlist));
		*opt += sizeof (prlist);
	}
}

/*
 * Set hardware specific fields
 */
static void
set_hw_spec_data(PKT *p, uint8_t **opt, uint8_t *pkttype)
{
	char mfg[DHCP_MAX_OPT_SIZE + 1], cbuf[DHCP_MAX_OPT_SIZE + 1];
	uint8_t *tp, *dp;
	int adjust_len, len, i;

	p->htype = mac_arp_type(mac_get_type());
	len = (uchar_t)mac_get_addr_len();
	if (len <= sizeof (p->chaddr)) {
		p->hlen = len;
		bcopy(mac_get_addr_buf(), p->chaddr, len);
	} else {
		uint8_t type = *(pkttype + 2);
		/*
		 * 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
		 * DISCOVER/REQUEST.
		 */
		if ((type == DISCOVER) || (type == REQUEST))
			p->flags = htons(BCAST_MASK);
	}

	if (opt && *opt) {
		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;

			(void) strncpy(mfg, get_mfg_name(), sizeof (mfg));
			if (strncmp(mfg, "SUNW", strlen("SUNW")) != 0) {
				len = strlen("SUNW.");
				(void) strcpy(cbuf, "SUNW.");
			} else {
				len = 0;
				cbuf[0] = '\0';
			}
			len += strlen(mfg);

			if ((len + 2) < DHCP_MAX_OPT_SIZE) {
				tp = (uint8_t *)strcat(cbuf, mfg);
				dp = &dhcp_classid[2];
				adjust_len = 0;
				for (i = 0; i < len; i++, tp++) {
					if (*tp == ',') {
						*dp++ = '.';
					} else if (*tp == ' ') {
						adjust_len++;
					} else {
						*dp++ = *tp;
					}
				}
				len -= adjust_len;
				dhcp_classid[1] = (uint8_t)len;
			} else
				prom_panic("Not enough space for class id");
#ifdef	DHCP_DEBUG
			printf("%s: Classid: %s\n", s_n, &dhcp_classid[2]);
#endif	/* DHCP_DEBUG */
		}
		bcopy(dhcp_classid, *opt, dhcp_classid[1] + 2);
		*opt += dhcp_classid[1] + 2;
	}
}

static void
flush_list(void)
{
	PKT_LIST *wk, *tmp;

	wk = list_hd;
	while (wk != NULL) {
		tmp = wk;
		wk = wk->next;
		bkmem_free((char *)tmp->pkt, tmp->len);
		bkmem_free((char *)tmp, sizeof (PKT_LIST));
	}
	list_hd = list_tl = NULL;
	pkt_counter = 0;
}

static void
remove_list(PKT_LIST *pl, int flag)
{
	if (list_hd == NULL)
		return;

	if (list_hd == list_tl) {
		list_hd = list_tl = NULL;
	} else if (list_hd == pl) {
		list_hd = pl->next;
		list_hd->prev = NULL;
	} else if (list_tl == pl) {
		list_tl = list_tl->prev;
		list_tl->next = NULL;
	} else {
		pl->prev->next = pl->next;
		pl->next->prev = pl->prev;
	}
	pkt_counter--;
	if (flag) {
		bkmem_free((char *)pl->pkt, pl->len);
		bkmem_free((char *)pl, sizeof (PKT_LIST));
	}
}

/*
 * 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.
 *
 * Returns 0 if no error processing packet, 1 if an error occurred and/or
 * collection of replies should stop. Used in inet() calls.
 */
static int
bootp_collect(int len)
{
	PKT		*s = (PKT *)dhcp_snd_bufp;
	PKT		*r = (PKT *)dhcp_rcv_bufp;
	PKT_LIST	*pl;

	if (len < sizeof (PKT)) {
		dprintf("%s: BOOTP reply too small: %d\n", s_n, len);
		return (1);
	}
	if (r->op == BOOTREPLY && r->xid == s->xid &&
	    bcmp((caddr_t)s->chaddr, (caddr_t)r->chaddr, s->hlen) == 0) {
		/* Add a packet to the pkt list */
		if (pkt_counter > (MAX_PKT_LIST - 1))
			return (1);	/* got enough packets already */
		if (((pl = (PKT_LIST *)bkmem_zalloc(sizeof (PKT_LIST))) ==
		    NULL) || ((pl->pkt = (PKT *)bkmem_zalloc(len)) == NULL)) {
			errno = ENOMEM;
			if (pl != NULL)
				bkmem_free((char *)pl, sizeof (PKT_LIST));
			return (1);
		}
		bcopy(dhcp_rcv_bufp, pl->pkt, len);
		pl->len = len;
		if (list_hd == NULL) {
			list_hd = list_tl = pl;
			pl->prev = NULL;
		} else {
			list_tl->next = pl;
			pl->prev = list_tl;
			list_tl = pl;
		}
		pkt_counter++;
		pl->next = NULL;
	}
	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)
{
	PKT	*s = (PKT *)dhcp_snd_bufp;

	if (list_hd != NULL) {
		/* remember the secs - we may need them later */
		dhcp_secs = ntohs(s->secs);
		return (1);
	}
	s->secs = htons((uint16_t)((prom_gettime() - dhcp_start_time)/1000));
	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
 * no reply is expected/returned. If 'tries' is non-zero, then 'tries'
 * 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
inet(uint32_t size, struct in_addr *src, struct in_addr *dest, uint32_t tries,
    uint32_t tol)
{
	int			done = B_FALSE, flags, len;
	uint32_t		attempts = 0;
	int			sd;
	uint32_t		wait_time;	/* Max time collect replies */
	uint32_t		init_timeout;	/* Max time wait ANY reply */
	uint32_t		now;
	struct sockaddr_in	saddr, daddr;

	if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		dprintf("%s: Can't open a socket.\n", s_n);
		return (errno);
	}

	flags = 0;

	bzero(&saddr, sizeof (struct sockaddr_in));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(IPPORT_BOOTPC);
	saddr.sin_addr.s_addr = htonl(src->s_addr);

	if (bind(sd, (struct sockaddr *)&saddr, sizeof (saddr)) < 0) {
		dprintf("%s: Cannot bind to port %d, errno: %d\n",
		    s_n, IPPORT_BOOTPC, errno);
		(void) socket_close(sd);
		return (errno);
	}

	if (ntohl(dest->s_addr) == INADDR_BROADCAST) {
		int dontroute = B_TRUE;
		(void) setsockopt(sd, SOL_SOCKET, SO_DONTROUTE,
		    (const void *)&dontroute, sizeof (dontroute));
	}

	bzero(&daddr, sizeof (struct sockaddr_in));
	daddr.sin_family = AF_INET;
	daddr.sin_port = htons(IPPORT_BOOTPS);
	daddr.sin_addr.s_addr = htonl(dest->s_addr);
	wait_time = prom_gettime() + tol;

	do {
		if (sendto(sd, (char *)dhcp_snd_bufp, size, flags,
		    (struct sockaddr *)&daddr, sizeof (daddr)) < 0) {
			dprintf("%s: sendto failed with errno: %d\n",
			    s_n, errno);
			(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;
		}
		init_timeout = now + timeout;
		wait_time = now + tol;
		do {
			if ((len = recvfrom(sd, (char *)dhcp_rcv_bufp,
			    (int)dhcp_buf_size, MSG_DONTWAIT, NULL,
			    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()) {
			done = B_TRUE;
			break;  /* got the goods */
		}
	} while (++attempts < tries);

	(void) socket_close(sd);

	return (done ? 0 : DHCP_NO_DATA);
}

/*
 * Print the message from the server.
 */
static void
prt_server_msg(DHCP_OPT *p)
{
	int len = p->len;
	char scratch[DHCP_MAX_OPT_SIZE + 1];

	if (len > DHCP_MAX_OPT_SIZE)
		len = DHCP_MAX_OPT_SIZE;
	bcopy(p->value, scratch, len);
	scratch[len] = '\0';
	printf("%s: Message from server: '%s'\n", s_n, scratch);
}

/*
 * 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)
{
	PKT_LIST	*wk, *tk, *best;
	int		err = 0;

	/* Pass one. Scan for options, set appropriate opt field. */
	wk = list_hd;
	while (wk != NULL) {
		if ((err = dhcp_options_scan(wk, B_TRUE)) != 0) {
			/* Garbled Options. Nuke this pkt. */
			if (boothowto & RB_DEBUG) {
				switch (err) {
				case DHCP_WRONG_MSG_TYPE:
					printf("%s: Unexpected DHCP message.\n",
					    s_n);
					break;
				case DHCP_GARBLED_MSG_TYPE:
					printf(
					    "%s: Garbled DHCP message type.\n",
					    s_n);
					break;
				case DHCP_BAD_OPT_OVLD:
					printf("%s: Bad option overload.\n",
					    s_n);
					break;
				}
			}
			tk = wk;
			wk = wk->next;
			remove_list(tk, B_TRUE);
			continue;
		}
		wk = wk->next;
	}

	/*
	 * 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
	 */
	best = NULL;
	for (wk = list_hd; wk != NULL; wk = wk->next) {
		wk->offset = 0;
		if (wk->opts[CD_DHCP_TYPE] &&
		    wk->opts[CD_DHCP_TYPE]->len == 1) {
			if (*wk->opts[CD_DHCP_TYPE]->value != OFFER) {
				dprintf("%s: Unexpected DHCP message."
				    " Expected OFFER message.\n", s_n);
				continue;
			}
			if (!wk->opts[CD_LEASE_TIME]) {
				dprintf("%s: DHCP OFFER message without lease "
				    "time parameter.\n", s_n);
				continue;
			} else {
				if (wk->opts[CD_LEASE_TIME]->len != 4) {
					dprintf("%s: Lease expiration time is "
					    "garbled.\n", s_n);
					continue;
				}
			}
			if (!wk->opts[CD_SERVER_ID]) {
				dprintf("%s: DHCP OFFER message without server "
				    "id parameter.\n", s_n);
				continue;
			} else {
				if (wk->opts[CD_SERVER_ID]->len != 4) {
					dprintf("%s: Server identifier "
					    "parameter is garbled.\n", s_n);
					continue;
				}
			}
			/* Valid DHCP OFFER. See if we got our parameters. */
			dprintf("%s: Found valid DHCP OFFER message.\n", s_n);

			wk->offset += 30;

			/*
			 * 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 (wk->opts[CD_VENDOR_SPEC])
				wk->offset += 80;
		} else
			dprintf("%s: Found valid BOOTP reply.\n", s_n);

		/*
		 * RFC1048 BOOTP?
		 */
		if (bcmp((caddr_t)wk->pkt->cookie, (caddr_t)magic,
		    sizeof (magic)) == 0) {
			wk->offset += 5;
			if (wk->opts[CD_SUBNETMASK])
				wk->offset++;
			if (wk->opts[CD_ROUTER])
				wk->offset++;
			if (wk->opts[CD_HOSTNAME])
				wk->offset += 5;

			/*
			 * Prefer options that have diskless boot significance
			 */
			if (ntohl(wk->pkt->siaddr.s_addr) != INADDR_ANY)
				wk->offset += 10; /* server ip */
			if (wk->opts[CD_OPTION_OVERLOAD] == NULL) {
				if (wk->pkt->sname[0] != '\0')
					wk->offset += 10; /* server name */
				if (wk->pkt->file[0] != '\0')
					wk->offset += 5; /* File to load */
			}
		}
#ifdef	DHCP_DEBUG
		printf("%s: This server configuration has '%d' points.\n", s_n,
		    wk->offset);
#endif	/* DHCP_DEBUG */
		if (!best)
			best = wk;
		else {
			if (best->offset < wk->offset)
				best = wk;
		}
	}
	if (best) {
#ifdef	DHCP_DEBUG
		printf("%s: Found best: points: %d\n", s_n, best->offset);
#endif	/* DHCP_DEBUG */
		remove_list(best, B_FALSE);
	} 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
dhcp_decline(char *msg, PKT_LIST *pl)
{
	PKT		*pkt;
	uint8_t		*opt, ulen;
	int		pkt_size;
	struct in_addr	nets, ours, t_server, t_yiaddr;

	if (pl != NULL) {
		if (pl->opts[CD_SERVER_ID] != NULL &&
		    pl->opts[CD_SERVER_ID]->len == sizeof (struct in_addr)) {
			bcopy(pl->opts[CD_SERVER_ID]->value,
			    &t_server, pl->opts[CD_SERVER_ID]->len);
		} else
			t_server.s_addr = htonl(INADDR_BROADCAST);
		t_yiaddr.s_addr = pl->pkt->yiaddr.s_addr;
	}
	pkt = (PKT *)dhcp_snd_bufp;
	opt = init_msg(pkt, opt_decline);
	set_hw_spec_data(pkt, &opt, opt_decline);

	*opt++ = CD_SERVER_ID;
	*opt++ = sizeof (struct in_addr);
	bcopy(&t_server, opt, sizeof (struct in_addr));
	opt += sizeof (struct in_addr);
	nets.s_addr = htonl(INADDR_BROADCAST);

	/*
	 * Use the given yiaddr in our ciaddr field so server can identify us.
	 */
	pkt->ciaddr.s_addr = t_yiaddr.s_addr;

	ipv4_getipaddr(&ours);	/* Could be 0, could be yiaddr */
	ours.s_addr = htonl(ours.s_addr);

	ulen = (uint8_t)strlen(msg);
	*opt++ = CD_MESSAGE;
	*opt++ = ulen;
	bcopy(msg, opt, ulen);
	opt += ulen;
	*opt++ = CD_END;

	pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
	if (pkt_size < sizeof (PKT))
		pkt_size = sizeof (PKT);

	timeout = 0;	/* reset timeout */
	(void) inet(pkt_size, &ours, &nets, 0, 0L);
}

/*
 * Implementings SELECTING state of DHCP client state machine.
 */
static int
dhcp_selecting(void)
{
	int		pkt_size;
	PKT		*pkt;
	uint8_t		*opt;
	uint16_t	size;
	uint32_t	lease;
	struct in_addr	nets, ours;

	pkt = (PKT *)dhcp_snd_bufp;
	opt = init_msg(pkt, opt_discover);
	pkt->secs = htons((uint16_t)((prom_gettime() - dhcp_start_time)/1000));

	*opt++ = CD_MAX_DHCP_SIZE;
	*opt++ = sizeof (size);
	size = (uint16_t)(dhcp_buf_size - sizeof (struct ip) -
	    sizeof (struct udphdr));
	size = htons(size);
	bcopy(&size, opt, sizeof (size));
	opt += sizeof (size);

	set_hw_spec_data(pkt, &opt, opt_discover);

	*opt++ = CD_LEASE_TIME;
	*opt++ = sizeof (lease);
	lease = htonl(DHCP_PERM);	/* ask for a permanent lease */
	bcopy(&lease, opt, sizeof (lease));
	opt += sizeof (lease);

	/*
	 * 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;
		bcopy(dhcp_clientid, opt, dhcp_clientid_len);
		opt += dhcp_clientid_len;
	}

	parameter_request_list(&opt);

	*opt++ = CD_END;

	pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
	if (pkt_size < sizeof (PKT))
		pkt_size = sizeof (PKT);

	nets.s_addr = INADDR_BROADCAST;
	ours.s_addr = INADDR_ANY;
	timeout = 0;	/* reset timeout */

	return (inet(pkt_size, &ours, &nets, DHCP_RETRIES, DHCP_WAIT));
}

/*
 * implements the REQUESTING state of the DHCP client state machine.
 */
static int
dhcp_requesting(void)
{
	PKT_LIST	*pl, *wk;
	PKT		*pkt, *pl_pkt;
	uint8_t		*opt;
	int		pkt_size, err;
	uint32_t	t_time;
	struct in_addr	nets, ours;
	DHCP_OPT	*doptp;
	uint16_t	size;

	/* here we scan the list of OFFERS, select the best one. */
	state_pl = NULL;

	if ((pl = select_best()) == NULL) {
		dprintf("%s: No valid BOOTP reply or DHCP OFFER was found.\n",
		    s_n);
		return (1);
	}

	pl_pkt = pl->pkt;

	/*
	 * 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.
	 */
	if (pl->opts[CD_DHCP_TYPE] == NULL) {
		if (mac_call_arp(&pl_pkt->yiaddr, NULL, DHCP_ARP_TIMEOUT)) {
			/* Someone responded! go back to SELECTING state. */
			printf("%s: Some host already using BOOTP %s.\n", s_n,
			    inet_ntoa(pl_pkt->yiaddr));
			bkmem_free((char *)pl->pkt, pl->len);
			bkmem_free((char *)pl, sizeof (PKT_LIST));
			return (1);
		}
		state_pl = pl;
		return (0);
	}

	/*
	 * if we got a message from the server, display it.
	 */
	if (pl->opts[CD_MESSAGE])
		prt_server_msg(pl->opts[CD_MESSAGE]);
	/*
	 * 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.
	 */
	pkt = (PKT *)dhcp_snd_bufp;
	opt = init_msg(pkt, opt_request);

	/* Time from Successful DISCOVER message. */
	pkt->secs = htons((uint16_t)dhcp_secs);

	size = (uint16_t)(dhcp_buf_size - sizeof (struct ip) -
	    sizeof (struct udphdr));
	size = htons(size);
	*opt++ = CD_MAX_DHCP_SIZE;
	*opt++ = sizeof (size);
	bcopy(&size, opt, sizeof (size));
	opt += sizeof (size);

	set_hw_spec_data(pkt, &opt, opt_request);
	*opt++ = CD_SERVER_ID;
	*opt++ = pl->opts[CD_SERVER_ID]->len;
	bcopy(pl->opts[CD_SERVER_ID]->value, opt,
	    pl->opts[CD_SERVER_ID]->len);
	opt += pl->opts[CD_SERVER_ID]->len;

	/* our offered lease minus boot time */
	*opt++ = CD_LEASE_TIME;
	*opt++ = 4;
	bcopy(pl->opts[CD_LEASE_TIME]->value, &t_time,
	    sizeof (t_time));
	t_time = ntohl(t_time);
	if ((uint32_t)t_time != DHCP_PERM)
		t_time -= (uint32_t)dhcp_secs;
	t_time = htonl(t_time);
	bcopy(&t_time, opt, sizeof (t_time));
	opt += sizeof (t_time);

	/* our offered IP address, as required. */
	*opt++ = CD_REQUESTED_IP_ADDR;
	*opt++ = sizeof (struct in_addr);
	bcopy(&pl_pkt->yiaddr, opt, sizeof (struct in_addr));
	opt += sizeof (struct in_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;
		bcopy(dhcp_clientid, opt, dhcp_clientid_len);
		opt += dhcp_clientid_len;
	}

	parameter_request_list(&opt);

	*opt++ = CD_END;

	/* Done with the OFFER pkt. */
	bkmem_free((char *)pl->pkt, pl->len);
	bkmem_free((char *)pl, sizeof (PKT_LIST));

	/*
	 * We make 4 attempts here. We wait for 2 seconds to accumulate
	 * requests, just in case a BOOTP server is too fast!
	 */
	pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
	if (pkt_size < sizeof (PKT))
		pkt_size = sizeof (PKT);

	nets.s_addr = INADDR_BROADCAST;
	ours.s_addr = INADDR_ANY;
	timeout = 0;	/* reset timeout */

	if ((err = inet(pkt_size, &ours, &nets, 4, (time_t)2L)) != 0)
		return (err);
	for (wk = list_hd; wk != NULL && state_pl == NULL; wk = wk->next) {
		if (dhcp_options_scan(wk, B_TRUE) != 0 ||
		    !wk->opts[CD_DHCP_TYPE])
			continue;	/* garbled options */
		switch (*wk->opts[CD_DHCP_TYPE]->value) {
		case ACK:
			remove_list(wk, B_FALSE);
			state_pl = wk;
			break;
		case NAK:
			printf("%s: rejected by DHCP server: %s\n",
			    s_n, inet_ntoa(*((struct in_addr *)wk->
			    opts[CD_SERVER_ID]->value)));
			if (wk->opts[CD_MESSAGE])
				prt_server_msg(wk->opts[CD_MESSAGE]);
			break;
		default:
			dprintf("%s: Unexpected DHCP message type.\n", s_n);
			break;
		}
	}
	flush_list();
	if (state_pl != NULL) {
		if (state_pl->opts[CD_MESSAGE])
			prt_server_msg(state_pl->opts[CD_MESSAGE]);
		pl_pkt = state_pl->pkt;
		/* arp our new address, just to make sure */
		if (!mac_call_arp(&pl_pkt->yiaddr, NULL, DHCP_ARP_TIMEOUT)) {
			/*
			 * No response. Check if lease is ok.
			 */
			doptp = state_pl->opts[CD_LEASE_TIME];
			if (state_pl->opts[CD_DHCP_TYPE] && (!doptp ||
			    (doptp->len % 4) != 0)) {
				dhcp_decline("Missing or corrupted lease time",
				    state_pl);
				err++;
			}
		} else {
			dhcp_decline("IP Address already being used!",
			    state_pl);
			err++;
		}
		if (err) {
			bkmem_free((char *)state_pl->pkt, state_pl->len);
			bkmem_free((char *)state_pl, sizeof (PKT_LIST));
			state_pl = NULL;
		} else
			return (0);	/* passes the tests! */
	}
	dprintf("%s: No valid DHCP acknowledge messages received.\n", s_n);
	return (1);
}

/*
 * Implements BOUND state of DHCP client state machine.
 */
static int
dhcp_bound(void)
{
	PKT		*pl_pkt = state_pl->pkt;
	DHCP_OPT	*doptp;
	uint8_t		*tp, *hp;
	int		items, i, fnd, k;
	char		hostname[MAXHOSTNAMELEN+1];
	struct in_addr	subnet, defr, savr, *ipp, xip;

#ifdef	DHCP_DEBUG
	if (dhcp_classid[0] != 0) {
		char 	scratch[DHCP_MAX_OPT_SIZE + 1];

		bcopy(&dhcp_classid[2], scratch, dhcp_classid[1]);
		scratch[dhcp_classid[1]] = '\0';
		printf("Your machine is of the class: '%s'.\n", scratch);
	}
#endif	/* DHCP_DEBUG */

	/* First, set the bare essentials. (IP layer parameters) */

	ipv4_getipaddr(&xip);
	if (xip.s_addr != INADDR_ANY)
		ipp = &xip;
	else {
		ipp = &pl_pkt->yiaddr;
		ipp->s_addr = ntohl(ipp->s_addr);
		ipv4_setipaddr(ipp);
	}

	ipp->s_addr = htonl(ipp->s_addr);

	if (boothowto & RB_VERBOSE)
		printf("%s: IP address is: %s\n", s_n, inet_ntoa(*ipp));

	/*
	 * Ensure that the Boot NFS READ size, if given, is an int16_t.
	 */
	if (state_pl->vs[VS_BOOT_NFS_READSIZE] != NULL) {
		doptp = state_pl->vs[VS_BOOT_NFS_READSIZE];
		if (doptp->len != sizeof (int16_t))
			state_pl->vs[VS_BOOT_NFS_READSIZE] = NULL;
	}

	/*
	 * Set subnetmask. Nice, but not required.
	 */
	if (state_pl->opts[CD_SUBNETMASK] != NULL) {
		doptp = state_pl->opts[CD_SUBNETMASK];
		if (doptp->len != 4)
			state_pl->opts[CD_SUBNETMASK] = NULL;
		else {
			bcopy(doptp->value, &subnet,
			    sizeof (struct in_addr));
			dprintf("%s: Setting netmask to: %s\n", s_n,
			    inet_ntoa(subnet));
			subnet.s_addr = ntohl(subnet.s_addr);
			ipv4_setnetmask(&subnet);
		}
	}

	/*
	 * Set default IP TTL. Nice, but not required.
	 */
	if (state_pl->opts[CD_IPTTL] != NULL) {
		doptp = state_pl->opts[CD_IPTTL];
		if (doptp->len > 1)
			state_pl->opts[CD_IPTTL] = NULL;
		else {
			doptp = state_pl->opts[CD_IPTTL];
			ipv4_setmaxttl(*(uint8_t *)doptp->value);
			dprintf("%s: Setting IP TTL to: %d\n", s_n,
			    *(uint8_t *)doptp->value);
		}
	}

	/*
	 * Set default router. Not required, although we'll know soon
	 * enough...
	 */
	if (state_pl->opts[CD_ROUTER] != NULL) {
		doptp = state_pl->opts[CD_ROUTER];
		if ((doptp->len % 4) != 0) {
			state_pl->opts[CD_ROUTER] = NULL;
		} else {
			if ((hp = (uint8_t *)bkmem_alloc(
			    mac_get_hdr_len())) == NULL) {
				errno = ENOMEM;
				return (-1);
			}
			items = doptp->len / sizeof (struct in_addr);
			tp = doptp->value;
			bcopy(tp, &savr, sizeof (struct in_addr));
			for (i = 0, fnd = 0; i < items; i++) {
				bcopy(tp, &defr, sizeof (struct in_addr));
				for (k = 0, fnd = 0; k < 2 && fnd == 0; k++) {
					fnd = mac_get_arp(&defr, hp,
					    mac_get_hdr_len(),
					    mac_get_arp_timeout());
				}
				if (fnd)
					break;
				dprintf(
				    "%s: Warning: Router %s is unreachable.\n",
				    s_n, inet_ntoa(defr));
				tp += sizeof (struct in_addr);
			}
			bkmem_free((char *)hp, mac_get_hdr_len());

			/*
			 * 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) {
				dprintf(
				    "%s: Warning: Using default router: %s\n",
				    s_n, inet_ntoa(savr));
				defr.s_addr = savr.s_addr;
			}
			/* ipv4_route expects network order IP addresses */
			(void) ipv4_route(IPV4_ADD_ROUTE, RT_DEFAULT, NULL,
			    &defr);
		}
	}

	/*
	 * Set hostname. Not required.
	 */
	if (state_pl->opts[CD_HOSTNAME] != NULL) {
		doptp = state_pl->opts[CD_HOSTNAME];
		i = doptp->len;
		if (i > MAXHOSTNAMELEN)
			i = MAXHOSTNAMELEN;
		bcopy(doptp->value, hostname, i);
		hostname[i] = '\0';
		(void) sethostname(hostname, i);
		if (boothowto & RB_VERBOSE)
			printf("%s: Hostname is %s\n", s_n, hostname);
	}

	/*
	 * 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
create_bootpresponse_bprop(PKT_LIST *pl)
{
	uint_t	buflen;

	if (pl == NULL || bootp_response != NULL)
		return;

	buflen = (pl->len * 2) + 1;	/* extra space for null (1) */
	if ((bootp_response = bkmem_alloc(buflen)) == NULL)
		return;

	if (octet_to_hexascii((uint8_t *)pl->pkt, pl->len, bootp_response,
	    &buflen) != 0) {
		bkmem_free(bootp_response, (pl->len * 2) + 1);
		bootp_response = NULL;
	}

#if defined(__sparc)
	prom_create_encoded_prop("bootp-response", pl->pkt, pl->len,
	    ENCODE_BYTES);
#endif	/* __sparc */
}

/*
 * 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
prom_cached_reply(int cache_present)
{
	PKT_LIST	*pl;
	int	len;
	pnode_t	chosen;
	char	*prop = PROM_BOOT_CACHED;

	chosen = prom_finddevice("/chosen");
	if (chosen == OBP_NONODE || chosen == OBP_BADNODE)
		chosen = prom_nextnode((pnode_t)0);	/* root node */

	if ((len = prom_getproplen(chosen, prop)) <= 0)
		return (B_FALSE);

	if (cache_present)
		return (B_TRUE);

	if (((pl = (PKT_LIST *)bkmem_zalloc(sizeof (PKT_LIST))) == NULL) ||
	    ((pl->pkt = (PKT *)bkmem_zalloc(len)) == NULL)) {
		errno = ENOMEM;
		if (pl != NULL)
			bkmem_free((char *)pl, sizeof (PKT_LIST));
		return (B_FALSE);
	}

	(void) prom_getprop(chosen, prop, (caddr_t)pl->pkt);

	pl->len = len;

	if (dhcp_options_scan(pl, B_TRUE) != 0) {
		/* garbled packet */
		bkmem_free((char *)pl->pkt, pl->len);
		bkmem_free((char *)pl, sizeof (PKT_LIST));
		return (B_FALSE);
	}

	state_pl = pl;
	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;
	int	done = B_FALSE;

	dhcp_buf_size = mac_get_mtu();
	dhcp_snd_bufp = (PKT *)bkmem_alloc(dhcp_buf_size);
	dhcp_rcv_bufp = (PKT *)bkmem_alloc(dhcp_buf_size);

	if (dhcp_snd_bufp == NULL || dhcp_rcv_bufp == NULL) {
		if (dhcp_snd_bufp != NULL)
			bkmem_free((char *)dhcp_snd_bufp, dhcp_buf_size);
		if (dhcp_rcv_bufp != NULL)
			bkmem_free((char *)dhcp_rcv_bufp, dhcp_buf_size);
		errno = ENOMEM;
		return (-1);
	}

	while (err == 0 && !done) {
		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 {
				dhcp_state = SELECTING;
				dhcp_start_time = prom_gettime();
			}
			break;

		case SELECTING:

			s_n = "SELECTING";
			err = dhcp_selecting();
			switch (err) {
			case 0:
				dhcp_state = REQUESTING;
				break;
			case DHCP_NO_DATA:
				dprintf(
				    "%s: No DHCP response after %d tries.\n",
				    s_n, DHCP_RETRIES);
				break;
			default:
				/* major network problems */
				dprintf("%s: Network transaction failed: %d\n",
				    s_n, err);
				break;
			}
			break;

		case REQUESTING:

			s_n = "REQUESTING";
			err = dhcp_requesting();
			switch (err) {
			case 0:
				dhcp_state = BOUND;
				break;
			case DHCP_NO_DATA:
				dprintf("%s: Request timed out.\n", s_n);
				dhcp_state = SELECTING;
				err = 0;
				(void) sleep(10);
				break;
			default:
				/* major network problems */
				dprintf("%s: Network transaction failed: %d\n",
				    s_n, err);
				break;
			}
			break;

		case BOUND:

			/*
			 * We just "give up" if bound state fails.
			 */
			s_n = "BOUND";
			if ((err = dhcp_bound()) == 0) {
				dhcp_state = CONFIGURED;
			}
			break;

		case CONFIGURED:
			s_n = "CONFIGURED";
			create_bootpresponse_bprop(state_pl);
			done = B_TRUE;
			break;
		}
	}
	bkmem_free((char *)dhcp_snd_bufp, dhcp_buf_size);
	bkmem_free((char *)dhcp_rcv_bufp, dhcp_buf_size);

	return (err);
}

/*
 * Returns a copy of the DHCP-supplied value of the parameter requested
 * by code.
 */
boolean_t
dhcp_getinfo(uchar_t optcat, uint16_t code, uint16_t optsize, void *value,
    size_t *vallenp)
{
	size_t		len = *vallenp;

	if (dhcp_getinfo_pl(state_pl, optcat, code,
	    optsize, value, &len)) {

		if (len <= *vallenp) {
			*vallenp = len;
			return (B_TRUE);
		}
	}

	return (B_FALSE);
}

/*
 * Sets the clientid option.
 */
void
dhcp_set_client_id(uint8_t *clientid, uint8_t clientid_len)
{
	bcopy(clientid, dhcp_clientid, clientid_len);
	dhcp_clientid_len = clientid_len;
}