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;
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;
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

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");
    }
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"
268
"   -P                  show ports as well as hosts\n"
269
"   -m limit            sets the upper limit for the bandwidth scale\n"
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");
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':
296
                config_set_string("interface", optarg);
pdw's avatar
pdw committed
297 298 299
                break;

            case 'f':
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':
308
                config_set_string("promiscuous", "true");
pdw's avatar
pdw committed
309 310
                break;

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

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':
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':
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;
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
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
};