From a248b6046eb5855fd5b0c9f2ea2a774ab2d7b612 Mon Sep 17 00:00:00 2001 From: pdw <> Date: Sun, 31 Mar 2002 22:57:51 +0000 Subject: [PATCH] Added -n option. --- Makefile | 5 +- TODO | 3 - iftop.8 | 11 +++- iftop.c | 177 ++++++++++++++++++++++++----------------------------- iftop.h | 1 - options.c | 119 +++++++++++++++++++++++++++++++++++ options.h | 29 +++++++++ resolver.c | 5 -- ui.c | 43 ++++++------- 9 files changed, 259 insertions(+), 134 deletions(-) create mode 100644 options.c create mode 100644 options.h diff --git a/Makefile b/Makefile index ae1e862..1993be5 100644 --- a/Makefile +++ b/Makefile @@ -32,8 +32,9 @@ CFLAGS += -g -Wall "-DIFTOP_VERSION=\"$(VERSION)\"" LDFLAGS += -g LDLIBS += -lpcap -lpthread -lcurses -lm -SRCS = iftop.c addr_hash.c hash.c ns_hash.c resolver.c ui.c util.c sorted_list.c -HDRS = addr_hash.h hash.h iftop.h ns_hash.h resolver.h sorted_list.h ui.h +SRCS = iftop.c addr_hash.c hash.c ns_hash.c resolver.c ui.c util.c sorted_list.c\ + options.c +HDRS = addr_hash.h hash.h iftop.h ns_hash.h resolver.h sorted_list.h ui.h options.h TXTS = README CHANGES INSTALL TODO iftop.8 COPYING OBJS = $(SRCS:.c=.o) diff --git a/TODO b/TODO index ed8e6cb..1673241 100644 --- a/TODO +++ b/TODO @@ -12,9 +12,6 @@ $Id$ * Aggregate traffic by source host / dest host rather than just by host pair. -* For promiscuous mode, give a way to define which packets are regarded as - `inside' and which `outside' the network. - * Cummulative byte counters. * Configurable sort criteria. diff --git a/iftop.8 b/iftop.8 index 5f9adf9..b750344 100644 --- a/iftop.8 +++ b/iftop.8 @@ -31,6 +31,10 @@ in a confusing display. You may wish to suppress display of DNS traffic by using filter code such as \fBnot port domain\fP, or switch it off entirely, by using the \fB-d\fP option or by pressing \fBR\fP when the program is running. +By default, \fBiftop\fP shows all IP packets that pass through the filter, and the direction of the packet is determined according to the direction the packet is moving across the interface. Using the \fB-n\fP option it is possible to get \fBiftop\fP to show packets entering and leaving a given network. For example, \fBiftop -n 10.0.0.0/255.0.0.0\fP will analyse packets flowing in and out of the 10.* network. + +\fBiftop\fP must be run as root. + Some other filter ideas: .TP \fBnot ether host ff:ff:ff:ff:ff:ff\fP @@ -43,6 +47,7 @@ Count web traffic only, unless it is being directed through a local web cache. How much bandwith are users wasting trying to figure out why the network is slow? + .SH OPTIONS .TP @@ -62,7 +67,11 @@ Listen to packets on \fIinterface\fP. \fB-f\fP \fIfilter code\fP Use \fIfilter code\fP to select the packets to count. Only IP packets are ever counted, so the specified code is evaluated as \fB(\fP\fIfilter code\fP\fB) and ip\fP. - +.TP +\fB-n\fP \fInet/mask\fP +Specifies a network for traffic analysis. If specified, iftop will only +include packets flowing in to or out of the given network, and packet direction +is determined relative to the network boundary, rather than to the interface. .SH SEE ALSO .BR tcpdump (8), .BR pcap (3), diff --git a/iftop.c b/iftop.c index 4d8fb15..f36156a 100644 --- a/iftop.c +++ b/iftop.c @@ -15,29 +15,25 @@ #include #include #include -#include #include "iftop.h" #include "addr_hash.h" #include "resolver.h" #include "ui.h" +#include "options.h" -/* Global options. */ -char *interface = "eth0"; -char *filtercode = NULL; unsigned char if_hw_addr[6]; /* ethernet address of interface. */ +extern options_t options; + hash_type* history; +history_type history_totals; time_t last_timestamp; int history_pos = 0; int history_len = 1; pthread_mutex_t tick_mutex; -/* Open non-promiscuous by default since this is intended to be run on a - * router. */ -int promiscuous = 0; - sig_atomic_t foad; static void finish(int sig) { @@ -52,6 +48,7 @@ static void finish(int sig) { void init_history() { history = addr_hash_create(); last_timestamp = time(NULL); + memset(&history_totals, 0, sizeof history_totals); } history_type* history_create() { @@ -72,7 +69,7 @@ void history_rotate() { if(d->last_write == history_pos) { addr_pair key = *(addr_pair*)(n->key); hash_delete(history, &key); - free(d); + free(d); } else { d->recv[history_pos] = 0; @@ -80,6 +77,10 @@ void history_rotate() { } n = next; } + + history_totals.sent[history_pos] = 0; + history_totals.recv[history_pos] = 0; + if(history_len < HISTORY_LENGTH) { history_len++; } @@ -102,52 +103,81 @@ void tick() { pthread_mutex_unlock(&tick_mutex); } +int in_filter_net(struct in_addr addr) { + int ret; + ret = ((addr.s_addr & options.netfiltermask.s_addr) == options.netfilternet.s_addr); + return ret; +} + static void handle_packet(char* args, const struct pcap_pkthdr* pkthdr,const char* packet) { struct ether_header *eptr; + int direction = 0; /* incoming */ eptr = (struct ether_header*)packet; - + tick(); if(ntohs(eptr->ether_type) == ETHERTYPE_IP) { struct ip* iptr; history_type* ht; addr_pair ap; - int promisc = 0; iptr = (struct ip*)(packet + sizeof(struct ether_header)); /* alignment? */ + if(options.netfilter == 0) { + /* + * Net filter is off, so assign direction based on MAC address + */ - if(memcmp(eptr->ether_shost, if_hw_addr, 6) == 0 ) { - /* Packet leaving this interface. */ - ap.src = iptr->ip_src; - ap.dst = iptr->ip_dst; - } - else if(memcmp(eptr->ether_dhost, if_hw_addr, 6) == 0 || memcmp("\xFF\xFF\xFF\xFF\xFF\xFF", eptr->ether_dhost, 6) == 0) { - ap.src = iptr->ip_dst; - ap.dst = iptr->ip_src; - } - /* - * This packet is not from or to this interface. Therefore assume - * it was picked up in promisc mode. - */ - else if(iptr->ip_src.s_addr < iptr->ip_dst.s_addr) { - ap.src = iptr->ip_src; - ap.dst = iptr->ip_dst; - promisc = 1; + if(memcmp(eptr->ether_shost, if_hw_addr, 6) == 0 ) { + /* Packet leaving this interface. */ + ap.src = iptr->ip_src; + ap.dst = iptr->ip_dst; + direction = 1; + } + else if(memcmp(eptr->ether_dhost, if_hw_addr, 6) == 0 || memcmp("\xFF\xFF\xFF\xFF\xFF\xFF", eptr->ether_dhost, 6) == 0) { + ap.src = iptr->ip_dst; + ap.dst = iptr->ip_src; + } + /* + * This packet is not from or to this interface. Therefore assume + * it was picked up in promisc mode, and account it as incoming. + */ + else if(iptr->ip_src.s_addr < iptr->ip_dst.s_addr) { + ap.src = iptr->ip_src; + ap.dst = iptr->ip_dst; + } + else { + ap.src = iptr->ip_dst; + ap.dst = iptr->ip_src; + } } else { - ap.src = iptr->ip_dst; - ap.dst = iptr->ip_src; - promisc = 1; + /* + * Net filter on, assign direction according to netmask + */ + if(in_filter_net(iptr->ip_src) & !in_filter_net(iptr->ip_dst)) { + /* out of network */ + ap.src = iptr->ip_src; + ap.dst = iptr->ip_dst; + direction = 1; + } + else if(in_filter_net(iptr->ip_dst) & !in_filter_net(iptr->ip_src)) { + /* into network */ + ap.src = iptr->ip_dst; + ap.dst = iptr->ip_src; + } + else { + /* drop packet */ + return ; + } } - - /* Add the address to be resolved */ + /* Add the addresses to be resolved */ resolve(&iptr->ip_dst, NULL, 0); + resolve(&iptr->ip_src, NULL, 0); if(hash_find(history, &ap, (void**)&ht) == HASH_STATUS_KEY_NOT_FOUND) { ht = history_create(); - ht->promisc = promisc; hash_insert(history, &ap, ht); } @@ -160,6 +190,14 @@ static void handle_packet(char* args, const struct pcap_pkthdr* pkthdr,const cha ht->recv[history_pos] += ntohs(iptr->ip_len); } + if(direction == 0) { + /* incoming */ + history_totals.recv[history_pos] += ntohs(iptr->ip_len); + } + else { + history_totals.sent[history_pos] += ntohs(iptr->ip_len); + } + } } @@ -181,7 +219,7 @@ void packet_loop(void* ptr) { foad = 1; return; } - strncpy(ifr.ifr_name, interface, IFNAMSIZ); + strncpy(ifr.ifr_name, options.interface, IFNAMSIZ); ifr.ifr_hwaddr.sa_family = AF_UNSPEC; if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1) { perror("ioctl(SIOCGIFHWADDR)"); @@ -197,15 +235,15 @@ void packet_loop(void* ptr) { resolver_initialise(); - pd = pcap_open_live(interface, CAPTURE_LENGTH, promiscuous, 1000, errbuf); + pd = pcap_open_live(options.interface, CAPTURE_LENGTH, options.promiscuous, 1000, errbuf); if(pd == NULL) { - fprintf(stderr, "pcap_open_live(%s): %s\n", interface, errbuf); + fprintf(stderr, "pcap_open_live(%s): %s\n", options.interface, errbuf); foad = 1; return; } - if (filtercode) { - str = xmalloc(strlen(filtercode) + sizeof "() and ip"); - sprintf(str, "(%s) and ip", filtercode); + if (options.filtercode) { + str = xmalloc(strlen(options.filtercode) + sizeof "() and ip"); + sprintf(str, "(%s) and ip", options.filtercode); } if (pcap_compile(pd, &F, str, 1, 0) == -1) { fprintf(stderr, "pcap_compile(%s): %s\n", str, pcap_geterr(pd)); @@ -217,76 +255,21 @@ void packet_loop(void* ptr) { foad = 1; return; } - if (filtercode) + if (options.filtercode) xfree(str); printf("Begin loop\n"); pcap_loop(pd,0,(pcap_handler)handle_packet,NULL); printf("end loop\n"); } -/* usage: - * Print usage information. */ -void usage(FILE *fp) { - fprintf(fp, -"iftop: display bandwidth usage on an interface by host\n" -"\n" -"Synopsis: iftop -h | [-d] [-p] [-i interface] [-f filter code]\n" -"\n" -" -h display this message\n" -" -d don't do hostname lookups\n" -" -p run in promiscuous mode (show traffic between other\n" -" hosts on the same network segment)\n" -" -i interface listen on named interface (default: eth0)\n" -" -f filter code use filter code to select packets to count\n" -" (default: none, but only IP packets are counted)\n" -"\n" -"iftop, version " IFTOP_VERSION "copyright (c) 2002 Paul Warren \n" - ); -} /* main: * Entry point. See usage(). */ -char optstr[] = "+i:f:dhp"; int main(int argc, char **argv) { pthread_t thread; struct sigaction sa = {0}; - extern int dnsresolution; /* in ui.c */ - int opt; - opterr = 0; - while ((opt = getopt(argc, argv, optstr)) != -1) { - switch (opt) { - case 'h': - usage(stdout); - return 0; - - case 'd': - dnsresolution = 0; - break; - - case 'i': - interface = optarg; - break; - - case 'f': - filtercode = optarg; - break; - - case 'p': - promiscuous = 1; - break; - - case '?': - fprintf(stderr, "iftop: unknown option -%c\n", optopt); - usage(stderr); - return 1; - - case ':': - fprintf(stderr, "iftop: option -%c requires an argument\n", optopt); - usage(stderr); - return 1; - } - } + options_read(argc, argv); sa.sa_handler = finish; sigaction(SIGINT, &sa, NULL); diff --git a/iftop.h b/iftop.h index bea11d1..3b25a84 100644 --- a/iftop.h +++ b/iftop.h @@ -14,7 +14,6 @@ typedef struct { long recv[HISTORY_LENGTH]; long sent[HISTORY_LENGTH]; int last_write; - int promisc; } history_type; void tick(); diff --git a/options.c b/options.c new file mode 100644 index 0000000..8a2599f --- /dev/null +++ b/options.c @@ -0,0 +1,119 @@ +/* + * options.c: + * + * + */ + +#include +#include +#include +#include +#include "options.h" + +options_t options; + +char optstr[] = "+i:f:n:dhp"; + +/* Global options. */ + +void set_defaults() { + options.interface = "eth0"; + options.filtercode = NULL; + options.netfilter = 0; + inet_aton("10.0.1.0", &options.netfilternet); + inet_aton("255.255.255.0", &options.netfiltermask); + options.dnsresolution = 1; + options.promiscuous = 0; +} + +void die(char *msg) { + fprintf(stderr, msg); + exit(1); +} + +void set_net_filter(char* arg) { + char* mask; + + mask = strstr(arg, "/"); + if(mask == NULL) { + die("Could not parse net/mask\n"); + } + *mask = '\0'; + mask++; + if(inet_aton(arg, &options.netfilternet) == 0) { + die("Invalid network address\n"); + } + if(inet_aton(mask, &options.netfiltermask) == 0) { + die("Invalid network mask\n"); + } + + options.netfilter = 1; + +} + +/* usage: + * Print usage information. */ +void usage(FILE *fp) { + fprintf(fp, +"iftop: display bandwidth usage on an interface by host\n" +"\n" +"Synopsis: iftop -h | [-d] [-p] [-i interface] [-f filter code]\n" +"\n" +" -h display this message\n" +" -d don't do hostname lookups\n" +" -p run in promiscuous mode (show traffic between other\n" +" hosts on the same network segment)\n" +" -i interface listen on named interface (default: eth0)\n" +" -f filter code use filter code to select packets to count\n" +" (default: none, but only IP packets are counted)\n" +" -n network/netmask show traffic flows in/out of network\n" +"\n" +"iftop, version " IFTOP_VERSION "copyright (c) 2002 Paul Warren \n" + ); +} + +void options_read(int argc, char **argv) { + int opt; + + set_defaults(); + + opterr = 0; + while ((opt = getopt(argc, argv, optstr)) != -1) { + switch (opt) { + case 'h': + usage(stdout); + exit(0); + + case 'd': + options.dnsresolution = 0; + break; + + case 'i': + options.interface = optarg; + break; + + case 'f': + options.filtercode = optarg; + break; + + case 'p': + options.promiscuous = 1; + break; + + case 'n': + set_net_filter(optarg); + break; + + 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); + } + } + +} diff --git a/options.h b/options.h new file mode 100644 index 0000000..c70cccf --- /dev/null +++ b/options.h @@ -0,0 +1,29 @@ +/* + * options.h: + * + */ + +#ifndef __OPTIONS_H_ /* include guard */ +#define __OPTIONS_H_ + +#include +#include +#include + +typedef struct { + /* interface to listen on */ + char *interface; + + /* pcap filter code */ + char *filtercode; + + /* Cross network filter */ + int netfilter; + struct in_addr netfilternet; + struct in_addr netfiltermask; + int dnsresolution; + int promiscuous; + +} options_t; + +#endif /* __OPTIONS_H_ */ diff --git a/resolver.c b/resolver.c index 7e27464..cef2bcd 100644 --- a/resolver.c +++ b/resolver.c @@ -74,20 +74,15 @@ void resolver_worker(void* ptr) { /* Check for errors. */ if (res || hp == NULL) { /* failed */ - //printf("[ Did not resolve %s ]\n", inet_ntoa(addr)); /* Leave the unresolved IP in the hash */ } else { /* success */ char* hostname; - //printf("[ Resolved: %s ]\n", hp->h_name); if(hash_find(ns_hash, &addr, (void**)&hostname) == HASH_STATUS_OK) { hash_delete(ns_hash, &addr); xfree(hostname); } - else { - //printf("[ Warning: Could not find hash entry for key: %s ]\n", inet_ntoa(addr)); - } hostname = strdup(hp->h_name); hash_insert(ns_hash, &addr, (void*)hostname); diff --git a/ui.c b/ui.c index 4db9219..25fe1fb 100644 --- a/ui.c +++ b/ui.c @@ -15,6 +15,7 @@ #include "iftop.h" #include "resolver.h" #include "sorted_list.h" +#include "options.h" #define HOSTNAME_LENGTH 256 @@ -36,6 +37,8 @@ extern hash_type* history; extern int history_pos; extern int history_len; +extern options_t options ; + void ui_finish(); int screen_line_compare(void* a, void* b) { @@ -136,8 +139,8 @@ void draw_totals(host_pair_line* totals) { draw_line_totals(y, totals); } +extern history_type history_totals; -int dnsresolution = 1; void ui_print() { hash_node_type* n = NULL; @@ -150,7 +153,6 @@ void ui_print() { int i; sorted_list_type screen_list; host_pair_line totals; - history_type history_totals; if (!line || lcols != COLS) { xfree(line); @@ -170,12 +172,11 @@ void ui_print() { attron(A_REVERSE); addstr(" R "); attroff(A_REVERSE); - addstr(dnsresolution ? " name resolution off " + addstr(options.dnsresolution ? " name resolution off " : " name resolution on "); draw_bar_scale(); memset(&totals, 0, sizeof totals); - memset(&history_totals, 0, sizeof history_totals); while(hash_next_item(history, &n) == HASH_STATUS_OK) { history_type* d = (history_type*)n->rec; @@ -195,34 +196,26 @@ void ui_print() { for(j = 0; j < HISTORY_DIVISIONS; j++) { if(i < history_divs[j]) { screen_line->recv[j] += d->recv[ii]; - totals.recv[j] += d->recv[ii]; - screen_line->sent[j] += d->sent[ii]; - - if(d->promisc == 1) { - /* Treat all promisc traffic as incoming */ - totals.recv[j] += d->sent[ii]; - } - else { - totals.sent[j] += d->sent[ii]; - } } } - history_totals.recv[ii] += d->recv[ii]; - if(d->promisc == 1) { - /* Treat all promisc traffic as incoming */ - history_totals.recv[ii] += d->sent[ii]; - } - else { - history_totals.sent[ii] += d->sent[ii]; - } } sorted_list_insert(&screen_list, screen_line); } for(i = 0; i < HISTORY_LENGTH; i++) { + int j; + int ii = (HISTORY_LENGTH + history_pos - i) % HISTORY_LENGTH; + + for(j = 0; j < HISTORY_DIVISIONS; j++) { + if(i < history_divs[j]) { + totals.recv[j] += history_totals.recv[ii]; + totals.sent[j] += history_totals.sent[ii]; + } + } + if(history_totals.recv[i] > peakrecv) { peakrecv = history_totals.recv[i]; } @@ -253,7 +246,7 @@ void ui_print() { L = sizeof hostname; } - if (dnsresolution) + if (options.dnsresolution) resolve(&screen_line->ap->src, hostname, L); else strcpy(hostname, inet_ntoa(screen_line->ap->src)); @@ -265,7 +258,7 @@ void ui_print() { mvaddstr(y+1, x, " <= "); x += 4; - if (dnsresolution) + if (options.dnsresolution) resolve(&screen_line->ap->dst, hostname, L); else strcpy(hostname, inet_ntoa(screen_line->ap->dst)); @@ -348,7 +341,7 @@ void ui_loop() { break; case 'R': - dnsresolution = !dnsresolution; + options.dnsresolution = !options.dnsresolution; break; } tick();