options.c 17.6 KB
Newer Older
pdw's avatar
pdw committed
1
2
3
4
5
6
/*
 * options.c:
 *
 *
 */

7
8
#include "config.h"

chris's avatar
""    
chris committed
9
10
#include <sys/types.h>

pdw's avatar
pdw committed
11
12
#include <stdio.h>
#include <string.h>
chris's avatar
""    
chris committed
13
14
15
16
#include <stdlib.h>
#include <unistd.h>

#include <sys/ioctl.h>
17
#include <sys/socket.h>
chris's avatar
""    
chris committed
18
19
#include <netinet/in.h>
#include <arpa/inet.h>
20
#include <net/if.h>
chris's avatar
""    
chris committed
21
22

#include "iftop.h"
pdw's avatar
pdw committed
23
#include "options.h"
pdw's avatar
pdw committed
24
#include "cfgfile.h"
chris's avatar
""    
chris committed
25
#include "integers.h"
pdw's avatar
pdw committed
26

chris's avatar
""    
chris committed
27
28
29
30
#if !defined(HAVE_INET_ATON) && defined(HAVE_INET_PTON)
#   define inet_aton(a, b)  inet_pton(AF_INET, (a), (b))
#endif

pdw's avatar
pdw committed
31
32
options_t options;

33
char optstr[] = "+i:f:nNF:G:lhpbBPm:c:";
pdw's avatar
pdw committed
34
35
36

/* Global options. */

chris's avatar
""    
chris committed
37
38
39
40
41
42
/* Selecting an interface on which to listen: */

/* This is a list of interface name prefixes which are `bad' in the sense
 * that they don't refer to interfaces of external type on which we are
 * likely to want to listen. We also compare candidate interfaces to lo. */
static char *bad_interface_names[] = {
43
            "lo:",
pdw's avatar
pdw committed
44
45
46
            "lo",
            "stf",     /* pseudo-device 6to4 tunnel interface */
            "gif",     /* psuedo-device generic tunnel interface */
chris's avatar
""    
chris committed
47
48
            "dummy",
            "vmnet",
49
50
            "wmaster", /* wmaster0 is an internal-use interface for mac80211, a Linux WiFi API. */
            NULL       /* last entry must be NULL */
chris's avatar
""    
chris committed
51
52
        };

pdw's avatar
pdw committed
53
54
config_enumeration_type sort_enumeration[] = {
	{ "2s", OPTION_SORT_DIV1 },
pdw's avatar
pdw committed
55
56
	{ "10s", OPTION_SORT_DIV2 },
	{ "40s", OPTION_SORT_DIV3 },
pdw's avatar
pdw committed
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
	{ "source", OPTION_SORT_SRC },
	{ "destination", OPTION_SORT_SRC },
	{ NULL, -1 }
};

config_enumeration_type linedisplay_enumeration[] = {
	{ "two-line", OPTION_LINEDISPLAY_TWO_LINE },
	{ "one-line-both", OPTION_LINEDISPLAY_ONE_LINE_BOTH },
	{ "one-line-sent", OPTION_LINEDISPLAY_ONE_LINE_SENT },
	{ "one-line-received", OPTION_LINEDISPLAY_ONE_LINE_RECV },
	{ NULL, -1 }
};

config_enumeration_type showports_enumeration[] = {
	{ "off", OPTION_PORTS_OFF },
	{ "source-only", OPTION_PORTS_SRC },
	{ "destination-only", OPTION_PORTS_DEST },
	{ "on", OPTION_PORTS_ON },
	{ NULL, -1 }
};

chris's avatar
""    
chris committed
78
79
80
81
82
83
84
85
86
87
static int is_bad_interface_name(char *i) {
    char **p;
    for (p = bad_interface_names; *p; ++p)
        if (strncmp(i, *p, strlen(*p)) == 0)
            return 1;
    return 0;
}

/* This finds the first interface which is up and is not the loopback
 * interface or one of the interface types listed in bad_interface_names. */
