/*
 *	Copyright (c) 1993-1997 JSC Rinet, Novosibirsk, Russia
 *
 * Redistribution and use in source forms, with and without modification,
 * are permitted provided that this entire comment appears intact.
 * Redistribution in binary form may occur without any restrictions.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' WITHOUT ANY WARRANTIES OF ANY KIND.
 */

/* display.c -- collect and display network traffic */

char copyright[] = "Copyright (c) 1993-1997 JSC Rinet, Novosibirsk, Russia";

#ifdef	HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef	HAVE_SLCURSES
#include <slcurses.h>
#endif
#ifdef	HAVE_NCURSES
#include <ncurses.h>
#endif
#ifdef	HAVE_CURSES
#include <curses.h>
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>
#ifdef	TIME_WITH_SYS_TIME
#include <time.h>
#endif

#include "trafshow.h"

typedef struct t_entry *p_entry;
struct t_entry *entries;
int n_entry = 0;
u_long bytes_total = 0;
u_long packets_total = 0;
int page = 0;
int page_size;
static int l_nflag, l_eflag;
static int n_entries;
static int err_pos;
extern struct t_entry t_mask;  /* traffic mask */

void
init_display(reinit)
	int reinit;
{
	if (!reinit) {
		n_entries = LINES * MAX_PAGES;
		entries = (struct t_entry *)calloc(n_entries, sizeof(struct t_entry));
		if (!entries) error(1, "init_display: calloc");
		l_nflag = nflag;
		l_eflag = eflag;
		alarm(scr_interval);
	} else {
		int i = LINES * MAX_PAGES;
		if (i > n_entries) {
			n_entries = i;
			entries = (struct t_entry *)realloc(entries, i * sizeof(struct t_entry));
			if (!entries) error(1, "init_display: realloc");
		}
		page = 0;
	}
	err_pos = strlen(device_name) + count_size + 15;
}

void
header_line()
{
	int i;

	mvprintw(0, 0, "%-*.*s %-*.*s %-*.*s %*.*s %-*.*s",
		 addr_size, addr_size, "From Address",
		 addr_size, addr_size, "To Address",
		 proto_size, proto_size, "Proto",
		 bytes_size, bytes_size, "Bytes",
		 cps_size, cps_size, "CPS");
	for (i = 0; i < COLS; i++) mvaddch(1, i, '=');
}

void
status_line(reinit)
	int reinit;
{
	static time_t tp = 0;
	static u_long bytes_prev = 0, packets_prev = 0;

	if (reinit) {
		time_t tn = time(NULL);
		register sec = tn - tp;
		if (sec < 1) sec = 1;
		mvprintw(LINES-1, 0, "(%.5s) %*ld kb/total %*d pkts/sec %*d bytes/sec",
			 device_name, count_size, bytes_total/1024,
			 count_size, (packets_total-packets_prev)/sec,
			 count_size, (bytes_total-bytes_prev)/sec);
		packets_prev = packets_total;
		bytes_prev = bytes_total;
		tp = tn;
	} else	mvprintw(LINES-1, 0, "(%.5s) %*ld kb/total",
			 device_name, count_size, bytes_total/1024);
	mvprintw(LINES-1, COLS-13, " Page %2d/%-2d", page+1, n_entry/page_size+1);
	clrtoeol();
}

/*
 * Pretty print an Internet address (net address + port).
 */
