xref: /titanic_50/usr/src/stand/lib/inet/dhcpv4.c (revision 87c584d28b917037834264beb248765bd95712f5)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Standalone dhcp client.
26  */
27 
28 #pragma ident	"%Z%%M%	%I%	%E% SMI"
29 
30 #include <sys/types.h>
31 #include <sys/salib.h>
32 #include <sys/bootconf.h>
33 #include <sys/bootcmn.h>
34 #include <sys/socket.h>
35 #include <sys/isa_defs.h>
36 #include <netinet/in.h>
37 #include <netinet/in_systm.h>
38 #include <netinet/inetutil.h>
39 #include <netinet/dhcp.h>
40 #include <netinet/ip.h>
41 #include <netinet/udp.h>
42 #include <dhcp_impl.h>
43 #include <net/if_types.h>
44 #include <sys/promif.h>
45 #include <sys/platnames.h>
46 #include <socket_inet.h>
47 
48 #include "ipv4.h"
49 #include "mac.h"
50 #include <sys/bootdebug.h>
51 #include "dhcpv4.h"
52 
53 static char		*s_n = "INIT";
54 static enum DHCPSTATE 	dhcp_state = INIT;
55 
56 static PKT		*dhcp_snd_bufp, *dhcp_rcv_bufp;
57 static int		dhcp_buf_size;
58 
59 static const uint8_t	magic[] = BOOTMAGIC;	/* RFC1048 */
60 static uint8_t		opt_discover[]  = { CD_DHCP_TYPE, 1, DISCOVER };
61 static uint8_t		opt_request[]   = { CD_DHCP_TYPE, 1, REQUEST };
62 static uint8_t		opt_decline[]   = { CD_DHCP_TYPE, 1, DECLINE };
63 
64 static uint8_t		dhcp_classid[DHCP_MAX_OPT_SIZE + 3];
65 static uint8_t		dhcp_clientid[DHCP_MAX_CID_LEN];
66 static uint8_t		dhcp_clientid_len = 0;
67 
68 static uint32_t		dhcp_start_time;	/* start time (msecs */
69 static time_t		dhcp_secs;
70 static uint32_t		timeout;	/* timeout in milliseconds */
71 
72 static int		pkt_counter;
73 PKT_LIST		*list_tl, *list_hd;
74 PKT_LIST		*state_pl = NULL;
75 
76 #define	PROM_BOOT_CACHED	"bootp-response"
77 extern char	*bootp_response;	/* bootprop.c */
78 extern int	pagesize;
79 
80 /*
81  * Do whatever reset actions/initialization actions are generic for every
82  * DHCP/bootp message. Set the message type.
83  *
84  * Returns: the updated options ptr.
85  */
86 static uint8_t *
87 init_msg(PKT *pkt, uint8_t *pkttype)
88 {
89 	static uint32_t xid;
90 
91 	bzero(pkt, dhcp_buf_size);
92 	bcopy(magic, pkt->cookie, sizeof (pkt->cookie));
93 	pkt->op = BOOTREQUEST;
94 	if (xid == 0)
95 		bcopy(mac_get_addr_buf()+2, &xid, 4);
96 	else
97 		xid++;
98 	pkt->xid = xid;
99 	bcopy(pkttype, pkt->options, 3);
100 	return ((uint8_t *)(pkt->options + 3));
101 }
102 
103 /*
104  *  Parameter request list.
105  */
106 static void
107 parameter_request_list(uint8_t **opt)
108 {
109 	/*
110 	 * This parameter request list is used in the normal 4-packet
111 	 * DHCPDISCOVER/OFFER/REQUEST/ACK exchange; it must not contain
112 	 * CD_REQUESTED_IP_ADDR or CD_LEASE_TIME.
113 	 */
114 	static uint8_t	prlist[] = {
115 	    CD_REQUEST_LIST,	/* parameter request list option number */
116 	    4,			/* number of options requested */
117 	    CD_SUBNETMASK,
118 	    CD_ROUTER,
119 	    CD_HOSTNAME,
120 	    CD_VENDOR_SPEC
121 	    };
122 	if (opt && *opt) {
123 		bcopy(prlist, *opt, sizeof (prlist));
124 		*opt += sizeof (prlist);
125 	}
126 }
127 
128 /*
129  * Set hardware specific fields
130  */
131 static void
132 set_hw_spec_data(PKT *p, uint8_t **opt, uint8_t *pkttype)
133 {
134 	char mfg[DHCP_MAX_OPT_SIZE + 1], cbuf[DHCP_MAX_OPT_SIZE + 1];
135 	uint8_t *tp, *dp;
136 	int adjust_len, len, i;
137 
138 	p->htype = mac_arp_type(mac_get_type());
139 	len = (uchar_t)mac_get_addr_len();
140 	if (len <= sizeof (p->chaddr)) {
141 		p->hlen = len;
142 		bcopy(mac_get_addr_buf(), p->chaddr, len);
143 	} else {
144 		uint8_t type = *(pkttype + 2);
145 		/*
146 		 * The mac address does not fit in the chaddr
147 		 * field, thus it can not be sent to the server,
148 		 * thus server can not unicast the reply. Per
149 		 * RFC 2131 4.4.1, client can set this bit in
150 		 * DISCOVER/REQUEST.
151 		 */
152 		if ((type == DISCOVER) || (type == REQUEST))
153 			p->flags = htons(BCAST_MASK);
154 	}
155 
156 	if (opt && *opt) {
157 		if (dhcp_classid[0] == '\0') {
158 			/*
159 			 * Classids based on mfg name: Commas (,) are
160 			 * converted to periods (.), and spaces ( ) are removed.
161 			 */
162 			dhcp_classid[0] = CD_CLASS_ID;
163 
164 			(void) strncpy(mfg, get_mfg_name(), sizeof (mfg));
165 			if (strncmp(mfg, "SUNW", strlen("SUNW")) != 0) {
166 				len = strlen("SUNW.");
167 				(void) strcpy(cbuf, "SUNW.");
168 			} else {
169 				len = 0;
170 				cbuf[0] = '\0';
171 			}
172 			len += strlen(mfg);
173 
174 			if ((len + 2) < DHCP_MAX_OPT_SIZE) {
175 				tp = (uint8_t *)strcat(cbuf, mfg);
176 				dp = &dhcp_classid[2];
177 				adjust_len = 0;
178 				for (i = 0; i < len; i++, tp++) {
179 					if (*tp == ',') {
180 						*dp++ = '.';
181 					} else if (*tp == ' ') {
182 						adjust_len++;
183 					} else {
184 						*dp++ = *tp;
185 					}
186 				}
187 				len -= adjust_len;
188 				dhcp_classid[1] = (uint8_t)len;
189 			} else
190 				prom_panic("Not enough space for class id");
191 #ifdef	DHCP_DEBUG
192 			printf("%s: Classid: %s\n", s_n, &dhcp_classid[2]);
193 #endif	/* DHCP_DEBUG */
194 		}
195 		bcopy(dhcp_classid, *opt, dhcp_classid[1] + 2);
196 		*opt += dhcp_classid[1] + 2;
197 	}
198 }
199 
200 static void
201 flush_list(void)
202 {
203 	PKT_LIST *wk, *tmp;
204 
205 	wk = list_hd;
206 	while (wk != NULL) {
207 		tmp = wk;
208 		wk = wk->next;
209 		bkmem_free((char *)tmp->pkt, tmp->len);
210 		bkmem_free((char *)tmp, sizeof (PKT_LIST));
211 	}
212 	list_hd = list_tl = NULL;
213 	pkt_counter = 0;
214 }
215 
216 static void
217 remove_list(PKT_LIST *pl, int flag)
218 {
219 	if (list_hd == NULL)
220 		return;
221 
222 	if (list_hd == list_tl) {
223 		list_hd = list_tl = NULL;
224 	} else if (list_hd == pl) {
225 		list_hd = pl->next;
226 		list_hd->prev = NULL;
227 	} else if (list_tl == pl) {
228 		list_tl = list_tl->prev;
229 		list_tl->next = NULL;
230 	} else {
231 		pl->prev->next = pl->next;
232 		pl->next->prev = pl->prev;
233 	}
234 	pkt_counter--;
235 	if (flag) {
236 		bkmem_free((char *)pl->pkt, pl->len);
237 		bkmem_free((char *)pl, sizeof (PKT_LIST));
238 	}
239 }
240 
241 /*
242  * Collects BOOTP responses. Length has to be right, it has to be
243  * a BOOTP reply pkt, with the same XID and HW address as ours. Adds
244  * them to the pkt list.
245  *
246  * Returns 0 if no error processing packet, 1 if an error occurred and/or
247  * collection of replies should stop. Used in inet() calls.
248  */
249 static int
250 bootp_collect(int len)
251 {
252 	PKT		*s = (PKT *)dhcp_snd_bufp;
253 	PKT		*r = (PKT *)dhcp_rcv_bufp;
254 	PKT_LIST	*pl;
255 
256 	if (len < sizeof (PKT)) {
257 		dprintf("%s: BOOTP reply too small: %d\n", s_n, len);
258 		return (1);
259 	}
260 	if (r->op == BOOTREPLY && r->xid == s->xid &&
261 	    bcmp((caddr_t)s->chaddr, (caddr_t)r->chaddr, s->hlen) == 0) {
262 		/* Add a packet to the pkt list */
263 		if (pkt_counter > (MAX_PKT_LIST - 1))
264 			return (1);	/* got enough packets already */
265 		if (((pl = (PKT_LIST *)bkmem_zalloc(sizeof (PKT_LIST))) ==
266 		    NULL) || ((pl->pkt = (PKT *)bkmem_zalloc(len)) == NULL)) {
267 			errno = ENOMEM;
268 			if (pl != NULL)
269 				bkmem_free((char *)pl, sizeof (PKT_LIST));
270 			return (1);
271 		}
272 		bcopy(dhcp_rcv_bufp, pl->pkt, len);
273 		pl->len = len;
274 		if (list_hd == NULL) {
275 			list_hd = list_tl = pl;
276 			pl->prev = NULL;
277 		} else {
278 			list_tl->next = pl;
279 			pl->prev = list_tl;
280 			list_tl = pl;
281 		}
282 		pkt_counter++;
283 		pl->next = NULL;
284 	}
285 	return (0);
286 }
287 
288 /*
289  * Checks if BOOTP exchange(s) were successful. Returns 1 if they
290  * were, 0 otherwise. Used in inet() calls.
291  */
292 static int
293 bootp_success(void)
294 {
295 	PKT	*s = (PKT *)dhcp_snd_bufp;
296 
297 	if (list_hd != NULL) {
298 		/* remember the secs - we may need them later */
299 		dhcp_secs = ntohs(s->secs);
300 		return (1);
301 	}
302 	s->secs = htons((uint16_t)((prom_gettime() - dhcp_start_time)/1000));
303 	return (0);
304 }
305 
306 /*
307  * This function accesses the network. Opens a connection, and binds to
308  * it if a client binding doesn't already exist. If 'tries' is 0, then
309  * no reply is expected/returned. If 'tries' is non-zero, then 'tries'
310  * attempts are made to get a valid response. If 'tol' is not zero,
311  * then this function will wait for 'tol' milliseconds for more than one
312  * response to a transmit.
313  *
314  * Returns 0 for success, errno otherwise.
315  */
316 static int
317 inet(uint32_t size, struct in_addr *src, struct in_addr *dest, uint32_t tries,
318     uint32_t tol)
319 {
320 	int			done = B_FALSE, flags, len;
321 	uint32_t		attempts = 0;
322 	int			sd;
323 	uint32_t		wait_time;	/* Max time collect replies */
324 	uint32_t		init_timeout;	/* Max time wait ANY reply */
325 	uint32_t		now;
326 	struct sockaddr_in	saddr, daddr;
327 
328 	if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
329 		dprintf("%s: Can't open a socket.\n", s_n);
330 		return (errno);
331 	}
332 
333 	flags = 0;
334 
335 	bzero(&saddr, sizeof (struct sockaddr_in));
336 	saddr.sin_family = AF_INET;
337 	saddr.sin_port = htons(IPPORT_BOOTPC);
338 	saddr.sin_addr.s_addr = htonl(src->s_addr);
339 
340 	if (bind(sd, (struct sockaddr *)&saddr, sizeof (saddr)) < 0) {
341 		dprintf("%s: Cannot bind to port %d, errno: %d\n",
342 		    s_n, IPPORT_BOOTPC, errno);
343 		(void) socket_close(sd);
344 		return (errno);
345 	}
346 
347 	if (ntohl(dest->s_addr) == INADDR_BROADCAST) {
348 		int dontroute = B_TRUE;
349 		(void) setsockopt(sd, SOL_SOCKET, SO_DONTROUTE,
350 		    (const void *)&dontroute, sizeof (dontroute));
351 	}
352 
353 	bzero(&daddr, sizeof (struct sockaddr_in));
354 	daddr.sin_family = AF_INET;
355 	daddr.sin_port = htons(IPPORT_BOOTPS);
356 	daddr.sin_addr.s_addr = htonl(dest->s_addr);
357 	wait_time = prom_gettime() + tol;
358 
359 	do {
360 		if (sendto(sd, (char *)dhcp_snd_bufp, size, flags,
361 		    (struct sockaddr *)&daddr, sizeof (daddr)) < 0) {
362 			dprintf("%s: sendto failed with errno: %d\n",
363 			    s_n, errno);
364 			(void) socket_close(sd);
365 			return (errno);
366 		}
367 		if (!tries)
368 			break;   /* don't bother to check for reply */
369 
370 		now = prom_gettime();
371 		if (timeout == 0)
372 			timeout = 4000;
373 		else {
374 			timeout <<= 1;
375 			if (timeout > 64000)
376 				timeout = 64000;
377 		}
378 		init_timeout = now + timeout;
379 		wait_time = now + tol;
380 		do {
381 			if ((len = recvfrom(sd, (char *)dhcp_rcv_bufp,
382 			    (int)dhcp_buf_size, MSG_DONTWAIT, NULL,
383 			    NULL)) < 0) {
384 				if (errno == EWOULDBLOCK)
385 					continue;	/* DONT WAIT */
386 				(void) socket_close(sd);
387 				flush_list();
388 				return (errno);
389 			}
390 
391 			if (bootp_collect(len))
392 				break;	/* Stop collecting */
393 
394 			if (tol != 0) {
395 				if (wait_time < prom_gettime())
396 					break; /* collection timeout */
397 			}
398 		} while (prom_gettime() < init_timeout);
399 
400 		if (bootp_success()) {
401 			done = B_TRUE;
402 			break;  /* got the goods */
403 		}
404 	} while (++attempts < tries);
405 
406 	(void) socket_close(sd);
407 
408 	return (done ? 0 : DHCP_NO_DATA);
409 }
410 
411 /*
412  * Print the message from the server.
413  */
414 static void
415 prt_server_msg(DHCP_OPT *p)
416 {
417 	int len = p->len;
418 	char scratch[DHCP_MAX_OPT_SIZE + 1];
419 
420 	if (len > DHCP_MAX_OPT_SIZE)
421 		len = DHCP_MAX_OPT_SIZE;
422 	bcopy(p->value, scratch, len);
423 	scratch[len] = '\0';
424 	printf("%s: Message from server: '%s'\n", s_n, scratch);
425 }
426 
427 /*
428  * This function scans the list of OFFERS, and returns the "best" offer.
429  * The criteria used for determining this is:
430  *
431  * The best:
432  * DHCP OFFER (not BOOTP), same client_id as ours, same class_id,
433  * Longest lease, all the options we need.
434  *
435  * Not quite as good:
436  * DHCP OFFER, no class_id, short lease, only some of the options we need.
437  *
438  * We're really reach'in
439  * BOOTP reply.
440  *
441  * DON'T select an offer from a server that gave us a configuration we
442  * couldn't use. Take this server off the "bad" list when this is done.
443  * Next time, we could potentially retry this server's configuration.
444  *
445  * NOTE: perhaps this bad server should have a counter associated with it.
446  */
447 static PKT_LIST *
448 select_best(void)
449 {
450 	PKT_LIST	*wk, *tk, *best;
451 	int		err = 0;
452 
453 	/* Pass one. Scan for options, set appropriate opt field. */
454 	wk = list_hd;
455 	while (wk != NULL) {
456 		if ((err = dhcp_options_scan(wk, B_TRUE)) != 0) {
457 			/* Garbled Options. Nuke this pkt. */
458 			if (boothowto & RB_DEBUG) {
459 				switch (err) {
460 				case DHCP_WRONG_MSG_TYPE:
461 					printf("%s: Unexpected DHCP message.\n",
462 					    s_n);
463 					break;
464 				case DHCP_GARBLED_MSG_TYPE:
465 					printf(
466 					    "%s: Garbled DHCP message type.\n",
467 					    s_n);
468 					break;
469 				case DHCP_BAD_OPT_OVLD:
470 					printf("%s: Bad option overload.\n",
471 					    s_n);
472 					break;
473 				}
474 			}
475 			tk = wk;
476 			wk = wk->next;
477 			remove_list(tk, B_TRUE);
478 			continue;
479 		}
480 		wk = wk->next;
481 	}
482 
483 	/*
484 	 * Pass two. Pick out the best offer. Point system.
485 	 * What's important?
486 	 *	0) DHCP
487 	 *	1) No option overload
488 	 *	2) Encapsulated vendor option
489 	 *	3) Non-null sname and siaddr fields
490 	 *	4) Non-null file field
491 	 *	5) Hostname
492 	 *	6) Subnetmask
493 	 *	7) Router
494 	 */
495 	best = NULL;
496 	for (wk = list_hd; wk != NULL; wk = wk->next) {
497 		wk->offset = 0;
498 		if (wk->opts[CD_DHCP_TYPE] &&
499 		    wk->opts[CD_DHCP_TYPE]->len == 1) {
500 			if (*wk->opts[CD_DHCP_TYPE]->value != OFFER) {
501 				dprintf("%s: Unexpected DHCP message."
502 				    " Expected OFFER message.\n", s_n);
503 				continue;
504 			}
505 			if (!wk->opts[CD_LEASE_TIME]) {
506 				dprintf("%s: DHCP OFFER message without lease "
507 				    "time parameter.\n", s_n);
508 				continue;
509 			} else {
510 				if (wk->opts[CD_LEASE_TIME]->len != 4) {
511 					dprintf("%s: Lease expiration time is "
512 					    "garbled.\n", s_n);
513 					continue;
514 				}
515 			}
516 			if (!wk->opts[CD_SERVER_ID]) {
517 				dprintf("%s: DHCP OFFER message without server "
518 				    "id parameter.\n", s_n);
519 				continue;
520 			} else {
521 				if (wk->opts[CD_SERVER_ID]->len != 4) {
522 					dprintf("%s: Server identifier "
523 					    "parameter is garbled.\n", s_n);
524 					continue;
525 				}
526 			}
527 			/* Valid DHCP OFFER. See if we got our parameters. */
528 			dprintf("%s: Found valid DHCP OFFER message.\n", s_n);
529 
530 			wk->offset += 30;
531 
532 			/*
533 			 * Also could be faked, though more difficult
534 			 * because the encapsulation is hard to encode
535 			 * on a BOOTP server; plus there's not as much
536 			 * real estate in the packet for options, so
537 			 * it's likely this option would get dropped.
538 			 */
539 			if (wk->opts[CD_VENDOR_SPEC])
540 				wk->offset += 80;
541 		} else
542 			dprintf("%s: Found valid BOOTP reply.\n", s_n);
543 
544 		/*
545 		 * RFC1048 BOOTP?
546 		 */
547 		if (bcmp((caddr_t)wk->pkt->cookie, (caddr_t)magic,
548 		    sizeof (magic)) == 0) {
549 			wk->offset += 5;
550 			if (wk->opts[CD_SUBNETMASK])
551 				wk->offset++;
552 			if (wk->opts[CD_ROUTER])
553 				wk->offset++;
554 			if (wk->opts[CD_HOSTNAME])
555 				wk->offset += 5;
556 
557 			/*
558 			 * Prefer options that have diskless boot significance
559 			 */
560 			if (ntohl(wk->pkt->siaddr.s_addr) != INADDR_ANY)
561 				wk->offset += 10; /* server ip */
562 			if (wk->opts[CD_OPTION_OVERLOAD] == NULL) {
563 				if (wk->pkt->sname[0] != '\0')
564 					wk->offset += 10; /* server name */
565 				if (wk->pkt->file[0] != '\0')
566 					wk->offset += 5; /* File to load */
567 			}
568 		}
569 #ifdef	DHCP_DEBUG
570 		printf("%s: This server configuration has '%d' points.\n", s_n,
571 		    wk->offset);
572 #endif	/* DHCP_DEBUG */
573 		if (!best)
574 			best = wk;
575 		else {
576 			if (best->offset < wk->offset)
577 				best = wk;
578 		}
579 	}
580 	if (best) {
581 #ifdef	DHCP_DEBUG
582 		printf("%s: Found best: points: %d\n", s_n, best->offset);
583 #endif	/* DHCP_DEBUG */
584 		remove_list(best, B_FALSE);
585 	} else {
586 		dprintf("%s: No valid BOOTP reply or DHCP OFFER was found.\n",
587 		    s_n);
588 	}
589 	flush_list();	/* toss the remaining list */
590 	return (best);
591 }
592 
593 /*
594  * Send a decline message to the generator of the DHCPACK.
595  */
596 static void
597 dhcp_decline(char *msg, PKT_LIST *pl)
598 {
599 	PKT		*pkt;
600 	uint8_t		*opt, ulen;
601 	int		pkt_size;
602 	struct in_addr	nets, ours, t_server, t_yiaddr;
603 
604 	if (pl != NULL) {
605 		if (pl->opts[CD_SERVER_ID] != NULL &&
606 		    pl->opts[CD_SERVER_ID]->len == sizeof (struct in_addr)) {
607 			bcopy(pl->opts[CD_SERVER_ID]->value,
608 			    &t_server, pl->opts[CD_SERVER_ID]->len);
609 		} else
610 			t_server.s_addr = htonl(INADDR_BROADCAST);
611 		t_yiaddr.s_addr = pl->pkt->yiaddr.s_addr;
612 	}
613 	pkt = (PKT *)dhcp_snd_bufp;
614 	opt = init_msg(pkt, opt_decline);
615 	set_hw_spec_data(pkt, &opt, opt_decline);
616 
617 	*opt++ = CD_SERVER_ID;
618 	*opt++ = sizeof (struct in_addr);
619 	bcopy(&t_server, opt, sizeof (struct in_addr));
620 	opt += sizeof (struct in_addr);
621 	nets.s_addr = htonl(INADDR_BROADCAST);
622 
623 	/*
624 	 * Use the given yiaddr in our ciaddr field so server can identify us.
625 	 */
626 	pkt->ciaddr.s_addr = t_yiaddr.s_addr;
627 
628 	ipv4_getipaddr(&ours);	/* Could be 0, could be yiaddr */
629 	ours.s_addr = htonl(ours.s_addr);
630 
631 	ulen = (uint8_t)strlen(msg);
632 	*opt++ = CD_MESSAGE;
633 	*opt++ = ulen;
634 	bcopy(msg, opt, ulen);
635 	opt += ulen;
636 	*opt++ = CD_END;
637 
638 	pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
639 	if (pkt_size < sizeof (PKT))
640 		pkt_size = sizeof (PKT);
641 
642 	timeout = 0;	/* reset timeout */
643 	(void) inet(pkt_size, &ours, &nets, 0, 0L);
644 }
645 
646 /*
647  * Implementings SELECTING state of DHCP client state machine.
648  */
649 static int
650 dhcp_selecting(void)
651 {
652 	int		pkt_size;
653 	PKT		*pkt;
654 	uint8_t		*opt;
655 	uint16_t	size;
656 	uint32_t	lease;
657 	struct in_addr	nets, ours;
658 
659 	pkt = (PKT *)dhcp_snd_bufp;
660 	opt = init_msg(pkt, opt_discover);
661 	pkt->secs = htons((uint16_t)((prom_gettime() - dhcp_start_time)/1000));
662 
663 	*opt++ = CD_MAX_DHCP_SIZE;
664 	*opt++ = sizeof (size);
665 	size = (uint16_t)(dhcp_buf_size - sizeof (struct ip) -
666 	    sizeof (struct udphdr));
667 	size = htons(size);
668 	bcopy(&size, opt, sizeof (size));
669 	opt += sizeof (size);
670 
671 	set_hw_spec_data(pkt, &opt, opt_discover);
672 
673 	*opt++ = CD_LEASE_TIME;
674 	*opt++ = sizeof (lease);
675 	lease = htonl(DHCP_PERM);	/* ask for a permanent lease */
676 	bcopy(&lease, opt, sizeof (lease));
677 	opt += sizeof (lease);
678 
679 	/*
680 	 * If a clientid was set using dhcp_set_client_id(), add this
681 	 * to the options.
682 	 */
683 	if (dhcp_clientid_len > 0) {
684 		*opt++ = CD_CLIENT_ID;
685 		*opt++ = dhcp_clientid_len;
686 		bcopy(dhcp_clientid, opt, dhcp_clientid_len);
687 		opt += dhcp_clientid_len;
688 	}
689 
690 	parameter_request_list(&opt);
691 
692 	*opt++ = CD_END;
693 
694 	pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
695 	if (pkt_size < sizeof (PKT))
696 		pkt_size = sizeof (PKT);
697 
698 	nets.s_addr = INADDR_BROADCAST;
699 	ours.s_addr = INADDR_ANY;
700 	timeout = 0;	/* reset timeout */
701 
702 	return (inet(pkt_size, &ours, &nets, DHCP_RETRIES, DHCP_WAIT));
703 }
704 
705 /*
706  * implements the REQUESTING state of the DHCP client state machine.
707  */
708 static int
709 dhcp_requesting(void)
710 {
711 	PKT_LIST	*pl, *wk;
712 	PKT		*pkt, *pl_pkt;
713 	uint8_t		*opt;
714 	int		pkt_size, err;
715 	uint32_t	t_time;
716 	struct in_addr	nets, ours;
717 	DHCP_OPT	*doptp;
718 	uint16_t	size;
719 
720 	/* here we scan the list of OFFERS, select the best one. */
721 	state_pl = NULL;
722 
723 	if ((pl = select_best()) == NULL) {
724 		dprintf("%s: No valid BOOTP reply or DHCP OFFER was found.\n",
725 		    s_n);
726 		return (1);
727 	}
728 
729 	pl_pkt = pl->pkt;
730 
731 	/*
732 	 * Check to see if we got an OFFER pkt(s). If not, then We only got
733 	 * a response from a BOOTP server. We'll go to the bound state and
734 	 * try to use that configuration.
735 	 */
736 	if (pl->opts[CD_DHCP_TYPE] == NULL) {
737 		if (mac_call_arp(&pl_pkt->yiaddr, NULL, DHCP_ARP_TIMEOUT)) {
738 			/* Someone responded! go back to SELECTING state. */
739 			printf("%s: Some host already using BOOTP %s.\n", s_n,
740 			    inet_ntoa(pl_pkt->yiaddr));
741 			bkmem_free((char *)pl->pkt, pl->len);
742 			bkmem_free((char *)pl, sizeof (PKT_LIST));
743 			return (1);
744 		}
745 		state_pl = pl;
746 		return (0);
747 	}
748 
749 	/*
750 	 * if we got a message from the server, display it.
751 	 */
752 	if (pl->opts[CD_MESSAGE])
753 		prt_server_msg(pl->opts[CD_MESSAGE]);
754 	/*
755 	 * Assemble a DHCPREQUEST, with the ciaddr field set to 0, since we
756 	 * got here from DISCOVER state. Keep secs field the same for relay
757 	 * agents. We start with the DHCPOFFER packet we got, and the
758 	 * options contained in it to make a requested option list.
759 	 */
760 	pkt = (PKT *)dhcp_snd_bufp;
761 	opt = init_msg(pkt, opt_request);
762 
763 	/* Time from Successful DISCOVER message. */
764 	pkt->secs = htons((uint16_t)dhcp_secs);
765 
766 	size = (uint16_t)(dhcp_buf_size - sizeof (struct ip) -
767 	    sizeof (struct udphdr));
768 	size = htons(size);
769 	*opt++ = CD_MAX_DHCP_SIZE;
770 	*opt++ = sizeof (size);
771 	bcopy(&size, opt, sizeof (size));
772 	opt += sizeof (size);
773 
774 	set_hw_spec_data(pkt, &opt, opt_request);
775 	*opt++ = CD_SERVER_ID;
776 	*opt++ = pl->opts[CD_SERVER_ID]->len;
777 	bcopy(pl->opts[CD_SERVER_ID]->value, opt,
778 	    pl->opts[CD_SERVER_ID]->len);
779 	opt += pl->opts[CD_SERVER_ID]->len;
780 
781 	/* our offered lease minus boot time */
782 	*opt++ = CD_LEASE_TIME;
783 	*opt++ = 4;
784 	bcopy(pl->opts[CD_LEASE_TIME]->value, &t_time,
785 	    sizeof (t_time));
786 	t_time = ntohl(t_time);
787 	if ((uint32_t)t_time != DHCP_PERM)
788 		t_time -= (uint32_t)dhcp_secs;
789 	t_time = htonl(t_time);
790 	bcopy(&t_time, opt, sizeof (t_time));
791 	opt += sizeof (t_time);
792 
793 	/* our offered IP address, as required. */
794 	*opt++ = CD_REQUESTED_IP_ADDR;
795 	*opt++ = sizeof (struct in_addr);
796 	bcopy(&pl_pkt->yiaddr, opt, sizeof (struct in_addr));
797 	opt += sizeof (struct in_addr);
798 
799 	/*
800 	 * If a clientid was set using dhcp_set_client_id(), add this
801 	 * to the options.
802 	 */
803 	if (dhcp_clientid_len > 0) {
804 		*opt++ = CD_CLIENT_ID;
805 		*opt++ = dhcp_clientid_len;
806 		bcopy(dhcp_clientid, opt, dhcp_clientid_len);
807 		opt += dhcp_clientid_len;
808 	}
809 
810 	parameter_request_list(&opt);
811 
812 	*opt++ = CD_END;
813 
814 	/* Done with the OFFER pkt. */
815 	bkmem_free((char *)pl->pkt, pl->len);
816 	bkmem_free((char *)pl, sizeof (PKT_LIST));
817 
818 	/*
819 	 * We make 4 attempts here. We wait for 2 seconds to accumulate
820 	 * requests, just in case a BOOTP server is too fast!
821 	 */
822 	pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
823 	if (pkt_size < sizeof (PKT))
824 		pkt_size = sizeof (PKT);
825 
826 	nets.s_addr = INADDR_BROADCAST;
827 	ours.s_addr = INADDR_ANY;
828 	timeout = 0;	/* reset timeout */
829 
830 	if ((err = inet(pkt_size, &ours, &nets, 4, (time_t)2L)) != 0)
831 		return (err);
832 	for (wk = list_hd; wk != NULL && state_pl == NULL; wk = wk->next) {
833 		if (dhcp_options_scan(wk, B_TRUE) != 0 ||
834 		    !wk->opts[CD_DHCP_TYPE])
835 			continue;	/* garbled options */
836 		switch (*wk->opts[CD_DHCP_TYPE]->value) {
837 		case ACK:
838 			remove_list(wk, B_FALSE);
839 			state_pl = wk;
840 			break;
841 		case NAK:
842 			printf("%s: rejected by DHCP server: %s\n",
843 			    s_n, inet_ntoa(*((struct in_addr *)wk->
844 			    opts[CD_SERVER_ID]->value)));
845 			if (wk->opts[CD_MESSAGE])
846 				prt_server_msg(wk->opts[CD_MESSAGE]);
847 			break;
848 		default:
849 			dprintf("%s: Unexpected DHCP message type.\n", s_n);
850 			break;
851 		}
852 	}
853 	flush_list();
854 	if (state_pl != NULL) {
855 		if (state_pl->opts[CD_MESSAGE])
856 			prt_server_msg(state_pl->opts[CD_MESSAGE]);
857 		pl_pkt = state_pl->pkt;
858 		/* arp our new address, just to make sure */
859 		if (!mac_call_arp(&pl_pkt->yiaddr, NULL, DHCP_ARP_TIMEOUT)) {
860 			/*
861 			 * No response. Check if lease is ok.
862 			 */
863 			doptp = state_pl->opts[CD_LEASE_TIME];
864 			if (state_pl->opts[CD_DHCP_TYPE] && (!doptp ||
865 			    (doptp->len % 4) != 0)) {
866 				dhcp_decline("Missing or corrupted lease time",
867 				    state_pl);
868 				err++;
869 			}
870 		} else {
871 			dhcp_decline("IP Address already being used!",
872 			    state_pl);
873 			err++;
874 		}
875 		if (err) {
876 			bkmem_free((char *)state_pl->pkt, state_pl->len);
877 			bkmem_free((char *)state_pl, sizeof (PKT_LIST));
878 			state_pl = NULL;
879 		} else
880 			return (0);	/* passes the tests! */
881 	}
882 	dprintf("%s: No valid DHCP acknowledge messages received.\n", s_n);
883 	return (1);
884 }
885 
886 /*
887  * Implements BOUND state of DHCP client state machine.
888  */
889 static int
890 dhcp_bound(void)
891 {
892 	PKT		*pl_pkt = state_pl->pkt;
893 	DHCP_OPT	*doptp;
894 	uint8_t		*tp, *hp;
895 	int		items, i, fnd, k;
896 	char		hostname[MAXHOSTNAMELEN+1];
897 	struct in_addr	subnet, defr, savr, *ipp, xip;
898 
899 #ifdef	DHCP_DEBUG
900 	if (dhcp_classid[0] != 0) {
901 		char 	scratch[DHCP_MAX_OPT_SIZE + 1];
902 
903 		bcopy(&dhcp_classid[2], scratch, dhcp_classid[1]);
904 		scratch[dhcp_classid[1]] = '\0';
905 		printf("Your machine is of the class: '%s'.\n", scratch);
906 	}
907 #endif	/* DHCP_DEBUG */
908 
909 	/* First, set the bare essentials. (IP layer parameters) */
910 
911 	ipv4_getipaddr(&xip);
912 	if (xip.s_addr != INADDR_ANY)
913 		ipp = &xip;
914 	else {
915 		ipp = &pl_pkt->yiaddr;
916 		ipp->s_addr = ntohl(ipp->s_addr);
917 		ipv4_setipaddr(ipp);
918 	}
919 
920 	ipp->s_addr = htonl(ipp->s_addr);
921 
922 	if (boothowto & RB_VERBOSE)
923 		printf("%s: IP address is: %s\n", s_n, inet_ntoa(*ipp));
924 
925 	/*
926 	 * Ensure that the Boot NFS READ size, if given, is an int16_t.
927 	 */
928 	if (state_pl->vs[VS_BOOT_NFS_READSIZE] != NULL) {
929 		doptp = state_pl->vs[VS_BOOT_NFS_READSIZE];
930 		if (doptp->len != sizeof (int16_t))
931 			state_pl->vs[VS_BOOT_NFS_READSIZE] = NULL;
932 	}
933 
934 	/*
935 	 * Set subnetmask. Nice, but not required.
936 	 */
937 	if (state_pl->opts[CD_SUBNETMASK] != NULL) {
938 		doptp = state_pl->opts[CD_SUBNETMASK];
939 		if (doptp->len != 4)
940 			state_pl->opts[CD_SUBNETMASK] = NULL;
941 		else {
942 			bcopy(doptp->value, &subnet,
943 			    sizeof (struct in_addr));
944 			dprintf("%s: Setting netmask to: %s\n", s_n,
945 			    inet_ntoa(subnet));
946 			subnet.s_addr = ntohl(subnet.s_addr);
947 			ipv4_setnetmask(&subnet);
948 		}
949 	}
950 
951 	/*
952 	 * Set default IP TTL. Nice, but not required.
953 	 */
954 	if (state_pl->opts[CD_IPTTL] != NULL) {
955 		doptp = state_pl->opts[CD_IPTTL];
956 		if (doptp->len > 1)
957 			state_pl->opts[CD_IPTTL] = NULL;
958 		else {
959 			doptp = state_pl->opts[CD_IPTTL];
960 			ipv4_setmaxttl(*(uint8_t *)doptp->value);
961 			dprintf("%s: Setting IP TTL to: %d\n", s_n,
962 			    *(uint8_t *)doptp->value);
963 		}
964 	}
965 
966 	/*
967 	 * Set default router. Not required, although we'll know soon
968 	 * enough...
969 	 */
970 	if (state_pl->opts[CD_ROUTER] != NULL) {
971 		doptp = state_pl->opts[CD_ROUTER];
972 		if ((doptp->len % 4) != 0) {
973 			state_pl->opts[CD_ROUTER] = NULL;
974 		} else {
975 			if ((hp = (uint8_t *)bkmem_alloc(
976 			    mac_get_hdr_len())) == NULL) {
977 				errno = ENOMEM;
978 				return (-1);
979 			}
980 			items = doptp->len / sizeof (struct in_addr);
981 			tp = doptp->value;
982 			bcopy(tp, &savr, sizeof (struct in_addr));
983 			for (i = 0, fnd = 0; i < items; i++) {
984 				bcopy(tp, &defr, sizeof (struct in_addr));
985 				for (k = 0, fnd = 0; k < 2 && fnd == 0; k++) {
986 					fnd = mac_get_arp(&defr, hp,
987 					    mac_get_hdr_len(),
988 					    mac_get_arp_timeout());
989 				}
990 				if (fnd)
991 					break;
992 				dprintf(
993 				    "%s: Warning: Router %s is unreachable.\n",
994 				    s_n, inet_ntoa(defr));
995 				tp += sizeof (struct in_addr);
996 			}
997 			bkmem_free((char *)hp, mac_get_hdr_len());
998 
999 			/*
1000 			 * If fnd is 0, we didn't find a working router. We'll
1001 			 * still try to use first default router. If we got
1002 			 * a bad router address (like not on the same net),
1003 			 * we're hosed anyway.
1004 			 */
1005 			if (!fnd) {
1006 				dprintf(
1007 				    "%s: Warning: Using default router: %s\n",
1008 				    s_n, inet_ntoa(savr));
1009 				defr.s_addr = savr.s_addr;
1010 			}
1011 			/* ipv4_route expects network order IP addresses */
1012 			(void) ipv4_route(IPV4_ADD_ROUTE, RT_DEFAULT, NULL,
1013 			    &defr);
1014 		}
1015 	}
1016 
1017 	/*
1018 	 * Set hostname. Not required.
1019 	 */
1020 	if (state_pl->opts[CD_HOSTNAME] != NULL) {
1021 		doptp = state_pl->opts[CD_HOSTNAME];
1022 		i = doptp->len;
1023 		if (i > MAXHOSTNAMELEN)
1024 			i = MAXHOSTNAMELEN;
1025 		bcopy(doptp->value, hostname, i);
1026 		hostname[i] = '\0';
1027 		(void) sethostname(hostname, i);
1028 		if (boothowto & RB_VERBOSE)
1029 			printf("%s: Hostname is %s\n", s_n, hostname);
1030 	}
1031 
1032 	/*
1033 	 * We don't care about the lease time.... We can't enforce it anyway.
1034 	 */
1035 	return (0);
1036 }
1037 
1038 /*
1039  * Convert the DHCPACK into a pure ASCII boot property for use by the kernel
1040  * later in the boot process.
1041  */
1042 static void
1043 create_bootpresponse_bprop(PKT_LIST *pl)
1044 {
1045 	uint_t	buflen;
1046 
1047 	if (pl == NULL || bootp_response != NULL)
1048 		return;
1049 
1050 	buflen = (pl->len * 2) + 1;	/* extra space for null (1) */
1051 	if ((bootp_response = bkmem_alloc(buflen)) == NULL)
1052 		return;
1053 
1054 	if (octet_to_hexascii((uint8_t *)pl->pkt, pl->len, bootp_response,
1055 	    &buflen) != 0) {
1056 		bkmem_free(bootp_response, (pl->len * 2) + 1);
1057 		bootp_response = NULL;
1058 	}
1059 
1060 #if defined(__sparc)
1061 	prom_create_encoded_prop("bootp-response", pl->pkt, pl->len,
1062 	    ENCODE_BYTES);
1063 #endif	/* __sparc */
1064 }
1065 
1066 /*
1067  * Examines /chosen node for "bootp-response" property. If it exists, this
1068  * property is the DHCPACK cached there by the PROM's DHCP implementation.
1069  *
1070  * If cache_present is B_TRUE, we simply return B_TRUE if the property exists
1071  * w/o decoding it. If cache_present is B_FALSE, we decode the packet and
1072  * use it as our state packet for the jump to BOUND mode. Note that it's good
1073  * enough that the cache exists w/o validation for determining if that's what
1074  * the user intended.
1075  *
1076  * We'll short-circuit the DHCP exchange by accepting this packet. We build a
1077  * PKT_LIST structure, and copy the bootp-response property value into a
1078  * PKT buffer we allocated. We then scan the PKT for options, and then
1079  * set state_pl to point to it.
1080  *
1081  * Returns B_TRUE if a packet was cached (and was processed correctly), false
1082  * otherwise. The caller needs to make the state change from SELECTING to
1083  * BOUND upon a B_TRUE return from this function.
1084  */
1085 int
1086 prom_cached_reply(int cache_present)
1087 {
1088 	PKT_LIST	*pl;
1089 	int	len;
1090 	pnode_t	chosen;
1091 	char	*prop = PROM_BOOT_CACHED;
1092 
1093 	chosen = prom_finddevice("/chosen");
1094 	if (chosen == OBP_NONODE || chosen == OBP_BADNODE)
1095 		chosen = prom_nextnode((pnode_t)0);	/* root node */
1096 
1097 	if ((len = prom_getproplen(chosen, prop)) <= 0)
1098 		return (B_FALSE);
1099 
1100 	if (cache_present)
1101 		return (B_TRUE);
1102 
1103 	if (((pl = (PKT_LIST *)bkmem_zalloc(sizeof (PKT_LIST))) == NULL) ||
1104 	    ((pl->pkt = (PKT *)bkmem_zalloc(len)) == NULL)) {
1105 		errno = ENOMEM;
1106 		if (pl != NULL)
1107 			bkmem_free((char *)pl, sizeof (PKT_LIST));
1108 		return (B_FALSE);
1109 	}
1110 
1111 	(void) prom_getprop(chosen, prop, (caddr_t)pl->pkt);
1112 
1113 	pl->len = len;
1114 
1115 	if (dhcp_options_scan(pl, B_TRUE) != 0) {
1116 		/* garbled packet */
1117 		bkmem_free((char *)pl->pkt, pl->len);
1118 		bkmem_free((char *)pl, sizeof (PKT_LIST));
1119 		return (B_FALSE);
1120 	}
1121 
1122 	state_pl = pl;
1123 	return (B_TRUE);
1124 }
1125 
1126 /*
1127  * Perform DHCP to acquire what we used to get using rarp/bootparams.
1128  * Returns 0 for success, nonzero otherwise.
1129  *
1130  * DHCP client state machine.
1131  */
1132 int
1133 dhcp(void)
1134 {
1135 	int	err = 0;
1136 	int	done = B_FALSE;
1137 
1138 	dhcp_buf_size = mac_get_mtu();
1139 	dhcp_snd_bufp = (PKT *)bkmem_alloc(dhcp_buf_size);
1140 	dhcp_rcv_bufp = (PKT *)bkmem_alloc(dhcp_buf_size);
1141 
1142 	if (dhcp_snd_bufp == NULL || dhcp_rcv_bufp == NULL) {
1143 		if (dhcp_snd_bufp != NULL)
1144 			bkmem_free((char *)dhcp_snd_bufp, dhcp_buf_size);
1145 		if (dhcp_rcv_bufp != NULL)
1146 			bkmem_free((char *)dhcp_rcv_bufp, dhcp_buf_size);
1147 		errno = ENOMEM;
1148 		return (-1);
1149 	}
1150 
1151 	while (err == 0 && !done) {
1152 		switch (dhcp_state) {
1153 		case INIT:
1154 
1155 			s_n = "INIT";
1156 			if (prom_cached_reply(B_FALSE)) {
1157 				dprintf("%s: Using PROM cached BOOT reply...\n",
1158 				    s_n);
1159 				dhcp_state = BOUND;
1160 			} else {
1161 				dhcp_state = SELECTING;
1162 				dhcp_start_time = prom_gettime();
1163 			}
1164 			break;
1165 
1166 		case SELECTING:
1167 
1168 			s_n = "SELECTING";
1169 			err = dhcp_selecting();
1170 			switch (err) {
1171 			case 0:
1172 				dhcp_state = REQUESTING;
1173 				break;
1174 			case DHCP_NO_DATA:
1175 				dprintf(
1176 				    "%s: No DHCP response after %d tries.\n",
1177 				    s_n, DHCP_RETRIES);
1178 				break;
1179 			default:
1180 				/* major network problems */
1181 				dprintf("%s: Network transaction failed: %d\n",
1182 				    s_n, err);
1183 				break;
1184 			}
1185 			break;
1186 
1187 		case REQUESTING:
1188 
1189 			s_n = "REQUESTING";
1190 			err = dhcp_requesting();
1191 			switch (err) {
1192 			case 0:
1193 				dhcp_state = BOUND;
1194 				break;
1195 			case DHCP_NO_DATA:
1196 				dprintf("%s: Request timed out.\n", s_n);
1197 				dhcp_state = SELECTING;
1198 				err = 0;
1199 				(void) sleep(10);
1200 				break;
1201 			default:
1202 				/* major network problems */
1203 				dprintf("%s: Network transaction failed: %d\n",
1204 				    s_n, err);
1205 				break;
1206 			}
1207 			break;
1208 
1209 		case BOUND:
1210 
1211 			/*
1212 			 * We just "give up" if bound state fails.
1213 			 */
1214 			s_n = "BOUND";
1215 			if ((err = dhcp_bound()) == 0) {
1216 				dhcp_state = CONFIGURED;
1217 			}
1218 			break;
1219 
1220 		case CONFIGURED:
1221 			s_n = "CONFIGURED";
1222 			create_bootpresponse_bprop(state_pl);
1223 			done = B_TRUE;
1224 			break;
1225 		}
1226 	}
1227 	bkmem_free((char *)dhcp_snd_bufp, dhcp_buf_size);
1228 	bkmem_free((char *)dhcp_rcv_bufp, dhcp_buf_size);
1229 
1230 	return (err);
1231 }
1232 
1233 /*
1234  * Returns a copy of the DHCP-supplied value of the parameter requested
1235  * by code.
1236  */
1237 boolean_t
1238 dhcp_getinfo(uchar_t optcat, uint16_t code, uint16_t optsize, void *value,
1239     size_t *vallenp)
1240 {
1241 	size_t		len = *vallenp;
1242 
1243 	if (dhcp_getinfo_pl(state_pl, optcat, code,
1244 	    optsize, value, &len)) {
1245 
1246 		if (len <= *vallenp) {
1247 			*vallenp = len;
1248 			return (B_TRUE);
1249 		}
1250 	}
1251 
1252 	return (B_FALSE);
1253 }
1254 
1255 /*
1256  * Sets the clientid option.
1257  */
1258 void
1259 dhcp_set_client_id(uint8_t *clientid, uint8_t clientid_len)
1260 {
1261 	bcopy(clientid, dhcp_clientid, clientid_len);
1262 	dhcp_clientid_len = clientid_len;
1263 }
1264