in_dhcp.c   [plain text]


/*
 * Copyright (c) 1988-2011 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 <sys/param.h>
#include <sys/types.h>
#include <mach/boolean.h>
#include <sys/kernel.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/mbuf.h>
#include <sys/vnode.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/uio_internal.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/route.h>
#include <net/dlil.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/ip_var.h>
#include <netinet/udp.h>
#include <netinet/udp_var.h>
#include <netinet/ip_icmp.h>
#include <netinet/bootp.h>
#include <netinet/dhcp.h>
#include <netinet/in_dhcp.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <netinet/dhcp_options.h>

#include <kern/kern_types.h>
#include <kern/kalloc.h>

#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 *)(void *)&ifra.ifra_addr) = blank_sin;
	((struct sockaddr_in *)(void *)&ifra.ifra_addr)->sin_addr = *addr;
    }
    if (mask) {
	*((struct sockaddr_in *)(void *)&ifra.ifra_mask) = blank_sin;
	((struct sockaddr_in *)(void *)&ifra.ifra_mask)->sin_addr = *mask;
    }
    if (broadcast) {
	*((struct sockaddr_in *)(void *)&ifra.ifra_broadaddr) = blank_sin;
	((struct sockaddr_in *)(void *)&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 *)(void *)context->request);
}

static __inline__ struct dhcp *
dhcp_context_reply(struct dhcp_context * context)
{
    return ((struct dhcp *)(void *)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;

    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)
{
    return ((struct sockaddr_dl *)(void *)ifp->if_lladdr->ifa_addr);
}

/*
 * 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, NULL);
}

/*
 * 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(&current_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(&current_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(&current_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);
}