static char *
inet_print(in, port, proto)
	struct in_addr in;
	u_short port, proto;
{
	register char *cp;
	char pline[20];
	int alen, plen;
	static char aline[1024];
	static char *icmp_type[ICMP_MAXTYPE+1] = {
		"echoreply", "unknown", "unknown", "dstunreach", "srcquench",
		"redirect", "unknown", "unknown", "echoreqst", "rtradvert",
		"rtrsolicit", "timeexceed", "paramprobl", "stampreqst",
		"stampreply", "inforeqst", "inforeply", "maskreqst",
		"maskreply" };

	if (l_nflag) cp = intoa(in.s_addr);
	else cp = ipaddr_string(&in);
	(void) sprintf(aline, "%-*.*s", addr_size, addr_size, cp);

	if (port) {
		if (proto == IPPROTO_TCP)
			cp = tcpport_string(port);
		else if (proto == IPPROTO_UDP)
			cp = udpport_string(port);
		else if (proto == IPPROTO_ICMP && port <= ICMP_MAXTYPE)
			cp = icmp_type[port];
		else cp = "unknown";

		plen = sprintf(pline, "..%.10s", cp);

		if ((cp = strchr(aline, ' ')) != NULL)
			alen = cp - aline;
		else	alen = addr_size;
		if ((alen + plen) >= addr_size)
			memcpy(&aline[addr_size - plen], pline, plen);
		else	memcpy(&aline[alen], pline, plen);
	}
	return aline;
}

static void
main_print(line, i)
	int line;
	register i;
{
	char *proto;
	int normal = TRUE;

	move(SCR_OFFS+line, 0);
#ifdef	HAVE_HAS_COLORS
	if (use_colors) normal = !color_on(&entries[i]);
#endif
	if (l_eflag) {
		printw("%-*.*s", addr_size, addr_size,
		       l_nflag ? entoa(entries[i].eh.ether_shost) :
		       etheraddr_string(entries[i].eh.ether_shost));
		addch(' ');
		printw("%-*.*s", addr_size, addr_size,
		       l_nflag ? entoa(entries[i].eh.ether_dhost) :
		       etheraddr_string(entries[i].eh.ether_dhost));

		proto = etherproto_string(entries[i].eh.ether_type);
	} else {
		addstr(inet_print(entries[i].src, entries[i].sport, entries[i].proto));
		addch(' ');
		addstr(inet_print(entries[i].dst, entries[i].dport, entries[i].proto));

		proto = getprotoname(entries[i].proto);
		if (proto == NULL) proto = "unknown";
	}

	printw(" %-*.*s", proto_size, proto_size, proto);
#ifdef	HAVE_HAS_COLORS
	if (!normal) attrset(A_NORMAL);
#endif
	printw(" %*ld", bytes_size, entries[i].bytes);
	if (entries[i].cps) printw(" %-*d", cps_size, entries[i].cps);
	clrtoeol();
}

static int
sortbybytes(e1, e2)
	register p_entry e1, e2;
{
	if (e1->bytes > e2->bytes) return -1;
	if (e1->bytes < e2->bytes) return 1;
	return 0;
}

/*
 * If scr_interval time any entry wasn't changed then purge it
 * and redraw whole screen.
 */
void
scr_redraw(del_old)
	int del_old;
{
	register i, j;

	if (del_old) {
		alarm(0);
		for (i = j = 0; i < n_entry; i++) {
			if (entries[i].bytes != entries[i].obytes)
				entries[i].cps = (entries[i].bytes - entries[i].obytes)/scr_interval;
			else	entries[i].bytes = 0, j++;
			entries[i].obytes = entries[i].bytes;
		}
		qsort(entries, n_entry, sizeof(struct t_entry), sortbybytes);
		n_entry -= j;
	}
	j = n_entry/page_size;
	if (page > j) page = j;

	move(SCR_OFFS, 0);
	if (n_entry) {
		for (i = 0, j = page * page_size;
		     i < page_size && j < n_entry; i++, j++)
			main_print(i, j);
	} else {
		clrtoeol();
		switch (alarm_flag) {
		case 1:
			addstr("Where is the tcp or udp packets contained data? It is exactly what I want! Now!");
			break;
		case 2:
			addstr("Nothing to show, this interface sleeping or broken. blah-blah-gluk-gluk-wait...");
			break;
		default:
			i = COLS/2 - strlen(copyright)/2;
			if (i < 0) i = 0;
			mvaddstr(SCR_OFFS, i, copyright);
			alarm_flag = 1;
		}
	}
	clrtobot();
	status_line(del_old);
	if (del_old) alarm(scr_interval);
}

