]> git.saurik.com Git - apple/xnu.git/blame - bsd/netinet/in_dhcp.c
xnu-1228.15.4.tar.gz
[apple/xnu.git] / bsd / netinet / in_dhcp.c
CommitLineData
2d21ac55
A
1/*
2 * Copyright (c) 1988-2007 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 *)&ifra.ifra_addr) = blank_sin;
109 ((struct sockaddr_in *)&ifra.ifra_addr)->sin_addr = *addr;
110 }
111 if (mask) {
112 *((struct sockaddr_in *)&ifra.ifra_mask) = blank_sin;
113 ((struct sockaddr_in *)&ifra.ifra_mask)->sin_addr = *mask;
114 }
115 if (broadcast) {
116 *((struct sockaddr_in *)&ifra.ifra_broadaddr) = blank_sin;
117 ((struct sockaddr_in *)&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 *)context->request);
144}
145
146static __inline__ struct dhcp *
147dhcp_context_reply(struct dhcp_context * context)
148{
149 return ((struct dhcp *)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#if 0
285 printf("len %d index %d family %d type 0x%x nlen %d alen %d"
286 " slen %d addr ", dl_p->sdl_len,
287 dl_p->sdl_index, dl_p->sdl_family, dl_p->sdl_type,
288 dl_p->sdl_nlen, dl_p->sdl_alen, dl_p->sdl_slen);
289#endif
290 for (i = 0; i < dl_p->sdl_alen; i++)
291 printf("%s%x", i ? ":" : "",
292 (link_address(dl_p))[i]);
293 printf("\n");
294 return;
295}
296
297static struct sockaddr_dl *
298link_from_ifnet(struct ifnet * ifp)
299{
300 struct ifaddr * addr;
301
302 ifnet_lock_shared(ifp);
303 TAILQ_FOREACH(addr, &ifp->if_addrhead, ifa_link) {
304 if (addr->ifa_addr->sa_family == AF_LINK) {
305 struct sockaddr_dl * dl_p = (struct sockaddr_dl *)(addr->ifa_addr);
306
307 ifnet_lock_done(ifp);
308 return (dl_p);
309 }
310 }
311 ifnet_lock_done(ifp);
312 return (NULL);
313}
314
315/*
316 * Function: send_packet
317 * Purpose:
318 * Send the request directly on the interface, bypassing the routing code.
319 */
320static int
321send_packet(struct ifnet * ifp, struct dhcp_packet * pkt, int pkt_size)
322{
323 struct mbuf * m;
324 struct sockaddr_in dest;
325
326 dest = blank_sin;
327 dest.sin_port = htons(IPPORT_BOOTPS);
328 dest.sin_addr.s_addr = INADDR_BROADCAST;
329 m = ip_pkt_to_mbuf((caddr_t)pkt, pkt_size);
330 return dlil_output(ifp, PF_INET, m, 0, (struct sockaddr *)&dest, 0);
331}
332
333/*
334 * Function: receive_packet
335 * Purpose:
336 * Return a received packet or an error if none available.
337 */
338static int
339receive_packet(struct socket * so, void * pp, int psize, int * actual_size)
340{
341 uio_t auio;
342 int error;
343 int rcvflg;
344 char uio_buf[ UIO_SIZEOF(1) ];
345
346 auio = uio_createwithbuffer(1, 0, UIO_SYSSPACE, UIO_READ,
347 &uio_buf[0], sizeof(uio_buf));
348 uio_addiov(auio, CAST_USER_ADDR_T(pp), psize);
349 rcvflg = MSG_WAITALL;
350
351 error = soreceive(so, (struct sockaddr **) 0, auio, 0, 0, &rcvflg);
352 *actual_size = psize - uio_resid(auio);
353 return (error);
354}
355
356/*
357 * Function: dhcp_timeout
358 * Purpose:
359 * Wakeup the process waiting for something on a socket.
360 */
361static void
362dhcp_timeout(void * arg)
363{
364 struct socket * * timer_arg = (struct socket * *)arg;
365 struct socket * so = *timer_arg;
366
367 dprintf(("dhcp: timeout\n"));
368
369 *timer_arg = NULL;
370 socket_lock(so, 1);
371 sowakeup(so, &so->so_rcv);
372 socket_unlock(so, 1);
373 return;
374}
375
376/*
377 * Function: rate_packet
378 * Purpose:
379 * Return an integer point rating value for the given dhcp packet.
380 * If yiaddr non-zero, the packet gets a rating of 1.
381 * Another point is given if the packet contains the subnet mask,
382 * and another if the router is present.
383 */
384#define GOOD_RATING 3
385static __inline__ int
386rate_packet(dhcpol_t * options_p)
387{
388 int len;
389 int rating = 1;
390
391 if (dhcpol_find(options_p, dhcptag_subnet_mask_e, &len, NULL) != NULL) {
392 rating++;
393 }
394 if (dhcpol_find(options_p, dhcptag_router_e, &len, NULL) != NULL) {
395 rating++;
396 }
397 return (rating);
398}
399
400static dhcp_msgtype_t
401get_dhcp_msgtype(dhcpol_t * options_p)
402{
403 int len;
404 const uint8_t * opt;
405
406 opt = dhcpol_find(options_p, dhcptag_dhcp_message_type_e, &len, NULL);
407 if (opt != NULL && len == 1) {
408 return (*opt);
409 }
410 return (dhcp_msgtype_none_e);
411}
412
413static int
414dhcp_get_ack(struct dhcp_context * context, int wait_ticks)
415{
416 int error = 0;
417 const struct in_addr * ip;
418 int len;
419 int n;
420 struct dhcp * reply;
421 struct in_addr server_id;
422 struct socket * timer_arg;
423
424 timer_arg = context->so;
425 reply = dhcp_context_reply(context);
426 timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, wait_ticks);
427 while (1) {
428 error = receive_packet(context->so, context->reply,
429 sizeof(context->reply), &n);
430 if (error == 0) {
431 dhcp_msgtype_t msg;
432 dhcpol_t options;
433
434 dprintf(("\ndhcp: received packet length %d\n", n));
435 if (n < (int)sizeof(struct dhcp)) {
436 dprintf(("dhcp: packet is too short %d < %d\n",
437 n, (int)sizeof(struct dhcp)));
438 continue;
439 }
440 if (ntohl(reply->dp_xid) != context->xid
441 || bcmp(reply->dp_chaddr, link_address(context->dl_p),
442 link_address_length(context->dl_p)) != 0) {
443 /* not for us */
444 continue;
445 }
446 (void)dhcpol_parse_packet(&options, reply, n);
447 server_id.s_addr = 0;
448 ip = (const struct in_addr *)
449 dhcpol_find(&options,
450 dhcptag_server_identifier_e, &len, NULL);
451 if (ip != NULL && len >= (int)sizeof(*ip)) {
452 server_id = *ip;
453 }
454 msg = get_dhcp_msgtype(&options);
455 if (msg == dhcp_msgtype_nak_e
456 && server_id.s_addr == context->server_id.s_addr) {
457 /* server NAK'd us, start over */
458 dhcpol_free(&options);
459 error = EPROTO;
460 untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
461 break;
462 }
463 if (msg != dhcp_msgtype_ack_e
464 || reply->dp_yiaddr.s_addr == 0
465 || reply->dp_yiaddr.s_addr == INADDR_BROADCAST) {
466 /* ignore the packet */
467 goto next_packet;
468 }
469 printf("dhcp: received ACK: server " IP_FORMAT
470 " IP address " IP_FORMAT "\n",
471 IP_LIST(&server_id), IP_LIST(&reply->dp_yiaddr));
472 context->iaddr = reply->dp_yiaddr;
473 ip = (const struct in_addr *)
474 dhcpol_find(&options,
475 dhcptag_subnet_mask_e, &len, NULL);
476 if (ip != NULL && len >= (int)sizeof(*ip)) {
477 context->netmask = *ip;
478 }
479 ip = (const struct in_addr *)
480 dhcpol_find(&options, dhcptag_router_e, &len, NULL);
481 if (ip != NULL && len >= (int)sizeof(*ip)) {
482 context->router = *ip;
483 }
484 dhcpol_free(&options);
485 untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
486 break;
487
488 next_packet:
489 dhcpol_free(&options);
490 }
491 else if ((error != EWOULDBLOCK)) {
492 /* if some other error occurred, we're done */
493 untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
494 break;
495 }
496 else if (timer_arg == NULL) {
497 /* timed out */
498 break;
499 }
500 else {
501 /* wait for a wait to arrive, or a timeout to occur */
502 socket_lock(context->so, 1);
503 error = sbwait(&context->so->so_rcv);
504 socket_unlock(context->so, 1);
505 }
506 }
507 return (error);
508}
509
510static int
511dhcp_select(struct dhcp_context * context)
512{
513 struct timeval current_time;
514 int error = 0;
515 dhcpoa_t * options_p;
516 struct dhcp_packet * request;
517 int request_size;
518 int retry;
519 int wait_ticks;
520
521 /* format a DHCP Request packet */
522 request = dhcp_context_request(context);
523 options_p = &context->request_options;
524
525 make_dhcp_request(&request->dhcp, DHCP_PAYLOAD_MIN,
526 dhcp_msgtype_request_e,
527 link_address(context->dl_p), ARPHRD_ETHER,
528 link_address_length(context->dl_p),
529 options_p);
530 /* insert server identifier and requested ip address */
531 dhcpoa_add(options_p, dhcptag_requested_ip_address_e,
532 sizeof(context->iaddr), &context->iaddr);
533 dhcpoa_add(options_p, dhcptag_server_identifier_e,
534 sizeof(context->server_id), &context->server_id);
535 dhcpoa_add(options_p, dhcptag_end_e, 0, 0);
536 request_size = sizeof(*request) + RFC_MAGIC_SIZE
537 + dhcpoa_used(options_p);
538 if (request_size < (int)sizeof(struct bootp_packet)) {
539 /* pad out to BOOTP-sized packet */
540 request_size = sizeof(struct bootp_packet);
541 }
542 init_dhcp_packet_header(request, request_size);
543
544 wait_ticks = INITIAL_WAIT_SECS * hz;
545#define SELECT_RETRY_COUNT 3
546 for (retry = 0; retry < SELECT_RETRY_COUNT; retry++) {
547 /* Send the request */
548 printf("dhcp: sending REQUEST: server " IP_FORMAT
549 " IP address " IP_FORMAT "\n",
550 IP_LIST(&context->server_id),
551 IP_LIST(&context->iaddr));
552 microtime(&current_time);
553 request->dhcp.dp_secs
554 = htons((u_short)
555 (current_time.tv_sec - context->start_time.tv_sec));
556 request->dhcp.dp_xid = htonl(context->xid);
557#ifdef RANDOM_IP_ID
558 request->ip.ip_id = ip_randomid();
559#else
560 request->ip.ip_id = htons(ip_id++);
561#endif
562 error = send_packet(context->ifp, request, request_size);
563 if (error != 0) {
564 printf("dhcp: send_packet failed with %d\n", error);
565 goto failed;
566 }
567
568 wait_ticks += random_range(-RAND_TICKS, RAND_TICKS);
569 dprintf(("dhcp: waiting %d ticks\n", wait_ticks));
570 error = dhcp_get_ack(context, wait_ticks);
571 switch (error) {
572 case 0:
573 /* we're done */
574 goto done;
575 case EPROTO:
576 printf("dhcp: server " IP_FORMAT " send us a NAK\n",
577 IP_LIST(&context->server_id));
578 goto failed;
579 case EWOULDBLOCK:
580 break;
581 default:
582 dprintf(("dhcp: failed to receive packets: %d\n", error));
583 goto failed;
584 }
585 wait_ticks *= 2;
586 if (wait_ticks > (MAX_WAIT_SECS * hz))
587 wait_ticks = MAX_WAIT_SECS * hz;
588 microtime(&current_time);
589 }
590 error = ETIMEDOUT;
591 goto failed;
592
593 done:
594 error = 0;
595
596 failed:
597 return (error);
598}
599
600static int
601dhcp_get_offer(struct dhcp_context * context, int wait_ticks)
602{
603 int error = 0;
604 int gather_count = 0;
605 const struct in_addr * ip;
606 int last_rating = 0;
607 int len;
608 int n;
609 int rating;
610 struct dhcp * reply;
611 struct in_addr server_id;
612 struct socket * timer_arg;
613
614 timer_arg = context->so;
615 reply = dhcp_context_reply(context);
616 timeout((timeout_fcn_t)dhcp_timeout, &timer_arg, wait_ticks);
617 while (1) {
618 error = receive_packet(context->so, context->reply,
619 sizeof(context->reply), &n);
620 if (error == 0) {
621 dhcpol_t options;
622
623 dprintf(("\ndhcp: received packet length %d\n", n));
624 if (n < (int)sizeof(struct dhcp)) {
625 dprintf(("dhcp: packet is too short %d < %d\n",
626 n, (int)sizeof(struct dhcp)));
627 continue;
628 }
629 if (ntohl(reply->dp_xid) != context->xid
630 || reply->dp_yiaddr.s_addr == 0
631 || reply->dp_yiaddr.s_addr == INADDR_BROADCAST
632 || bcmp(reply->dp_chaddr,
633 link_address(context->dl_p),
634 link_address_length(context->dl_p)) != 0) {
635 /* not for us */
636 continue;
637 }
638 (void)dhcpol_parse_packet(&options, reply, n);
639 if (get_dhcp_msgtype(&options) != dhcp_msgtype_offer_e) {
640 /* not an offer */
641 goto next_packet;
642 }
643 ip = (const struct in_addr *)
644 dhcpol_find(&options,
645 dhcptag_server_identifier_e, &len, NULL);
646 if (ip == NULL || len < (int)sizeof(*ip)) {
647 /* missing/invalid server identifier */
648 goto next_packet;
649 }
650 printf("dhcp: received OFFER: server " IP_FORMAT
651 " IP address " IP_FORMAT "\n",
652 IP_LIST(ip), IP_LIST(&reply->dp_yiaddr));
653 server_id = *ip;
654 rating = rate_packet(&options);
655 if (rating > last_rating) {
656 context->iaddr = reply->dp_yiaddr;
657 ip = (const struct in_addr *)
658 dhcpol_find(&options,
659 dhcptag_subnet_mask_e, &len, NULL);
660 if (ip != NULL && len >= (int)sizeof(*ip)) {
661 context->netmask = *ip;
662 }
663 ip = (const struct in_addr *)
664 dhcpol_find(&options, dhcptag_router_e, &len, NULL);
665 if (ip != NULL && len >= (int)sizeof(*ip)) {
666 context->router = *ip;
667 }
668 context->server_id = server_id;
669 }
670 if (rating >= GOOD_RATING) {
671 dhcpol_free(&options);
672 /* packet is good enough */
673 untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
674 break;
675 }
676 if (gather_count == 0) {
677 untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
678 timer_arg = context->so;
679 timeout((timeout_fcn_t)dhcp_timeout, &timer_arg,
680 hz * GATHER_TIME_SECS);
681 }
682 gather_count = 1;
683 next_packet:
684 dhcpol_free(&options);
685 }
686 else if ((error != EWOULDBLOCK)) {
687 untimeout((timeout_fcn_t)dhcp_timeout, &timer_arg);
688 break;
689 }
690 else if (timer_arg == NULL) { /* timed out */
691 if (gather_count != 0) {
692 dprintf(("dhcp: gathering time has expired\n"));
693 error = 0;
694 }
695 break;
696 }
697 else {
698 socket_lock(context->so, 1);
699 error = sbwait(&context->so->so_rcv);
700 socket_unlock(context->so, 1);
701 }
702 }
703 return (error);
704}
705
706/*
707 * Function: dhcp_init
708 * Purpose:
709 * Start in the DHCP INIT state sending DISCOVER's. When we get OFFER's,
710 * try to select one of them by sending a REQUEST and waiting for an ACK.
711 */
712static int
713dhcp_init(struct dhcp_context * context)
714{
715 struct timeval current_time;
716 int error = 0;
717 uint32_t lease_option = htonl(SUGGESTED_LEASE_LENGTH);
718 dhcpoa_t * options_p;
719 struct dhcp_packet * request;
720 int request_size;
721 int retry;
722 int wait_ticks;
723
724 /* remember the time we started */
725 microtime(&context->start_time);
726 current_time = context->start_time;
727
728 request = dhcp_context_request(context);
729 options_p = &context->request_options;
730
731 retry:
732 /* format a DHCP DISCOVER packet */
733 make_dhcp_request(&request->dhcp, DHCP_PAYLOAD_MIN,
734 dhcp_msgtype_discover_e,
735 link_address(context->dl_p), ARPHRD_ETHER,
736 link_address_length(context->dl_p),
737 options_p);
738 /* add the requested lease time */
739 dhcpoa_add(options_p, dhcptag_lease_time_e,
740 sizeof(lease_option), &lease_option);
741 dhcpoa_add(options_p, dhcptag_end_e, 0, 0);
742 request_size = sizeof(*request) + RFC_MAGIC_SIZE
743 + dhcpoa_used(options_p);
744 if (request_size < (int)sizeof(struct bootp_packet)) {
745 /* pad out to BOOTP-sized packet */
746 request_size = sizeof(struct bootp_packet);
747 }
748 init_dhcp_packet_header(request, request_size);
749
750 wait_ticks = INITIAL_WAIT_SECS * hz;
751 for (retry = 0; retry < context->max_try; retry++) {
752 /* Send the request */
753 printf("dhcp: sending DISCOVER\n");
754 request->dhcp.dp_secs
755 = htons((u_short)(current_time.tv_sec
756 - context->start_time.tv_sec));
757 request->dhcp.dp_xid = htonl(context->xid);
758#ifdef RANDOM_IP_ID
759 request->ip.ip_id = ip_randomid();
760#else
761 request->ip.ip_id = htons(ip_id++);
762#endif
763 error = send_packet(context->ifp, request, request_size);
764 if (error != 0) {
765 printf("dhcp: send_packet failed with %d\n", error);
766 goto failed;
767 }
768 wait_ticks += random_range(-RAND_TICKS, RAND_TICKS);
769 dprintf(("dhcp: waiting %d ticks\n", wait_ticks));
770 error = dhcp_get_offer(context, wait_ticks);
771 if (error == 0) {
772 /* send a REQUEST */
773 error = dhcp_select(context);
774 if (error == 0) {
775 /* we're done !*/
776 goto done;
777 }
778 if (error != EPROTO && error != ETIMEDOUT) {
779 /* fatal error */
780 dprintf(("dhcp: dhcp_select failed %d\n", error));
781 goto failed;
782 }
783 /* wait 10 seconds, and try again */
784 printf("dhcp: trying again in 10 seconds\n");
785 tsleep(&error, PRIBIO, "dhcp_init", 10 * hz);
786 context->xid++;
787 goto retry;
788 }
789 else if (error != EWOULDBLOCK) {
790 dprintf(("dhcp: failed to receive packets: %d\n", error));
791 goto failed;
792 }
793 wait_ticks *= 2;
794 if (wait_ticks > (MAX_WAIT_SECS * hz))
795 wait_ticks = MAX_WAIT_SECS * hz;
796 microtime(&current_time);
797 }
798 error = ETIMEDOUT;
799 goto failed;
800
801 done:
802 error = 0;
803
804 failed:
805 return (error);
806}
807
808static void
809dhcp_context_free(struct dhcp_context * context, struct proc * procp)
810{
811 if (context == NULL) {
812 return;
813 }
814 if (context->so != NULL) {
815 int error;
816
817 /* disable reception of DHCP packets before address assignment */
818 context->ifr.ifr_intval = 0;
819 error = ifioctl(context->so, SIOCAUTOADDR,
820 (caddr_t)&context->ifr, procp);
821 if (error) {
822 printf("dhcp: SIOCAUTOADDR failed: %d\n", error);
823 }
824 soclose(context->so);
825 }
826 kfree(context, sizeof(*context));
827 return;
828}
829
830static struct dhcp_context *
831dhcp_context_create(struct ifnet * ifp, int max_try,
832 struct proc * procp, int * error_p)
833{
834 struct dhcp_context * context = NULL;
835 struct sockaddr_dl * dl_p;
836 struct in_addr lo_addr;
837 struct in_addr lo_mask;
838 int error;
839 struct sockaddr_in sin;
840
841 /* get the hardware address from the interface */
842 dl_p = link_from_ifnet(ifp);
843 if (dl_p == NULL) {
844 printf("dhcp: can't get link address\n");
845 error = ENXIO;
846 goto failed;
847 }
848
849 printf("dhcp: h/w addr ");
850 link_print(dl_p);
851 if (dl_p->sdl_type != IFT_ETHER) {
852 printf("dhcp: hardware type %d not supported\n",
853 dl_p->sdl_type);
854 error = ENXIO;
855 goto failed;
856 }
857
858 context = (struct dhcp_context *)kalloc(sizeof(*context));
859 if (context == NULL) {
860 printf("dhcp: failed to allocate context\n");
861 error = ENOMEM;
862 goto failed;
863 }
864 bzero(context, sizeof(*context));
865
866 /* get a socket */
867 error = socreate(AF_INET, &context->so, SOCK_DGRAM, 0);
868 if (error != 0) {
869 printf("dhcp: socreate failed %d\n", error);
870 goto failed;
871 }
872
873 /* assign 127.0.0.1 to lo0 so that the bind will succeed */
874 lo_addr.s_addr = htonl(INADDR_LOOPBACK);
875 lo_mask.s_addr = htonl(IN_CLASSA_NET);
876 error = inet_aifaddr(context->so, "lo0", &lo_addr, &lo_mask, NULL);
877 if (error != 0) {
878 printf("dhcp: assigning loopback address failed %d\n", error);
879 }
880
881 /* enable reception of DHCP packets before an address is assigned */
882 snprintf(context->ifr.ifr_name,
883 sizeof(context->ifr.ifr_name), "%s%d", ifp->if_name,
884 ifp->if_unit);
885 context->ifr.ifr_intval = 1;
886
887 error = ifioctl(context->so, SIOCAUTOADDR, (caddr_t)&context->ifr, procp);
888 if (error) {
889 printf("dhcp: SIOCAUTOADDR failed: %d\n", error);
890 goto failed;
891 }
892 dprintf(("dhcp: SIOCAUTOADDR done\n"));
893
894 error = ifioctl(context->so, SIOCPROTOATTACH, (caddr_t)&context->ifr,
895 procp);
896 if (error) {
897 printf("dhcp: SIOCPROTOATTACH failed: %d\n", error);
898 goto failed;
899 }
900 dprintf(("dhcp: SIOCPROTOATTACH done\n"));
901
902 /* bind the socket */
903 sin.sin_len = sizeof(sin);
904 sin.sin_family = AF_INET;
905 sin.sin_port = htons(IPPORT_BOOTPC);
906 sin.sin_addr.s_addr = INADDR_ANY;
907 error = sobind(context->so, (struct sockaddr *)&sin);
908 if (error) {
909 printf("dhcp: sobind failed, %d\n", error);
910 goto failed;
911 }
912
913 /* make it non-blocking I/O */
914 socket_lock(context->so, 1);
915 context->so->so_state |= SS_NBIO;
916 socket_unlock(context->so, 1);
917
918 /* save passed-in information */
919 context->max_try = max_try;
920 context->dl_p = dl_p;
921 context->ifp = ifp;
922
923 /* get a random transaction id */
924 context->xid = random();
925
926 return (context);
927
928 failed:
929 dhcp_context_free(context, procp);
930 *error_p = error;
931 return (NULL);
932}
933
934/*
935 * Routine: dhcp
936 * Function:
937 * Do DHCP over the specified interface to retrieve the IP address,
938 * subnet mask, and router.
939 */
940int
941dhcp(struct ifnet * ifp, struct in_addr * iaddr_p, int max_try,
942 struct in_addr * netmask_p, struct in_addr * router_p,
943 struct proc * procp)
944{
945 int error = 0;
946 struct dhcp_context * context;
947
948 context = dhcp_context_create(ifp, max_try, procp, &error);
949 if (context == NULL) {
950 return (error);
951 }
952
953 /* start DHCP in the INIT state */
954 error = dhcp_init(context);
955 if (error == 0) {
956 *iaddr_p = context->iaddr;
957 *netmask_p = context->netmask;
958 *router_p = context->router;
959 }
960 dhcp_context_free(context, procp);
961 return (error);
962}