1 /*
2  *    jnettop, network online traffic visualiser
3  *    Copyright (C) 2002-2006 Jakub Skopal
4  *
5  *    This program is free software; you can redistribute it and/or modify
6  *    it under the terms of the GNU General Public License as published by
7  *    the Free Software Foundation; either version 2 of the License, or
8  *    (at your option) any later version.
9  *
10  *    This program is distributed in the hope that it will be useful,
11  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *    GNU General Public License for more details.
14  *
15  *    You should have received a copy of the GNU General Public License
16  *    along with this program; if not, write to the Free Software
17  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  *    $Header: /cvsroot/jnettop/jnettop/jcursesdisplay.c,v 1.2 2006/04/11 15:21:05 merunka Exp $
20  *
21  */
22 
23 #include "jbase.h"
24 #include "jdevice.h"
25 #include "jprocessor.h"
26 #include "jconfig.h"
27 #include "jutil.h"
28 #include "jcursesdisplay.h"
29 
30 #ifdef SUPPORT_NCURSES
31 
32 gboolean	recycleJnettop;
33 
34 GMutex		*statusMutex;
35 char		*statusMessage;
36 GTimeVal	statusTimeout;
37 
38 GMutex		*displayStreamsMutex;
39 jbase_stream	**displayStreams;
40 int		displayStreamsCount;
41 gchar 		line0FormatString[512], line1FormatString[512], line2FormatString[512];
42 
43 gboolean	onoffBitValues;
44 gboolean	onoffPackets;
45 
46 #define		DISPLAYMODE_NORMAL		0
47 #define		DISPLAYMODE_BPFFILTERS		1
48 #define		DISPLAYMODE_HELP		2
49 #define		DISPLAYMODE_SORTING		3
50 
51 int		displayMode = DISPLAYMODE_NORMAL;
52 
53 WINDOW		*listWindow;
54 
55 int	activeLines=1, activeColumns=1;
56 
57 GCompareFunc	currentByBytesCompareFunc = (GCompareFunc) jprocessor_compare_ByBytesStat;
58 GCompareFunc	currentByPacketsCompareFunc = (GCompareFunc) jprocessor_compare_ByPacketsStat;
59 
drawStatus(const gchar * msg)60 static void drawStatus(const gchar *msg) {
61 	g_mutex_lock(statusMutex);
62 	statusMessage = g_strdup(msg);
63 	g_get_current_time(&statusTimeout);
64 	g_time_val_add(&statusTimeout, 1000000);
65 	g_mutex_unlock(statusMutex);
66 	attron(A_BOLD);
67 	mvprintw(2, 0, "%s", statusMessage);
68 	clrtoeol();
69 	attroff(A_BOLD);
70 	refresh();
71 }
72 
drawScreen()73 static void drawScreen() {
74 	if (LINES != activeLines || COLS != activeColumns || !activeLines || !activeColumns) {
75 		activeLines = LINES;
76 		activeColumns = COLS;
77 
78 		if (activeLines < 20 || activeColumns < 80) {
79 			endwin();
80 			fprintf(stderr, "Too small terminal (detected size: %dx%d), minimum required size: 80x20\n", activeColumns, activeLines);
81 			exit(255);
82 		}
83 
84 		attrset(A_NORMAL);
85 
86 		mvprintw(0, 0, "run XXX:XX:XX device XXXXXXXXXX pkt[f]ilter: XXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
87 		mvprintw(1, 0, "[c]ntfilter: XXX [b]ps=XXXXXXX [l]ocal aggr.: XXXX [r]emote aggr.: XXXX   ");
88 		mvprintw(0, activeColumns-1, ".");
89 
90 		{
91 			int addrColumns = (activeColumns - 48) / 2;
92 			sprintf(line0FormatString, "%%-%d.%ds %%7.7s %%7.7s %%8.8s", activeColumns-25, activeColumns-25);
93 			sprintf(line1FormatString, " %%-%d.%ds %%5.5s %%6.6s  %%-%d.%ds %%5.5s  %%7.7s %%7.7s %%8.8s", addrColumns, addrColumns, addrColumns, addrColumns);
94 			sprintf(line2FormatString, "  %%-%d.%ds", activeColumns-3, activeColumns-3);
95 		}
96 
97 		if (listWindow) {
98 			delwin(listWindow);
99 		}
100 		listWindow = newwin(activeLines-8, activeColumns, 5, 0);
101 	}
102 	g_mutex_lock(statusMutex);
103 	if (statusMessage == NULL) {
104 		mvprintw(2, 0, "[q]uit [h]elp [s]orting [p]ackets [.] pause ");
105 		if (jdevice_DevicesCount>1) {
106 			mvprintw(2, 44, "[0]-[9] switch device");
107 		}
108 	} else {
109 		GTimeVal tv;
110 		attron(A_BOLD);
111 		mvprintw(2, 0, statusMessage);
112 		attroff(A_BOLD);
113 		g_get_current_time(&tv);
114 		if (tv.tv_sec >= statusTimeout.tv_sec) {
115 			g_free(statusMessage);
116 			statusMessage = NULL;
117 		}
118 	}
119 	g_mutex_unlock(statusMutex);
120 	clrtoeol();
121 }
122 
drawHeader()123 static void drawHeader() {
124 	GTimeVal	currentTime;
125 	gchar		timeBuffer[32];
126 	gchar srcbps[10], dstbps[10], bps[10], total[10], totalsrc[10], totaldst[10];
127 	int i;
128 	struct tm tm;
129 
130 	attron(A_BOLD);
131 
132 	g_get_current_time(&currentTime);
133 	localtime_r(&currentTime.tv_sec, &tm);
134 	sprintf(timeBuffer, "%3d:%02d:%02d", (int)((currentTime.tv_sec-jprocessor_Stats.startTime.tv_sec)/3600), (int)((currentTime.tv_sec-jprocessor_Stats.startTime.tv_sec)%3600/60), (int)((currentTime.tv_sec-jprocessor_Stats.startTime.tv_sec)%60));
135 	mvprintw(0, 4, "%s", timeBuffer);
136 	if (jcapture_ActiveDevice)
137 		mvprintw(0, 21, "%-10s", jcapture_ActiveDevice->name);
138 	mvprintw(0, 45, "%-29.29s", jconfig_GetSelectedBpfFilterName());
139 	mvprintw(1, 13, "%s", jprocessor_ContentFiltering?"on ":"off");
140 	mvprintw(1, 23, "%s", onoffPackets ? "pckts/s" : (onoffBitValues?"bits/s ":"bytes/s"));
141 	mvprintw(1, 46, "%s", JBASE_AGGREGATION[jprocessor_LocalAggregation]);
142 	mvprintw(1, 67, "%s", JBASE_AGGREGATION[jprocessor_RemoteAggregation]);
143 
144 	attroff(A_BOLD);
145 
146 	jutil_formatNumber(onoffPackets?jprocessor_Stats.totalPPS:(onoffBitValues?8:1)*jprocessor_Stats.totalBPS, onoffPackets, bps, 6);
147 	g_strlcat(bps, "/s", sizeof(bps));
148 	jutil_formatNumber(onoffPackets?jprocessor_Stats.totalSrcPPS:(onoffBitValues?8:1)*jprocessor_Stats.totalSrcBPS, onoffPackets, srcbps, 6);
149 	g_strlcat(srcbps, "/s", sizeof(srcbps));
150 	jutil_formatNumber(onoffPackets?jprocessor_Stats.totalDstPPS:(onoffBitValues?8:1)*jprocessor_Stats.totalDstBPS, onoffPackets, dstbps, 6);
151 	g_strlcat(dstbps, "/s", sizeof(dstbps));
152 	mvprintw(activeLines-2, 0, line0FormatString, "TOTAL", srcbps, dstbps, bps);
153 
154 	jutil_formatNumber(onoffPackets?jprocessor_Stats.totalPackets:(onoffBitValues?8:1)*jprocessor_Stats.totalBytes, onoffPackets, total, 6);
155 	jutil_formatNumber(onoffPackets?jprocessor_Stats.totalSrcPackets:(onoffBitValues?8:1)*jprocessor_Stats.totalSrcBytes, onoffPackets, totalsrc, 6);
156 	jutil_formatNumber(onoffPackets?jprocessor_Stats.totalDstPackets:(onoffBitValues?8:1)*jprocessor_Stats.totalDstBytes, onoffPackets, totaldst, 6);
157 	mvprintw(activeLines-1, 0, line1FormatString, "", "", "", "", "", totalsrc, totaldst, total);
158 
159 	mvchgat(activeLines-2, 0, activeColumns-25, A_BOLD, 0, NULL);
160 
161 	for (i=0; i<activeColumns; i++)
162 		mvaddch(activeLines-3, i, ACS_HLINE);
163 
164 	attron(A_REVERSE);
165 
166 	mvprintw(3, 0, line0FormatString, "LOCAL <-> REMOTE", onoffPackets ? "TXPPS" : "TXBPS",
167 		onoffPackets ? "RXPPS" : "RXBPS", onoffPackets ? "TOTALPPS" : "TOTALBPS");
168 	mvprintw(4, 0, line1FormatString, "(IP)", "PORT", "PROTO", "(IP)", "PORT", "TX", "RX", "TOTAL");
169 
170 	attroff(A_REVERSE);
171 }
172 
processStreamsFunc(GPtrArray * streamArray)173 static void processStreamsFunc(GPtrArray * streamArray) {
174 	guint		i, j;
175 	int		lines,oldLines;
176 	jbase_stream	**streams,**oldStreams;
177 
178 	lines = (activeLines - 8) / 3;
179 	streams = g_new0(jbase_stream *, lines);
180 
181 	for (i=0,j=0; i<streamArray->len && j<lines; i++) {
182 		jbase_stream *s = (jbase_stream *)g_ptr_array_index(streamArray, i);
183 		if (s->dead > 5) {
184 			continue;
185 		}
186 		s->displayed ++;
187 		streams[j++] = s;
188 	}
189 	lines = j;
190 
191 	g_mutex_lock(displayStreamsMutex);
192 	oldStreams = displayStreams;
193 	oldLines   = displayStreamsCount;
194 	displayStreams = streams;
195 	displayStreamsCount = lines;
196 	g_mutex_unlock(displayStreamsMutex);
197 
198 	for (i=0; i<oldLines; i++) {
199 		oldStreams[i]->displayed --;
200 	}
201 
202 	if (oldStreams)
203 		g_free(oldStreams);
204 }
205 
doDisplayStreams()206 static void	doDisplayStreams() {
207 	int i;
208 	for (i=0; i<displayStreamsCount; i++) {
209 		gchar srcaddr[INET6_ADDRSTRLEN + 1], dstaddr[INET6_ADDRSTRLEN + 1];
210 		gchar srcport[10], dstport[10], srcbps[10], dstbps[10], bps[10];
211 		gchar total[10], totalsrc[10], totaldst[10];
212 		uint tmp;
213 		gchar linebuffer[1024];
214 		const gchar *psrcaddr, *pdstaddr;
215 		jbase_stream *s = displayStreams[i];
216 		tmp = onoffPackets ? s->totalpps : (onoffBitValues?8:1)*s->totalbps;
217 		jutil_formatNumber(tmp, onoffPackets, bps, 6);
218 		g_strlcat(bps, "/s", sizeof(bps));
219 		tmp = onoffPackets ? s->srcpps : (onoffBitValues?8:1)*s->srcbps;
220 		jutil_formatNumber(tmp, onoffPackets, srcbps, 6);
221 		g_strlcat(srcbps, "/s", sizeof(srcbps));
222 		tmp = onoffPackets ? s->dstpps : (onoffBitValues?8:1)*s->dstbps;
223 		jutil_formatNumber(tmp, onoffPackets, dstbps, 6);
224 		g_strlcat(dstbps, "/s", sizeof(dstbps));
225 		jutil_formatNumber(onoffPackets ? s->totalpackets : s->totalbytes, onoffPackets, total, 6);
226 		jutil_formatNumber(onoffPackets ? s->srcpackets : s->srcbytes, onoffPackets, totalsrc, 6);
227 		jutil_formatNumber(onoffPackets ? s->dstpackets : s->dstbytes, onoffPackets, totaldst, 6);
228 		jutil_Address2String(JBASE_AF(s->proto), &s->src, srcaddr, INET6_ADDRSTRLEN);
229 		if (s->srcresolv == NULL || s->srcresolv->name == NULL) {
230 			psrcaddr = srcaddr;
231 		} else {
232 			psrcaddr = s->srcresolv->name;
233 		}
234 		jutil_Address2String(JBASE_AF(s->proto), &s->dst, dstaddr, INET6_ADDRSTRLEN);
235 		if (s->dstresolv == NULL || s->dstresolv->name == NULL) {
236 			pdstaddr = dstaddr;
237 		} else {
238 			pdstaddr = s->dstresolv->name;
239 		}
240 		if (s->srcport == -1)
241 			strcpy(srcport, "AGGR.");
242 		else
243 			sprintf(srcport, "%d", s->srcport);
244 		if (s->dstport == -1)
245 			strcpy(dstport, "AGGR.");
246 		else
247 			sprintf(dstport, "%d", s->dstport);
248 		sprintf(linebuffer, "%s <-> %s", psrcaddr, pdstaddr);
249 		mvwprintw(listWindow, i*3, 0, line0FormatString, linebuffer, srcbps, dstbps, bps);
250 		mvwchgat(listWindow, i*3, 0, activeColumns-25, A_BOLD, 0, NULL);
251 		mvwprintw(listWindow, i*3+1, 0, line1FormatString, srcaddr, srcport, JBASE_PROTOCOLS[s->proto], dstaddr, dstport, totalsrc, totaldst, total);
252 		mvwprintw(listWindow, i*3+2, 0, line2FormatString, s->filterDataString);
253 	}
254 }
255 
doDisplayWholeScreen()256 static void    doDisplayWholeScreen() {
257 	drawScreen();
258 	drawHeader();
259 	werase(listWindow);
260 }
261 
displayLoop()262 static void displayLoop() {
263 	g_usleep(500000);
264 
265 	while (jcapture_IsRunning) {
266 		int i;
267 
268 		g_mutex_lock(displayStreamsMutex);
269 		doDisplayWholeScreen();
270 
271 		switch (displayMode) {
272 		case DISPLAYMODE_NORMAL:
273 			doDisplayStreams();
274 			break;
275 		case DISPLAYMODE_BPFFILTERS:
276 			wattron(listWindow, A_BOLD);
277 			mvwprintw(listWindow, 1, 0, "Select rule you want to apply:");
278 			wattroff(listWindow, A_BOLD);
279 			mvwprintw(listWindow, 3, 5, "[.] None");
280 			for (i=0; i<JCONFIG_BPFFILTERS_LEN; i++) {
281 				mvwprintw(listWindow, i+5, 5, "[%c] %s", 'a'+i, JCONFIG_BPFFILTERS_GETNAME(i));
282 			}
283 			if (JCONFIG_BPFFILTERS_LEN == 0) {
284 				mvwprintw(listWindow, 6, 5, "You have no predefined filter rules. See README file for explanation");
285 				mvwprintw(listWindow, 7, 5, "on how to predefine filter rules");
286 			}
287 			break;
288 		case DISPLAYMODE_HELP:
289 			mvwprintw(listWindow, 2, 0, "I must write something here... :)");
290 			mvwprintw(listWindow, 4, 0, "Press any key to return.");
291 			break;
292 		case DISPLAYMODE_SORTING:
293 			mvwprintw(listWindow, 1, 0, "Select sorting column");
294 			mvwprintw(listWindow, 3, 0, " [.] on/off");
295 			mvwprintw(listWindow, 5, 0, " [t]xbps/txpps");
296 			mvwprintw(listWindow, 6, 0, " [r]xbps/rxpps");
297 			mvwprintw(listWindow, 7, 0, " total [b]ps/total pps");
298 		}
299 
300 		g_mutex_unlock(displayStreamsMutex);
301 
302 		wnoutrefresh(listWindow);
303 		refresh();
304 
305 		i = getch();
306 		if (i==ERR) {
307 			g_usleep(1000000);
308 		} else {
309 			switch (displayMode) {
310 			case DISPLAYMODE_NORMAL:
311 				switch (i) {
312 					case '.':
313 						drawStatus("Paused. Press any key to resume.");
314 						while (getch() == ERR) {
315 							g_usleep(100000);
316 						}
317 						break;
318 					case 'q':
319 					case 'Q':
320 						drawStatus("Please wait, shutting down...");
321 						jcapture_Kill();
322 						break;
323 					case 'c':
324 						jprocessor_SetContentFiltering( !jprocessor_ContentFiltering );
325 						break;
326 					case 'b':
327 						onoffBitValues = !onoffBitValues;
328 						break;
329 					case 'p':
330 						onoffPackets = !onoffPackets;
331 						jprocessor_SetSorting( jprocessor_Sorting, onoffPackets ? currentByPacketsCompareFunc : currentByBytesCompareFunc );
332 						break;
333 					case 's':
334 						displayMode = DISPLAYMODE_SORTING;
335 						break;
336 					case 'f':
337 						displayMode = DISPLAYMODE_BPFFILTERS;
338 						break;
339 					case 'h':
340 						displayMode = DISPLAYMODE_HELP;
341 						break;
342 					case 'l':
343 						jprocessor_SetLocalAggregation((jprocessor_LocalAggregation + 1) % 3);
344 						break;
345 					case 'r':
346 						jprocessor_SetRemoteAggregation((jprocessor_RemoteAggregation + 1) % 3);
347 						break;
348 					case '0':
349 					case '1':
350 					case '2':
351 					case '3':
352 					case '4':
353 					case '5':
354 					case '6':
355 					case '7':
356 					case '8':
357 					case '9':
358 						i -= '0';
359 						if (jdevice_DevicesCount>1 && jdevice_DevicesCount>i) {
360 							drawStatus("Please wait, cleaning up...");
361 							jconfig_Settings.device = jdevice_Devices + i;
362 							jconfig_Settings.deviceName = jconfig_Settings.device->name;
363 							recycleJnettop = TRUE;
364 							jcapture_Kill();
365 						}
366 						break;
367 				}
368 				break;
369 			case DISPLAYMODE_BPFFILTERS:
370 				if ((i == '.') || ((i >= 'a') && (i < 'a' + (JCONFIG_BPFFILTERS_LEN)))) {
371 					drawStatus("Please wait, cleaning up...");
372 					switch (i) {
373 					case '.':
374 						JCONFIG_BPFFILTERS_SETNONE;
375 						break;
376 					default:
377 						JCONFIG_BPFFILTERS_SETSELECTEDFILTER(i-'a');
378 						break;
379 					}
380 					recycleJnettop = TRUE;
381 					jcapture_Kill();
382 					displayMode = DISPLAYMODE_NORMAL;
383 					break;
384 				}
385 				break;
386 			case DISPLAYMODE_SORTING:
387 				switch (i) {
388 					case '.':
389 						jprocessor_SetSorting(!jprocessor_Sorting, NULL);
390 						if (!jprocessor_Sorting)
391 							drawStatus("Streams sorting suspended.");
392 						else
393 							drawStatus("Streams sorting resumed.");
394 						displayMode = DISPLAYMODE_NORMAL;
395 						break;
396 					case 't':
397 						currentByBytesCompareFunc = (GCompareFunc) jprocessor_compare_ByTxBytesStat;
398 						currentByPacketsCompareFunc = (GCompareFunc) jprocessor_compare_ByTxPacketsStat;
399 						jprocessor_SetSorting(-1, onoffPackets ? currentByPacketsCompareFunc : currentByBytesCompareFunc );
400 						displayMode = DISPLAYMODE_NORMAL;
401 						break;
402 					case 'r':
403 						currentByBytesCompareFunc = (GCompareFunc) jprocessor_compare_ByRxBytesStat;
404 						currentByPacketsCompareFunc = (GCompareFunc) jprocessor_compare_ByRxPacketsStat;
405 						jprocessor_SetSorting(-1, onoffPackets ? currentByPacketsCompareFunc : currentByBytesCompareFunc );
406 						displayMode = DISPLAYMODE_NORMAL;
407 						break;
408 					case 'b':
409 						currentByBytesCompareFunc = (GCompareFunc) jprocessor_compare_ByBytesStat;
410 						currentByPacketsCompareFunc = (GCompareFunc) jprocessor_compare_ByPacketsStat;
411 						jprocessor_SetSorting(-1, onoffPackets ? currentByPacketsCompareFunc : currentByBytesCompareFunc );
412 						displayMode = DISPLAYMODE_NORMAL;
413 						break;
414 					default:
415 						drawStatus("Invalid key.");
416 						break;
417 				}
418 				break;
419 			case DISPLAYMODE_HELP:
420 				displayMode = DISPLAYMODE_NORMAL;
421 				break;
422 			}
423 		}
424 	}
425 }
426 
jcursesdisplay_PreSetup()427 static gboolean	jcursesdisplay_PreSetup() {
428 	return TRUE;
429 }
430 
jcursesdisplay_Setup()431 static void	jcursesdisplay_Setup() {
432 	displayStreamsMutex = g_mutex_new();
433 	statusMutex = g_mutex_new();
434 
435 	jprocessor_SetProcessStreamsFunc((ProcessStreamsFunc) processStreamsFunc);
436 
437 	initscr();
438 	cbreak();
439 	noecho();
440 	nonl();
441 	intrflush(stdscr, FALSE);
442 	keypad(stdscr, TRUE);
443 	nodelay(stdscr, TRUE);
444 
445 	onoffBitValues = FALSE;
446 }
447 
jcursesdisplay_PreRunSetup()448 static gboolean jcursesdisplay_PreRunSetup() {
449 	return TRUE;
450 }
451 
jcursesdisplay_PreRun()452 static void jcursesdisplay_PreRun() {
453 	displayStreams = NULL;
454 	displayStreamsCount = 0;
455 
456 	activeLines = 0;
457 	activeColumns = 0;
458 
459 	if (statusMessage) {
460 		g_free(statusMessage);
461 		statusMessage = NULL;
462 	}
463 
464 	clear();
465 	drawScreen();
466 
467 	recycleJnettop = FALSE;
468 }
469 
jcursesdisplay_Run()470 static gboolean jcursesdisplay_Run() {
471 	displayLoop();
472 	return recycleJnettop;
473 }
474 
jcursesdisplay_Shutdown()475 static void	jcursesdisplay_Shutdown() {
476 	endwin();
477 }
478 
jcursesdisplay_DrawStatus(const gchar * msg)479 static void	jcursesdisplay_DrawStatus(const gchar *msg) {
480 	drawStatus(msg);
481 }
482 
jcursesdisplay_ProcessArgument(const gchar ** arg,int argc)483 static int	jcursesdisplay_ProcessArgument(const gchar **arg, int argc) {
484 	if (!strcmp(*arg, "-b") || !strcmp(*arg, "--bit-units")) {
485 		onoffBitValues = TRUE;
486 		return 1;
487 	}
488 	return 0;
489 }
490 
491 jbase_display	jcursesdisplay_Functions = {
492 	TRUE,
493 	jcursesdisplay_PreSetup,
494 	jcursesdisplay_Setup,
495 	jcursesdisplay_PreRunSetup,
496 	jcursesdisplay_PreRun,
497 	jcursesdisplay_Run,
498 	jcursesdisplay_Shutdown,
499 	jcursesdisplay_DrawStatus,
500 	jcursesdisplay_ProcessArgument
501 };
502 
503 #else
504 
505 jbase_display	jcursesdisplay_Functions = { FALSE };
506 
507 #endif
508