1/*
2 * Copyright (c) 1988-2011 Apple Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29/*
30 * in_dhcp.c
31 * - use DHCP to allocate an IP address and get the subnet mask and router
32 */
33
34/*
35 * Modification History
36 *
37 * April 17, 2007	Dieter Siegmund	(dieter@apple.com)
38 * - created based on in_bootp.c
39 */
40
41#include <sys/param.h>
42#include <sys/types.h>
43#include <mach/boolean.h>
44#include <sys/kernel.h>
45#include <sys/errno.h>
46#include <sys/file.h>
47#include <sys/uio.h>
48#include <sys/ioctl.h>
49#include <sys/time.h>
50#include <sys/mbuf.h>
51#include <sys/vnode.h>
52#include <sys/socket.h>
53#include <sys/socketvar.h>
54#include <sys/uio_internal.h>
55#include <net/if.h>
56#include <net/if_dl.h>
57#include <net/if_types.h>
58#include <net/route.h>
59#include <net/dlil.h>
60#include <netinet/in.h>
61#include <netinet/in_systm.h>
62#include <netinet/if_ether.h>
63#include <netinet/ip.h>
64#include <netinet/ip_var.h>
65#include <netinet/udp.h>
66#include <netinet/udp_var.h>
67#include <netinet/ip_icmp.h>
68#include <netinet/bootp.h>
69#include <netinet/dhcp.h>
70#include <netinet/in_dhcp.h>
71#include <sys/systm.h>
72#include <sys/malloc.h>
73#include <netinet/dhcp_options.h>
74
75#include <kern/kern_types.h>
76#include <kern/kalloc.h>
77
78#ifdef	DHCP_DEBUG
79#define	dprintf(x) printf x;
80#else	/* !DHCP_DEBUG */
81#define	dprintf(x)
82#endif	/* DHCP_DEBUG */
83
84#define INITIAL_WAIT_SECS		2
85#define MAX_WAIT_SECS			64
86#define GATHER_TIME_SECS		4
87#define RAND_TICKS			(hz)	/* one second */
88
89const struct sockaddr_in blank_sin = {
90    sizeof(struct sockaddr_in),
91    AF_INET,
92    0,
93    { 0 },
94    { 0, 0, 0, 0, 0, 0, 0, 0 }
95};
96
97__private_extern__ int
98inet_aifaddr(struct socket * so, const char * name,
99	     const struct in_addr * addr,
100	     const struct in_addr * mask,
101	     const struct in_addr * broadcast)
102{
103    struct ifaliasreq	ifra;
104
105    bzero(&ifra, sizeof(ifra));
106    strlcpy(ifra.ifra_name, name, sizeof(ifra.ifra_name));
107    if (addr) {
108	*((struct sockaddr_in *)(void *)&ifra.ifra_addr) = blank_sin;
109	((struct sockaddr_in *)(void *)&ifra.ifra_addr)->sin_addr = *addr;
110    }
111    if (mask) {
112	*((struct sockaddr_in *)(void *)&ifra.ifra_mask) = blank_sin;
113	((struct sockaddr_in *)(void *)&ifra.ifra_mask)->sin_addr = *mask;
114    }
115    if (broadcast) {
116	*((struct sockaddr_in *)(void *)&ifra.ifra_broadaddr) = blank_sin;
117	((struct sockaddr_in *)(void *)&ifra.ifra_broadaddr)->sin_addr = *broadcast;
118    }
119    return (ifioctl(so, SIOCAIFADDR, (caddr_t)&ifra, current_proc()));
120}
121
122
123struct dhcp_context {
124    struct ifnet *		ifp;
125    struct sockaddr_dl *	dl_p;
126    struct ifreq 		ifr;
127    struct socket *		so;
128    uint8_t			request[DHCP_PACKET_MIN];
129    dhcpoa_t			request_options;
130    uint8_t			reply[DHCP_PAYLOAD_MIN];
131    struct timeval		start_time;
132    uint32_t			xid;
133    int				max_try;
134    struct in_addr  		iaddr;
135    struct in_addr  		netmask;
136    struct in_addr 		router;
137    struct in_addr		server_id;
138};
139
140static __inline__ struct dhcp_packet *
141dhcp_context_request(struct dhcp_context * context)
142{
143    return ((struct dhcp_packet *)(void *)context->request);
144}
145
146static __inline__ struct dhcp *
147dhcp_context_reply(struct dhcp_context * context)
148{
149    return ((struct dhcp *)(void *)context->reply);
150}
151
152struct mbuf * ip_pkt_to_mbuf(caddr_t pkt, int pktsize);
153
154static int
155receive_packet(struct socket * so, void * pp, int psize,
156	       int * actual_size);
157
158/* ip address formatting macros */
159#define IP_FORMAT	"%d.%d.%d.%d"
160#define IP_CH(ip)	((const uint8_t *)ip)
161#define IP_LIST(ip)	IP_CH(ip)[0],IP_CH(ip)[1],IP_CH(ip)[2],IP_CH(ip)[3]
162
163#define SUGGESTED_LEASE_LENGTH		(60 * 60 * 24 * 30 * 3) /* 3 months */
164
165static const uint8_t dhcp_params[] = {
166    dhcptag_subnet_mask_e,
167    dhcptag_router_e,
168};
169
170#define	N_DHCP_PARAMS 	(sizeof(dhcp_params) / sizeof(dhcp_params[0]))
171
172static __inline__ long
173random_range(long bottom, long top)
174{
175    long number = top - bottom + 1;
176    long range_size = LONG_MAX / number;
177    return (((long)random()) / range_size + bottom);
178}
179
180static void
181init_dhcp_packet_header(struct dhcp_packet * pkt, int pkt_size)
182{
183    bzero(&pkt->ip, sizeof(pkt->ip));
184    bzero(&pkt->udp, sizeof(pkt->udp));
185    pkt->ip.ip_v = IPVERSION;
186    pkt->ip.ip_hl = sizeof(struct ip) >> 2;
187    pkt->ip.ip_ttl = MAXTTL;
188    pkt->ip.ip_p = IPPROTO_UDP;
189    pkt->ip.ip_src.s_addr = 0;
190    pkt->ip.ip_dst.s_addr = htonl(INADDR_BROADCAST);
191    pkt->ip.ip_len = htons(pkt_size);
192    pkt->ip.ip_sum = 0;
193    pkt->udp.uh_sport = htons(IPPORT_BOOTPC);
194    pkt->udp.uh_dport = htons(IPPORT_BOOTPS);
195    pkt->udp.uh_sum = 0;
196    pkt->udp.uh_ulen = htons(pkt_size - sizeof(pkt->ip));
197    return;
198}
199
200/*
201 * Function: make_dhcp_request
202 * Purpose:
203 *   Initialize the DHCP-specific parts of the message.
204 */
205static void
206make_dhcp_request(struct dhcp * request, int request_size,
207		  dhcp_msgtype_t msg,
208		  const uint8_t * hwaddr, uint8_t hwtype, int hwlen,
209		  dhcpoa_t * options_p)
210{
211    uint8_t		cid[ETHER_ADDR_LEN + 1];
212    uint8_t		rfc_magic[RFC_MAGIC_SIZE] = RFC_OPTIONS_MAGIC;
213
214    if (hwlen > (int)sizeof(cid)) {
215	printf("dhcp: hwlen is %d (> %d), truncating\n", hwlen,
216	       (int)sizeof(cid));
217	hwlen = sizeof(cid);
218    }
219    bzero(request, request_size);
220    request->dp_op = BOOTREQUEST;
221    request->dp_htype = hwtype;
222    request->dp_hlen = hwlen;
223    bcopy(hwaddr, request->dp_chaddr, hwlen);
224    bcopy(rfc_magic, request->dp_options, RFC_MAGIC_SIZE);
225    dhcpoa_init(options_p, request->dp_options + RFC_MAGIC_SIZE,
226		request_size - sizeof(struct dhcp) - RFC_MAGIC_SIZE);
227    /* make the request a dhcp packet */
228    dhcpoa_add_dhcpmsg(options_p, msg);
229
230    /* add the list of required parameters */
231    dhcpoa_add(options_p, dhcptag_parameter_request_list_e,
232	       N_DHCP_PARAMS, dhcp_params);
233
234    /* add the DHCP client identifier */
235    cid[0] = hwtype;
236    bcopy(hwaddr, cid + 1, hwlen);
237    dhcpoa_add(options_p, dhcptag_client_identifier_e, hwlen + 1, cid);
238
239    return;
240}
241
242/*
243 * Function: ip_pkt_to_mbuf
244 * Purpose:
245 *   Put the given IP packet into an mbuf, calculate the
246 *   IP checksum.
247 */
248struct mbuf *
249ip_pkt_to_mbuf(caddr_t pkt, int pktsize)
250{
251    struct ip *		ip;
252    struct mbuf	*	m;
253
254    m = (struct mbuf *)m_devget(pkt, pktsize, 0, NULL, NULL);
255    if (m == 0) {
256	printf("dhcp: ip_pkt_to_mbuf: m_devget failed\n");
257	return NULL;
258    }
259    m->m_flags |= M_BCAST;
260    /* Compute the checksum */
261    ip = mtod(m, struct ip *);
262    ip->ip_sum = 0;
263    ip->ip_sum = in_cksum(m, sizeof(struct ip));
264    return (m);
265}
266
267static __inline__ u_char *
268link_address(struct sockaddr_dl * dl_p)
269{
270    return (u_char *)(dl_p->sdl_data + dl_p->sdl_nlen);
271}
272
273static __inline__ int
274link_address_length(struct sockaddr_dl * dl_p)
275{
276    return (dl_p->sdl_alen);
277}
278
279static __inline__ void
280link_print(struct sockaddr_dl * dl_p)
281{
282    int i;
283
284    for (i = 0; i < dl_p->sdl_alen; i++)
285	printf("%s%x", i ? ":" : "",
286	       (link_address(dl_p))[i]);
287    printf("\n");
288    return;
289}
290
291static struct sockaddr_dl *
292link_from_ifnet(struct ifnet * ifp)
293{
294    return ((struct sockaddr_dl *)(void *)ifp->if_lladdr->ifa_addr);
295}
296
297/*
298 * Function: send_packet
299 * Purpose:
300 *     Send the request directly on the interface, bypassing the routing code.
301 */
302static int
303send_packet(struct ifnet * ifp, struct dhcp_packet * pkt, int pkt_size)
304{
305    struct mbuf	*	m;
306    struct sockaddr_in	dest;
307
308    dest = blank_sin;
309    dest.sin_port = htons(IPPORT_BOOTPS);
310    dest.sin_addr.s_addr = INADDR_BROADCAST;
311    m = ip_pkt_to_mbuf((caddr_t)pkt, pkt_size);
312    return dlil_output(ifp, PF_INET, m, 0, (struct sockaddr *)&dest, 0, NULL);
313}
314
315/*
316 * Function: receive_packet
317 * Purpose:
318 *   Return a received packet or an error if none available.
319 */
320static int
321receive_packet(struct socket * so, void * pp, int psize, int * actual_size)
322{
323    uio_t	auio;
324    int		error;
325    int		rcvflg;
326    char	uio_buf[ UIO_SIZEOF(1) ];
327
328    auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ,
329				&uio_buf[0], sizeof(uio_buf));
330    uio_addiov(auio, CAST_USER_ADDR_T(pp), psize);
331    rcvflg = MSG_WAITALL;
332
333    error = soreceive(so, (struct sockaddr **) 0, auio, 0, 0, &rcvflg);
334    *actual_size = psize - uio_resid(auio);
335    return (error);
336}
337
338/*
339 * Function: dhcp_timeout
340 * Purpose:
341 *   Wakeup the process waiting for something on a socket.
342 */
343static void
344dhcp_timeout(void * arg)
345{
346    struct socket * * timer_arg = (struct socket * *)arg;
347    struct socket * so = *timer_arg;
348
349    dprintf(("dhcp: timeout\n"));
350
351    *timer_arg = NULL;
352    socket_lock(so, 1);
353    sowakeup(so, &so->so_rcv);
354    socket_unlock(so, 1);
355    return;
356}
357
358/*
359 * Function: rate_packet
360 * Purpose:
361 *   Return an integer point rating value for the given dhcp packet.
362 *   If yiaddr non-zero, the packet gets a rating of 1.
363 *   Another point is given if the packet contains the subnet mask,
364 *   and another if the router is present.
365 */
366#define GOOD_RATING	3
367static __inline__ int
368rate_packet(dhcpol_t * options_p)
369{
370    int		len;
371    int 	rating = 1;
372
373    if (dhcpol_find(options_p, dhcptag_subnet_mask_e, &len, NULL) != NULL) {
374	rating++;
375    }
376    if (dhcpol_find(options_p, dhcptag_router_e, &len, NULL) != NULL) {
377	rating++;
378    }
379    return (rating);
380}
381
382static dhcp_msgtype_t
383get_dhcp_msgtype(dhcpol_t * options_p)
384{
385    int				len;
386    const uint8_t * 		opt;
387
388    opt = dhcpol_find(options_p, dhcptag_dhcp_message_type_e, &len, NULL);
389    if (opt != NULL && len == 1) {
390	return (*opt);
391    }
392    return (dhcp_msgtype_none_e);
393}
394
395static int
396dhcp_get_ack(struct dhcp_context * context, int wait_ticks)
397{
398    int				error = 0;
399    const struct in_addr * 	ip;
400    int				len;
401    int				n;
402    struct dhcp *		reply;
403    struct in_addr		server_id;
404    struct socket * 		timer_arg;
405
406    timer_arg = context->so;
407    reply = dhcp_context_reply(context);
408    timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, wait_ticks);
409    while (1) {
410	error = receive_packet(context->so, context->reply,
411			       sizeof(context->reply), &n);
412	if (error == 0) {
413	    dhcp_msgtype_t	msg;
414	    dhcpol_t		options;
415
416	    dprintf(("\ndhcp: received packet length %d\n", n));
417	    if (n < (int)sizeof(struct dhcp)) {
418		dprintf(("dhcp: packet is too short %d < %d\n",
419			 n, (int)sizeof(struct dhcp)));
420		continue;
421	    }
422	    if (ntohl(reply->dp_xid) != context->xid
423		|| bcmp(reply->dp_chaddr, link_address(context->dl_p),
424			link_address_length(context->dl_p)) != 0) {
425		/* not for us */
426		continue;
427	    }
428	    (void)dhcpol_parse_packet(&options, reply, n);
429	    server_id.s_addr = 0;
430	    ip = (const struct in_addr *)
431		dhcpol_find(&options,
432			    dhcptag_server_identifier_e, &len, NULL);
433	    if (ip != NULL && len >= (int)sizeof(*ip)) {
434		server_id = *ip;
435	    }
436	    msg = get_dhcp_msgtype(&options);
437	    if (msg == dhcp_msgtype_nak_e
438		&& server_id.s_addr == context->server_id.s_addr) {
439		/* server NAK'd us, start over */
440		dhcpol_free(&options);
441		error = EPROTO;
442		untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
443		break;
444	    }
445	    if (msg != dhcp_msgtype_ack_e
446		|| reply->dp_yiaddr.s_addr == 0
447		|| reply->dp_yiaddr.s_addr == INADDR_BROADCAST) {
448		/* ignore the packet */
449		goto next_packet;
450	    }
451	    printf("dhcp: received ACK: server " IP_FORMAT
452		   " IP address "  IP_FORMAT "\n",
453		   IP_LIST(&server_id), IP_LIST(&reply->dp_yiaddr));
454	    context->iaddr = reply->dp_yiaddr;
455	    ip = (const struct in_addr *)
456		dhcpol_find(&options,
457			    dhcptag_subnet_mask_e, &len, NULL);
458	    if (ip != NULL && len >= (int)sizeof(*ip)) {
459		context->netmask = *ip;
460	    }
461	    ip = (const struct in_addr *)
462		dhcpol_find(&options, dhcptag_router_e, &len, NULL);
463	    if (ip != NULL && len >= (int)sizeof(*ip)) {
464		context->router = *ip;
465	    }
466	    dhcpol_free(&options);
467	    untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
468	    break;
469
470	next_packet:
471	    dhcpol_free(&options);
472	}
473	else if ((error != EWOULDBLOCK)) {
474	    /* if some other error occurred, we're done */
475	    untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
476	    break;
477	}
478	else if (timer_arg == NULL) {
479	    /* timed out */
480	    break;
481	}
482	else {
483	    /* wait for a wait to arrive, or a timeout to occur */
484	    socket_lock(context->so, 1);
485	    error = sbwait(&context->so->so_rcv);
486	    socket_unlock(context->so, 1);
487	}
488    }
489    return (error);
490}
491
492static int
493dhcp_select(struct dhcp_context * context)
494{
495    struct timeval		current_time;
496    int				error = 0;
497    dhcpoa_t *			options_p;
498    struct dhcp_packet *	request;
499    int				request_size;
500    int				retry;
501    int				wait_ticks;
502
503    /* format a DHCP Request packet */
504    request = dhcp_context_request(context);
505    options_p = &context->request_options;
506
507    make_dhcp_request(&request->dhcp, DHCP_PAYLOAD_MIN,
508		      dhcp_msgtype_request_e,
509		      link_address(context->dl_p), ARPHRD_ETHER,
510		      link_address_length(context->dl_p),
511		      options_p);
512    /* insert server identifier and requested ip address */
513    dhcpoa_add(options_p, dhcptag_requested_ip_address_e,
514	       sizeof(context->iaddr), &context->iaddr);
515    dhcpoa_add(options_p, dhcptag_server_identifier_e,
516	       sizeof(context->server_id), &context->server_id);
517    dhcpoa_add(options_p, dhcptag_end_e, 0, 0);
518    request_size = sizeof(*request) + RFC_MAGIC_SIZE
519	+ dhcpoa_used(options_p);
520    if (request_size < (int)sizeof(struct bootp_packet)) {
521	/* pad out to BOOTP-sized packet */
522	request_size = sizeof(struct bootp_packet);
523    }
524    init_dhcp_packet_header(request, request_size);
525
526    wait_ticks = INITIAL_WAIT_SECS * hz;
527#define SELECT_RETRY_COUNT	3
528    for (retry = 0; retry < SELECT_RETRY_COUNT; retry++) {
529	/* Send the request */
530	printf("dhcp: sending REQUEST: server " IP_FORMAT
531	       " IP address " IP_FORMAT "\n",
532	       IP_LIST(&context->server_id),
533	       IP_LIST(&context->iaddr));
534	microtime(&current_time);
535	request->dhcp.dp_secs
536	    = htons((u_short)
537		    (current_time.tv_sec - context->start_time.tv_sec));
538	request->dhcp.dp_xid = htonl(context->xid);
539#ifdef RANDOM_IP_ID
540	request->ip.ip_id = ip_randomid();
541#else
542	request->ip.ip_id = htons(ip_id++);
543#endif
544	error = send_packet(context->ifp, request, request_size);
545	if (error != 0) {
546	    printf("dhcp: send_packet failed with %d\n", error);
547	    goto failed;
548	}
549
550	wait_ticks += random_range(-RAND_TICKS, RAND_TICKS);
551	dprintf(("dhcp: waiting %d ticks\n", wait_ticks));
552	error = dhcp_get_ack(context, wait_ticks);
553	switch (error) {
554	case 0:
555	    /* we're done */
556	    goto done;
557	case EPROTO:
558	    printf("dhcp: server " IP_FORMAT " send us a NAK\n",
559		   IP_LIST(&context->server_id));
560	    goto failed;
561	case EWOULDBLOCK:
562	    break;
563	default:
564	    dprintf(("dhcp: failed to receive packets: %d\n", error));
565	    goto failed;
566	}
567	wait_ticks *= 2;
568	if (wait_ticks > (MAX_WAIT_SECS * hz))
569	    wait_ticks = MAX_WAIT_SECS * hz;
570	microtime(&current_time);
571    }
572    error = ETIMEDOUT;
573    goto failed;
574
575 done:
576    error = 0;
577
578 failed:
579    return (error);
580}
581
582static int
583dhcp_get_offer(struct dhcp_context * context, int wait_ticks)
584{
585    int				error = 0;
586    int	 			gather_count = 0;
587    const struct in_addr * 	ip;
588    int				last_rating = 0;
589    int				len;
590    int				n;
591    int 			rating;
592    struct dhcp *		reply;
593    struct in_addr		server_id;
594    struct socket * 		timer_arg;
595
596    timer_arg = context->so;
597    reply = dhcp_context_reply(context);
598    timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, wait_ticks);
599    while (1) {
600	error = receive_packet(context->so, context->reply,
601			       sizeof(context->reply), &n);
602	if (error == 0) {
603	    dhcpol_t		options;
604
605	    dprintf(("\ndhcp: received packet length %d\n", n));
606	    if (n < (int)sizeof(struct dhcp)) {
607		dprintf(("dhcp: packet is too short %d < %d\n",
608			 n, (int)sizeof(struct dhcp)));
609		continue;
610	    }
611	    if (ntohl(reply->dp_xid) != context->xid
612		|| reply->dp_yiaddr.s_addr == 0
613		|| reply->dp_yiaddr.s_addr == INADDR_BROADCAST
614		|| bcmp(reply->dp_chaddr,
615			link_address(context->dl_p),
616			link_address_length(context->dl_p)) != 0) {
617		/* not for us */
618		continue;
619	    }
620	    (void)dhcpol_parse_packet(&options, reply, n);
621	    if (get_dhcp_msgtype(&options) != dhcp_msgtype_offer_e) {
622		/* not an offer */
623		goto next_packet;
624	    }
625	    ip = (const struct in_addr *)
626		dhcpol_find(&options,
627			    dhcptag_server_identifier_e, &len, NULL);
628	    if (ip == NULL || len < (int)sizeof(*ip)) {
629		/* missing/invalid server identifier */
630		goto next_packet;
631	    }
632	    printf("dhcp: received OFFER: server " IP_FORMAT
633		   " IP address "  IP_FORMAT "\n",
634		   IP_LIST(ip), IP_LIST(&reply->dp_yiaddr));
635	    server_id = *ip;
636	    rating = rate_packet(&options);
637	    if (rating > last_rating) {
638		context->iaddr = reply->dp_yiaddr;
639		ip = (const struct in_addr *)
640		    dhcpol_find(&options,
641				dhcptag_subnet_mask_e, &len, NULL);
642		if (ip != NULL && len >= (int)sizeof(*ip)) {
643		    context->netmask = *ip;
644		}
645		ip = (const struct in_addr *)
646		    dhcpol_find(&options, dhcptag_router_e, &len, NULL);
647		if (ip != NULL && len >= (int)sizeof(*ip)) {
648		    context->router = *ip;
649		}
650		context->server_id = server_id;
651	    }
652	    if (rating >= GOOD_RATING) {
653		dhcpol_free(&options);
654		/* packet is good enough */
655		untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
656		break;
657	    }
658	    if (gather_count == 0) {
659		untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
660		timer_arg = context->so;
661		timeout((timeout_fcn_t)dhcp_timeout, &timer_arg,
662			hz * GATHER_TIME_SECS);
663	    }
664	    gather_count = 1;
665	next_packet:
666	    dhcpol_free(&options);
667	}
668	else if ((error != EWOULDBLOCK)) {
669	    untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
670	    break;
671	}
672	else if (timer_arg == NULL) { /* timed out */
673	    if (gather_count != 0) {
674		dprintf(("dhcp: gathering time has expired\n"));
675		error = 0;
676	    }
677	    break;
678	}
679	else {
680	    socket_lock(context->so, 1);
681	    error = sbwait(&context->so->so_rcv);
682	    socket_unlock(context->so, 1);
683	}
684    }
685    return (error);
686}
687
688/*
689 * Function: dhcp_init
690 * Purpose:
691 *   Start in the DHCP INIT state sending DISCOVER's.  When we get OFFER's,
692 *   try to select one of them by sending a REQUEST and waiting for an ACK.
693 */
694static int
695dhcp_init(struct dhcp_context * context)
696{
697    struct timeval		current_time;
698    int				error = 0;
699    uint32_t			lease_option = htonl(SUGGESTED_LEASE_LENGTH);
700    dhcpoa_t *			options_p;
701    struct dhcp_packet *	request;
702    int				request_size;
703    int				retry;
704    int				wait_ticks;
705
706    /* remember the time we started */
707    microtime(&context->start_time);
708    current_time = context->start_time;
709
710    request = dhcp_context_request(context);
711    options_p = &context->request_options;
712
713 retry:
714    /* format a DHCP DISCOVER packet */
715    make_dhcp_request(&request->dhcp, DHCP_PAYLOAD_MIN,
716		      dhcp_msgtype_discover_e,
717		      link_address(context->dl_p), ARPHRD_ETHER,
718		      link_address_length(context->dl_p),
719		      options_p);
720    /* add the requested lease time */
721    dhcpoa_add(options_p, dhcptag_lease_time_e,
722	       sizeof(lease_option), &lease_option);
723    dhcpoa_add(options_p, dhcptag_end_e, 0, 0);
724    request_size = sizeof(*request) + RFC_MAGIC_SIZE
725	+ dhcpoa_used(options_p);
726    if (request_size < (int)sizeof(struct bootp_packet)) {
727	/* pad out to BOOTP-sized packet */
728	request_size = sizeof(struct bootp_packet);
729    }
730    init_dhcp_packet_header(request, request_size);
731
732    wait_ticks = INITIAL_WAIT_SECS * hz;
733    for (retry = 0; retry < context->max_try; retry++) {
734	/* Send the request */
735	printf("dhcp: sending DISCOVER\n");
736	request->dhcp.dp_secs
737	    = htons((u_short)(current_time.tv_sec
738			      - context->start_time.tv_sec));
739	request->dhcp.dp_xid = htonl(context->xid);
740#ifdef RANDOM_IP_ID
741	request->ip.ip_id = ip_randomid();
742#else
743	request->ip.ip_id = htons(ip_id++);
744#endif
745	error = send_packet(context->ifp, request, request_size);
746	if (error != 0) {
747	    printf("dhcp: send_packet failed with %d\n", error);
748	    goto failed;
749	}
750	wait_ticks += random_range(-RAND_TICKS, RAND_TICKS);
751	dprintf(("dhcp: waiting %d ticks\n", wait_ticks));
752	error = dhcp_get_offer(context, wait_ticks);
753	if (error == 0) {
754	    /* send a REQUEST */
755	    error = dhcp_select(context);
756	    if (error == 0) {
757		/* we're done !*/
758		goto done;
759	    }
760	    if (error != EPROTO && error != ETIMEDOUT) {
761		/* fatal error */
762		dprintf(("dhcp: dhcp_select failed %d\n", error));
763		goto failed;
764	    }
765	    /* wait 10 seconds, and try again */
766	    printf("dhcp: trying again in 10 seconds\n");
767	    tsleep(&error, PRIBIO, "dhcp_init", 10 * hz);
768	    context->xid++;
769	    goto retry;
770	}
771	else if (error != EWOULDBLOCK) {
772	    dprintf(("dhcp: failed to receive packets: %d\n", error));
773	    goto failed;
774	}
775	wait_ticks *= 2;
776	if (wait_ticks > (MAX_WAIT_SECS * hz))
777	    wait_ticks = MAX_WAIT_SECS * hz;
778	microtime(&current_time);
779    }
780    error = ETIMEDOUT;
781    goto failed;
782
783 done:
784    error = 0;
785
786 failed:
787    return (error);
788}
789
790static void
791dhcp_context_free(struct dhcp_context * context, struct proc * procp)
792{
793    if (context == NULL) {
794	return;
795    }
796    if (context->so != NULL) {
797	int		error;
798
799	/* disable reception of DHCP packets before address assignment */
800	context->ifr.ifr_intval = 0;
801	error = ifioctl(context->so, SIOCAUTOADDR,
802			(caddr_t)&context->ifr, procp);
803	if (error) {
804	    printf("dhcp: SIOCAUTOADDR failed: %d\n", error);
805	}
806	soclose(context->so);
807    }
808    kfree(context, sizeof(*context));
809    return;
810}
811
812static struct dhcp_context *
813dhcp_context_create(struct ifnet * ifp, int max_try,
814		    struct proc * procp, int * error_p)
815{
816    struct dhcp_context	*	context = NULL;
817    struct sockaddr_dl *	dl_p;
818    struct in_addr		lo_addr;
819    struct in_addr		lo_mask;
820    int				error;
821    struct sockaddr_in		sin;
822
823    /* get the hardware address from the interface */
824    dl_p = link_from_ifnet(ifp);
825    if (dl_p == NULL) {
826	printf("dhcp: can't get link address\n");
827	error = ENXIO;
828	goto failed;
829    }
830
831    printf("dhcp: h/w addr ");
832    link_print(dl_p);
833    if (dl_p->sdl_type != IFT_ETHER) {
834	printf("dhcp: hardware type %d not supported\n",
835	       dl_p->sdl_type);
836	error = ENXIO;
837	goto failed;
838    }
839
840    context = (struct dhcp_context *)kalloc(sizeof(*context));
841    if (context == NULL) {
842	printf("dhcp: failed to allocate context\n");
843	error = ENOMEM;
844	goto failed;
845    }
846    bzero(context, sizeof(*context));
847
848    /* get a socket */
849    error = socreate(AF_INET, &context->so, SOCK_DGRAM, 0);
850    if (error != 0) {
851	printf("dhcp: socreate failed %d\n", error);
852	goto failed;
853    }
854
855    /* assign 127.0.0.1 to lo0 so that the bind will succeed */
856    lo_addr.s_addr = htonl(INADDR_LOOPBACK);
857    lo_mask.s_addr = htonl(IN_CLASSA_NET);
858    error = inet_aifaddr(context->so, "lo0", &lo_addr, &lo_mask, NULL);
859    if (error != 0) {
860	printf("dhcp: assigning loopback address failed %d\n", error);
861    }
862
863    /* enable reception of DHCP packets before an address is assigned */
864    snprintf(context->ifr.ifr_name,
865	     sizeof(context->ifr.ifr_name), "%s%d", ifp->if_name,
866	     ifp->if_unit);
867    context->ifr.ifr_intval = 1;
868
869    error = ifioctl(context->so, SIOCAUTOADDR, (caddr_t)&context->ifr, procp);
870    if (error) {
871	printf("dhcp: SIOCAUTOADDR failed: %d\n", error);
872	goto failed;
873    }
874    dprintf(("dhcp: SIOCAUTOADDR done\n"));
875
876    error = ifioctl(context->so, SIOCPROTOATTACH, (caddr_t)&context->ifr,
877		    procp);
878    if (error) {
879	printf("dhcp: SIOCPROTOATTACH failed: %d\n", error);
880	goto failed;
881    }
882    dprintf(("dhcp: SIOCPROTOATTACH done\n"));
883
884    /* bind the socket */
885    sin.sin_len = sizeof(sin);
886    sin.sin_family = AF_INET;
887    sin.sin_port = htons(IPPORT_BOOTPC);
888    sin.sin_addr.s_addr = INADDR_ANY;
889    error = sobind(context->so, (struct sockaddr *)&sin);
890    if (error) {
891	printf("dhcp: sobind failed, %d\n", error);
892	goto failed;
893    }
894
895    /* make it non-blocking I/O */
896    socket_lock(context->so, 1);
897    context->so->so_state |= SS_NBIO;
898    socket_unlock(context->so, 1);
899
900    /* save passed-in information */
901    context->max_try = max_try;
902    context->dl_p = dl_p;
903    context->ifp = ifp;
904
905    /* get a random transaction id */
906    context->xid = random();
907
908    return (context);
909
910 failed:
911    dhcp_context_free(context, procp);
912    *error_p = error;
913    return (NULL);
914}
915
916/*
917 * Routine: dhcp
918 * Function:
919 *   Do DHCP over the specified interface to retrieve the IP address,
920 *   subnet mask, and router.
921 */
922int
923dhcp(struct ifnet * ifp, struct in_addr * iaddr_p, int max_try,
924     struct in_addr * netmask_p, struct in_addr * router_p,
925     struct proc * procp)
926{
927    int				error = 0;
928    struct dhcp_context	*	context;
929
930    context = dhcp_context_create(ifp, max_try, procp, &error);
931    if (context == NULL) {
932	return (error);
933    }
934
935    /* start DHCP in the INIT state */
936    error = dhcp_init(context);
937    if (error == 0) {
938	*iaddr_p = context->iaddr;
939	*netmask_p = context->netmask;
940	*router_p = context->router;
941    }
942    dhcp_context_free(context, procp);
943    return (error);
944}
945