X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/0c530ab8987f0ae6a1a3d9284f40182b88852816..2d21ac55c334faf3a56e5634905ed6987fc787d4:/bsd/netinet/in_dhcp.c?ds=inline diff --git a/bsd/netinet/in_dhcp.c b/bsd/netinet/in_dhcp.c new file mode 100644 index 000000000..c6fdffdd8 --- /dev/null +++ b/bsd/netinet/in_dhcp.c @@ -0,0 +1,962 @@ +/* + * Copyright (c) 1988-2007 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ + +/* + * in_dhcp.c + * - use DHCP to allocate an IP address and get the subnet mask and router + */ + +/* + * Modification History + * + * April 17, 2007 Dieter Siegmund (dieter@apple.com) + * - created based on in_bootp.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef DHCP_DEBUG +#define dprintf(x) printf x; +#else /* !DHCP_DEBUG */ +#define dprintf(x) +#endif /* DHCP_DEBUG */ + +#define INITIAL_WAIT_SECS 2 +#define MAX_WAIT_SECS 64 +#define GATHER_TIME_SECS 4 +#define RAND_TICKS (hz) /* one second */ + +const struct sockaddr_in blank_sin = { + sizeof(struct sockaddr_in), + AF_INET, + 0, + { 0 }, + { 0, 0, 0, 0, 0, 0, 0, 0 } +}; + +__private_extern__ int +inet_aifaddr(struct socket * so, const char * name, + const struct in_addr * addr, + const struct in_addr * mask, + const struct in_addr * broadcast) +{ + struct ifaliasreq ifra; + + bzero(&ifra, sizeof(ifra)); + strlcpy(ifra.ifra_name, name, sizeof(ifra.ifra_name)); + if (addr) { + *((struct sockaddr_in *)&ifra.ifra_addr) = blank_sin; + ((struct sockaddr_in *)&ifra.ifra_addr)->sin_addr = *addr; + } + if (mask) { + *((struct sockaddr_in *)&ifra.ifra_mask) = blank_sin; + ((struct sockaddr_in *)&ifra.ifra_mask)->sin_addr = *mask; + } + if (broadcast) { + *((struct sockaddr_in *)&ifra.ifra_broadaddr) = blank_sin; + ((struct sockaddr_in *)&ifra.ifra_broadaddr)->sin_addr = *broadcast; + } + return (ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, current_proc())); +} + + +struct dhcp_context { + struct ifnet * ifp; + struct sockaddr_dl * dl_p; + struct ifreq ifr; + struct socket * so; + uint8_t request[DHCP_PACKET_MIN]; + dhcpoa_t request_options; + uint8_t reply[DHCP_PAYLOAD_MIN]; + struct timeval start_time; + uint32_t xid; + int max_try; + struct in_addr iaddr; + struct in_addr netmask; + struct in_addr router; + struct in_addr server_id; +}; + +static __inline__ struct dhcp_packet * +dhcp_context_request(struct dhcp_context * context) +{ + return ((struct dhcp_packet *)context->request); +} + +static __inline__ struct dhcp * +dhcp_context_reply(struct dhcp_context * context) +{ + return ((struct dhcp *)context->reply); +} + +struct mbuf * ip_pkt_to_mbuf(caddr_t pkt, int pktsize); + +static int +receive_packet(struct socket * so, void * pp, int psize, + int * actual_size); + +/* ip address formatting macros */ +#define IP_FORMAT "%d.%d.%d.%d" +#define IP_CH(ip) ((const uint8_t *)ip) +#define IP_LIST(ip) IP_CH(ip)[0],IP_CH(ip)[1],IP_CH(ip)[2],IP_CH(ip)[3] + +#define SUGGESTED_LEASE_LENGTH (60 * 60 * 24 * 30 * 3) /* 3 months */ + +static const uint8_t dhcp_params[] = { + dhcptag_subnet_mask_e, + dhcptag_router_e, +}; + +#define N_DHCP_PARAMS (sizeof(dhcp_params) / sizeof(dhcp_params[0])) + +static __inline__ long +random_range(long bottom, long top) +{ + long number = top - bottom + 1; + long range_size = LONG_MAX / number; + return (((long)random()) / range_size + bottom); +} + +static void +init_dhcp_packet_header(struct dhcp_packet * pkt, int pkt_size) +{ + bzero(&pkt->ip, sizeof(pkt->ip)); + bzero(&pkt->udp, sizeof(pkt->udp)); + pkt->ip.ip_v = IPVERSION; + pkt->ip.ip_hl = sizeof(struct ip) >> 2; + pkt->ip.ip_ttl = MAXTTL; + pkt->ip.ip_p = IPPROTO_UDP; + pkt->ip.ip_src.s_addr = 0; + pkt->ip.ip_dst.s_addr = htonl(INADDR_BROADCAST); + pkt->ip.ip_len = htons(pkt_size); + pkt->ip.ip_sum = 0; + pkt->udp.uh_sport = htons(IPPORT_BOOTPC); + pkt->udp.uh_dport = htons(IPPORT_BOOTPS); + pkt->udp.uh_sum = 0; + pkt->udp.uh_ulen = htons(pkt_size - sizeof(pkt->ip)); + return; +} + +/* + * Function: make_dhcp_request + * Purpose: + * Initialize the DHCP-specific parts of the message. + */ +static void +make_dhcp_request(struct dhcp * request, int request_size, + dhcp_msgtype_t msg, + const uint8_t * hwaddr, uint8_t hwtype, int hwlen, + dhcpoa_t * options_p) +{ + uint8_t cid[ETHER_ADDR_LEN + 1]; + uint8_t rfc_magic[RFC_MAGIC_SIZE] = RFC_OPTIONS_MAGIC; + + if (hwlen > (int)sizeof(cid)) { + printf("dhcp: hwlen is %d (> %d), truncating\n", hwlen, + (int)sizeof(cid)); + hwlen = sizeof(cid); + } + bzero(request, request_size); + request->dp_op = BOOTREQUEST; + request->dp_htype = hwtype; + request->dp_hlen = hwlen; + bcopy(hwaddr, request->dp_chaddr, hwlen); + bcopy(rfc_magic, request->dp_options, RFC_MAGIC_SIZE); + dhcpoa_init(options_p, request->dp_options + RFC_MAGIC_SIZE, + request_size - sizeof(struct dhcp) - RFC_MAGIC_SIZE); + /* make the request a dhcp packet */ + dhcpoa_add_dhcpmsg(options_p, msg); + + /* add the list of required parameters */ + dhcpoa_add(options_p, dhcptag_parameter_request_list_e, + N_DHCP_PARAMS, dhcp_params); + + /* add the DHCP client identifier */ + cid[0] = hwtype; + bcopy(hwaddr, cid + 1, hwlen); + dhcpoa_add(options_p, dhcptag_client_identifier_e, hwlen + 1, cid); + + return; +} + +/* + * Function: ip_pkt_to_mbuf + * Purpose: + * Put the given IP packet into an mbuf, calculate the + * IP checksum. + */ +struct mbuf * +ip_pkt_to_mbuf(caddr_t pkt, int pktsize) +{ + struct ip * ip; + struct mbuf * m; + + m = (struct mbuf *)m_devget(pkt, pktsize, 0, NULL, NULL); + if (m == 0) { + printf("dhcp: ip_pkt_to_mbuf: m_devget failed\n"); + return NULL; + } + m->m_flags |= M_BCAST; + /* Compute the checksum */ + ip = mtod(m, struct ip *); + ip->ip_sum = 0; + ip->ip_sum = in_cksum(m, sizeof(struct ip)); + return (m); +} + +static __inline__ u_char * +link_address(struct sockaddr_dl * dl_p) +{ + return (u_char *)(dl_p->sdl_data + dl_p->sdl_nlen); +} + +static __inline__ int +link_address_length(struct sockaddr_dl * dl_p) +{ + return (dl_p->sdl_alen); +} + +static __inline__ void +link_print(struct sockaddr_dl * dl_p) +{ + int i; + +#if 0 + printf("len %d index %d family %d type 0x%x nlen %d alen %d" + " slen %d addr ", dl_p->sdl_len, + dl_p->sdl_index, dl_p->sdl_family, dl_p->sdl_type, + dl_p->sdl_nlen, dl_p->sdl_alen, dl_p->sdl_slen); +#endif + for (i = 0; i < dl_p->sdl_alen; i++) + printf("%s%x", i ? ":" : "", + (link_address(dl_p))[i]); + printf("\n"); + return; +} + +static struct sockaddr_dl * +link_from_ifnet(struct ifnet * ifp) +{ + struct ifaddr * addr; + + ifnet_lock_shared(ifp); + TAILQ_FOREACH(addr, &ifp->if_addrhead, ifa_link) { + if (addr->ifa_addr->sa_family == AF_LINK) { + struct sockaddr_dl * dl_p = (struct sockaddr_dl *)(addr->ifa_addr); + + ifnet_lock_done(ifp); + return (dl_p); + } + } + ifnet_lock_done(ifp); + return (NULL); +} + +/* + * Function: send_packet + * Purpose: + * Send the request directly on the interface, bypassing the routing code. + */ +static int +send_packet(struct ifnet * ifp, struct dhcp_packet * pkt, int pkt_size) +{ + struct mbuf * m; + struct sockaddr_in dest; + + dest = blank_sin; + dest.sin_port = htons(IPPORT_BOOTPS); + dest.sin_addr.s_addr = INADDR_BROADCAST; + m = ip_pkt_to_mbuf((caddr_t)pkt, pkt_size); + return dlil_output(ifp, PF_INET, m, 0, (struct sockaddr *)&dest, 0); +} + +/* + * Function: receive_packet + * Purpose: + * Return a received packet or an error if none available. + */ +static int +receive_packet(struct socket * so, void * pp, int psize, int * actual_size) +{ + uio_t auio; + int error; + int rcvflg; + char uio_buf[ UIO_SIZEOF(1) ]; + + auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ, + &uio_buf[0], sizeof(uio_buf)); + uio_addiov(auio, CAST_USER_ADDR_T(pp), psize); + rcvflg = MSG_WAITALL; + + error = soreceive(so, (struct sockaddr **) 0, auio, 0, 0, &rcvflg); + *actual_size = psize - uio_resid(auio); + return (error); +} + +/* + * Function: dhcp_timeout + * Purpose: + * Wakeup the process waiting for something on a socket. + */ +static void +dhcp_timeout(void * arg) +{ + struct socket * * timer_arg = (struct socket * *)arg; + struct socket * so = *timer_arg; + + dprintf(("dhcp: timeout\n")); + + *timer_arg = NULL; + socket_lock(so, 1); + sowakeup(so, &so->so_rcv); + socket_unlock(so, 1); + return; +} + +/* + * Function: rate_packet + * Purpose: + * Return an integer point rating value for the given dhcp packet. + * If yiaddr non-zero, the packet gets a rating of 1. + * Another point is given if the packet contains the subnet mask, + * and another if the router is present. + */ +#define GOOD_RATING 3 +static __inline__ int +rate_packet(dhcpol_t * options_p) +{ + int len; + int rating = 1; + + if (dhcpol_find(options_p, dhcptag_subnet_mask_e, &len, NULL) != NULL) { + rating++; + } + if (dhcpol_find(options_p, dhcptag_router_e, &len, NULL) != NULL) { + rating++; + } + return (rating); +} + +static dhcp_msgtype_t +get_dhcp_msgtype(dhcpol_t * options_p) +{ + int len; + const uint8_t * opt; + + opt = dhcpol_find(options_p, dhcptag_dhcp_message_type_e, &len, NULL); + if (opt != NULL && len == 1) { + return (*opt); + } + return (dhcp_msgtype_none_e); +} + +static int +dhcp_get_ack(struct dhcp_context * context, int wait_ticks) +{ + int error = 0; + const struct in_addr * ip; + int len; + int n; + struct dhcp * reply; + struct in_addr server_id; + struct socket * timer_arg; + + timer_arg = context->so; + reply = dhcp_context_reply(context); + timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, wait_ticks); + while (1) { + error = receive_packet(context->so, context->reply, + sizeof(context->reply), &n); + if (error == 0) { + dhcp_msgtype_t msg; + dhcpol_t options; + + dprintf(("\ndhcp: received packet length %d\n", n)); + if (n < (int)sizeof(struct dhcp)) { + dprintf(("dhcp: packet is too short %d < %d\n", + n, (int)sizeof(struct dhcp))); + continue; + } + if (ntohl(reply->dp_xid) != context->xid + || bcmp(reply->dp_chaddr, link_address(context->dl_p), + link_address_length(context->dl_p)) != 0) { + /* not for us */ + continue; + } + (void)dhcpol_parse_packet(&options, reply, n); + server_id.s_addr = 0; + ip = (const struct in_addr *) + dhcpol_find(&options, + dhcptag_server_identifier_e, &len, NULL); + if (ip != NULL && len >= (int)sizeof(*ip)) { + server_id = *ip; + } + msg = get_dhcp_msgtype(&options); + if (msg == dhcp_msgtype_nak_e + && server_id.s_addr == context->server_id.s_addr) { + /* server NAK'd us, start over */ + dhcpol_free(&options); + error = EPROTO; + untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg); + break; + } + if (msg != dhcp_msgtype_ack_e + || reply->dp_yiaddr.s_addr == 0 + || reply->dp_yiaddr.s_addr == INADDR_BROADCAST) { + /* ignore the packet */ + goto next_packet; + } + printf("dhcp: received ACK: server " IP_FORMAT + " IP address " IP_FORMAT "\n", + IP_LIST(&server_id), IP_LIST(&reply->dp_yiaddr)); + context->iaddr = reply->dp_yiaddr; + ip = (const struct in_addr *) + dhcpol_find(&options, + dhcptag_subnet_mask_e, &len, NULL); + if (ip != NULL && len >= (int)sizeof(*ip)) { + context->netmask = *ip; + } + ip = (const struct in_addr *) + dhcpol_find(&options, dhcptag_router_e, &len, NULL); + if (ip != NULL && len >= (int)sizeof(*ip)) { + context->router = *ip; + } + dhcpol_free(&options); + untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg); + break; + + next_packet: + dhcpol_free(&options); + } + else if ((error != EWOULDBLOCK)) { + /* if some other error occurred, we're done */ + untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg); + break; + } + else if (timer_arg == NULL) { + /* timed out */ + break; + } + else { + /* wait for a wait to arrive, or a timeout to occur */ + socket_lock(context->so, 1); + error = sbwait(&context->so->so_rcv); + socket_unlock(context->so, 1); + } + } + return (error); +} + +static int +dhcp_select(struct dhcp_context * context) +{ + struct timeval current_time; + int error = 0; + dhcpoa_t * options_p; + struct dhcp_packet * request; + int request_size; + int retry; + int wait_ticks; + + /* format a DHCP Request packet */ + request = dhcp_context_request(context); + options_p = &context->request_options; + + make_dhcp_request(&request->dhcp, DHCP_PAYLOAD_MIN, + dhcp_msgtype_request_e, + link_address(context->dl_p), ARPHRD_ETHER, + link_address_length(context->dl_p), + options_p); + /* insert server identifier and requested ip address */ + dhcpoa_add(options_p, dhcptag_requested_ip_address_e, + sizeof(context->iaddr), &context->iaddr); + dhcpoa_add(options_p, dhcptag_server_identifier_e, + sizeof(context->server_id), &context->server_id); + dhcpoa_add(options_p, dhcptag_end_e, 0, 0); + request_size = sizeof(*request) + RFC_MAGIC_SIZE + + dhcpoa_used(options_p); + if (request_size < (int)sizeof(struct bootp_packet)) { + /* pad out to BOOTP-sized packet */ + request_size = sizeof(struct bootp_packet); + } + init_dhcp_packet_header(request, request_size); + + wait_ticks = INITIAL_WAIT_SECS * hz; +#define SELECT_RETRY_COUNT 3 + for (retry = 0; retry < SELECT_RETRY_COUNT; retry++) { + /* Send the request */ + printf("dhcp: sending REQUEST: server " IP_FORMAT + " IP address " IP_FORMAT "\n", + IP_LIST(&context->server_id), + IP_LIST(&context->iaddr)); + microtime(¤t_time); + request->dhcp.dp_secs + = htons((u_short) + (current_time.tv_sec - context->start_time.tv_sec)); + request->dhcp.dp_xid = htonl(context->xid); +#ifdef RANDOM_IP_ID + request->ip.ip_id = ip_randomid(); +#else + request->ip.ip_id = htons(ip_id++); +#endif + error = send_packet(context->ifp, request, request_size); + if (error != 0) { + printf("dhcp: send_packet failed with %d\n", error); + goto failed; + } + + wait_ticks += random_range(-RAND_TICKS, RAND_TICKS); + dprintf(("dhcp: waiting %d ticks\n", wait_ticks)); + error = dhcp_get_ack(context, wait_ticks); + switch (error) { + case 0: + /* we're done */ + goto done; + case EPROTO: + printf("dhcp: server " IP_FORMAT " send us a NAK\n", + IP_LIST(&context->server_id)); + goto failed; + case EWOULDBLOCK: + break; + default: + dprintf(("dhcp: failed to receive packets: %d\n", error)); + goto failed; + } + wait_ticks *= 2; + if (wait_ticks > (MAX_WAIT_SECS * hz)) + wait_ticks = MAX_WAIT_SECS * hz; + microtime(¤t_time); + } + error = ETIMEDOUT; + goto failed; + + done: + error = 0; + + failed: + return (error); +} + +static int +dhcp_get_offer(struct dhcp_context * context, int wait_ticks) +{ + int error = 0; + int gather_count = 0; + const struct in_addr * ip; + int last_rating = 0; + int len; + int n; + int rating; + struct dhcp * reply; + struct in_addr server_id; + struct socket * timer_arg; + + timer_arg = context->so; + reply = dhcp_context_reply(context); + timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, wait_ticks); + while (1) { + error = receive_packet(context->so, context->reply, + sizeof(context->reply), &n); + if (error == 0) { + dhcpol_t options; + + dprintf(("\ndhcp: received packet length %d\n", n)); + if (n < (int)sizeof(struct dhcp)) { + dprintf(("dhcp: packet is too short %d < %d\n", + n, (int)sizeof(struct dhcp))); + continue; + } + if (ntohl(reply->dp_xid) != context->xid + || reply->dp_yiaddr.s_addr == 0 + || reply->dp_yiaddr.s_addr == INADDR_BROADCAST + || bcmp(reply->dp_chaddr, + link_address(context->dl_p), + link_address_length(context->dl_p)) != 0) { + /* not for us */ + continue; + } + (void)dhcpol_parse_packet(&options, reply, n); + if (get_dhcp_msgtype(&options) != dhcp_msgtype_offer_e) { + /* not an offer */ + goto next_packet; + } + ip = (const struct in_addr *) + dhcpol_find(&options, + dhcptag_server_identifier_e, &len, NULL); + if (ip == NULL || len < (int)sizeof(*ip)) { + /* missing/invalid server identifier */ + goto next_packet; + } + printf("dhcp: received OFFER: server " IP_FORMAT + " IP address " IP_FORMAT "\n", + IP_LIST(ip), IP_LIST(&reply->dp_yiaddr)); + server_id = *ip; + rating = rate_packet(&options); + if (rating > last_rating) { + context->iaddr = reply->dp_yiaddr; + ip = (const struct in_addr *) + dhcpol_find(&options, + dhcptag_subnet_mask_e, &len, NULL); + if (ip != NULL && len >= (int)sizeof(*ip)) { + context->netmask = *ip; + } + ip = (const struct in_addr *) + dhcpol_find(&options, dhcptag_router_e, &len, NULL); + if (ip != NULL && len >= (int)sizeof(*ip)) { + context->router = *ip; + } + context->server_id = server_id; + } + if (rating >= GOOD_RATING) { + dhcpol_free(&options); + /* packet is good enough */ + untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg); + break; + } + if (gather_count == 0) { + untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg); + timer_arg = context->so; + timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, + hz * GATHER_TIME_SECS); + } + gather_count = 1; + next_packet: + dhcpol_free(&options); + } + else if ((error != EWOULDBLOCK)) { + untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg); + break; + } + else if (timer_arg == NULL) { /* timed out */ + if (gather_count != 0) { + dprintf(("dhcp: gathering time has expired\n")); + error = 0; + } + break; + } + else { + socket_lock(context->so, 1); + error = sbwait(&context->so->so_rcv); + socket_unlock(context->so, 1); + } + } + return (error); +} + +/* + * Function: dhcp_init + * Purpose: + * Start in the DHCP INIT state sending DISCOVER's. When we get OFFER's, + * try to select one of them by sending a REQUEST and waiting for an ACK. + */ +static int +dhcp_init(struct dhcp_context * context) +{ + struct timeval current_time; + int error = 0; + uint32_t lease_option = htonl(SUGGESTED_LEASE_LENGTH); + dhcpoa_t * options_p; + struct dhcp_packet * request; + int request_size; + int retry; + int wait_ticks; + + /* remember the time we started */ + microtime(&context->start_time); + current_time = context->start_time; + + request = dhcp_context_request(context); + options_p = &context->request_options; + + retry: + /* format a DHCP DISCOVER packet */ + make_dhcp_request(&request->dhcp, DHCP_PAYLOAD_MIN, + dhcp_msgtype_discover_e, + link_address(context->dl_p), ARPHRD_ETHER, + link_address_length(context->dl_p), + options_p); + /* add the requested lease time */ + dhcpoa_add(options_p, dhcptag_lease_time_e, + sizeof(lease_option), &lease_option); + dhcpoa_add(options_p, dhcptag_end_e, 0, 0); + request_size = sizeof(*request) + RFC_MAGIC_SIZE + + dhcpoa_used(options_p); + if (request_size < (int)sizeof(struct bootp_packet)) { + /* pad out to BOOTP-sized packet */ + request_size = sizeof(struct bootp_packet); + } + init_dhcp_packet_header(request, request_size); + + wait_ticks = INITIAL_WAIT_SECS * hz; + for (retry = 0; retry < context->max_try; retry++) { + /* Send the request */ + printf("dhcp: sending DISCOVER\n"); + request->dhcp.dp_secs + = htons((u_short)(current_time.tv_sec + - context->start_time.tv_sec)); + request->dhcp.dp_xid = htonl(context->xid); +#ifdef RANDOM_IP_ID + request->ip.ip_id = ip_randomid(); +#else + request->ip.ip_id = htons(ip_id++); +#endif + error = send_packet(context->ifp, request, request_size); + if (error != 0) { + printf("dhcp: send_packet failed with %d\n", error); + goto failed; + } + wait_ticks += random_range(-RAND_TICKS, RAND_TICKS); + dprintf(("dhcp: waiting %d ticks\n", wait_ticks)); + error = dhcp_get_offer(context, wait_ticks); + if (error == 0) { + /* send a REQUEST */ + error = dhcp_select(context); + if (error == 0) { + /* we're done !*/ + goto done; + } + if (error != EPROTO && error != ETIMEDOUT) { + /* fatal error */ + dprintf(("dhcp: dhcp_select failed %d\n", error)); + goto failed; + } + /* wait 10 seconds, and try again */ + printf("dhcp: trying again in 10 seconds\n"); + tsleep(&error, PRIBIO, "dhcp_init", 10 * hz); + context->xid++; + goto retry; + } + else if (error != EWOULDBLOCK) { + dprintf(("dhcp: failed to receive packets: %d\n", error)); + goto failed; + } + wait_ticks *= 2; + if (wait_ticks > (MAX_WAIT_SECS * hz)) + wait_ticks = MAX_WAIT_SECS * hz; + microtime(¤t_time); + } + error = ETIMEDOUT; + goto failed; + + done: + error = 0; + + failed: + return (error); +} + +static void +dhcp_context_free(struct dhcp_context * context, struct proc * procp) +{ + if (context == NULL) { + return; + } + if (context->so != NULL) { + int error; + + /* disable reception of DHCP packets before address assignment */ + context->ifr.ifr_intval = 0; + error = ifioctl(context->so, SIOCAUTOADDR, + (caddr_t)&context->ifr, procp); + if (error) { + printf("dhcp: SIOCAUTOADDR failed: %d\n", error); + } + soclose(context->so); + } + kfree(context, sizeof(*context)); + return; +} + +static struct dhcp_context * +dhcp_context_create(struct ifnet * ifp, int max_try, + struct proc * procp, int * error_p) +{ + struct dhcp_context * context = NULL; + struct sockaddr_dl * dl_p; + struct in_addr lo_addr; + struct in_addr lo_mask; + int error; + struct sockaddr_in sin; + + /* get the hardware address from the interface */ + dl_p = link_from_ifnet(ifp); + if (dl_p == NULL) { + printf("dhcp: can't get link address\n"); + error = ENXIO; + goto failed; + } + + printf("dhcp: h/w addr "); + link_print(dl_p); + if (dl_p->sdl_type != IFT_ETHER) { + printf("dhcp: hardware type %d not supported\n", + dl_p->sdl_type); + error = ENXIO; + goto failed; + } + + context = (struct dhcp_context *)kalloc(sizeof(*context)); + if (context == NULL) { + printf("dhcp: failed to allocate context\n"); + error = ENOMEM; + goto failed; + } + bzero(context, sizeof(*context)); + + /* get a socket */ + error = socreate(AF_INET, &context->so, SOCK_DGRAM, 0); + if (error != 0) { + printf("dhcp: socreate failed %d\n", error); + goto failed; + } + + /* assign 127.0.0.1 to lo0 so that the bind will succeed */ + lo_addr.s_addr = htonl(INADDR_LOOPBACK); + lo_mask.s_addr = htonl(IN_CLASSA_NET); + error = inet_aifaddr(context->so, "lo0", &lo_addr, &lo_mask, NULL); + if (error != 0) { + printf("dhcp: assigning loopback address failed %d\n", error); + } + + /* enable reception of DHCP packets before an address is assigned */ + snprintf(context->ifr.ifr_name, + sizeof(context->ifr.ifr_name), "%s%d", ifp->if_name, + ifp->if_unit); + context->ifr.ifr_intval = 1; + + error = ifioctl(context->so, SIOCAUTOADDR, (caddr_t)&context->ifr, procp); + if (error) { + printf("dhcp: SIOCAUTOADDR failed: %d\n", error); + goto failed; + } + dprintf(("dhcp: SIOCAUTOADDR done\n")); + + error = ifioctl(context->so, SIOCPROTOATTACH, (caddr_t)&context->ifr, + procp); + if (error) { + printf("dhcp: SIOCPROTOATTACH failed: %d\n", error); + goto failed; + } + dprintf(("dhcp: SIOCPROTOATTACH done\n")); + + /* bind the socket */ + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_port = htons(IPPORT_BOOTPC); + sin.sin_addr.s_addr = INADDR_ANY; + error = sobind(context->so, (struct sockaddr *)&sin); + if (error) { + printf("dhcp: sobind failed, %d\n", error); + goto failed; + } + + /* make it non-blocking I/O */ + socket_lock(context->so, 1); + context->so->so_state |= SS_NBIO; + socket_unlock(context->so, 1); + + /* save passed-in information */ + context->max_try = max_try; + context->dl_p = dl_p; + context->ifp = ifp; + + /* get a random transaction id */ + context->xid = random(); + + return (context); + + failed: + dhcp_context_free(context, procp); + *error_p = error; + return (NULL); +} + +/* + * Routine: dhcp + * Function: + * Do DHCP over the specified interface to retrieve the IP address, + * subnet mask, and router. + */ +int +dhcp(struct ifnet * ifp, struct in_addr * iaddr_p, int max_try, + struct in_addr * netmask_p, struct in_addr * router_p, + struct proc * procp) +{ + int error = 0; + struct dhcp_context * context; + + context = dhcp_context_create(ifp, max_try, procp, &error); + if (context == NULL) { + return (error); + } + + /* start DHCP in the INIT state */ + error = dhcp_init(context); + if (error == 0) { + *iaddr_p = context->iaddr; + *netmask_p = context->netmask; + *router_p = context->router; + } + dhcp_context_free(context, procp); + return (error); +}