/* vim:ts=8:sts=8:sw=4:noai:noexpandtab * * PGM packet monitor. * * Copyright (c) 2006-2007 Miru Limited. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* PGM internals */ #include /* example dependencies */ #include #include struct ncurses_window; typedef void (*paint_func)(struct ncurses_window*); typedef void (*resize_func)(struct ncurses_window*, int, int); struct ncurses_window { WINDOW* window; PANEL* panel; char* title; paint_func paint; resize_func resize; }; struct pgm_stat { gulong count, snap_count; gulong bytes, snap_bytes; gulong tsdu; gulong duplicate; gulong invalid; struct timeval last; struct timeval last_valid; struct timeval last_invalid; }; struct pgm_netstat { struct in_addr addr; gulong corrupt; }; struct pgm_hoststat { pgm_tsi_t tsi; struct in_addr last_addr; struct in_addr nla; gulong txw_secs; gulong txw_trail; gulong txw_lead; gulong txw_sqns; gulong rxw_trail; gulong rxw_lead; gulong rxw_trail_init; gboolean window_defined; gboolean rxw_constrained; gulong spm_sqn; struct pgm_stat spm, poll, polr, odata, rdata, nak, nnak, ncf, spmr, general; struct timeval session_start; }; /* globals */ static int g_port = 7500; static const char* g_network = "239.192.0.1"; static struct in_addr g_filter = { 0 }; static GIOChannel* g_io_channel = NULL; static GIOChannel* g_stdin_channel = NULL; static GMainLoop* g_loop = NULL; static guint g_status_height = 6; static guint g_info_width = 10; static time_t start_time; static struct ncurses_window *g_peer, *g_info, *g_status, *g_active; static GList* g_window_list = NULL; static guint g_paint_interval = ( 1 * 1000 ) / 15; static guint g_snap_interval = 10 * 1000; static struct timeval g_last_snap, g_now; static GList* g_status_list = NULL; static guint32 g_packets = 0; static GHashTable *g_hosts = NULL; static GHashTable *g_nets = NULL; static void init_ncurses (void); static void paint_ncurses (void); static void resize_ncurses (int, int); static void paint_peer (struct ncurses_window*); static gboolean tsi_row (gpointer, gpointer, gpointer); static void paint_info (struct ncurses_window*); static void paint_status (struct ncurses_window*); static void resize_peer (struct ncurses_window*, int, int); static void resize_info (struct ncurses_window*, int, int); static void resize_status (struct ncurses_window*, int, int); static void write_status (const gchar*, ...) G_GNUC_PRINTF (1, 2); static void write_statusv (const gchar*, va_list); static void on_signal (int, gpointer); static void on_winch (int); static gboolean on_startup (gpointer); static gboolean on_snap (gpointer); static gboolean on_paint (gpointer); static gboolean on_io_data (GIOChannel*, GIOCondition, gpointer); static gboolean on_io_error (GIOChannel*, GIOCondition, gpointer); static gboolean on_stdin_data (GIOChannel*, GIOCondition, gpointer); int main ( G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[] ) { GError* err = NULL; pgm_error_t* pgm_err = NULL; setlocale (LC_ALL, ""); log_init (); g_message ("pgmtop"); if (!pgm_init (&pgm_err)) { g_error ("Unable to start PGM engine: %s", pgm_err->message); pgm_error_free (pgm_err); return EXIT_FAILURE; } g_loop = g_main_loop_new (NULL, FALSE); /* setup signal handlers */ signal (SIGSEGV, on_sigsegv); signal (SIGHUP, SIG_IGN); pgm_signal_install (SIGINT, on_signal, g_loop); pgm_signal_install (SIGTERM, on_signal, g_loop); /* delayed startup */ g_message ("scheduling startup.n"); g_timeout_add(0, (GSourceFunc)on_startup, g_loop); /* dispatch loop */ g_message ("entering main event loop ..."); g_main_loop_run (g_loop); endwin(); g_message ("event loop terminated, cleaning up."); /* cleanup */ g_main_loop_unref (g_loop); g_loop = NULL; if (g_io_channel) { g_message ("closing socket."); g_io_channel_shutdown (g_io_channel, FALSE, &err); g_io_channel = NULL; } if (g_stdin_channel) { g_message ("unbinding stdin."); g_io_channel_unref (g_stdin_channel); g_stdin_channel = NULL; } g_message ("PGM engine shutdown."); pgm_shutdown (); g_message ("finished."); return EXIT_SUCCESS; } static struct ncurses_window* create_window ( char* name, paint_func paint, resize_func resize ) { struct ncurses_window* nw = g_malloc0 (sizeof(struct ncurses_window)); nw->window = newwin (0, 0, 0, 0); nw->panel = new_panel (nw->window); nw->title = name; nw->paint = paint; nw->resize = resize; g_window_list = g_list_append (g_window_list, nw); return nw; } /* +-Peer list --------------++-Info-+ * | || | * | < peer window > || < info window > * | || | * +-------------------------++------+ * +-Status--------------------------+ * | < status window > | * +---------------------------------+ */ static void init_ncurses (void) { /* setup ncurses terminal display */ initscr(); /* init ncurses library */ // signal_install (SIGWINCH, on_winch); noecho(); /* hide entered keys */ cbreak(); /* setup ncurses windows */ g_peer = create_window ("Peers", paint_peer, resize_peer); g_info = create_window ("Info", paint_info, resize_info); start_time = time (0); g_status = create_window ("Status", paint_status, resize_status); scrollok (g_status->window, 1); g_active = g_peer; top_panel (g_active->panel); paint_ncurses(); } static void resize_ncurses ( int hsize, int vsize ) { GList* nw_list = g_window_list; while (nw_list) { struct ncurses_window* nw = (struct ncurses_window*)nw_list->data; nw->resize (nw, hsize, vsize); nw_list = nw_list->next; } } static void paint_ncurses (void) { static int hsize = 0, vsize = 0; if (hsize != COLS || vsize != LINES) { hsize = COLS; vsize = LINES; resize_ncurses(hsize, vsize); } GList* nw_list = g_window_list; while (nw_list) { struct ncurses_window* nw = (struct ncurses_window*)nw_list->data; werase (nw->window); box (nw->window, ACS_VLINE, ACS_HLINE); mvwaddstr (nw->window, 0, 2, nw->title); nw->paint (nw); nw_list = nw_list->next; } /* have cursor stay at top left of active window */ wmove (g_active->window, 0, 0); update_panels(); /* update virtual screen */ doupdate(); /* update real screen */ } /* peer window */ static void paint_peer ( struct ncurses_window* nw ) { /* 1 2 3 4 5 6 7 8 * 012345678901234567890123456789012345678901234567890123456789012345678901234567890 * TSI Packets Bytes Packet/s Bit/s Data Inv Dupe * 100.200.300.400.500.600.70000 1,000K 1,000MB 1,000 1,000 100% 100% 100% */ mvwaddstr (nw->window, 1, 1, "TSI"); mvwaddstr (nw->window, 1, 32, "Packets"); mvwaddstr (nw->window, 1, 40, "Bytes"); mvwaddstr (nw->window, 1, 48, "Packet/s"); mvwaddstr (nw->window, 1, 58, "Bit/s"); mvwaddstr (nw->window, 1, 68, "Data"); mvwaddstr (nw->window, 1, 73, "Inv"); mvwaddstr (nw->window, 1, 78, "Dupe"); if (g_hosts) { int row = 2; gettimeofday(&g_now, NULL); g_hash_table_foreach (g_hosts, (GHFunc)tsi_row, &row); } } static char* print_si ( float* v ) { static char prefix[5] = ""; if (*v > 100 * 1000 * 1000) { strcpy (prefix, "G"); *v /= 1000.0 * 1000.0 * 1000.0; } else if (*v > 100 * 1000) { strcpy (prefix, "M"); *v /= 1000.0 * 1000.0; } else if (*v > 100) { strcpy (prefix, "K"); *v /= 1000.0; } else { *prefix = 0; } return prefix; } static gboolean tsi_row ( G_GNUC_UNUSED gpointer key, gpointer value, gpointer user_data ) { struct pgm_hoststat* hoststat = value; int* row = user_data; float secs = (g_now.tv_sec - g_last_snap.tv_sec) + ( (g_now.tv_usec - g_last_snap.tv_usec) / 1000.0 / 1000.0 ); /* TSI */ char* tsi_string = pgm_tsi_print (&hoststat->tsi); mvwaddstr (g_peer->window, *row, 1, tsi_string); /* Packets */ char buffer[100]; float v = hoststat->general.count; char* prefix = print_si (&v); snprintf (buffer, sizeof(buffer), "%lu%s", (gulong)v, prefix); mvwaddstr (g_peer->window, *row, 32, buffer); /* Bytes */ v = hoststat->general.bytes; prefix = print_si (&v); snprintf (buffer, sizeof(buffer), "%lu%s", (gulong)v, prefix); mvwaddstr (g_peer->window, *row, 40, buffer); /* Packet/s */ v = ( hoststat->general.count - hoststat->general.snap_count ) / secs; prefix = print_si (&v); snprintf (buffer, sizeof(buffer), "%.1f%s", v, prefix); mvwaddstr (g_peer->window, *row, 48, buffer); /* Bit/s */ float bitrate = ((float)( hoststat->general.bytes - hoststat->general.snap_bytes ) * 8.0 / secs); char* bitprefix = print_si (&bitrate); snprintf (buffer, sizeof(buffer), "%.1f%s", bitrate, bitprefix); mvwaddstr (g_peer->window, *row, 58, buffer); /* % Data */ snprintf (buffer, sizeof(buffer), "%d%%", (int)((100.0 * hoststat->odata.tsdu) / hoststat->general.bytes)); mvwaddstr (g_peer->window, *row, 68, buffer); /* % Invalid */ snprintf (buffer, sizeof(buffer), "%d%%", (int)(hoststat->general.invalid ? (100.0 * hoststat->general.invalid) / hoststat->general.count : 0.0)); mvwaddstr (g_peer->window, *row, 73, buffer); /* % Duplicate */ snprintf (buffer, sizeof(buffer), "%d%%", (int)(hoststat->general.duplicate ? (100.0 * hoststat->general.duplicate) / hoststat->general.count : 0.0)); mvwaddstr (g_peer->window, *row, 78, buffer); *row = *row + 1; return FALSE; } static void resize_peer ( struct ncurses_window* nw, int hsize, /* COLS */ int vsize /* LINES */ ) { wresize (nw->window, vsize - g_status_height, hsize - g_info_width); replace_panel (nw->panel, nw->window); move_panel (nw->panel, 0, 0); } /* info window */ static void paint_info ( struct ncurses_window* nw ) { char buffer[20]; mvwaddstr (nw->window, 1, 2, "Peers"); snprintf (buffer, sizeof(buffer), "%d", g_hosts ? g_hash_table_size (g_hosts) : 0); mvwaddstr (nw->window, 2, 2, buffer); mvwaddstr (nw->window, 3, 2, "Packets"); snprintf (buffer, sizeof(buffer), "%d", g_packets); mvwaddstr (nw->window, 4, 2, buffer); mvwaddstr (nw->window, LINES - g_status_height - 2, 2, "Elapsed"); time_t elapsed = time(0) - start_time; snprintf (buffer, sizeof(buffer), "%02d:%02d:%02d", (int) (elapsed / 60) / 60, (int) (elapsed / 60) % 60, (int) elapsed % 60); mvwaddstr (nw->window, LINES - g_status_height - 1, 1, buffer); } static void resize_info ( struct ncurses_window* nw, int hsize, /* COLS */ int vsize /* LINES */ ) { wresize (nw->window, vsize - g_status_height, g_info_width); replace_panel (nw->panel, nw->window); move_panel (nw->panel, 0, hsize - g_info_width); } /* status window */ static void paint_status ( G_GNUC_UNUSED struct ncurses_window* nw ) { if (!g_status_list) return; guint len = g_list_length (g_status_list); while (len > g_status_height) { g_free (g_status_list->data); g_status_list = g_list_delete_link (g_status_list, g_status_list); len--; } guint y = 1; GList* list = g_status_list; while (list) { mvwaddstr (g_status->window, y++, 3, (char*)list->data); list = list->next; } } static void resize_status ( struct ncurses_window* nw, int hsize, /* COLS */ int vsize /* LINES */ ) { wresize (nw->window, g_status_height, hsize); replace_panel (nw->panel, nw->window); move_panel (nw->panel, vsize - g_status_height, 0); } static void write_status ( const gchar* format, ... ) { va_list args; va_start (args, format); write_statusv (format, args); va_end (args); } static void write_statusv ( const gchar* format, va_list args1 ) { char buffer[1024]; vsnprintf (buffer, sizeof(buffer), format, args1); g_status_list = g_list_append (g_status_list, g_memdup (buffer, strlen(buffer)+1)); } static void on_signal ( int signum, gpointer user_data ) { GMainLoop* loop = (GMainLoop*)user_data; puts ("on_signal"); g_main_loop_quit (loop); } /* terminal resize signal */ static void on_winch ( G_GNUC_UNUSED int signum ) { paint_ncurses (); } static gboolean on_startup ( gpointer user_data ) { GMainLoop* loop = (GMainLoop*)user_data; int e; puts ("startup."); /* find PGM protocol id */ // TODO: fix valgrind errors int ipproto_pgm = IPPROTO_PGM; #if HAVE_GETPROTOBYNAME_R char b[1024]; struct protoent protobuf, *proto; e = getprotobyname_r ("pgm", &protobuf, b, sizeof(b), &proto); if (e != -1 && proto != NULL) { if (proto->p_proto != ipproto_pgm) { print f("Setting PGM protocol number to %i from /etc/protocols.\n", proto->p_proto); ipproto_pgm = proto->p_proto; } } #else struct protoent *proto = getprotobyname ("pgm"); if (proto != NULL) { if (proto->p_proto != ipproto_pgm) { printf("Setting PGM protocol number to %i from /etc/protocols.\n", proto->p_proto); ipproto_pgm = proto->p_proto; } } #endif /* open socket for snooping */ puts ("opening raw socket."); int sock = socket (PF_INET, SOCK_RAW, ipproto_pgm); if (sock < 0) { int _e = errno; puts ("on_startup() failed"); if (_e == EPERM && 0 != getuid()) { puts ("PGM protocol requires this program to run as superuser."); } g_main_loop_quit (loop); return FALSE; } /* drop out of setuid 0 */ if (0 == getuid ()) { puts ("dropping superuser privileges."); setuid ((gid_t)65534); setgid ((uid_t)65534); } char _t = 1; e = setsockopt (sock, IPPROTO_IP, IP_HDRINCL, &_t, sizeof(_t)); if (e < 0) { printw ("on_startup() failed\n"); close (sock); g_main_loop_quit (loop); return FALSE; } /* buffers */ int buffer_size = 0; socklen_t len = 0; e = getsockopt (sock, SOL_SOCKET, SO_RCVBUF, &buffer_size, &len); if (e == 0) { printf ("receive buffer set at %i bytes.\n", buffer_size); } e = getsockopt (sock, SOL_SOCKET, SO_SNDBUF, &buffer_size, &len); if (e == 0) { printf ("send buffer set at %i bytes.\n", buffer_size); } /* bind */ struct sockaddr_in addr; memset (&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); e = bind (sock, (struct sockaddr*)&addr, sizeof(addr)); if (e < 0) { printw ("on_startup() failed\n"); close (sock); g_main_loop_quit (loop); return FALSE; } /* multicast */ struct ip_mreq mreq; memset (&mreq, 0, sizeof(mreq)); mreq.imr_interface.s_addr = htonl(INADDR_ANY); printf ("listening on interface %s.\n", inet_ntoa(mreq.imr_interface)); mreq.imr_multiaddr.s_addr = inet_addr(g_network); printf ("subscription on multicast address %s.\n", inet_ntoa(mreq.imr_multiaddr)); e = setsockopt (sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); if (e < 0) { printw ("on_startup() failed\n"); close (sock); g_main_loop_quit (loop); return FALSE; } /* multicast loopback */ /* multicast ttl */ /* add socket to event manager */ g_io_channel = g_io_channel_unix_new (sock); printf ("socket opened with encoding %s.\n", g_io_channel_get_encoding(g_io_channel)); /* guint event = */ g_io_add_watch (g_io_channel, G_IO_IN | G_IO_PRI, on_io_data, NULL); /* guint event = */ g_io_add_watch (g_io_channel, G_IO_ERR | G_IO_HUP | G_IO_NVAL, on_io_error, NULL); /* add stdin to event manager */ g_stdin_channel = g_io_channel_unix_new (fileno(stdin)); printf ("binding stdin with encoding %s.\n", g_io_channel_get_encoding(g_stdin_channel)); g_io_add_watch (g_stdin_channel, G_IO_IN | G_IO_PRI, on_stdin_data, NULL); /* periodic timer to snapshot statistics */ g_timeout_add (g_snap_interval, (GSourceFunc)on_snap, NULL); /* period timer to update screen */ g_timeout_add (g_paint_interval, (GSourceFunc)on_paint, NULL); puts ("READY"); init_ncurses(); return FALSE; } static gboolean on_paint ( G_GNUC_UNUSED gpointer data ) { paint_ncurses(); return TRUE; } static guint tsi_hash ( gconstpointer v ) { return g_str_hash(pgm_tsi_print(v)); } static gint tsi_equal ( gconstpointer v, gconstpointer v2 ) { return memcmp (v, v2, (6 * sizeof(guint8)) + sizeof(guint16)) == 0; } static gboolean on_io_data ( GIOChannel* source, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer user_data ) { struct timeval now; struct pgm_sk_buff_t* skb = pgm_alloc_skb (4096); struct sockaddr_storage src, dst; struct sockaddr_in* sin = (struct sockaddr_in*)&src; socklen_t src_addr_len = sizeof(src); int fd = g_io_channel_unix_get_fd(source); skb->len = recvfrom(fd, skb->head, 4096, MSG_DONTWAIT, (struct sockaddr*)&src, &src_addr_len); gettimeofday (&now, NULL); g_packets++; GError* err = NULL; gboolean is_valid = pgm_parse_raw (skb, (struct sockaddr*)&dst, &err); if (!is_valid && err && PGM_PACKET_ERROR_CKSUM == err->code) { /* corrupt packet */ if (!g_nets) { g_nets = g_hash_table_new (g_int_hash, g_int_equal); } struct pgm_netstat* netstat = g_hash_table_lookup (g_nets, &sin->sin_addr); if (netstat == NULL) { write_status ("new host publishing corrupt data, local nla %s", inet_ntoa(sin->sin_addr)); netstat = g_malloc0(sizeof(struct pgm_netstat)); netstat->addr = sin->sin_addr; g_hash_table_insert (g_nets, (gpointer)&netstat->addr, (gpointer)netstat); } netstat->corrupt++; pgm_free_skb (skb); return TRUE; } else if (!is_valid) { /* general error */ pgm_free_skb (skb); return TRUE; } /* search for existing session */ if (!g_hosts) { g_hosts = g_hash_table_new (tsi_hash, tsi_equal); } struct pgm_hoststat* hoststat = g_hash_table_lookup (g_hosts, &skb->tsi); if (hoststat == NULL) { write_status ("new tsi %s with local nla %s", pgm_tsi_print (&skb->tsi), inet_ntoa(sin->sin_addr)); hoststat = g_malloc0(sizeof(struct pgm_hoststat)); memcpy (&hoststat->tsi, &skb->tsi, sizeof(pgm_tsi_t)); hoststat->session_start = now; g_hash_table_insert (g_hosts, (gpointer)&hoststat->tsi, (gpointer)hoststat); } /* increment statistics */ memcpy (&hoststat->last_addr, &sin->sin_addr, sizeof(sin->sin_addr)); hoststat->general.count++; hoststat->general.bytes += skb->len; hoststat->general.last = now; skb->data = (guint8*)skb->data + sizeof(struct pgm_header); skb->len -= sizeof(struct pgm_header); /* repurpose is_valid for PGM subtype */ is_valid = FALSE; switch (skb->pgm_header->pgm_type) { case PGM_SPM: hoststat->spm.count++; hoststat->spm.bytes += skb->len; hoststat->spm.last = now; is_valid = pgm_verify_spm (skb); if (!is_valid) { hoststat->spm.invalid++; hoststat->spm.last_invalid = now; } else { const struct pgm_spm* spm = (struct pgm_spm*)skb->data; hoststat->nla.s_addr = spm->spm_nla.s_addr; if (pgm_uint32_lte (g_ntohl( spm->spm_sqn ), hoststat->spm_sqn)) { hoststat->general.duplicate++; break; } hoststat->spm_sqn = g_ntohl( spm->spm_sqn ); hoststat->txw_trail = g_ntohl( spm->spm_trail ); hoststat->txw_lead = g_ntohl( spm->spm_lead ); hoststat->rxw_trail = hoststat->txw_trail; hoststat->window_defined = TRUE; } break; case PGM_ODATA: hoststat->odata.count++; hoststat->odata.bytes += skb->len; hoststat->odata.last = now; const struct pgm_data* data = (struct pgm_data*)skb->data; if (!hoststat->window_defined) { hoststat->rxw_lead = g_ntohl (data->data_sqn) - 1; hoststat->rxw_trail = hoststat->rxw_trail_init = hoststat->rxw_lead + 1; hoststat->rxw_constrained = TRUE; hoststat->window_defined = TRUE; } else { if (! pgm_uint32_gte( g_ntohl (data->data_sqn) , hoststat->rxw_trail ) ) { hoststat->odata.invalid++; hoststat->odata.last_invalid = now; break; } hoststat->rxw_trail = g_ntohl (data->data_trail); } if (hoststat->rxw_constrained && hoststat->txw_trail > hoststat->rxw_trail_init) { hoststat->rxw_constrained = FALSE; } if ( pgm_uint32_lte ( g_ntohl (data->data_sqn), hoststat->rxw_lead ) ) { hoststat->general.duplicate++; break; } else { hoststat->rxw_lead = g_ntohl (data->data_sqn); hoststat->odata.tsdu += g_ntohs (skb->pgm_header->pgm_tsdu_length); } break; case PGM_RDATA: hoststat->rdata.count++; hoststat->rdata.bytes += skb->len; hoststat->rdata.last = now; break; case PGM_POLL: hoststat->poll.count++; hoststat->poll.bytes += skb->len; hoststat->poll.last = now; break; case PGM_POLR: hoststat->polr.count++; hoststat->polr.bytes += skb->len; hoststat->polr.last = now; break; case PGM_NAK: hoststat->nak.count++; hoststat->nak.bytes += skb->len; hoststat->nak.last = now; is_valid = pgm_verify_nak (skb); if (!is_valid) { hoststat->nak.invalid++; hoststat->nak.last_invalid = now; } break; case PGM_NNAK: hoststat->nnak.count++; hoststat->nnak.bytes += skb->len; hoststat->nnak.last = now; break; case PGM_NCF: hoststat->ncf.count++; hoststat->ncf.bytes += skb->len; hoststat->ncf.last = now; break; case PGM_SPMR: hoststat->spmr.count++; hoststat->spmr.bytes += skb->len; hoststat->spmr.last = now; is_valid = pgm_verify_spmr (skb); if (!is_valid) { hoststat->spmr.invalid++; hoststat->spmr.last_invalid = now; } break; default: break; } if (!is_valid) { hoststat->general.invalid++; hoststat->general.last_invalid = now; } else { hoststat->general.last_valid = now; } pgm_free_skb (skb); return TRUE; } static gboolean on_io_error ( GIOChannel* source, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer data ) { puts ("on_error."); GError *err; g_io_channel_shutdown (source, FALSE, &err); /* remove event */ return FALSE; } /* process input commands from stdin/fd */ static gboolean on_stdin_data ( G_GNUC_UNUSED GIOChannel* source, G_GNUC_UNUSED GIOCondition condition, G_GNUC_UNUSED gpointer data ) { int ch = wgetch (g_active->window); if (ch == ERR) { goto out; } /* force redraw */ if (ch == 12) { clearok (curscr, TRUE); paint_ncurses (); goto out; } if (ch == 'q') { g_main_loop_quit(g_loop); } out: return TRUE; } static gboolean snap_stat ( G_GNUC_UNUSED gpointer key, gpointer value, G_GNUC_UNUSED gpointer user_data ) { struct pgm_hoststat* hoststat = value; #define SNAP_STAT(name) \ { \ hoststat->name.snap_count = hoststat->name.count; \ hoststat->name.snap_bytes = hoststat->name.bytes; \ } SNAP_STAT(spm); SNAP_STAT(poll); SNAP_STAT(polr); SNAP_STAT(odata); SNAP_STAT(rdata); SNAP_STAT(nak); SNAP_STAT(nnak); SNAP_STAT(ncf); SNAP_STAT(spmr); SNAP_STAT(general); return FALSE; } static gboolean on_snap ( gpointer data ) { if (!g_hosts) return TRUE; gettimeofday (&g_last_snap, NULL); g_hash_table_foreach (g_hosts, (GHFunc)snap_stat, NULL); return TRUE; } /* eof */