chris's avatar
""    
chris committed
88
static char *get_first_interface(void) {
89
    struct if_nameindex * nameindex;
90
    struct ifreq ifr;
chris's avatar
""    
chris committed
91
    char *i = NULL;
92
    int j = 0;
93
    int s;
chris's avatar
""    
chris committed
94
    /* Use if_nameindex(3) instead? */
95
96
97

    nameindex = if_nameindex();
    if(nameindex == NULL) {
pdw's avatar
pdw committed
98
        return NULL;
99
100
    }

101
102
    s = socket(AF_INET, SOCK_DGRAM, 0); /* any sort of IP socket will do */

103
104
    while(nameindex[j].if_index != 0) {
        if (strcmp(nameindex[j].if_name, "lo") != 0 && !is_bad_interface_name(nameindex[j].if_name)) {
105
106
107
108
109
            strncpy(ifr.ifr_name, nameindex[j].if_name, sizeof(ifr.ifr_name));
            if ((s == -1) || (ioctl(s, SIOCGIFFLAGS, &ifr) != -1) || (ifr.ifr_flags & IFF_UP)) {
                i = xstrdup(nameindex[j].if_name);
                break;
            }
chris's avatar
""    
chris committed
110
        }
111
        j++;
chris's avatar
""    
chris committed
112
    }
113
    if_freenameindex(nameindex);
chris's avatar
""    
chris committed
114
115
116
    return i;
}

pdw's avatar
pdw committed
117
118
void options_set_defaults() {
    char *s;
chris's avatar
""    
chris committed
119
120
121
122
123
124
    /* Should go through the list of interfaces, and find the first one which
     * is up and is not lo or dummy*. */
    options.interface = get_first_interface();
    if (!options.interface)
        options.interface = "eth0";

pdw's avatar
pdw committed
125
126
127
128
    options.filtercode = NULL;
    options.netfilter = 0;
    inet_aton("10.0.1.0", &options.netfilternet);
    inet_aton("255.255.255.0", &options.netfiltermask);
129
130
131
132
    options.netfilter6 = 0;
    inet_pton(AF_INET6, "fe80::", &options.netfilter6net);	/* Link-local */
    inet_pton(AF_INET6, "ffff::", &options.netfilter6mask);
    options.link_local = 0;
pdw's avatar
pdw committed
133
    options.dnsresolution = 1;
134
    options.portresolution = 1;
135
136
137
138
#ifdef NEED_PROMISCUOUS_FOR_OUTGOING
    options.promiscuous = 1;
    options.promiscuous_but_choosy = 1;
#else
pdw's avatar
pdw committed
139
    options.promiscuous = 0;
140
141
    options.promiscuous_but_choosy = 0;
#endif
pdw's avatar
pdw committed
142
    options.showbars = 1;
pdw's avatar
pdw committed
143
    options.showports = OPTION_PORTS_OFF;
144
145
    options.aggregate_src = 0;
    options.aggregate_dest = 0;
pdw's avatar
pdw committed
146
    options.paused = 0;
pdw's avatar
pdw committed
147
    options.showhelp = 0;
148
    options.bandwidth_in_bytes = 0;
pdw's avatar
pdw committed
149
    options.sort = OPTION_SORT_DIV2;
pdw's avatar
pdw committed
150
    options.screenfilter = NULL;
151
    options.freezeorder = 0;
pdw's avatar
pdw committed
152
153
    options.linedisplay = OPTION_LINEDISPLAY_TWO_LINE;
    options.screen_offset = 0;
pdw's avatar
pdw committed
154
    options.show_totals = 0;
155
    options.max_bandwidth = 0; /* auto */
pdw's avatar
pdw committed
156
157
    options.log_scale = 0;
    options.bar_interval = 1;
pdw's avatar
pdw committed
158

pdw's avatar
pdw committed
159
    /* Figure out the name for the config file */
pdw's avatar
pdw committed
160
161
162
163
164
165
166
167
168
    s = getenv("HOME");
    if(s != NULL) {
        int i = strlen(s) + 9 + 1;
        options.config_file = xmalloc(i);
        snprintf(options.config_file,i,"%s/.iftoprc",s);
    }
    else {
        options.config_file = xstrdup("iftoprc");
    }
pdw's avatar
pdw committed
169
    options.config_file_specified = 0;
pdw's avatar
pdw committed
170
    
pdw's avatar
pdw committed
171
172
}

