Commit a248b604 authored by pdw's avatar pdw
Browse files

Added -n option.

parent 94a696b7
...@@ -32,8 +32,9 @@ CFLAGS += -g -Wall "-DIFTOP_VERSION=\"$(VERSION)\"" ...@@ -32,8 +32,9 @@ CFLAGS += -g -Wall "-DIFTOP_VERSION=\"$(VERSION)\""
LDFLAGS += -g LDFLAGS += -g
LDLIBS += -lpcap -lpthread -lcurses -lm 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 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 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 TXTS = README CHANGES INSTALL TODO iftop.8 COPYING
OBJS = $(SRCS:.c=.o) OBJS = $(SRCS:.c=.o)
......
...@@ -12,9 +12,6 @@ $Id$ ...@@ -12,9 +12,6 @@ $Id$
* Aggregate traffic by source host / dest host rather than just by host pair. * 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. * Cummulative byte counters.
* Configurable sort criteria. * Configurable sort criteria.
......
...@@ -31,6 +31,10 @@ in a confusing display. You may wish to suppress display of DNS traffic by ...@@ -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, 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 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: Some other filter ideas:
.TP .TP
\fBnot ether host ff:ff:ff:ff:ff:ff\fP \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. ...@@ -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 How much bandwith are users wasting trying to figure out why the network is
slow? slow?
.SH OPTIONS .SH OPTIONS
.TP .TP
...@@ -62,7 +67,11 @@ Listen to packets on \fIinterface\fP. ...@@ -62,7 +67,11 @@ Listen to packets on \fIinterface\fP.
\fB-f\fP \fIfilter code\fP \fB-f\fP \fIfilter code\fP
Use \fIfilter code\fP to select the packets to count. Only IP packets are ever 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. 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 .SH SEE ALSO
.BR tcpdump (8), .BR tcpdump (8),
.BR pcap (3), .BR pcap (3),
......
...@@ -15,29 +15,25 @@ ...@@ -15,29 +15,25 @@
#include <curses.h> #include <curses.h>
#include <signal.h> #include <signal.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include "iftop.h" #include "iftop.h"
#include "addr_hash.h" #include "addr_hash.h"
#include "resolver.h" #include "resolver.h"
#include "ui.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. */ unsigned char if_hw_addr[6]; /* ethernet address of interface. */
extern options_t options;
hash_type* history; hash_type* history;
history_type history_totals;
time_t last_timestamp; time_t last_timestamp;
int history_pos = 0; int history_pos = 0;
int history_len = 1; int history_len = 1;
pthread_mutex_t tick_mutex; 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; sig_atomic_t foad;
static void finish(int sig) { static void finish(int sig) {
...@@ -52,6 +48,7 @@ static void finish(int sig) { ...@@ -52,6 +48,7 @@ static void finish(int sig) {
void init_history() { void init_history() {
history = addr_hash_create(); history = addr_hash_create();
last_timestamp = time(NULL); last_timestamp = time(NULL);
memset(&history_totals, 0, sizeof history_totals);
} }
history_type* history_create() { history_type* history_create() {
...@@ -72,7 +69,7 @@ void history_rotate() { ...@@ -72,7 +69,7 @@ void history_rotate() {
if(d->last_write == history_pos) { if(d->last_write == history_pos) {
addr_pair key = *(addr_pair*)(n->key); addr_pair key = *(addr_pair*)(n->key);
hash_delete(history, &key); hash_delete(history, &key);
free(d); free(d);
} }
else { else {
d->recv[history_pos] = 0; d->recv[history_pos] = 0;
...@@ -80,6 +77,10 @@ void history_rotate() { ...@@ -80,6 +77,10 @@ void history_rotate() {
} }
n = next; n = next;
} }
history_totals.sent[history_pos] = 0;
history_totals.recv[history_pos] = 0;
if(history_len < HISTORY_LENGTH) { if(history_len < HISTORY_LENGTH) {
history_len++; history_len++;
} }
...@@ -102,52 +103,81 @@ void tick() { ...@@ -102,52 +103,81 @@ void tick() {
pthread_mutex_unlock(&tick_mutex); 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) static void handle_packet(char* args, const struct pcap_pkthdr* pkthdr,const char* packet)
{ {
struct ether_header *eptr; struct ether_header *eptr;
int direction = 0; /* incoming */
eptr = (struct ether_header*)packet; eptr = (struct ether_header*)packet;
tick(); tick();
if(ntohs(eptr->ether_type) == ETHERTYPE_IP) { if(ntohs(eptr->ether_type) == ETHERTYPE_IP) {
struct ip* iptr; struct ip* iptr;
history_type* ht; history_type* ht;
addr_pair ap; addr_pair ap;
int promisc = 0;
iptr = (struct ip*)(packet + sizeof(struct ether_header)); /* alignment? */ iptr = (struct ip*)(packet + sizeof(struct ether_header)); /* alignment? */
if(options.netfilter == 0) {
if(memcmp(eptr->ether_shost, if_hw_addr, 6) == 0 ) { /*
/* Packet leaving this interface. */ * Net filter is off, so assign direction based on MAC address
ap.src = iptr->ip_src; */
ap.dst = iptr->ip_dst;
} if(memcmp(eptr->ether_shost, if_hw_addr, 6) == 0 ) {
else if(memcmp(eptr->ether_dhost, if_hw_addr, 6) == 0 || memcmp("\xFF\xFF\xFF\xFF\xFF\xFF", eptr->ether_dhost, 6) == 0) { /* Packet leaving this interface. */
ap.src = iptr->ip_dst; ap.src = iptr->ip_src;
ap.dst = iptr->ip_src; ap.dst = iptr->ip_dst;
} direction = 1;
/* }
* This packet is not from or to this interface. Therefore assume else if(memcmp(eptr->ether_dhost, if_hw_addr, 6) == 0 || memcmp("\xFF\xFF\xFF\xFF\xFF\xFF", eptr->ether_dhost, 6) == 0) {
* it was picked up in promisc mode. ap.src = iptr->ip_dst;
*/ ap.dst = iptr->ip_src;
else if(iptr->ip_src.s_addr < iptr->ip_dst.s_addr) { }
ap.src = iptr->ip_src; /*
ap.dst = iptr->ip_dst; * This packet is not from or to this interface. Therefore assume
promisc = 1; * 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 { else {
ap.src = iptr->ip_dst; /*
ap.dst = iptr->ip_src; * Net filter on, assign direction according to netmask
promisc = 1; */
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 addresses to be resolved */
/* Add the address to be resolved */
resolve(&iptr->ip_dst, NULL, 0); resolve(&iptr->ip_dst, NULL, 0);
resolve(&iptr->ip_src, NULL, 0);
if(hash_find(history, &ap, (void**)&ht) == HASH_STATUS_KEY_NOT_FOUND) { if(hash_find(history, &ap, (void**)&ht) == HASH_STATUS_KEY_NOT_FOUND) {
ht = history_create(); ht = history_create();
ht->promisc = promisc;
hash_insert(history, &ap, ht); hash_insert(history, &ap, ht);
} }
...@@ -160,6 +190,14 @@ static void handle_packet(char* args, const struct pcap_pkthdr* pkthdr,const cha ...@@ -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); 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) { ...@@ -181,7 +219,7 @@ void packet_loop(void* ptr) {
foad = 1; foad = 1;
return; return;
} }
strncpy(ifr.ifr_name, interface, IFNAMSIZ); strncpy(ifr.ifr_name, options.interface, IFNAMSIZ);
ifr.ifr_hwaddr.sa_family = AF_UNSPEC; ifr.ifr_hwaddr.sa_family = AF_UNSPEC;
if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1) { if (ioctl(s, SIOCGIFHWADDR, &ifr) == -1) {
perror("ioctl(SIOCGIFHWADDR)"); perror("ioctl(SIOCGIFHWADDR)");
...@@ -197,15 +235,15 @@ void packet_loop(void* ptr) { ...@@ -197,15 +235,15 @@ void packet_loop(void* ptr) {
resolver_initialise(); 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) { 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; foad = 1;
return; return;
} }
if (filtercode) { if (options.filtercode) {
str = xmalloc(strlen(filtercode) + sizeof "() and ip"); str = xmalloc(strlen(options.filtercode) + sizeof "() and ip");
sprintf(str, "(%s) and ip", filtercode); sprintf(str, "(%s) and ip", options.filtercode);
} }
if (pcap_compile(pd, &F, str, 1, 0) == -1) { if (pcap_compile(pd, &F, str, 1, 0) == -1) {
fprintf(stderr, "pcap_compile(%s): %s\n", str, pcap_geterr(pd)); fprintf(stderr, "pcap_compile(%s): %s\n", str, pcap_geterr(pd));
...@@ -217,76 +255,21 @@ void packet_loop(void* ptr) { ...@@ -217,76 +255,21 @@ void packet_loop(void* ptr) {
foad = 1; foad = 1;
return; return;
} }
if (filtercode) if (options.filtercode)
xfree(str); xfree(str);
printf("Begin loop\n"); printf("Begin loop\n");
pcap_loop(pd,0,(pcap_handler)handle_packet,NULL); pcap_loop(pd,0,(pcap_handler)handle_packet,NULL);
printf("end loop\n"); 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 <pdw@ex-parrot.com>\n"
);
}
/* main: /* main:
* Entry point. See usage(). */ * Entry point. See usage(). */
char optstr[] = "+i:f:dhp";
int main(int argc, char **argv) { int main(int argc, char **argv) {
pthread_t thread; pthread_t thread;
struct sigaction sa = {0}; struct sigaction sa = {0};
extern int dnsresolution; /* in ui.c */
int opt; options_read(argc, argv);
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;
}
}
sa.sa_handler = finish; sa.sa_handler = finish;
sigaction(SIGINT, &sa, NULL); sigaction(SIGINT, &sa, NULL);
......
...@@ -14,7 +14,6 @@ typedef struct { ...@@ -14,7 +14,6 @@ typedef struct {
long recv[HISTORY_LENGTH]; long recv[HISTORY_LENGTH];
long sent[HISTORY_LENGTH]; long sent[HISTORY_LENGTH];
int last_write; int last_write;
int promisc;
} history_type; } history_type;
void tick(); void tick();
......
/*
* options.c:
*
*
*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#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 <pdw@ex-parrot.com>\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);
}
}
}
/*
* options.h:
*
*/
#ifndef __OPTIONS_H_ /* include guard */
#define __OPTIONS_H_
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
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_ */
...@@ -74,20 +74,15 @@ void resolver_worker(void* ptr) { ...@@ -74,20 +74,15 @@ void resolver_worker(void* ptr) {
/* Check for errors. */ /* Check for errors. */
if (res || hp == NULL) { if (res || hp == NULL) {
/* failed */ /* failed */
//printf("[ Did not resolve %s ]\n", inet_ntoa(addr));
/* Leave the unresolved IP in the hash */ /* Leave the unresolved IP in the hash */
} }
else { else {
/* success */ /* success */
char* hostname; char* hostname;
//printf("[ Resolved: %s ]\n", hp->h_name);
if(hash_find(ns_hash, &addr, (void**)&hostname) == HASH_STATUS_OK) { if(hash_find(ns_hash, &addr, (void**)&hostname) == HASH_STATUS_OK) {
hash_delete(ns_hash, &addr); hash_delete(ns_hash, &addr);
xfree(hostname); xfree(hostname);
} }
else {
//printf("[ Warning: Could not find hash entry for key: %s ]\n", inet_ntoa(addr));
}
hostname = strdup(hp->h_name); hostname = strdup(hp->h_name);
hash_insert(ns_hash, &addr, (void*)hostname);