source: trunk/mindi-busybox/networking/udhcp/dhcpc.c @ 904

Last change on this file since 904 was 821, checked in by Bruno Cornec, 14 years ago

Addition of busybox 1.2.1 as a mindi-busybox new package
This should avoid delivering binary files in mindi not built there (Fedora and Debian are quite serious about that)

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