diff options
Diffstat (limited to 'networking/udhcp/dhcpc.c')
-rw-r--r-- | networking/udhcp/dhcpc.c | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c new file mode 100644 index 000000000..71315ff0a --- /dev/null +++ b/networking/udhcp/dhcpc.c | |||
@@ -0,0 +1,509 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* dhcpc.c | ||
3 | * | ||
4 | * udhcp DHCP client | ||
5 | * | ||
6 | * Russ Dill <Russ.Dill@asu.edu> July 2001 | ||
7 | * | ||
8 | * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. | ||
9 | */ | ||
10 | |||
11 | #include <getopt.h> | ||
12 | |||
13 | #include "common.h" | ||
14 | #include "dhcpd.h" | ||
15 | #include "dhcpc.h" | ||
16 | #include "options.h" | ||
17 | |||
18 | |||
19 | static int state; | ||
20 | /* Something is definitely wrong here. IPv4 addresses | ||
21 | * in variables of type long?? BTW, we use inet_ntoa() | ||
22 | * in the code. Manpage says that struct in_addr has a member of type long (!) | ||
23 | * which holds IPv4 address, and the struct is passed by value (!!) | ||
24 | */ | ||
25 | static unsigned long requested_ip; /* = 0 */ | ||
26 | static unsigned long server_addr; | ||
27 | static unsigned long timeout; | ||
28 | static int packet_num; /* = 0 */ | ||
29 | static int fd = -1; | ||
30 | |||
31 | #define LISTEN_NONE 0 | ||
32 | #define LISTEN_KERNEL 1 | ||
33 | #define LISTEN_RAW 2 | ||
34 | static int listen_mode; | ||
35 | |||
36 | struct client_config_t client_config; | ||
37 | |||
38 | |||
39 | /* just a little helper */ | ||
40 | static void change_mode(int new_mode) | ||
41 | { | ||
42 | DEBUG("entering %s listen mode", | ||
43 | new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); | ||
44 | if (fd >= 0) close(fd); | ||
45 | fd = -1; | ||
46 | listen_mode = new_mode; | ||
47 | } | ||
48 | |||
49 | |||
50 | /* perform a renew */ | ||
51 | static void perform_renew(void) | ||
52 | { | ||
53 | bb_info_msg("Performing a DHCP renew"); | ||
54 | switch (state) { | ||
55 | case BOUND: | ||
56 | change_mode(LISTEN_KERNEL); | ||
57 | case RENEWING: | ||
58 | case REBINDING: | ||
59 | state = RENEW_REQUESTED; | ||
60 | break; | ||
61 | case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ | ||
62 | udhcp_run_script(NULL, "deconfig"); | ||
63 | case REQUESTING: | ||
64 | case RELEASED: | ||
65 | change_mode(LISTEN_RAW); | ||
66 | state = INIT_SELECTING; | ||
67 | break; | ||
68 | case INIT_SELECTING: | ||
69 | break; | ||
70 | } | ||
71 | |||
72 | /* start things over */ | ||
73 | packet_num = 0; | ||
74 | |||
75 | /* Kill any timeouts because the user wants this to hurry along */ | ||
76 | timeout = 0; | ||
77 | } | ||
78 | |||
79 | |||
80 | /* perform a release */ | ||
81 | static void perform_release(void) | ||
82 | { | ||
83 | char buffer[16]; | ||
84 | struct in_addr temp_addr; | ||
85 | |||
86 | /* send release packet */ | ||
87 | if (state == BOUND || state == RENEWING || state == REBINDING) { | ||
88 | temp_addr.s_addr = server_addr; | ||
89 | sprintf(buffer, "%s", inet_ntoa(temp_addr)); | ||
90 | temp_addr.s_addr = requested_ip; | ||
91 | bb_info_msg("Unicasting a release of %s to %s", | ||
92 | inet_ntoa(temp_addr), buffer); | ||
93 | send_release(server_addr, requested_ip); /* unicast */ | ||
94 | udhcp_run_script(NULL, "deconfig"); | ||
95 | } | ||
96 | bb_info_msg("Entering released state"); | ||
97 | |||
98 | change_mode(LISTEN_NONE); | ||
99 | state = RELEASED; | ||
100 | timeout = 0x7fffffff; | ||
101 | } | ||
102 | |||
103 | |||
104 | static void client_background(void) | ||
105 | { | ||
106 | udhcp_background(client_config.pidfile); | ||
107 | client_config.foreground = 1; /* Do not fork again. */ | ||
108 | client_config.background_if_no_lease = 0; | ||
109 | } | ||
110 | |||
111 | |||
112 | static uint8_t* alloc_dhcp_option(int code, const char *str, int extra) | ||
113 | { | ||
114 | uint8_t *storage; | ||
115 | int len = strlen(str); | ||
116 | if (len > 255) len = 255; | ||
117 | storage = xzalloc(len + extra + OPT_DATA); | ||
118 | storage[OPT_CODE] = code; | ||
119 | storage[OPT_LEN] = len + extra; | ||
120 | memcpy(storage + extra + OPT_DATA, str, len); | ||
121 | return storage; | ||
122 | } | ||
123 | |||
124 | |||
125 | int udhcpc_main(int argc, char *argv[]) | ||
126 | { | ||
127 | uint8_t *temp, *message; | ||
128 | char *str_c, *str_V, *str_h, *str_F, *str_r, *str_T, *str_t; | ||
129 | unsigned long t1 = 0, t2 = 0, xid = 0; | ||
130 | unsigned long start = 0, lease = 0; | ||
131 | long now; | ||
132 | unsigned opt; | ||
133 | int max_fd; | ||
134 | int sig; | ||
135 | int retval; | ||
136 | int len; | ||
137 | int no_clientid = 0; | ||
138 | fd_set rfds; | ||
139 | struct timeval tv; | ||
140 | struct dhcpMessage packet; | ||
141 | struct in_addr temp_addr; | ||
142 | |||
143 | enum { | ||
144 | OPT_c = 1 << 0, | ||
145 | OPT_C = 1 << 1, | ||
146 | OPT_V = 1 << 2, | ||
147 | OPT_f = 1 << 3, | ||
148 | OPT_b = 1 << 4, | ||
149 | OPT_H = 1 << 5, | ||
150 | OPT_h = 1 << 6, | ||
151 | OPT_F = 1 << 7, | ||
152 | OPT_i = 1 << 8, | ||
153 | OPT_n = 1 << 9, | ||
154 | OPT_p = 1 << 10, | ||
155 | OPT_q = 1 << 11, | ||
156 | OPT_R = 1 << 12, | ||
157 | OPT_r = 1 << 13, | ||
158 | OPT_s = 1 << 14, | ||
159 | OPT_T = 1 << 15, | ||
160 | OPT_t = 1 << 16, | ||
161 | OPT_v = 1 << 17, | ||
162 | }; | ||
163 | #if ENABLE_GETOPT_LONG | ||
164 | static const struct option arg_options[] = { | ||
165 | { "clientid", required_argument, 0, 'c' }, | ||
166 | { "clientid-none", no_argument, 0, 'C' }, | ||
167 | { "vendorclass", required_argument, 0, 'V' }, | ||
168 | { "foreground", no_argument, 0, 'f' }, | ||
169 | { "background", no_argument, 0, 'b' }, | ||
170 | { "hostname", required_argument, 0, 'H' }, | ||
171 | { "hostname", required_argument, 0, 'h' }, | ||
172 | { "fqdn", required_argument, 0, 'F' }, | ||
173 | { "interface", required_argument, 0, 'i' }, | ||
174 | { "now", no_argument, 0, 'n' }, | ||
175 | { "pidfile", required_argument, 0, 'p' }, | ||
176 | { "quit", no_argument, 0, 'q' }, | ||
177 | { "release", no_argument, 0, 'R' }, | ||
178 | { "request", required_argument, 0, 'r' }, | ||
179 | { "script", required_argument, 0, 's' }, | ||
180 | { "timeout", required_argument, 0, 'T' }, | ||
181 | { "version", no_argument, 0, 'v' }, | ||
182 | { "retries", required_argument, 0, 't' }, | ||
183 | { 0, 0, 0, 0 } | ||
184 | }; | ||
185 | #endif | ||
186 | /* Default options. */ | ||
187 | client_config.interface = "eth0"; | ||
188 | client_config.script = DEFAULT_SCRIPT; | ||
189 | client_config.retries = 3; | ||
190 | client_config.timeout = 3; | ||
191 | |||
192 | /* Parse command line */ | ||
193 | opt_complementary = "?:c--C:C--c" // mutually exclusive | ||
194 | ":hH:Hh"; // -h and -H are the same | ||
195 | #if ENABLE_GETOPT_LONG | ||
196 | applet_long_options = arg_options; | ||
197 | #endif | ||
198 | opt = getopt32(argc, argv, "c:CV:fbH:h:F:i:np:qRr:s:T:t:v", | ||
199 | &str_c, &str_V, &str_h, &str_h, &str_F, | ||
200 | &client_config.interface, &client_config.pidfile, &str_r, | ||
201 | &client_config.script, &str_T, &str_t | ||
202 | ); | ||
203 | |||
204 | if (opt & OPT_c) | ||
205 | client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0); | ||
206 | if (opt & OPT_C) | ||
207 | no_clientid = 1; | ||
208 | if (opt & OPT_V) | ||
209 | client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0); | ||
210 | if (opt & OPT_f) | ||
211 | client_config.foreground = 1; | ||
212 | if (opt & OPT_b) | ||
213 | client_config.background_if_no_lease = 1; | ||
214 | if (opt & OPT_h) | ||
215 | client_config.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0); | ||
216 | if (opt & OPT_F) { | ||
217 | client_config.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3); | ||
218 | /* Flags: 0000NEOS | ||
219 | S: 1 => Client requests Server to update A RR in DNS as well as PTR | ||
220 | O: 1 => Server indicates to client that DNS has been updated regardless | ||
221 | E: 1 => Name data is DNS format, i.e. <4>host<6>domain<4>com<0> not "host.domain.com" | ||
222 | N: 1 => Client requests Server to not update DNS | ||
223 | */ | ||
224 | client_config.fqdn[OPT_DATA + 0] = 0x1; | ||
225 | /* client_config.fqdn[OPT_DATA + 1] = 0; - redundant */ | ||
226 | /* client_config.fqdn[OPT_DATA + 2] = 0; - redundant */ | ||
227 | } | ||
228 | // if (opt & OPT_i) client_config.interface = ... | ||
229 | if (opt & OPT_n) | ||
230 | client_config.abort_if_no_lease = 1; | ||
231 | // if (opt & OPT_p) client_config.pidfile = ... | ||
232 | if (opt & OPT_q) | ||
233 | client_config.quit_after_lease = 1; | ||
234 | if (opt & OPT_R) | ||
235 | client_config.release_on_quit = 1; | ||
236 | if (opt & OPT_r) | ||
237 | requested_ip = inet_addr(str_r); | ||
238 | // if (opt & OPT_s) client_config.script = ... | ||
239 | if (opt & OPT_T) | ||
240 | client_config.timeout = xatoi_u(str_T); | ||
241 | if (opt & OPT_t) | ||
242 | client_config.retries = xatoi_u(str_t); | ||
243 | if (opt & OPT_v) { | ||
244 | printf("version %s\n\n", BB_VER); | ||
245 | return 0; | ||
246 | } | ||
247 | |||
248 | /* Start the log, sanitize fd's, and write a pid file */ | ||
249 | udhcp_start_log_and_pid(client_config.pidfile); | ||
250 | |||
251 | if (read_interface(client_config.interface, &client_config.ifindex, | ||
252 | NULL, client_config.arp) < 0) | ||
253 | return 1; | ||
254 | |||
255 | /* if not set, and not suppressed, setup the default client ID */ | ||
256 | if (!client_config.clientid && !no_clientid) { | ||
257 | client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7); | ||
258 | client_config.clientid[OPT_DATA] = 1; | ||
259 | memcpy(client_config.clientid + OPT_DATA+1, client_config.arp, 6); | ||
260 | } | ||
261 | |||
262 | if (!client_config.vendorclass) | ||
263 | client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, "udhcp "BB_VER, 0); | ||
264 | |||
265 | /* setup the signal pipe */ | ||
266 | udhcp_sp_setup(); | ||
267 | |||
268 | state = INIT_SELECTING; | ||
269 | udhcp_run_script(NULL, "deconfig"); | ||
270 | change_mode(LISTEN_RAW); | ||
271 | |||
272 | for (;;) { | ||
273 | tv.tv_sec = timeout - uptime(); | ||
274 | tv.tv_usec = 0; | ||
275 | |||
276 | if (listen_mode != LISTEN_NONE && fd < 0) { | ||
277 | if (listen_mode == LISTEN_KERNEL) | ||
278 | fd = listen_socket(INADDR_ANY, CLIENT_PORT, client_config.interface); | ||
279 | else | ||
280 | fd = raw_socket(client_config.ifindex); | ||
281 | if (fd < 0) { | ||
282 | bb_perror_msg("FATAL: cannot listen on socket"); | ||
283 | return 0; | ||
284 | } | ||
285 | } | ||
286 | max_fd = udhcp_sp_fd_set(&rfds, fd); | ||
287 | |||
288 | if (tv.tv_sec > 0) { | ||
289 | DEBUG("Waiting on select..."); | ||
290 | retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); | ||
291 | } else retval = 0; /* If we already timed out, fall through */ | ||
292 | |||
293 | now = uptime(); | ||
294 | if (retval == 0) { | ||
295 | /* timeout dropped to zero */ | ||
296 | switch (state) { | ||
297 | case INIT_SELECTING: | ||
298 | if (packet_num < client_config.retries) { | ||
299 | if (packet_num == 0) | ||
300 | xid = random_xid(); | ||
301 | |||
302 | /* send discover packet */ | ||
303 | send_discover(xid, requested_ip); /* broadcast */ | ||
304 | |||
305 | timeout = now + client_config.timeout; | ||
306 | packet_num++; | ||
307 | } else { | ||
308 | udhcp_run_script(NULL, "leasefail"); | ||
309 | if (client_config.background_if_no_lease) { | ||
310 | bb_info_msg("No lease, forking to background"); | ||
311 | client_background(); | ||
312 | } else if (client_config.abort_if_no_lease) { | ||
313 | bb_info_msg("No lease, failing"); | ||
314 | return 1; | ||
315 | } | ||
316 | /* wait to try again */ | ||
317 | packet_num = 0; | ||
318 | timeout = now + 60; | ||
319 | } | ||
320 | break; | ||
321 | case RENEW_REQUESTED: | ||
322 | case REQUESTING: | ||
323 | if (packet_num < client_config.retries) { | ||
324 | /* send request packet */ | ||
325 | if (state == RENEW_REQUESTED) | ||
326 | send_renew(xid, server_addr, requested_ip); /* unicast */ | ||
327 | else send_selecting(xid, server_addr, requested_ip); /* broadcast */ | ||
328 | |||
329 | timeout = now + ((packet_num == 2) ? 10 : 2); | ||
330 | packet_num++; | ||
331 | } else { | ||
332 | /* timed out, go back to init state */ | ||
333 | if (state == RENEW_REQUESTED) udhcp_run_script(NULL, "deconfig"); | ||
334 | state = INIT_SELECTING; | ||
335 | timeout = now; | ||
336 | packet_num = 0; | ||
337 | change_mode(LISTEN_RAW); | ||
338 | } | ||
339 | break; | ||
340 | case BOUND: | ||
341 | /* Lease is starting to run out, time to enter renewing state */ | ||
342 | state = RENEWING; | ||
343 | change_mode(LISTEN_KERNEL); | ||
344 | DEBUG("Entering renew state"); | ||
345 | /* fall right through */ | ||
346 | case RENEWING: | ||
347 | /* Either set a new T1, or enter REBINDING state */ | ||
348 | if ((t2 - t1) <= (lease / 14400 + 1)) { | ||
349 | /* timed out, enter rebinding state */ | ||
350 | state = REBINDING; | ||
351 | timeout = now + (t2 - t1); | ||
352 | DEBUG("Entering rebinding state"); | ||
353 | } else { | ||
354 | /* send a request packet */ | ||
355 | send_renew(xid, server_addr, requested_ip); /* unicast */ | ||
356 | |||
357 | t1 = (t2 - t1) / 2 + t1; | ||
358 | timeout = t1 + start; | ||
359 | } | ||
360 | break; | ||
361 | case REBINDING: | ||
362 | /* Either set a new T2, or enter INIT state */ | ||
363 | if ((lease - t2) <= (lease / 14400 + 1)) { | ||
364 | /* timed out, enter init state */ | ||
365 | state = INIT_SELECTING; | ||
366 | bb_info_msg("Lease lost, entering init state"); | ||
367 | udhcp_run_script(NULL, "deconfig"); | ||
368 | timeout = now; | ||
369 | packet_num = 0; | ||
370 | change_mode(LISTEN_RAW); | ||
371 | } else { | ||
372 | /* send a request packet */ | ||
373 | send_renew(xid, 0, requested_ip); /* broadcast */ | ||
374 | |||
375 | t2 = (lease - t2) / 2 + t2; | ||
376 | timeout = t2 + start; | ||
377 | } | ||
378 | break; | ||
379 | case RELEASED: | ||
380 | /* yah, I know, *you* say it would never happen */ | ||
381 | timeout = 0x7fffffff; | ||
382 | break; | ||
383 | } | ||
384 | } else if (retval > 0 && listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)) { | ||
385 | /* a packet is ready, read it */ | ||
386 | |||
387 | if (listen_mode == LISTEN_KERNEL) | ||
388 | len = udhcp_get_packet(&packet, fd); | ||
389 | else len = get_raw_packet(&packet, fd); | ||
390 | |||
391 | if (len == -1 && errno != EINTR) { | ||
392 | DEBUG("error on read, %s, reopening socket", strerror(errno)); | ||
393 | change_mode(listen_mode); /* just close and reopen */ | ||
394 | } | ||
395 | if (len < 0) continue; | ||
396 | |||
397 | if (packet.xid != xid) { | ||
398 | DEBUG("Ignoring XID %lx (our xid is %lx)", | ||
399 | (unsigned long) packet.xid, xid); | ||
400 | continue; | ||
401 | } | ||
402 | |||
403 | /* Ignore packets that aren't for us */ | ||
404 | if (memcmp(packet.chaddr, client_config.arp, 6)) { | ||
405 | DEBUG("Packet does not have our chaddr - ignoring"); | ||
406 | continue; | ||
407 | } | ||
408 | |||
409 | if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { | ||
410 | bb_error_msg("cannot get option from packet - ignoring"); | ||
411 | continue; | ||
412 | } | ||
413 | |||
414 | switch (state) { | ||
415 | case INIT_SELECTING: | ||
416 | /* Must be a DHCPOFFER to one of our xid's */ | ||
417 | if (*message == DHCPOFFER) { | ||
418 | temp = get_option(&packet, DHCP_SERVER_ID); | ||
419 | if (temp) { | ||
420 | server_addr = *(uint32_t*)temp; | ||
421 | xid = packet.xid; | ||
422 | requested_ip = packet.yiaddr; | ||
423 | |||
424 | /* enter requesting state */ | ||
425 | state = REQUESTING; | ||
426 | timeout = now; | ||
427 | packet_num = 0; | ||
428 | } else { | ||
429 | bb_error_msg("no server ID in message"); | ||
430 | } | ||
431 | } | ||
432 | break; | ||
433 | case RENEW_REQUESTED: | ||
434 | case REQUESTING: | ||
435 | case RENEWING: | ||
436 | case REBINDING: | ||
437 | if (*message == DHCPACK) { | ||
438 | temp = get_option(&packet, DHCP_LEASE_TIME); | ||
439 | if (!temp) { | ||
440 | bb_error_msg("no lease time with ACK, using 1 hour lease"); | ||
441 | lease = 60 * 60; | ||
442 | } else { | ||
443 | lease = ntohl(*(uint32_t*)temp); | ||
444 | } | ||
445 | |||
446 | /* enter bound state */ | ||
447 | t1 = lease / 2; | ||
448 | |||
449 | /* little fixed point for n * .875 */ | ||
450 | t2 = (lease * 0x7) >> 3; | ||
451 | temp_addr.s_addr = packet.yiaddr; | ||
452 | bb_info_msg("Lease of %s obtained, lease time %ld", | ||
453 | inet_ntoa(temp_addr), lease); | ||
454 | start = now; | ||
455 | timeout = t1 + start; | ||
456 | requested_ip = packet.yiaddr; | ||
457 | udhcp_run_script(&packet, | ||
458 | ((state == RENEWING || state == REBINDING) ? "renew" : "bound")); | ||
459 | |||
460 | state = BOUND; | ||
461 | change_mode(LISTEN_NONE); | ||
462 | if (client_config.quit_after_lease) { | ||
463 | if (client_config.release_on_quit) | ||
464 | perform_release(); | ||
465 | return 0; | ||
466 | } | ||
467 | if (!client_config.foreground) | ||
468 | client_background(); | ||
469 | |||
470 | } else if (*message == DHCPNAK) { | ||
471 | /* return to init state */ | ||
472 | bb_info_msg("Received DHCP NAK"); | ||
473 | udhcp_run_script(&packet, "nak"); | ||
474 | if (state != REQUESTING) | ||
475 | udhcp_run_script(NULL, "deconfig"); | ||
476 | state = INIT_SELECTING; | ||
477 | timeout = now; | ||
478 | requested_ip = 0; | ||
479 | packet_num = 0; | ||
480 | change_mode(LISTEN_RAW); | ||
481 | sleep(3); /* avoid excessive network traffic */ | ||
482 | } | ||
483 | break; | ||
484 | /* case BOUND, RELEASED: - ignore all packets */ | ||
485 | } | ||
486 | } else if (retval > 0 && (sig = udhcp_sp_read(&rfds))) { | ||
487 | switch (sig) { | ||
488 | case SIGUSR1: | ||
489 | perform_renew(); | ||
490 | break; | ||
491 | case SIGUSR2: | ||
492 | perform_release(); | ||
493 | break; | ||
494 | case SIGTERM: | ||
495 | bb_info_msg("Received SIGTERM"); | ||
496 | if (client_config.release_on_quit) | ||
497 | perform_release(); | ||
498 | return 0; | ||
499 | } | ||
500 | } else if (retval == -1 && errno == EINTR) { | ||
501 | /* a signal was caught */ | ||
502 | } else { | ||
503 | /* An error occured */ | ||
504 | bb_perror_msg("select"); | ||
505 | } | ||
506 | |||
507 | } | ||
508 | return 0; | ||
509 | } | ||