chris's avatar
""    
chris committed
173
static void die(char *msg) {
174
    fprintf(stderr, "%s", msg);
pdw's avatar
pdw committed
175
176
177
    exit(1);
}

178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
static void set_max_bandwidth(char* arg) {
    char* units;
    long long mult = 1;
    long long value;
    units = arg + strspn(arg, "0123456789");
    if(strlen(units) > 1) {
        die("Invalid units\n");
    }
    if(strlen(units) == 1) {
        if(*units == 'k' || *units == 'K') {
            mult = 1024;
        }
        else if(*units == 'm' || *units == 'M') {
            mult = 1024 * 1024;
        }
        else if(*units == 'g' || *units == 'G') {
            mult = 1024 * 1024 * 1024;
        }
    }
    *units = '\0';
    if(sscanf(arg, "%lld", &value) != 1) {
        die("Error reading max bandwidth\n");
    }
    options.max_bandwidth = value * mult;
}

chris's avatar
""    
chris committed
204
static void set_net_filter(char* arg) {
pdw's avatar
pdw committed
205
206
    char* mask;

chris's avatar
""    
chris committed
207
208
    mask = strchr(arg, '/');
    if (mask == NULL) {
pdw's avatar
pdw committed
209
210
211
212
        die("Could not parse net/mask\n");
    }
    *mask = '\0';
    mask++;
chris's avatar
""    
chris committed
213
    if (inet_aton(arg, &options.netfilternet) == 0)
pdw's avatar
pdw committed
214
        die("Invalid network address\n");
chris's avatar
""    
chris committed
215
    /* Accept a netmask like /24 or /255.255.255.0. */
216
217
    if (mask[strspn(mask, "0123456789")] == '\0') {
        /* Whole string is numeric */
chris's avatar
""    
chris committed
218
219
        int n;
        n = atoi(mask);
220
        if (n > 32) {
chris's avatar
""    
chris committed
221
            die("Invalid netmask");
222
        }
chris's avatar
""    
chris committed
223
        else {
224
225
226
227
228
229
230
            if(n == 32) {
              /* This needs to be special cased, although I don't fully 
               * understand why -pdw 
               */
              options.netfiltermask.s_addr = htonl(0xffffffffl);
            }
            else {
pdw's avatar
pdw committed
231
              u_int32_t mm = 0xffffffffl;
232
233
234
              mm >>= n;
              options.netfiltermask.s_addr = htonl(~mm);
            }
chris's avatar
""    
chris committed
235
        }
236
237
    } 
    else if (inet_aton(mask, &options.netfiltermask) == 0) {
chris's avatar
""    
chris committed
238
        die("Invalid netmask\n");
239
240
    }
    options.netfilternet.s_addr = options.netfilternet.s_addr & options.netfiltermask.s_addr;
pdw's avatar
pdw committed
241
242
243
244
245
246
247

    options.netfilter = 1;

}

/* usage:
 * Print usage information. */