void
scr_update()
{
#ifdef	HAVE_HAS_COLORS
	move(LINES-1, COLS-1);
#else
	move(0, COLS-1);
#endif
	refresh();
}

/*
 * Add new entry or add bytes to existed record.
 */
static void
collect(e)
	register p_entry e;
{
	register i, j;

	packets_total++;
	bytes_total += e->bytes;
	j = page * page_size;

	e->src.s_addr &= t_mask.src.s_addr;
	e->dst.s_addr &= t_mask.dst.s_addr;
	e->sport &= t_mask.sport;
	e->dport &= t_mask.dport;
	e->proto &= t_mask.proto;

	for (i = 0; i < n_entry; i++) {
		if (memcmp(&e->eh, &entries[i].eh, sizeof(e->eh)) == 0 &&
		    e->src.s_addr == entries[i].src.s_addr &&
		    e->sport == entries[i].sport &&
		    e->dst.s_addr == entries[i].dst.s_addr &&
		    e->dport == entries[i].dport &&
		    e->proto == entries[i].proto) {
			entries[i].bytes += e->bytes;
			if (i >= j && i < j + page_size)
				mvprintw(i-j+SCR_OFFS, addr_size*2+proto_size+3,
					 "%*ld", bytes_size, entries[i].bytes);
			return;
		}
	}
	if (i >= LINES * MAX_PAGES) {
		mvaddstr(LINES-1, COLS-13, " Overflow");
		clrtoeol();
		return;
	}
	/* add new entry */
	entries[i] = *e;
	entries[i].obytes = entries[i].cps = 0;
	if (i >= j && i < j + page_size) main_print(i-j, i);
	n_entry++;
	status_line(0);
}

