]> git.saurik.com Git - apple/xnu.git/blob - bsd/netinet/in_dhcp.c
xnu-2422.110.17.tar.gz
[apple/xnu.git] / bsd / netinet / in_dhcp.c
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
89 const 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
98 inet_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
123 struct 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
140 static __inline__ struct dhcp_packet *
141 dhcp_context_request(struct dhcp_context * context)
142 {
143 return ((struct dhcp_packet *)(void *)context->request);
144 }
145
146 static __inline__ struct dhcp *
147 dhcp_context_reply(struct dhcp_context * context)
148 {
149 return ((struct dhcp *)(void *)context->reply);
150 }
151
152 struct mbuf * ip_pkt_to_mbuf(caddr_t pkt, int pktsize);
153
154 static int
155 receive_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
165 static 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
172 static __inline__ long
173 random_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
180 static void
181 init_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 */
205 static void
206 make_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 */
248 struct mbuf *
249 ip_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
267 static __inline__ u_char *
268 link_address(struct sockaddr_dl * dl_p)
269 {
270 return (u_char *)(dl_p->sdl_data + dl_p->sdl_nlen);
271 }
272
273 static __inline__ int
274 link_address_length(struct sockaddr_dl * dl_p)
275 {
276 return (dl_p->sdl_alen);
277 }
278
279 static __inline__ void
280 link_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
291 static struct sockaddr_dl *
292 link_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 */
302 static int
303 send_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 */
320 static int
321 receive_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 */
343 static void
344 dhcp_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
367 static __inline__ int
368 rate_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
382 static dhcp_msgtype_t
383 get_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
395 static int
396 dhcp_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
492 static int
493 dhcp_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
578 static int
579 dhcp_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 */
690 static int
691 dhcp_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
782 static void
783 dhcp_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
804 static struct dhcp_context *
805 dhcp_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 */
913 int
914 dhcp(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 }