chris's avatar
""    
chris committed
248
static void usage(FILE *fp) {
pdw's avatar
pdw committed
249
250
251
    fprintf(fp,
"iftop: display bandwidth usage on an interface by host\n"
"\n"
252
"Synopsis: iftop -h | [-npblNBP] [-i interface] [-f filter code]\n"
253
"                               [-F net/mask] [-G net6/mask6]\n"
pdw's avatar
pdw committed
254
255
"\n"
"   -h                  display this message\n"
256
"   -n                  don't do hostname lookups\n"
chris's avatar
""    
chris committed
257
"   -N                  don't convert port numbers to services\n"
pdw's avatar
pdw committed
258
259
"   -p                  run in promiscuous mode (show traffic between other\n"
"                       hosts on the same network segment)\n"
chris's avatar
""    
chris committed
260
"   -b                  don't display a bar graph of traffic\n"
261
"   -B                  Display bandwidth in bytes\n"
chris's avatar
""    
chris committed
262
"   -i interface        listen on named interface\n"
pdw's avatar
pdw committed
263
264
"   -f filter code      use filter code to select packets to count\n"
"                       (default: none, but only IP packets are counted)\n"
265
266
267
"   -F net/mask         show traffic flows in/out of IPv4 network\n"
"   -G net6/mask6       show traffic flows in/out of IPv6 network\n"
"   -l                  display and count link-local IPv6 traffic (default: off)\n"
pdw's avatar
pdw committed
268
"   -P                  show ports as well as hosts\n"
269
"   -m limit            sets the upper limit for the bandwidth scale\n"
pdw's avatar
pdw committed
270
"   -c config file      specifies an alternative configuration file\n"
pdw's avatar
pdw committed
271
"\n"
pdw's avatar
pdw committed
272
273
"iftop, version " IFTOP_VERSION "\n"
"copyright (c) 2002 Paul Warren <pdw@ex-parrot.com> and contributors\n"
pdw's avatar
pdw committed
274
275
276
            );
}

pdw's avatar
pdw committed
277
void options_read_args(int argc, char **argv) {
pdw's avatar
pdw committed
278
279
280
281
282
283
284
285
286
    int opt;

    opterr = 0;
    while ((opt = getopt(argc, argv, optstr)) != -1) {
        switch (opt) {
            case 'h':
                usage(stdout);
                exit(0);

287
            case 'n':
pdw's avatar
pdw committed
288
                config_set_string("dns-resolution","false");
pdw's avatar
pdw committed
289
290
291
                break;

            case 'N':
pdw's avatar
pdw committed
292
                config_set_string("port-resolution","false");
pdw's avatar
pdw committed
293
294
295
                break;

            case 'i':
pdw's avatar
pdw committed
296
                config_set_string("interface", optarg);
pdw's avatar
pdw committed
297
298
299
                break;

            case 'f':
pdw's avatar
pdw committed
300
                config_set_string("filter-code", optarg);
pdw's avatar
pdw committed
301
302
                break;

303
304
305
306
            case 'l':
                config_set_string("link-local", "true");
                break;

pdw's avatar
pdw committed
307
            case 'p':
pdw's avatar
pdw committed
308
                config_set_string("promiscuous", "true");
pdw's avatar
pdw committed
309
310
                break;

pdw's avatar
pdw committed
311
            case 'P':
pdw's avatar
pdw committed
312
                config_set_string("port-display", "on");
pdw's avatar
pdw committed
313
314
                break;

pdw's avatar
pdw committed
315
316
            case 'F':
                config_set_string("net-filter", optarg);
pdw's avatar
pdw committed
317
                break;
318
            
319
320
321
322
            case 'G':
                config_set_string("net-filter6", optarg);
                break;

323
            case 'm':
pdw's avatar
pdw committed
324
                config_set_string("max-bandwidth", optarg);
325
326
                break;

chris's avatar
""    
chris committed
327
            case 'b':
pdw's avatar
pdw committed
328
                config_set_string("show-bars", "false");
chris's avatar
""    
chris committed
329
330
                break;

331
            case 'B':
pdw's avatar
pdw committed
332
333
334
335
336
337
338
                config_set_string("use-bytes", "true");
                break;

            case 'c':
                xfree(options.config_file);
                options.config_file = xstrdup(optarg);
                options.config_file_specified = 1;
339
                break;
pdw's avatar
pdw committed
340

pdw's avatar
pdw committed
341
342
343
344
345
346
347
348
349
350
351
352
            case '?':
                fprintf(stderr, "iftop: unknown option -%c\n", optopt);
                usage(stderr);
                exit(1);

            case ':':
                fprintf(stderr, "iftop: option -%c requires an argument\n", optopt);
                usage(stderr);
                exit(1);
        }
    }

pdw's avatar
pdw committed
353

pdw's avatar
0.10.    
pdw committed
354
355
356
357
358
359
    if (optind != argc) {
        fprintf(stderr, "iftop: found arguments following options\n");
        fprintf(stderr, "*** some options have changed names since v0.9 ***\n");
        usage(stderr);
        exit(1);
    }
pdw's avatar
pdw committed
360
}
pdw's avatar
pdw committed
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463

