1/*
2 * Copyright (c) 1988-2013 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) - 1;
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	request->ip.ip_id = ip_randomid();
540	error = send_packet(context->ifp, request, request_size);
541	if (error != 0) {
542	    printf("dhcp: send_packet failed with %d\n", error);
543	    goto failed;
544	}
545
546	wait_ticks += random_range(-RAND_TICKS, RAND_TICKS);
547	dprintf(("dhcp: waiting %d ticks\n", wait_ticks));
548	error = dhcp_get_ack(context, wait_ticks);
549	switch (error) {
550	case 0:
551	    /* we're done */
552	    goto done;
553	case EPROTO:
554	    printf("dhcp: server " IP_FORMAT " send us a NAK\n",
555		   IP_LIST(&context->server_id));
556	    goto failed;
557	case EWOULDBLOCK:
558	    break;
559	default:
560	    dprintf(("dhcp: failed to receive packets: %d\n", error));
561	    goto failed;
562	}
563	wait_ticks *= 2;
564	if (wait_ticks > (MAX_WAIT_SECS * hz))
565	    wait_ticks = MAX_WAIT_SECS * hz;
566	microtime(&current_time);
567    }
568    error = ETIMEDOUT;
569    goto failed;
570
571 done:
572    error = 0;
573
574 failed:
575    return (error);
576}
577
578static int
579dhcp_get_offer(struct dhcp_context * context, int wait_ticks)
580{
581    int				error = 0;
582    int	 			gather_count = 0;
583    const struct in_addr * 	ip;
584    int				last_rating = 0;
585    int				len;
586    int				n;
587    int 			rating;
588    struct dhcp *		reply;
589    struct in_addr		server_id;
590    struct socket * 		timer_arg;
591
592    timer_arg = context->so;
593    reply = dhcp_context_reply(context);
594    timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, wait_ticks);
595    while (1) {
596	error = receive_packet(context->so, context->reply,
597			       sizeof(context->reply), &n);
598	if (error == 0) {
599	    dhcpol_t		options;
600
601	    dprintf(("\ndhcp: received packet length %d\n", n));
602	    if (n < (int)sizeof(struct dhcp)) {
603		dprintf(("dhcp: packet is too short %d < %d\n",
604			 n, (int)sizeof(struct dhcp)));
605		continue;
606	    }
607	    if (ntohl(reply->dp_xid) != context->xid
608		|| reply->dp_yiaddr.s_addr == 0
609		|| reply->dp_yiaddr.s_addr == INADDR_BROADCAST
610		|| bcmp(reply->dp_chaddr,
611			link_address(context->dl_p),
612			link_address_length(context->dl_p)) != 0) {
613		/* not for us */
614		continue;
615	    }
616	    (void)dhcpol_parse_packet(&options, reply, n);
617	    if (get_dhcp_msgtype(&options) != dhcp_msgtype_offer_e) {
618		/* not an offer */
619		goto next_packet;
620	    }
621	    ip = (const struct in_addr *)
622		dhcpol_find(&options,
623			    dhcptag_server_identifier_e, &len, NULL);
624	    if (ip == NULL || len < (int)sizeof(*ip)) {
625		/* missing/invalid server identifier */
626		goto next_packet;
627	    }
628	    printf("dhcp: received OFFER: server " IP_FORMAT
629		   " IP address "  IP_FORMAT "\n",
630		   IP_LIST(ip), IP_LIST(&reply->dp_yiaddr));
631	    server_id = *ip;
632	    rating = rate_packet(&options);
633	    if (rating > last_rating) {
634		context->iaddr = reply->dp_yiaddr;
635		ip = (const struct in_addr *)
636		    dhcpol_find(&options,
637				dhcptag_subnet_mask_e, &len, NULL);
638		if (ip != NULL && len >= (int)sizeof(*ip)) {
639		    context->netmask = *ip;
640		}
641		ip = (const struct in_addr *)
642		    dhcpol_find(&options, dhcptag_router_e, &len, NULL);
643		if (ip != NULL && len >= (int)sizeof(*ip)) {
644		    context->router = *ip;
645		}
646		context->server_id = server_id;
647	    }
648	    if (rating >= GOOD_RATING) {
649		dhcpol_free(&options);
650		/* packet is good enough */
651		untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
652		break;
653	    }
654	    if (gather_count == 0) {
655		untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
656		timer_arg = context->so;
657		timeout((timeout_fcn_t)dhcp_timeout, &timer_arg,
658			hz * GATHER_TIME_SECS);
659	    }
660	    gather_count = 1;
661	next_packet:
662	    dhcpol_free(&options);
663	}
664	else if ((error != EWOULDBLOCK)) {
665	    untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
666	    break;
667	}
668	else if (timer_arg == NULL) { /* timed out */
669	    if (gather_count != 0) {
670		dprintf(("dhcp: gathering time has expired\n"));
671		error = 0;
672	    }
673	    break;
674	}
675	else {
676	    socket_lock(context->so, 1);
677	    error = sbwait(&context->so->so_rcv);
678	    socket_unlock(context->so, 1);
679	}
680    }
681    return (error);
682}
683
684/*
685 * Function: dhcp_init
686 * Purpose:
687 *   Start in the DHCP INIT state sending DISCOVER's.  When we get OFFER's,
688 *   try to select one of them by sending a REQUEST and waiting for an ACK.
689 */
690static int
691dhcp_init(struct dhcp_context * context)
692{
693    struct timeval		current_time;
694    int				error = 0;
695    uint32_t			lease_option = htonl(SUGGESTED_LEASE_LENGTH);
696    dhcpoa_t *			options_p;
697    struct dhcp_packet *	request;
698    int				request_size;
699    int				retry;
700    int				wait_ticks;
701
702    /* remember the time we started */
703    microtime(&context->start_time);
704    current_time = context->start_time;
705
706    request = dhcp_context_request(context);
707    options_p = &context->request_options;
708
709 retry:
710    /* format a DHCP DISCOVER packet */
711    make_dhcp_request(&request->dhcp, DHCP_PAYLOAD_MIN,
712		      dhcp_msgtype_discover_e,
713		      link_address(context->dl_p), ARPHRD_ETHER,
714		      link_address_length(context->dl_p),
715		      options_p);
716    /* add the requested lease time */
717    dhcpoa_add(options_p, dhcptag_lease_time_e,
718	       sizeof(lease_option), &lease_option);
719    dhcpoa_add(options_p, dhcptag_end_e, 0, 0);
720    request_size = sizeof(*request) + RFC_MAGIC_SIZE
721	+ dhcpoa_used(options_p);
722    if (request_size < (int)sizeof(struct bootp_packet)) {
723	/* pad out to BOOTP-sized packet */
724	request_size = sizeof(struct bootp_packet);
725    }
726    init_dhcp_packet_header(request, request_size);
727
728    wait_ticks = INITIAL_WAIT_SECS * hz;
729    for (retry = 0; retry < context->max_try; retry++) {
730	/* Send the request */
731	printf("dhcp: sending DISCOVER\n");
732	request->dhcp.dp_secs
733	    = htons((u_short)(current_time.tv_sec
734			      - context->start_time.tv_sec));
735	request->dhcp.dp_xid = htonl(context->xid);
736	request->ip.ip_id = ip_randomid();
737	error = send_packet(context->ifp, request, request_size);
738	if (error != 0) {
739	    printf("dhcp: send_packet failed with %d\n", error);
740	    goto failed;
741	}
742	wait_ticks += random_range(-RAND_TICKS, RAND_TICKS);
743	dprintf(("dhcp: waiting %d ticks\n", wait_ticks));
744	error = dhcp_get_offer(context, wait_ticks);
745	if (error == 0) {
746	    /* send a REQUEST */
747	    error = dhcp_select(context);
748	    if (error == 0) {
749		/* we're done !*/
750		goto done;
751	    }
752	    if (error != EPROTO && error != ETIMEDOUT) {
753		/* fatal error */
754		dprintf(("dhcp: dhcp_select failed %d\n", error));
755		goto failed;
756	    }
757	    /* wait 10 seconds, and try again */
758	    printf("dhcp: trying again in 10 seconds\n");
759	    tsleep(&error, PRIBIO, "dhcp_init", 10 * hz);
760	    context->xid++;
761	    goto retry;
762	}
763	else if (error != EWOULDBLOCK) {
764	    dprintf(("dhcp: failed to receive packets: %d\n", error));
765	    goto failed;
766	}
767	wait_ticks *= 2;
768	if (wait_ticks > (MAX_WAIT_SECS * hz))
769	    wait_ticks = MAX_WAIT_SECS * hz;
770	microtime(&current_time);
771    }
772    error = ETIMEDOUT;
773    goto failed;
774
775 done:
776    error = 0;
777
778 failed:
779    return (error);
780}
781
782static void
783dhcp_context_free(struct dhcp_context * context, struct proc * procp)
784{
785    if (context == NULL) {
786	return;
787    }
788    if (context->so != NULL) {
789	int		error;
790
791	/* disable reception of DHCP packets before address assignment */
792	context->ifr.ifr_intval = 0;
793	error = ifioctl(context->so, SIOCAUTOADDR,
794			(caddr_t)&context->ifr, procp);
795	if (error) {
796	    printf("dhcp: SIOCAUTOADDR failed: %d\n", error);
797	}
798	soclose(context->so);
799    }
800    kfree(context, sizeof(*context));
801    return;
802}
803
804static struct dhcp_context *
805dhcp_context_create(struct ifnet * ifp, int max_try,
806		    struct proc * procp, int * error_p)
807{
808    struct dhcp_context	*	context = NULL;
809    struct sockaddr_dl *	dl_p;
810    struct in_addr		lo_addr;
811    struct in_addr		lo_mask;
812    int				error;
813    struct sockaddr_in		sin;
814
815    /* get the hardware address from the interface */
816    dl_p = link_from_ifnet(ifp);
817    if (dl_p == NULL) {
818	printf("dhcp: can't get link address\n");
819	error = ENXIO;
820	goto failed;
821    }
822
823    printf("dhcp: h/w addr ");
824    link_print(dl_p);
825    if (dl_p->sdl_type != IFT_ETHER) {
826	printf("dhcp: hardware type %d not supported\n",
827	       dl_p->sdl_type);
828	error = ENXIO;
829	goto failed;
830    }
831
832    context = (struct dhcp_context *)kalloc(sizeof(*context));
833    if (context == NULL) {
834	printf("dhcp: failed to allocate context\n");
835	error = ENOMEM;
836	goto failed;
837    }
838    bzero(context, sizeof(*context));
839
840    /* get a socket */
841    error = socreate(AF_INET, &context->so, SOCK_DGRAM, 0);
842    if (error != 0) {
843	printf("dhcp: socreate failed %d\n", error);
844	goto failed;
845    }
846
847    /* assign 127.0.0.1 to lo0 so that the bind will succeed */
848    lo_addr.s_addr = htonl(INADDR_LOOPBACK);
849    lo_mask.s_addr = htonl(IN_CLASSA_NET);
850    error = inet_aifaddr(context->so, "lo0", &lo_addr, &lo_mask, NULL);
851    if (error != 0) {
852	printf("dhcp: assigning loopback address failed %d\n", error);
853    }
854
855    /* enable reception of DHCP packets before an address is assigned */
856    snprintf(context->ifr.ifr_name,
857	     sizeof(context->ifr.ifr_name), "%s", if_name(ifp));
858    context->ifr.ifr_intval = 1;
859
860    error = ifioctl(context->so, SIOCAUTOADDR, (caddr_t)&context->ifr, procp);
861    if (error) {
862	printf("dhcp: SIOCAUTOADDR failed: %d\n", error);
863	goto failed;
864    }
865    dprintf(("dhcp: SIOCAUTOADDR done\n"));
866
867    error = ifioctl(context->so, SIOCPROTOATTACH, (caddr_t)&context->ifr,
868		    procp);
869    if (error) {
870	printf("dhcp: SIOCPROTOATTACH failed: %d\n", error);
871	goto failed;
872    }
873    dprintf(("dhcp: SIOCPROTOATTACH done\n"));
874
875    /* bind the socket */
876    sin.sin_len = sizeof(sin);
877    sin.sin_family = AF_INET;
878    sin.sin_port = htons(IPPORT_BOOTPC);
879    sin.sin_addr.s_addr = INADDR_ANY;
880    error = sobindlock(context->so, (struct sockaddr *)&sin, 1);
881    if (error) {
882	printf("dhcp: sobind failed, %d\n", error);
883	goto failed;
884    }
885
886    /* make it non-blocking I/O */
887    socket_lock(context->so, 1);
888    context->so->so_state |= SS_NBIO;
889    socket_unlock(context->so, 1);
890
891    /* save passed-in information */
892    context->max_try = max_try;
893    context->dl_p = dl_p;
894    context->ifp = ifp;
895
896    /* get a random transaction id */
897    context->xid = random();
898
899    return (context);
900
901 failed:
902    dhcp_context_free(context, procp);
903    *error_p = error;
904    return (NULL);
905}
906
907/*
908 * Routine: dhcp
909 * Function:
910 *   Do DHCP over the specified interface to retrieve the IP address,
911 *   subnet mask, and router.
912 */
913int
914dhcp(struct ifnet * ifp, struct in_addr * iaddr_p, int max_try,
915     struct in_addr * netmask_p, struct in_addr * router_p,
916     struct proc * procp)
917{
918    int				error = 0;
919    struct dhcp_context	*	context;
920
921    context = dhcp_context_create(ifp, max_try, procp, &error);
922    if (context == NULL) {
923	return (error);
924    }
925
926    /* start DHCP in the INIT state */
927    error = dhcp_init(context);
928    if (error == 0) {
929	*iaddr_p = context->iaddr;
930	*netmask_p = context->netmask;
931	*router_p = context->router;
932    }
933    dhcp_context_free(context, procp);
934    return (error);
935}
936