dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / stand / lib / inet / dhcpv4.c
blob89ba033b61faa00c8baa59fed9230bbb4cec7838
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 * Standalone dhcp client.
28 #pragma ident "%Z%%M% %I% %E% SMI"
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>
48 #include "ipv4.h"
49 #include "mac.h"
50 #include <sys/bootdebug.h>
51 #include "dhcpv4.h"
53 static char *s_n = "INIT";
54 static enum DHCPSTATE dhcp_state = INIT;
56 static PKT *dhcp_snd_bufp, *dhcp_rcv_bufp;
57 static int dhcp_buf_size;
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 };
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;
68 static uint32_t dhcp_start_time; /* start time (msecs */
69 static time_t dhcp_secs;
70 static uint32_t timeout; /* timeout in milliseconds */
72 static int pkt_counter;
73 PKT_LIST *list_tl, *list_hd;
74 PKT_LIST *state_pl = NULL;
76 #define PROM_BOOT_CACHED "bootp-response"
77 extern char *bootp_response; /* bootprop.c */
78 extern int pagesize;
81 * Do whatever reset actions/initialization actions are generic for every
82 * DHCP/bootp message. Set the message type.
84 * Returns: the updated options ptr.
86 static uint8_t *
87 init_msg(PKT *pkt, uint8_t *pkttype)
89 static uint32_t xid;
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));
104 * Parameter request list.
106 static void
107 parameter_request_list(uint8_t **opt)
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.
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
122 if (opt && *opt) {
123 bcopy(prlist, *opt, sizeof (prlist));
124 *opt += sizeof (prlist);
129 * Set hardware specific fields
131 static void
132 set_hw_spec_data(PKT *p, uint8_t **opt, uint8_t *pkttype)
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;
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);
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.
152 if ((type == DISCOVER) || (type == REQUEST))
153 p->flags = htons(BCAST_MASK);
156 if (opt && *opt) {
157 if (dhcp_classid[0] == '\0') {
159 * Classids based on mfg name: Commas (,) are
160 * converted to periods (.), and spaces ( ) are removed.
162 dhcp_classid[0] = CD_CLASS_ID;
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';
172 len += strlen(mfg);
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;
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 */
195 bcopy(dhcp_classid, *opt, dhcp_classid[1] + 2);
196 *opt += dhcp_classid[1] + 2;
200 static void
201 flush_list(void)
203 PKT_LIST *wk, *tmp;
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));
212 list_hd = list_tl = NULL;
213 pkt_counter = 0;
216 static void
217 remove_list(PKT_LIST *pl, int flag)
219 if (list_hd == NULL)
220 return;
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;
234 pkt_counter--;
235 if (flag) {
236 bkmem_free((char *)pl->pkt, pl->len);
237 bkmem_free((char *)pl, sizeof (PKT_LIST));
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.
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.
249 static int
250 bootp_collect(int len)
252 PKT *s = (PKT *)dhcp_snd_bufp;
253 PKT *r = (PKT *)dhcp_rcv_bufp;
254 PKT_LIST *pl;
256 if (len < sizeof (PKT)) {
257 dprintf("%s: BOOTP reply too small: %d\n", s_n, len);
258 return (1);
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);
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;
282 pkt_counter++;
283 pl->next = NULL;
285 return (0);
289 * Checks if BOOTP exchange(s) were successful. Returns 1 if they
290 * were, 0 otherwise. Used in inet() calls.
292 static int
293 bootp_success(void)
295 PKT *s = (PKT *)dhcp_snd_bufp;
297 if (list_hd != NULL) {
298 /* remember the secs - we may need them later */
299 dhcp_secs = ntohs(s->secs);
300 return (1);
302 s->secs = htons((uint16_t)((prom_gettime() - dhcp_start_time)/1000));
303 return (0);
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.
314 * Returns 0 for success, errno otherwise.
316 static int
317 inet(uint32_t size, struct in_addr *src, struct in_addr *dest, uint32_t tries,
318 uint32_t tol)
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;
328 if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
329 dprintf("%s: Can't open a socket.\n", s_n);
330 return (errno);
333 flags = 0;
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);
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);
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));
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;
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);
367 if (!tries)
368 break; /* don't bother to check for reply */
370 now = prom_gettime();
371 if (timeout == 0)
372 timeout = 4000;
373 else {
374 timeout <<= 1;
375 if (timeout > 64000)
376 timeout = 64000;
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);
391 if (bootp_collect(len))
392 break; /* Stop collecting */
394 if (tol != 0) {
395 if (wait_time < prom_gettime())
396 break; /* collection timeout */
398 } while (prom_gettime() < init_timeout);
400 if (bootp_success()) {
401 done = B_TRUE;
402 break; /* got the goods */
404 } while (++attempts < tries);
406 (void) socket_close(sd);
408 return (done ? 0 : DHCP_NO_DATA);
412 * Print the message from the server.
414 static void
415 prt_server_msg(DHCP_OPT *p)
417 int len = p->len;
418 char scratch[DHCP_MAX_OPT_SIZE + 1];
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);
428 * This function scans the list of OFFERS, and returns the "best" offer.
429 * The criteria used for determining this is:
431 * The best:
432 * DHCP OFFER (not BOOTP), same client_id as ours, same class_id,
433 * Longest lease, all the options we need.
435 * Not quite as good:
436 * DHCP OFFER, no class_id, short lease, only some of the options we need.
438 * We're really reach'in
439 * BOOTP reply.
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.
445 * NOTE: perhaps this bad server should have a counter associated with it.
447 static PKT_LIST *
448 select_best(void)
450 PKT_LIST *wk, *tk, *best;
451 int err = 0;
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;
475 tk = wk;
476 wk = wk->next;
477 remove_list(tk, B_TRUE);
478 continue;
480 wk = wk->next;
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
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;
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;
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;
527 /* Valid DHCP OFFER. See if we got our parameters. */
528 dprintf("%s: Found valid DHCP OFFER message.\n", s_n);
530 wk->offset += 30;
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.
539 if (wk->opts[CD_VENDOR_SPEC])
540 wk->offset += 80;
541 } else
542 dprintf("%s: Found valid BOOTP reply.\n", s_n);
545 * RFC1048 BOOTP?
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;
558 * Prefer options that have diskless boot significance
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 */
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;
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);
589 flush_list(); /* toss the remaining list */
590 return (best);
594 * Send a decline message to the generator of the DHCPACK.
596 static void
597 dhcp_decline(char *msg, PKT_LIST *pl)
599 PKT *pkt;
600 uint8_t *opt, ulen;
601 int pkt_size;
602 struct in_addr nets, ours, t_server, t_yiaddr;
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;
613 pkt = (PKT *)dhcp_snd_bufp;
614 opt = init_msg(pkt, opt_decline);
615 set_hw_spec_data(pkt, &opt, opt_decline);
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);
624 * Use the given yiaddr in our ciaddr field so server can identify us.
626 pkt->ciaddr.s_addr = t_yiaddr.s_addr;
628 ipv4_getipaddr(&ours); /* Could be 0, could be yiaddr */
629 ours.s_addr = htonl(ours.s_addr);
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;
638 pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
639 if (pkt_size < sizeof (PKT))
640 pkt_size = sizeof (PKT);
642 timeout = 0; /* reset timeout */
643 (void) inet(pkt_size, &ours, &nets, 0, 0L);
647 * Implementings SELECTING state of DHCP client state machine.
649 static int
650 dhcp_selecting(void)
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;
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));
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);
671 set_hw_spec_data(pkt, &opt, opt_discover);
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);
680 * If a clientid was set using dhcp_set_client_id(), add this
681 * to the options.
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;
690 parameter_request_list(&opt);
692 *opt++ = CD_END;
694 pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
695 if (pkt_size < sizeof (PKT))
696 pkt_size = sizeof (PKT);
698 nets.s_addr = INADDR_BROADCAST;
699 ours.s_addr = INADDR_ANY;
700 timeout = 0; /* reset timeout */
702 return (inet(pkt_size, &ours, &nets, DHCP_RETRIES, DHCP_WAIT));
706 * implements the REQUESTING state of the DHCP client state machine.
708 static int
709 dhcp_requesting(void)
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;
720 /* here we scan the list of OFFERS, select the best one. */
721 state_pl = NULL;
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);
729 pl_pkt = pl->pkt;
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.
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);
745 state_pl = pl;
746 return (0);
750 * if we got a message from the server, display it.
752 if (pl->opts[CD_MESSAGE])
753 prt_server_msg(pl->opts[CD_MESSAGE]);
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.
760 pkt = (PKT *)dhcp_snd_bufp;
761 opt = init_msg(pkt, opt_request);
763 /* Time from Successful DISCOVER message. */
764 pkt->secs = htons((uint16_t)dhcp_secs);
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);
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;
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);
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);
800 * If a clientid was set using dhcp_set_client_id(), add this
801 * to the options.
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;
810 parameter_request_list(&opt);
812 *opt++ = CD_END;
814 /* Done with the OFFER pkt. */
815 bkmem_free((char *)pl->pkt, pl->len);
816 bkmem_free((char *)pl, sizeof (PKT_LIST));
819 * We make 4 attempts here. We wait for 2 seconds to accumulate
820 * requests, just in case a BOOTP server is too fast!
822 pkt_size = (uint8_t *)opt - (uint8_t *)pkt;
823 if (pkt_size < sizeof (PKT))
824 pkt_size = sizeof (PKT);
826 nets.s_addr = INADDR_BROADCAST;
827 ours.s_addr = INADDR_ANY;
828 timeout = 0; /* reset timeout */
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;
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)) {
861 * No response. Check if lease is ok.
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++;
870 } else {
871 dhcp_decline("IP Address already being used!",
872 state_pl);
873 err++;
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! */
882 dprintf("%s: No valid DHCP acknowledge messages received.\n", s_n);
883 return (1);
887 * Implements BOUND state of DHCP client state machine.
889 static int
890 dhcp_bound(void)
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;
899 #ifdef DHCP_DEBUG
900 if (dhcp_classid[0] != 0) {
901 char scratch[DHCP_MAX_OPT_SIZE + 1];
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);
907 #endif /* DHCP_DEBUG */
909 /* First, set the bare essentials. (IP layer parameters) */
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);
920 ipp->s_addr = htonl(ipp->s_addr);
922 if (boothowto & RB_VERBOSE)
923 printf("%s: IP address is: %s\n", s_n, inet_ntoa(*ipp));
926 * Ensure that the Boot NFS READ size, if given, is an int16_t.
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;
935 * Set subnetmask. Nice, but not required.
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);
952 * Set default IP TTL. Nice, but not required.
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);
967 * Set default router. Not required, although we'll know soon
968 * enough...
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);
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());
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);
997 bkmem_free((char *)hp, mac_get_hdr_len());
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.
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;
1011 /* ipv4_route expects network order IP addresses */
1012 (void) ipv4_route(IPV4_ADD_ROUTE, RT_DEFAULT, NULL,
1013 &defr);
1018 * Set hostname. Not required.
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);
1033 * We don't care about the lease time.... We can't enforce it anyway.
1035 return (0);
1039 * Convert the DHCPACK into a pure ASCII boot property for use by the kernel
1040 * later in the boot process.
1042 static void
1043 create_bootpresponse_bprop(PKT_LIST *pl)
1045 uint_t buflen;
1047 if (pl == NULL || bootp_response != NULL)
1048 return;
1050 buflen = (pl->len * 2) + 1; /* extra space for null (1) */
1051 if ((bootp_response = bkmem_alloc(buflen)) == NULL)
1052 return;
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;
1063 * Examines /chosen node for "bootp-response" property. If it exists, this
1064 * property is the DHCPACK cached there by the PROM's DHCP implementation.
1066 * If cache_present is B_TRUE, we simply return B_TRUE if the property exists
1067 * w/o decoding it. If cache_present is B_FALSE, we decode the packet and
1068 * use it as our state packet for the jump to BOUND mode. Note that it's good
1069 * enough that the cache exists w/o validation for determining if that's what
1070 * the user intended.
1072 * We'll short-circuit the DHCP exchange by accepting this packet. We build a
1073 * PKT_LIST structure, and copy the bootp-response property value into a
1074 * PKT buffer we allocated. We then scan the PKT for options, and then
1075 * set state_pl to point to it.
1077 * Returns B_TRUE if a packet was cached (and was processed correctly), false
1078 * otherwise. The caller needs to make the state change from SELECTING to
1079 * BOUND upon a B_TRUE return from this function.
1082 prom_cached_reply(int cache_present)
1084 PKT_LIST *pl;
1085 int len;
1086 pnode_t chosen;
1087 char *prop = PROM_BOOT_CACHED;
1089 chosen = prom_finddevice("/chosen");
1090 if (chosen == OBP_NONODE || chosen == OBP_BADNODE)
1091 chosen = prom_nextnode((pnode_t)0); /* root node */
1093 if ((len = prom_getproplen(chosen, prop)) <= 0)
1094 return (B_FALSE);
1096 if (cache_present)
1097 return (B_TRUE);
1099 if (((pl = (PKT_LIST *)bkmem_zalloc(sizeof (PKT_LIST))) == NULL) ||
1100 ((pl->pkt = (PKT *)bkmem_zalloc(len)) == NULL)) {
1101 errno = ENOMEM;
1102 if (pl != NULL)
1103 bkmem_free((char *)pl, sizeof (PKT_LIST));
1104 return (B_FALSE);
1107 (void) prom_getprop(chosen, prop, (caddr_t)pl->pkt);
1109 pl->len = len;
1111 if (dhcp_options_scan(pl, B_TRUE) != 0) {
1112 /* garbled packet */
1113 bkmem_free((char *)pl->pkt, pl->len);
1114 bkmem_free((char *)pl, sizeof (PKT_LIST));
1115 return (B_FALSE);
1118 state_pl = pl;
1119 return (B_TRUE);
1123 * Perform DHCP to acquire what we used to get using rarp/bootparams.
1124 * Returns 0 for success, nonzero otherwise.
1126 * DHCP client state machine.
1129 dhcp(void)
1131 int err = 0;
1132 int done = B_FALSE;
1134 dhcp_buf_size = mac_get_mtu();
1135 dhcp_snd_bufp = (PKT *)bkmem_alloc(dhcp_buf_size);
1136 dhcp_rcv_bufp = (PKT *)bkmem_alloc(dhcp_buf_size);
1138 if (dhcp_snd_bufp == NULL || dhcp_rcv_bufp == NULL) {
1139 if (dhcp_snd_bufp != NULL)
1140 bkmem_free((char *)dhcp_snd_bufp, dhcp_buf_size);
1141 if (dhcp_rcv_bufp != NULL)
1142 bkmem_free((char *)dhcp_rcv_bufp, dhcp_buf_size);
1143 errno = ENOMEM;
1144 return (-1);
1147 while (err == 0 && !done) {
1148 switch (dhcp_state) {
1149 case INIT:
1151 s_n = "INIT";
1152 if (prom_cached_reply(B_FALSE)) {
1153 dprintf("%s: Using PROM cached BOOT reply...\n",
1154 s_n);
1155 dhcp_state = BOUND;
1156 } else {
1157 dhcp_state = SELECTING;
1158 dhcp_start_time = prom_gettime();
1160 break;
1162 case SELECTING:
1164 s_n = "SELECTING";
1165 err = dhcp_selecting();
1166 switch (err) {
1167 case 0:
1168 dhcp_state = REQUESTING;
1169 break;
1170 case DHCP_NO_DATA:
1171 dprintf(
1172 "%s: No DHCP response after %d tries.\n",
1173 s_n, DHCP_RETRIES);
1174 break;
1175 default:
1176 /* major network problems */
1177 dprintf("%s: Network transaction failed: %d\n",
1178 s_n, err);
1179 break;
1181 break;
1183 case REQUESTING:
1185 s_n = "REQUESTING";
1186 err = dhcp_requesting();
1187 switch (err) {
1188 case 0:
1189 dhcp_state = BOUND;
1190 break;
1191 case DHCP_NO_DATA:
1192 dprintf("%s: Request timed out.\n", s_n);
1193 dhcp_state = SELECTING;
1194 err = 0;
1195 (void) sleep(10);
1196 break;
1197 default:
1198 /* major network problems */
1199 dprintf("%s: Network transaction failed: %d\n",
1200 s_n, err);
1201 break;
1203 break;
1205 case BOUND:
1208 * We just "give up" if bound state fails.
1210 s_n = "BOUND";
1211 if ((err = dhcp_bound()) == 0) {
1212 dhcp_state = CONFIGURED;
1214 break;
1216 case CONFIGURED:
1217 s_n = "CONFIGURED";
1218 create_bootpresponse_bprop(state_pl);
1219 done = B_TRUE;
1220 break;
1223 bkmem_free((char *)dhcp_snd_bufp, dhcp_buf_size);
1224 bkmem_free((char *)dhcp_rcv_bufp, dhcp_buf_size);
1226 return (err);
1230 * Returns a copy of the DHCP-supplied value of the parameter requested
1231 * by code.
1233 boolean_t
1234 dhcp_getinfo(uchar_t optcat, uint16_t code, uint16_t optsize, void *value,
1235 size_t *vallenp)
1237 size_t len = *vallenp;
1239 if (dhcp_getinfo_pl(state_pl, optcat, code,
1240 optsize, value, &len)) {
1242 if (len <= *vallenp) {
1243 *vallenp = len;
1244 return (B_TRUE);
1248 return (B_FALSE);
1252 * Sets the clientid option.
1254 void
1255 dhcp_set_client_id(uint8_t *clientid, uint8_t clientid_len)
1257 bcopy(clientid, dhcp_clientid, clientid_len);
1258 dhcp_clientid_len = clientid_len;