/* options_config_get_string:
 * Gets a value from the config, sets *value to a copy of the value, if
 * found.  Leaves the option unchanged otherwise. */
int options_config_get_string(const char *name, char** value) {
    char *s;
    s = config_get_string(name);
    if(s != NULL) {
        *value = xstrdup(s);
        return 1;
    }
    return 0;
}

int options_config_get_bool(const char *name, int* value) {
    if(config_get_string(name)) {
        *value = config_get_bool(name);
        return 1;
    }
    return 0;
}

int options_config_get_int(const char *name, int* value) {
    if(config_get_string(name)) {
        config_get_int(name, value);
        return 1;
    }
    return 0;
}

int options_config_get_enum(char *name, config_enumeration_type* enumeration, int *result) {
    int i;
    if(config_get_string(name)) {
        if(config_get_enum(name, enumeration, &i)) {
            *result = i; 
            return 1;
        }
    }
    return 0;
}

int options_config_get_promiscuous() {
    if(config_get_string("promiscuous")) {
        options.promiscuous = config_get_bool("promiscuous");
        if(options.promiscuous) {
            /* User has explicitly requested promiscuous mode, so don't be
             * choosy */
            options.promiscuous_but_choosy = 0;
        }
        return 1;
    }
    return 0;
}

int options_config_get_bw_rate(char *directive, long long* result) {
    char* units;
    long long mult = 1;
    long long value;
    char *s;
    s = config_get_string(directive);
    if(s) {
        units = s + strspn(s, "0123456789");
        if(strlen(units) > 1) {
            fprintf(stderr, "Invalid units in value: %s\n", s);
            return 0;
        }
        if(strlen(units) == 1) {
            if(*units == 'k' || *units == 'K') {
                mult = 1024;
            }
            else if(*units == 'm' || *units == 'M') {
                mult = 1024 * 1024;
            }
            else if(*units == 'g' || *units == 'G') {
                mult = 1024 * 1024 * 1024;
            }
            else if(*units == 'b' || *units == 'B') {
                /* bits => mult = 1 */
            }
            else {
                fprintf(stderr, "Invalid units in value: %s\n", s);
                return 0;
            }
        }
        *units = '\0';
        if(sscanf(s, "%lld", &value) != 1) {
            fprintf(stderr, "Error reading rate: %s\n", s);
        }
        options.max_bandwidth = value * mult;
        return 1;
    }
    return 0;
}

/*
 * Read the net filter option.  
 */
int options_config_get_net_filter() {
    char* s;
    s = config_get_string("net-filter");
    if(s) {
        char* mask;

464
465
        options.netfilter = 0;

pdw's avatar
pdw committed
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
        mask = strchr(s, '/');
        if (mask == NULL) {
            fprintf(stderr, "Could not parse net/mask: %s\n", s);
            return 0;
        }
        *mask = '\0';
        mask++;
        if (inet_aton(s, &options.netfilternet) == 0) {
            fprintf(stderr, "Invalid network address: %s\n", s);
            return 0;
        }
        /* Accept a netmask like /24 or /255.255.255.0. */
        if (mask[strspn(mask, "0123456789")] == '\0') {
            /* Whole string is numeric */
            int n;
            n = atoi(mask);
            if (n > 32) {
483
                fprintf(stderr, "Invalid netmask length: %s\n", mask);
pdw's avatar
pdw committed
484
485
486
487
488
489
490
491
492
493
494
495
496
497
            }
            else {
                if(n == 32) {
                  /* This needs to be special cased, although I don't fully 
                   * understand why -pdw 
                   */
                  options.netfiltermask.s_addr = htonl(0xffffffffl);
                }
                else {
                  u_int32_t mm = 0xffffffffl;
                  mm >>= n;
                  options.netfiltermask.s_addr = htonl(~mm);
                }
            }
498
            options.netfilter = 1;
pdw's avatar
pdw committed
499
        } 
500
501
502
503
504
505
506
        else {
            if (inet_aton(mask, &options.netfiltermask) != 0)
                options.netfilter = 1;
            else {
                fprintf(stderr, "Invalid netmask: %s\n", s);
                return 0;
            }
pdw's avatar
pdw committed
507
508
509
510
511
512
513
        }
        options.netfilternet.s_addr = options.netfilternet.s_addr & options.netfiltermask.s_addr;
        return 1;
    }
    return 0;
}

