/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipv4.h" #include "mac.h" #include #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; }