/* * Ths code in this file is part of tcptrack. For more information see * http://www.rhythm.cx/~steve/devel/tcptrack * * Copyright (C) Steve Benson - 2003 * * tcptrack is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your * option) any later version. * * tcptrack is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "TextUI.h" #include #include #include #include #include #include "util.h" #include "defs.h" #include "IPv4Address.h" #include "TCPTrack.h" #include "GenericError.h" extern TCPTrack *app; TextUI::TextUI( TCContainer *c ) { container = c; iter=NULL; doffset=0; state=USTATE_IDLE; paused=false; sorted=true; pthread_mutex_init( &state_mutex, NULL ); } void TextUI::init() { // // Initialize ncurses. // TODO: make these exceptions more specific & meaningful. // WINDOW *w=initscr(); if( w==NULL ) throw GenericError("Unable to initialize ncurses display: initscr returned NULL."); keypad(w,TRUE); int x,y; getmaxyx(w,y,x); // this is an ncurses macro if( x<80 ) throw GenericError("tcptrack requires a screen at least 80 columns wide to run."); if( y<3 ) throw GenericError("tcptrack requires a screen at least 3 rows tall to run."); size_x=x; size_y=y; bottom=y; // // Set up and run the displayer thread. // run_displayer = true; pthread_attr_t attr; if( pthread_attr_init( &attr ) != 0 ) throw GenericError("pthread_attr_init() failed"); pthread_attr_setstacksize( &attr, SS_TUI ); if( pthread_create(&displayer_tid,&attr,displayer_thread_func,this) != 0 ) throw GenericError("pthread_create() returned an error."); state=USTATE_RUNNING; } void TextUI::stop() { pthread_mutex_lock(&state_mutex); if( state != USTATE_RUNNING ) { pthread_mutex_unlock(&state_mutex); return; } state=USTATE_STOPPING; pthread_mutex_unlock(&state_mutex); // now that state is set to USTATE_STOPPING, // the display draw loop will see this and exit. just wait for it. pthread_join(displayer_tid,NULL); state=USTATE_DONE; } TextUI::~TextUI() { stop(); } // main drawer thread loop void TextUI::displayer_run() { // redraw the display this freqently... struct timespec t; t.tv_sec=0; t.tv_nsec=DISPLAY_REFRESH; // used for select(), to grab keyboard input when there is some. fd_set fdset; struct timeval tv; int rv; iter=container->getSortedIteratorPtr(); while( state==USTATE_RUNNING || state==USTATE_IDLE ) { FD_ZERO(&fdset); FD_SET(0,&fdset); tv.tv_sec=0; tv.tv_usec=100; rv=select(1,&fdset,NULL,NULL,&tv); if( rv ) { int c = getch(); if( c==KEY_DOWN ) { ++doffset; // this is checked for sanity later } else if( c==KEY_UP ) { if( doffset>0 ) --doffset; } else if( c=='q' ) { app->shutdown(); } else if( c=='s' ) sorted=!sorted; else if( c=='p' ) { if( paused==true ) { // going from paused to unpaused paused=false; container->purge(true); } else { // going from unpaused to paused paused=true; container->purge(false); } } } container->lock(); // check the offset into the container for sanity. if( doffset>0 ) if( container->numConnections()>0 ) { if( doffset >= container->numConnections() ) doffset = container->numConnections()-1; } else doffset=0; // if we aren't reusing an old iterator (ie, if unpaused), // get a fresh one. if( iter==NULL ) iter=container->getSortedIteratorPtr(); drawui(); if( paused==false ) { // gonna get a new one next time if not paused... // so can this one. delete iter; iter=NULL; } container->unlock(); } endwin(); } void TextUI::drawui() { int row=1; erase(); attron(A_REVERSE); move(0,0); printw(" "); move(0,1); printw("Client"); move(0,23); printw("Server"); move(0,45); printw("State"); move(0,58); printw("Idle"); move(0,63); printw("A"); move(0,65); printw("Speed"); attroff(A_REVERSE); move(1,0); SortedIterator * i=iter; i->rewind(); int Bps_total=0; // the total speed while( TCPConnection *ic=i->getNext() ) Bps_total+=ic->getPayloadBytesPerSecond(); i->rewind(); if( sorted ) i->sort(); int ic_i=0; // for scrolling while( TCPConnection *ic=i->getNext() ) { if( row == size_y-2 ) break; ++ic_i; if( ic_i <= doffset ) continue; if( paused==false && (ic->getState() == TCP_STATE_CLOSED || ic->getState() == TCP_STATE_RESET) && time(NULL) - ic->getLastPktTimestamp() > app->remto ) { continue; } move(row,1); printw("%s:%d", ic->srcAddr().ptr(), ic->srcPort() ); move(row,23); printw("%s:%d", ic->dstAddr().ptr(), ic->dstPort()); move(row,45); printw(" "); move(row,45); if( ic->getState() == TCP_STATE_SYN_SYNACK ) printw("SYN_SENT"); else if( ic->getState() == TCP_STATE_SYNACK_ACK ) printw("SYN|ACK-ACK"); else if( ic->getState() == TCP_STATE_UP ) printw("ESTABLISHED"); else if( ic->getState() == TCP_STATE_FIN_FINACK ) printw("CLOSING"); else if( ic->getState() == TCP_STATE_CLOSED ) printw("CLOSED"); else if( ic->getState() == TCP_STATE_RESET ) printw("RESET"); move(row,58); if( ic->getIdleSeconds() < 60 ) printw("%ds",ic->getIdleSeconds()); else if( ic->getIdleSeconds() > 59 ) printw("%dm",ic->getIdleSeconds()/60); else if( ic->getIdleSeconds() > 3559 ) printw("%dh",ic->getIdleSeconds()/3600); move(row,63); if( ic->activityToggle() ) printw("*"); else printw(" "); move(row,65); unsigned int Bps = ic->getPayloadBytesPerSecond(); print_bps(Bps); row++; } attron(A_REVERSE); move(bottom-2,0); printw(" "); move(bottom-2,1); printw("TOTAL"); move(bottom-2,65); print_bps(Bps_total); move(bottom-1,0); printw(" "); move(bottom-1,1); if( container->numConnections() > 0 ) printw("Connections %d-%d of %d",doffset+1,ic_i,container->numConnections()); else printw("Connections 0-0 of 0"); move(bottom-1,56); if( paused==true ) { attron(A_UNDERLINE); printw("P"); attroff(A_UNDERLINE); printw("aused"); } else { printw("Un"); attron(A_UNDERLINE); printw("p"); attroff(A_UNDERLINE); printw("aused"); } move(bottom-1,66); if( sorted==false ) { printw("Un"); attron(A_UNDERLINE); printw("s"); attroff(A_UNDERLINE); printw("orted"); } else { attron(A_UNDERLINE); printw("S"); attroff(A_UNDERLINE); printw("orted"); } attroff(A_REVERSE); refresh(); } // display the speed with the right format void TextUI::print_bps(int Bps) { if( Bps < 1024 ) printw("%d B/s",Bps); if( Bps >= 1024 && Bps < 1024*1024 ) printw("%d KB/s",Bps/1024); if( Bps >= 1024*1024 && Bps <= 99999999 ) printw("%d MB/s",Bps/(1024*1024)); if( Bps > 99999999 ) // not enough room to display it printw("LUDICROUS"); // LUDICROUS SPEED!! } // reset the terminal. used only for unclean exits. void TextUI::reset() { endwin(); } ///////// void *displayer_thread_func( void *arg ) { TextUI *ui = (TextUI *) arg; ui->displayer_run(); return NULL; }