514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
/*
 * Read the net filter IPv6 option.  
 */
int options_config_get_net_filter6() {
    char* s;
    int j;

    s = config_get_string("net-filter6");
    if(s) {
        char* mask;

        options.netfilter6 = 0;

        mask = strchr(s, '/');
        if (mask == NULL) {
            fprintf(stderr, "Could not parse IPv6 net/prefix: %s\n", s);
            return 0;
        }
        *mask = '\0';
        mask++;
        if (inet_pton(AF_INET6, s, &options.netfilter6net) == 0) {
            fprintf(stderr, "Invalid IPv6 network address: %s\n", s);
            return 0;
        }
        /* Accept prefix lengths and address expressions. */
        if (mask[strspn(mask, "0123456789")] == '\0') {
            /* Whole string is numeric */
            unsigned int n;

            n = atoi(mask);
            if (n > 128 || n < 1) {
                fprintf(stderr, "Invalid IPv6 prefix length: %s\n", mask);
            }
            else {
                int bl, rem;
549
550
                const uint8_t mm = 0xff;
                uint8_t part = mm;
551

552
553
554
                bl = n / 8;
                rem = n % 8;
                part <<= 8 - rem;
555
                for (j=0; j < bl; ++j)
556
557
                    options.netfilter6mask.s6_addr[j] = mm;

558
                if (rem > 0)
559
                    options.netfilter6mask.s6_addr[bl] = part;
560
                options.netfilter6 = 1;
561
              }
562
563
564
565
566
567
568
569
570
571
        }
        else {
            if (inet_pton(AF_INET6, mask, &options.netfilter6mask) != 0)
                options.netfilter6 = 1;
            else {
                fprintf(stderr, "Invalid IPv6 netmask: %s\n", s);
                return 0;
            }
        }
        /* Prepare any comparison by masking the provided filtered net. */
572
573
        for (j=0; j < 16; ++j)
            options.netfilter6net.s6_addr[j] &= options.netfilter6mask.s6_addr[j];
574
575
576
577
578

        return 1;
    }
    return 0;
}
pdw's avatar
pdw committed
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595

void options_make() {
    options_config_get_string("interface", &options.interface);
    options_config_get_bool("dns-resolution", &options.dnsresolution);
    options_config_get_bool("port-resolution", &options.portresolution);
    options_config_get_string("filter-code", &options.filtercode);
    options_config_get_bool("show-bars", &options.showbars);
    options_config_get_promiscuous();
    options_config_get_bool("hide-source", &options.aggregate_src);
    options_config_get_bool("hide-destination", &options.aggregate_dest);
    options_config_get_bool("use-bytes", &options.bandwidth_in_bytes);
    options_config_get_enum("sort", sort_enumeration, (int*)&options.sort);
    options_config_get_enum("line-display", linedisplay_enumeration, (int*)&options.linedisplay);
    options_config_get_bool("show-totals", &options.show_totals);
    options_config_get_bool("log-scale", &options.log_scale);
    options_config_get_bw_rate("max-bandwidth", &options.max_bandwidth);
    options_config_get_enum("port-display", showports_enumeration, (int*)&options.showports);
pdw's avatar
pdw committed
596
    options_config_get_string("screen-filter", &options.screenfilter);
597
    options_config_get_bool("link-local", &options.link_local);
pdw's avatar
pdw committed
598
    options_config_get_net_filter();
599
    options_config_get_net_filter6();
pdw's avatar
pdw committed
600
};