]>
Commit | Line | Data |
---|---|---|
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 | ||
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 *)&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 | ||
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 *)context->request); | |
144 | } | |
145 | ||
146 | static __inline__ struct dhcp * | |
147 | dhcp_context_reply(struct dhcp_context * context) | |
148 | { | |
149 | return ((struct dhcp *)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 | #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 | ||
297 | static struct sockaddr_dl * | |
298 | link_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 | */ | |
320 | static int | |
321 | send_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 | */ | |
338 | static int | |
339 | receive_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 | */ | |
361 | static void | |
362 | dhcp_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 | |
385 | static __inline__ int | |
386 | rate_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 | ||
400 | static dhcp_msgtype_t | |
401 | get_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 | ||
413 | static int | |
414 | dhcp_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 | ||
510 | static int | |
511 | dhcp_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(¤t_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(¤t_time); | |
589 | } | |
590 | error = ETIMEDOUT; | |
591 | goto failed; | |
592 | ||
593 | done: | |
594 | error = 0; | |
595 | ||
596 | failed: | |
597 | return (error); | |
598 | } | |
599 | ||
600 | static int | |
601 | dhcp_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 | */ | |
712 | static int | |
713 | dhcp_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(¤t_time); | |
797 | } | |
798 | error = ETIMEDOUT; | |
799 | goto failed; | |
800 | ||
801 | done: | |
802 | error = 0; | |
803 | ||
804 | failed: | |
805 | return (error); | |
806 | } | |
807 | ||
808 | static void | |
809 | dhcp_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 | ||
830 | static struct dhcp_context * | |
831 | dhcp_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 | */ | |
940 | int | |
941 | dhcp(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 | } |