/* For terms of usage/redistribution/modification see the LICENSE file */
/* For authors and contributors see the AUTHORS file */
/***
ifstats.c - the interface statistics module
***/
#include "iptraf-ng-compat.h"
#include "tui/labels.h"
#include "tui/listbox.h"
#include "tui/msgboxes.h"
#include "tui/winops.h"
#include "ifaces.h"
#include "fltdefs.h"
#include "packet.h"
#include "options.h"
#include "log.h"
#include "dirs.h"
#include "deskman.h"
#include "attrs.h"
#include "serv.h"
#include "timer.h"
#include "logvars.h"
#include "promisc.h"
#include "error.h"
#include "ifstats.h"
#include "rate.h"
#define SCROLLUP 0
#define SCROLLDOWN 1
struct iflist {
char ifname[IFNAMSIZ];
int ifindex;
unsigned int encap;
unsigned long long iptotal;
unsigned long long ip6total;
unsigned long badtotal;
unsigned long long noniptotal;
unsigned long long total;
unsigned int spanbr;
unsigned long br;
struct rate rate;
unsigned long peakrate;
unsigned int index;
struct iflist *prev_entry;
struct iflist *next_entry;
};
struct iftab {
struct iflist *head;
struct iflist *tail;
struct iflist *firstvisible;
struct iflist *lastvisible;
WINDOW *borderwin;
PANEL *borderpanel;
WINDOW *statwin;
PANEL *statpanel;
};
/*
* USR1 log-rotation signal handlers
*/
static void rotate_gstat_log(int s __unused)
{
rotate_flag = 1;
strcpy(target_logname, GSTATLOG);
signal(SIGUSR1, rotate_gstat_log);
}
static void writegstatlog(struct iftab *table, unsigned long nsecs, FILE *fd)
{
struct iflist *ptmp = table->head;
char atime[TIME_TARGET_MAX];
genatime(time(NULL), atime);
fprintf(fd, "\n*** General interface statistics log generated %s\n\n",
atime);
while (ptmp != NULL) {
fprintf(fd,
"%s: %llu total, %llu IP, %llu non-IP, %lu IP checksum errors",
ptmp->ifname, ptmp->total, ptmp->iptotal,
ptmp->noniptotal, ptmp->badtotal);
if (nsecs > 5) {
char buf[64];
rate_print(ptmp->br / nsecs, buf, sizeof(buf));
fprintf(fd, ", average activity %s", buf);
rate_print(ptmp->peakrate, buf, sizeof(buf));
fprintf(fd, ", peak activity %s", buf);
rate_print(rate_get_average(&ptmp->rate), buf, sizeof(buf));
fprintf(fd, ", last 5-second average activity %s", buf);
}
fprintf(fd, "\n");
ptmp = ptmp->next_entry;
}
fprintf(fd, "\n%lu seconds running time\n", nsecs);
fflush(fd);
}
/*
* Function to check if an interface is already in the interface list.
* This eliminates duplicate interface entries due to aliases
*/
static int ifinlist(struct iflist *list, char *ifname)
{
struct iflist *ptmp = list;
int result = 0;
while ((ptmp != NULL) && (result == 0)) {
result = (strcmp(ifname, ptmp->ifname) == 0);
ptmp = ptmp->next_entry;
}
return result;
}
/*
* Initialize the list of interfaces. This linked list is used in the
* selection boxes as well as in the general interface statistics screen.
*
* This function parses the /proc/net/dev file and grabs the interface names
* from there. The SIOGIFFLAGS ioctl() call is used to determine whether the
* interfaces are active. Inactive interfaces are omitted from selection
* lists.
*/
static void initiflist(struct iflist **list)
{
char ifname[IFNAMSIZ];
*list = NULL;
FILE *fd = open_procnetdev();
if (fd == NULL) {
tui_error(ANYKEY_MSG, "Unable to obtain interface list");
return;
}
while (get_next_iface(fd, ifname, sizeof(ifname))) {
if (!*ifname)
continue;
if (ifinlist(*list, ifname)) /* ignore entry if already in */
continue; /* interface list */
/*
* Check if the interface is actually up running. This prevents
* inactive devices in /proc/net/dev from actually appearing in
* interface lists used by IPTraf.
*/
if (!dev_up(ifname))
continue;
int ifindex = dev_get_ifindex(ifname);
if (ifindex < 0)
continue;
/*
* At this point, the interface is now sure to be up and running.
*/
struct iflist *itmp = xmallocz(sizeof(struct iflist));
strcpy(itmp->ifname, ifname);
itmp->ifindex = ifindex;
rate_alloc(&itmp->rate, 5);
/* make the linked list sorted by ifindex */
struct iflist *cur = *list, *last = NULL;
while (cur != NULL && cur->ifindex < ifindex) {
last = cur;
cur = cur->next_entry;
}
itmp->prev_entry = last;
itmp->next_entry = cur;
if (cur)
cur->prev_entry = itmp;
if (last)
last->next_entry = itmp;
else
*list = itmp;
}
fclose(fd);
/* let the index follow the sorted linked list */
unsigned int index = 1;
struct iflist *cur;
for (cur = *list; cur != NULL; cur = cur->next_entry)
cur->index = index++;
}
static struct iflist *positionptr(struct iflist *iflist, const int ifindex)
{
struct iflist *ptmp = iflist;
struct iflist *last = ptmp;
while ((ptmp != NULL) && (ptmp->ifindex != ifindex)) {
last = ptmp;
ptmp = ptmp->next_entry;
}
/* no interface was found, try to create new one */
if (ptmp == NULL) {
struct iflist *itmp = xmallocz(sizeof(struct iflist));
itmp->ifindex = ifindex;
itmp->index = last->index + 1;
int r = dev_get_ifname(ifindex, itmp->ifname);
if (r != 0) {
write_error("Error getting interface name");
return(NULL);
}
/* last can't be NULL otherwise we will have empty iflist */
last->next_entry = itmp;
itmp->prev_entry = last;
itmp->next_entry = NULL;
ptmp = itmp;
}
return(ptmp);
}
static void destroyiflist(struct iflist *list)
{
struct iflist *ctmp;
struct iflist *ptmp;
if (list != NULL) {
ptmp = list;
ctmp = ptmp->next_entry;
do {
rate_destroy(&ptmp->rate);
free(ptmp);
ptmp = ctmp;
if (ctmp != NULL)
ctmp = ctmp->next_entry;
} while (ptmp != NULL);
}
}
static void no_ifaces_error(void)
{
write_error("No active interfaces. Check their status or the /proc filesystem");
}
static void updaterates(struct iftab *table, unsigned long msecs)
{
struct iflist *ptmp = table->head;
unsigned long rate;
while (ptmp != NULL) {
rate_add_rate(&ptmp->rate, ptmp->spanbr, msecs);
rate = rate_get_average(&ptmp->rate);
if (rate > ptmp->peakrate)
ptmp->peakrate = rate;
ptmp->spanbr = 0;
ptmp = ptmp->next_entry;
}
}
static void showrates(struct iftab *table)
{
struct iflist *ptmp = table->firstvisible;
unsigned int idx = table->firstvisible->index;
unsigned long rate;
char buf[64];
wattrset(table->statwin, HIGHATTR);
do {
rate = rate_get_average(&ptmp->rate);
rate_print(rate, buf, sizeof(buf));
wmove(table->statwin, ptmp->index - idx, 63 * COLS / 80);
wprintw(table->statwin, "%s", buf);
ptmp = ptmp->next_entry;
} while (ptmp != table->lastvisible->next_entry);
}
static void printifentry(struct iflist *ptmp, WINDOW * win, unsigned int idx)
{
unsigned int target_row;
if ((ptmp->index < idx) || (ptmp->index > idx + (LINES - 5)))
return;
target_row = ptmp->index - idx;
wattrset(win, STDATTR);
wmove(win, target_row, 1);
wprintw(win, "%s", ptmp->ifname);
wattrset(win, HIGHATTR);
wmove(win, target_row, 14 * COLS / 80);
printlargenum(ptmp->total, win);
wmove(win, target_row, 24 * COLS / 80);
printlargenum(ptmp->iptotal, win);
wmove(win, target_row, 34 * COLS / 80);
printlargenum(ptmp->ip6total, win);
wmove(win, target_row, 44 * COLS / 80);
printlargenum(ptmp->noniptotal, win);
wmove(win, target_row, 53 * COLS / 80);
wprintw(win, "%7lu", ptmp->badtotal);
}
static void print_if_entries(struct iftab *table)
{
struct iflist *ptmp = table->firstvisible;
unsigned int i = 1;
unsigned int winht = LINES - 4;
do {
printifentry(ptmp, table->statwin, table->firstvisible->index);
if (i <= winht)
table->lastvisible = ptmp;
ptmp = ptmp->next_entry;
i++;
} while ((ptmp != NULL) && (i <= winht));
}
static void labelstats(WINDOW *win)
{
wmove(win, 0, 1);
wprintw(win, " Iface ");
/* 14, 24, 34, ... from printifentry() */
/* 10 = strlen(printed number); from printlargenum() */
/* 7 = strlen(" Total ") */
/* 1 = align the string on 'l' from " Total " */
wmove(win, 0, (14 * COLS / 80) + 10 - 7 + 1);
wprintw(win, " Total ");
wmove(win, 0, (24 * COLS / 80) + 10 - 6 + 1);
wprintw(win, " IPv4 ");
wmove(win, 0, (34 * COLS / 80) + 10 - 6 + 1);
wprintw(win, " IPv6 ");
wmove(win, 0, (44 * COLS / 80) + 10 - 7 + 1);
wprintw(win, " NonIP ");
wmove(win, 0, (53 * COLS / 80) + 8 - 7 + 1);
wprintw(win, " BadIP ");
wmove(win, 0, (63 * COLS / 80) + 14 - 10);
wprintw(win, " Activity ");
}
static void initiftab(struct iftab *table)
{
table->borderwin = newwin(LINES - 2, COLS, 1, 0);
table->borderpanel = new_panel(table->borderwin);
move(LINES - 1, 1);
scrollkeyhelp();
stdexitkeyhelp();
wattrset(table->borderwin, BOXATTR);
tx_box(table->borderwin, ACS_VLINE, ACS_HLINE);
labelstats(table->borderwin);
table->statwin = newwin(LINES - 4, COLS - 2, 2, 1);
table->statpanel = new_panel(table->statwin);
tx_stdwinset(table->statwin);
wtimeout(table->statwin, -1);
wattrset(table->statwin, STDATTR);
tx_colorwin(table->statwin);
wattrset(table->statwin, BOXATTR);
wmove(table->borderwin, LINES - 3, 32 * COLS / 80);
wprintw(table->borderwin,
" Total, IP, NonIP, and BadIP are packet counts ");
}
/*
* Scrolling routines for the general interface statistics window
*/
static void scrollgstatwin(struct iftab *table, int direction)
{
char buf[255];
sprintf(buf, "%%%dc", COLS - 2);
wattrset(table->statwin, STDATTR);
if (direction == SCROLLUP) {
if (table->lastvisible->next_entry != NULL) {
wscrl(table->statwin, 1);
table->lastvisible = table->lastvisible->next_entry;
table->firstvisible = table->firstvisible->next_entry;
wmove(table->statwin, LINES - 5, 0);
scrollok(table->statwin, 0);
wprintw(table->statwin, buf, ' ');
scrollok(table->statwin, 1);
printifentry(table->lastvisible, table->statwin,
table->firstvisible->index);
}
} else {
if (table->firstvisible != table->head) {
wscrl(table->statwin, -1);
table->firstvisible = table->firstvisible->prev_entry;
table->lastvisible = table->lastvisible->prev_entry;
wmove(table->statwin, 0, 0);
wprintw(table->statwin, buf, ' ');
printifentry(table->firstvisible, table->statwin,
table->firstvisible->index);
}
}
}
static void pagegstatwin(struct iftab *table, int direction)
{
int i = 1;
if (direction == SCROLLUP) {
while ((i <= LINES - 5)
&& (table->lastvisible->next_entry != NULL)) {
i++;
scrollgstatwin(table, direction);
}
} else {
while ((i <= LINES - 5) && (table->firstvisible != table->head)) {
i++;
scrollgstatwin(table, direction);
}
}
}
/*
* The general interface statistics function
*/
void ifstats(time_t facilitytime)
{
int logging = options.logging;
struct iftab table;
int pkt_result = 0;
struct iflist *ptmp = NULL;
FILE *logfile = NULL;
int ch;
int fd;
struct timeval tv;
time_t starttime = 0;
time_t statbegin = 0;
time_t now = 0;
struct timeval start_tv;
time_t startlog = 0;
struct timeval updtime;
initiflist(&(table.head));
if (!table.head) {
no_ifaces_error();
return;
}
initiftab(&table);
LIST_HEAD(promisc);
if (options.promisc) {
promisc_init(&promisc, NULL);
promisc_set_list(&promisc);
}
if (logging) {
if (strcmp(current_logfile, "") == 0) {
strcpy(current_logfile, GSTATLOG);
if (!daemonized)
input_logfile(current_logfile, &logging);
}
}
if (logging) {
opentlog(&logfile, GSTATLOG);
if (logfile == NULL)
logging = 0;
}
if (logging) {
signal(SIGUSR1, rotate_gstat_log);
rotate_flag = 0;
writelog(logging, logfile,
"******** General interface statistics started ********");
}
table.firstvisible = table.head;
print_if_entries(&table);
update_panels();
doupdate();
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if(fd == -1) {
write_error("Unable to obtain monitoring socket");
goto err;
}
exitloop = 0;
gettimeofday(&tv, NULL);
start_tv = tv;
updtime = tv;
starttime = startlog = statbegin = tv.tv_sec;
PACKET_INIT(pkt);
while (!exitloop) {
gettimeofday(&tv, NULL);
now = tv.tv_sec;
if ((now - starttime) >= 1) {
unsigned long msecs;
msecs = timeval_diff_msec(&tv, &start_tv);
updaterates(&table, msecs);
showrates(&table);
printelapsedtime(statbegin, now, LINES - 3, 1,
table.borderwin);
starttime = now;
start_tv = tv;
}
if (logging) {
check_rotate_flag(&logfile);
if ((now - startlog) >= options.logspan) {
writegstatlog(&table,
time(NULL) - statbegin,
logfile);
startlog = now;
}
}
if (screen_update_needed(&tv, &updtime)) {
print_if_entries(&table);
update_panels();
doupdate();
updtime = tv;
}
if ((facilitytime != 0)
&& (((now - statbegin) / 60) >= facilitytime))
exitloop = 1;
if (packet_get(fd, &pkt, &ch, table.statwin) == -1) {
write_error("Packet receive failed");
exitloop = 1;
break;
}
switch (ch) {
case ERR:
/* no key ready, do nothing */
break;
case KEY_UP:
scrollgstatwin(&table, SCROLLDOWN);
break;
case KEY_DOWN:
scrollgstatwin(&table, SCROLLUP);
break;
case KEY_PPAGE:
case '-':
pagegstatwin(&table, SCROLLDOWN);
break;
case KEY_NPAGE:
case ' ':
pagegstatwin(&table, SCROLLUP);
break;
case 12:
case 'l':
case 'L':
tx_refresh_screen();
break;
case 'Q':
case 'q':
case 'X':
case 'x':
case 27:
case 24:
exitloop = 1;
break;
}
if (pkt.pkt_len <= 0)
continue;
pkt_result = packet_process(&pkt, NULL, NULL, NULL,
MATCH_OPPOSITE_USECONFIG,
options.v6inv4asv6);
if (pkt_result != PACKET_OK
&& pkt_result != MORE_FRAGMENTS)
continue;
ptmp = positionptr(table.head, pkt.pkt_ifindex);
if (!ptmp)
continue;
ptmp->total++;
ptmp->spanbr += pkt.pkt_len;
ptmp->br += pkt.pkt_len;
if (pkt.pkt_protocol == ETH_P_IP) {
ptmp->iptotal++;
if (pkt_result == CHECKSUM_ERROR) {
(ptmp->badtotal)++;
continue;
}
} else if (pkt.pkt_protocol == ETH_P_IPV6) {
ptmp->ip6total++;
} else {
(ptmp->noniptotal)++;
}
}
close(fd);
err:
if (options.promisc) {
promisc_restore_list(&promisc);
promisc_destroy(&promisc);
}
del_panel(table.statpanel);
delwin(table.statwin);
del_panel(table.borderpanel);
delwin(table.borderwin);
update_panels();
doupdate();
if (logging) {
signal(SIGUSR1, SIG_DFL);
writegstatlog(&table, time(NULL) - statbegin, logfile);
writelog(logging, logfile,
"******** General interface statistics stopped ********");
fclose(logfile);
}
destroyiflist(table.head);
pkt_cleanup();
strcpy(current_logfile, "");
}
void selectiface(char *ifname, int withall, int *aborted)
{
int ch;
struct iflist *list;
struct iflist *ptmp;
struct scroll_list scrolllist;
initiflist(&list);
if (list == NULL) {
no_ifaces_error();
*aborted = 1;
return;
}
if ((withall) && (list != NULL)) {
ptmp = xmalloc(sizeof(struct iflist));
strncpy(ptmp->ifname, "All interfaces", sizeof(ptmp->ifname));
ptmp->ifindex = 0;
rate_alloc(&ptmp->rate, 5); /* FIXME: need iflist_entry_init() */
ptmp->prev_entry = NULL;
list->prev_entry = ptmp;
ptmp->next_entry = list;
list = ptmp;
}
tx_listkeyhelp(STDATTR, HIGHATTR);
ptmp = list;
tx_init_listbox(&scrolllist, 24, 14, (COLS - 24) / 2 - 9,
(LINES - 14) / 2, STDATTR, BOXATTR, BARSTDATTR,
HIGHATTR);
tx_set_listbox_title(&scrolllist, "Select Interface", 1);
while (ptmp != NULL) {
tx_add_list_entry(&scrolllist, (char *) ptmp, ptmp->ifname);
ptmp = ptmp->next_entry;
}
tx_show_listbox(&scrolllist);
tx_operate_listbox(&scrolllist, &ch, aborted);
tx_close_listbox(&scrolllist);
if (!(*aborted) && (list != NULL)) {
ptmp = (struct iflist *) scrolllist.textptr->nodeptr;
if ((withall) && (ptmp->prev_entry == NULL)) /* All Interfaces */
strcpy(ifname, "");
else
strcpy(ifname, ptmp->ifname);
}
tx_destroy_list(&scrolllist);
destroyiflist(list);
update_panels();
doupdate();
}