void
display(eh, ip, length)
	struct ether_header *eh;
	register struct ip *ip;
	int length;
{
	register iplen;
	int hlen, not_ip = 0;
	register u_char *cp;
	struct t_entry te;

	if ((u_char *)(ip + 1) > snapend) {
		if (!eflag) return; /* not ip proto? discard silently */
		not_ip++;
	} else if (length < sizeof(*ip)) {
		if (!eflag) {
			mvprintw(LINES-1, err_pos, "\
truncated-ip: discard %d bytes", length);
			clrtoeol();
			goto refresh_screen;
		}
		not_ip++;
	} else if (ip->ip_v != IPVERSION) {
		if (!eflag) {
			mvprintw(LINES-1, err_pos, "\
ip ver != %d: discard %d bytes", IPVERSION, length);
			clrtoeol();
			goto refresh_screen;
		}
		not_ip++;
	} else if ((iplen = ntohs(ip->ip_len)) < 1) {
		if (!eflag) return; /* empty ip packet? discard silently */
		not_ip++;
	} else if (length < iplen) {
		if (!eflag) {
			mvprintw(LINES-1, err_pos, "\
truncated-ip: %d bytes missing", iplen - length);
			clrtoeol();
			goto refresh_screen;
		}
		not_ip++;
	}

	if (eh)	te.eh = *eh;
	else memset(&te.eh, 0, sizeof(te.eh));

	te.sport = te.dport = 0;

	if (!not_ip) {	/* parse ip packet */

		/*
		* If this is fragment zero, hand it to the next higher level
		* protocol to fetch ports or icmp type else it is fragmented
		* ip datagram.
		*/
		if ((ntohs(ip->ip_off) & 0x1fff) == 0) {
			/* advance to high level protocol header */
			hlen = ip->ip_hl * 4;
			cp = (u_char *)ip + hlen;
			if (ip->ip_p == IPPROTO_TCP) {
				if (cp + sizeof(struct tcphdr) > snapend ||
				    iplen - hlen < sizeof(struct tcphdr)) {
					mvprintw(LINES-1, err_pos, "\
truncated-tcp: wrong ip hdrlen");
					clrtoeol();
					goto refresh_screen;
				}
				te.sport = ntohs(((struct tcphdr *)cp)->th_sport);
				te.dport = ntohs(((struct tcphdr *)cp)->th_dport);
			} else if (ip->ip_p == IPPROTO_UDP) {
				if (cp + sizeof(struct udphdr) > snapend ||
				    iplen - hlen < sizeof(struct udphdr)) {
					mvprintw(LINES-1, err_pos, "\
truncated-udp: wrong ip hdrlen");
					clrtoeol();
					goto refresh_screen;
				}
				te.sport = ntohs(((struct udphdr *)cp)->uh_sport);
				te.dport = ntohs(((struct udphdr *)cp)->uh_dport);
			} else if (ip->ip_p == IPPROTO_ICMP) {
				if (cp + sizeof(struct icmp) > snapend ||
				    iplen - hlen < sizeof(struct icmp)) {
					mvprintw(LINES-1, err_pos, "\
truncated-icmp: wrong ip hdrlen");
					clrtoeol();
					goto refresh_screen;
				}
				te.sport = ((struct icmp *)cp)->icmp_type;
			}
		}
		te.src.s_addr = ip->ip_src.s_addr;
		te.dst.s_addr = ip->ip_dst.s_addr;
		te.proto = ip->ip_p;
		te.bytes = iplen;
	} else {	/* other than ip protocol packets */
		te.src.s_addr = te.dst.s_addr = 0;
		te.proto = 0;
		te.bytes = length;
	}

	collect(&te);

	if (alarm_flag) {
		alarm_flag = 0;
		if (resize_flag) {
			resize_flag = 0;
			init_term(TRUE);
		}
		scr_redraw(TRUE);
	}

refresh_screen:
	if (kflag && kbhit(0) && get_keyb()) scr_redraw(FALSE);

	scr_update();
	return;
}

int
inputchar()
{
	return getch();
}

int
get_keyb()
{
	int ch;

	if ((ch = getch()) == ERR) error(1, "get_keyb: getch");
	switch(ch) {
	case ESC:
		switch(get_arrowkey(inputchar)) {
		case KEYMAP_UP:
		case KEYMAP_PAGE_UP:
			goto page_up;
		case KEYMAP_DOWN:
		case KEYMAP_PAGE_DOWN:
			goto page_down;
		case KEYMAP_LEFT:
		case KEYMAP_HOME:
			goto home_list;
		case KEYMAP_RIGHT:
		case KEYMAP_END:
			goto end_list;
		default:
			mvaddstr(LINES-1, COLS-13, " Bad command");
			return 0;
		}
		break;

	case 'k':		/* line up */
	case ctrl('P'):
	case '\b':		/* page up */
	case 'b':
	case ctrl('U'):
	case ctrl('B'):
page_up:	
		if (page > 0) page--;
		break;

	case 'j':
	case ctrl('N'):		/* line down */
	case ' ':		/* page down */
	case ctrl('D'):
	case ctrl('F'):
page_down:
		if (page < n_entry/page_size) page++;
		break;

	case ctrl('A'):		/* home */
home_list:
		page = 0;
		break;

	case ctrl('E'):		/* end */
end_list:
		page = n_entry/page_size;
		break;

	case '\r':		/* enter */
	case '\n':
		l_eflag ^= 1;
		break;

	case '\t':		/* tab */
		l_nflag ^= 1;
		break;

	case 'q':		/* quit */
		cleanup(SIGINT);

	case ctrl('L'):		/* refresh screen */
		clear();
		header_line();
		scr_redraw(FALSE);
		scr_update();
		break;

	default:
		mvaddstr(LINES-1, COLS-13, " Bad command");
		return 0;
	}
	return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1