/* mtr -- a network diagnostic tool Copyright (C) 1997,1998 Matt Kimball This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include "mtr.h" #include #include #include #include #include #include /* MacOSX may need this before socket.h...*/ #if defined(HAVE_SYS_TYPES_H) #include #endif #include #include #include #if defined(HAVE_NCURSES_H) #include #elif defined(HAVE_NCURSES_CURSES_H) #include #elif defined(HAVE_CURSES_H) #include #elif defined(HAVE_CURSESX_H) #include #else #error No curses header file available #endif /* This go-around is needed only when compiling with antique version of curses. getmaxyx is part of Technical Standard X/Open Curses Issue 4, Version 2 (1996). http://pubs.opengroup.org/onlinepubs/9693989999/toc.pdf see page 106 */ #ifndef getmaxyx #define getmaxyx(win,y,x) ((y) = (win)->_maxy + 1, (x) = (win)->_maxx + 1) #endif #include "mtr.h" #include "mtr-curses.h" #include "net.h" #include "dns.h" #include "asn.h" #include "display.h" #include "utils.h" enum { NUM_FACTORS = 8 }; static double factors[NUM_FACTORS]; static int scale[NUM_FACTORS]; static char block_map[NUM_FACTORS]; enum { black = 1, red, green, yellow, blue, magenta, cyan, white }; static const int block_col[NUM_FACTORS + 1] = { COLOR_PAIR(red) | A_BOLD, A_NORMAL, COLOR_PAIR(green), COLOR_PAIR(green) | A_BOLD, COLOR_PAIR(yellow) | A_BOLD, COLOR_PAIR(magenta) | A_BOLD, COLOR_PAIR(magenta), COLOR_PAIR(red), COLOR_PAIR(red) | A_BOLD }; static void pwcenter( char *str) { int maxx; size_t cx; int __unused_int ATTRIBUTE_UNUSED; getmaxyx(stdscr, __unused_int, maxx); cx = (size_t) (maxx - strlen(str)) / 2; printw("%*s%s", (int) cx, "", str); } static char *format_number( int n, int w, char *buf) { if (w != 5) /* XXX todo: implement w != 5.. */ snprintf(buf, w + 1, "%s", "unimpl"); else if (n < 100000) /* buf is good as-is */ ; else if (n < 1000000) snprintf(buf, w + 1, "%3dk%1d", n / 1000, (n % 1000) / 100); else if (n < 10000000) snprintf(buf, w + 1, "%1dM%03d", n / 1000000, (n % 1000000) / 1000); else if (n < 100000000) snprintf(buf, w + 1, "%2dM%02d", n / 1000000, (n % 1000000) / 10000); else if (n < 1000000000) snprintf(buf, w + 1, "%3dM%01d", n / 1000000, (n % 1000000) / 100000); else /* if (n < 10000000000) */ snprintf(buf, w + 1, "%1dG%03d", n / 1000000000, (n % 1000000000) / 1000000); return buf; } int mtr_curses_keyaction( struct mtr_ctl *ctl) { int c = getch(); int i = 0; float f = 0.0; char buf[MAXFLD + 1]; if (c == 'Q') { /* must be checked before c = tolower(c) */ mvprintw(2, 0, "Type of Service(tos): %d\n", ctl->tos); mvprintw(3, 0, "default 0x00, min cost 0x02, rel 0x04,, thr 0x08, low del 0x10...\n"); move(2, 22); refresh(); while ((c = getch()) != '\n' && i < MAXFLD) { attron(A_BOLD); printw("%c", c); attroff(A_BOLD); refresh(); buf[i++] = c; /* need more checking on 'c' */ } buf[i] = '\0'; ctl->tos = atoi(buf); if (ctl->tos > 255 || ctl->tos < 0) ctl->tos = 0; return ActionNone; } c = tolower(c); switch (c) { case 'q': case -1: case 3: return ActionQuit; case 12: return ActionClear; case 19: case 'p': return ActionPause; case 17: case ' ': return ActionResume; case 'r': return ActionReset; case 'd': return ActionDisplay; case 'e': return ActionMPLS; case 'n': return ActionDNS; #ifdef HAVE_IPINFO case 'y': return ActionII; case 'z': return ActionAS; #endif case '+': return ActionScrollDown; case '-': return ActionScrollUp; case 's': mvprintw(2, 0, "Change Packet Size: %d\n", ctl->cpacketsize); mvprintw(3, 0, "Size Range: %d-%d, < 0:random.\n", MINPACKET, MAXPACKET); move(2, 20); refresh(); while ((c = getch()) != '\n' && i < MAXFLD) { attron(A_BOLD); printw("%c", c); attroff(A_BOLD); refresh(); buf[i++] = c; /* need more checking on 'c' */ } buf[i] = '\0'; ctl->cpacketsize = atoi(buf); return ActionNone; case 'b': mvprintw(2, 0, "Ping Bit Pattern: %d\n", ctl->bitpattern); mvprintw(3, 0, "Pattern Range: 0(0x00)-255(0xff), <0 random.\n"); move(2, 18); refresh(); while ((c = getch()) != '\n' && i < MAXFLD) { attron(A_BOLD); printw("%c", c); attroff(A_BOLD); refresh(); buf[i++] = c; /* need more checking on 'c' */ } buf[i] = '\0'; ctl->bitpattern = atoi(buf); if (ctl->bitpattern > 255) ctl->bitpattern = -1; return ActionNone; case 'i': mvprintw(2, 0, "Interval : %0.0f\n\n", ctl->WaitTime); move(2, 11); refresh(); while ((c = getch()) != '\n' && i < MAXFLD) { attron(A_BOLD); printw("%c", c); attroff(A_BOLD); refresh(); buf[i++] = c; /* need more checking on 'c' */ } buf[i] = '\0'; f = atof(buf); if (f <= 0.0) return ActionNone; if (!running_as_root() && (f < 1.0)) return ActionNone; ctl->WaitTime = f; return ActionNone; case 'f': mvprintw(2, 0, "First TTL: %d\n\n", ctl->fstTTL); move(2, 11); refresh(); while ((c = getch()) != '\n' && i < MAXFLD) { attron(A_BOLD); printw("%c", c); attroff(A_BOLD); refresh(); buf[i++] = c; /* need more checking on 'c' */ } buf[i] = '\0'; i = atoi(buf); if (i < 1 || i > ctl->maxTTL) return ActionNone; ctl->fstTTL = i; return ActionNone; case 'm': mvprintw(2, 0, "Max TTL: %d\n\n", ctl->maxTTL); move(2, 9); refresh(); while ((c = getch()) != '\n' && i < MAXFLD) { attron(A_BOLD); printw("%c", c); attroff(A_BOLD); refresh(); buf[i++] = c; /* need more checking on 'c' */ } buf[i] = '\0'; i = atoi(buf); if (i < ctl->fstTTL || i > (MaxHost - 1)) return ActionNone; ctl->maxTTL = i; return ActionNone; /* fields to display & their ordering */ case 'o': mvprintw(2, 0, "Fields: %s\n\n", ctl->fld_active); for (i = 0; i < MAXFLD; i++) { if (data_fields[i].descr != NULL) printw(" %s\n", data_fields[i].descr); } printw("\n"); move(2, 8); /* length of "Fields: " */ refresh(); i = 0; while ((c = getch()) != '\n' && i < MAXFLD) { if (strchr(ctl->available_options, c)) { attron(A_BOLD); printw("%c", c); attroff(A_BOLD); refresh(); buf[i++] = c; /* Only permit values in "available_options" be entered */ } else { printf("\a"); /* Illegal character. Beep, ring the bell. */ } } buf[i] = '\0'; if (strlen(buf) > 0) xstrncpy(ctl->fld_active, buf, 2 * MAXFLD); return ActionNone; case 'j': if (strchr(ctl->fld_active, 'N')) /* GeoMean and jitter */ xstrncpy(ctl->fld_active, "DR AGJMXI", 2 * MAXFLD); else /* default */ xstrncpy(ctl->fld_active, "LS NABWV", 2 * MAXFLD); return ActionNone; case 'u': switch (ctl->mtrtype) { case IPPROTO_ICMP: case IPPROTO_TCP: ctl->mtrtype = IPPROTO_UDP; break; case IPPROTO_UDP: ctl->mtrtype = IPPROTO_ICMP; break; } return ActionNone; case 't': switch (ctl->mtrtype) { case IPPROTO_ICMP: case IPPROTO_UDP: ctl->mtrtype = IPPROTO_TCP; break; case IPPROTO_TCP: ctl->mtrtype = IPPROTO_ICMP; break; } return ActionNone; /* reserve to display help message -Min */ case '?': case 'h': mvprintw(2, 0, "Command:\n"); printw(" ?|h help\n"); printw(" p pause (SPACE to resume)\n"); printw(" d switching display mode\n"); printw(" e toggle MPLS information on/off\n"); printw(" n toggle DNS on/off\n"); printw(" r reset all counters\n"); printw (" o str set the columns to display, default str='LRS N BAWV'\n"); printw (" j toggle latency(LS NABWV)/jitter(DR AGJMXI) stats\n"); printw(" c report cycle n, default n=infinite\n"); printw (" i set the ping interval to n seconds, default n=1\n"); printw (" f set the initial time-to-live(ttl), default n=1\n"); printw (" m set the max time-to-live, default n= # of hops\n"); printw(" s set the packet size to n or random(n<0)\n"); printw (" b set ping bit pattern to c(0..255) or random(c<0)\n"); printw(" Q set ping packet's TOS to t\n"); printw(" u switch between ICMP ECHO and UDP datagrams\n"); printw(" t switch between ICMP ECHO and TCP\n"); #ifdef HAVE_IPINFO printw(" y switching IP info\n"); printw(" z toggle ASN info on/off\n"); #endif printw("\n"); printw(" press any key to go back..."); getch(); /* read and ignore 'any key' */ return ActionNone; default: /* ignore unknown input */ return ActionNone; } } static void format_field( char *dst, int dst_length, const char *format, int n) { if (index(format, 'N')) { *dst++ = ' '; format_number(n, 5, dst); } else if (strchr(format, 'f')) { /* this is for fields where we measure integer microseconds but display floating point miliseconds. Convert to float here. */ snprintf(dst, dst_length, format, n / 1000.0); /* this was marked as a temporary hack over 10 years ago. -- REW */ } else { snprintf(dst, dst_length, format, n); } } static void mtr_curses_hosts( struct mtr_ctl *ctl, int startstat) { int max; int at; struct mplslen *mpls, *mplss; ip_t *addr, *addrs; int addrcmp_result; int err; int y; char *name; int i, j, k; int hd_len; char buf[1024]; int __unused_int ATTRIBUTE_UNUSED; max = net_max(ctl); for (at = net_min(ctl) + ctl->display_offset; at < max; at++) { printw("%2d. ", at + 1); err = net_err(at); addr = net_addr(at); mpls = net_mpls(at); addrcmp_result = addrcmp( (void *) addr, (void *) &ctl->unspec_addr, ctl->af); if (err == 0 && addrcmp_result != 0) { name = dns_lookup(ctl, addr); if (!net_up(at)) attron(A_BOLD); #ifdef HAVE_IPINFO if (is_printii(ctl)) printw(fmt_ipinfo(ctl, addr)); #endif if (name != NULL) { if (ctl->show_ips) printw("%s (%s)", name, strlongip(ctl, addr)); else printw("%s", name); } else { printw("%s", strlongip(ctl, addr)); } attroff(A_BOLD); getyx(stdscr, y, __unused_int); move(y, startstat); /* net_xxx returns times in usecs. Just display millisecs */ hd_len = 0; for (i = 0; i < MAXFLD; i++) { /* Ignore options that don't exist */ /* On the other hand, we now check the input side. Shouldn't happen, can't be careful enough. */ j = ctl->fld_index[ctl->fld_active[i]]; if (j == -1) continue; format_field(buf + hd_len, sizeof(buf) - hd_len, data_fields[j].format, data_fields[j].net_xxx(at)); hd_len += data_fields[j].length; } buf[hd_len] = 0; printw("%s", buf); for (k = 0; k < mpls->labels && ctl->enablempls; k++) { printw("\n [MPLS: Lbl %lu TC %u S %u TTL %u]", mpls->label[k], mpls->tc[k], mpls->s[k], mpls->ttl[k]); } /* Multi path */ for (i = 0; i < MAXPATH; i++) { addrs = net_addrs(at, i); mplss = net_mplss(at, i); if (addrcmp((void *) addrs, (void *) addr, ctl->af) == 0) continue; if (addrcmp ((void *) addrs, (void *) &ctl->unspec_addr, ctl->af) == 0) break; name = dns_lookup(ctl, addrs); if (!net_up(at)) attron(A_BOLD); printw("\n "); #ifdef HAVE_IPINFO if (is_printii(ctl)) printw(fmt_ipinfo(ctl, addrs)); #endif if (name != NULL) { if (ctl->show_ips) printw("%s (%s)", name, strlongip(ctl, addrs)); else printw("%s", name); } else { printw("%s", strlongip(ctl, addrs)); } for (k = 0; k < mplss->labels && ctl->enablempls; k++) { printw("\n [MPLS: Lbl %lu TC %u S %u TTL %u]", mplss->label[k], mplss->tc[k], mplss->s[k], mplss->ttl[k]); } attroff(A_BOLD); } } else { attron(A_BOLD); printw("(%s)", host_error_to_string(err)); attroff(A_BOLD); } printw("\n"); } move(2, 0); } static void mtr_gen_scale( struct mtr_ctl *ctl) { int *saved, i, max, at; int range; static int low_ms, high_ms; low_ms = 1000000; high_ms = -1; for (i = 0; i < NUM_FACTORS; i++) { scale[i] = 0; } max = net_max(ctl); for (at = ctl->display_offset; at < max; at++) { saved = net_saved_pings(at); for (i = 0; i < SAVED_PINGS; i++) { if (saved[i] < 0) continue; if (saved[i] < low_ms) { low_ms = saved[i]; } if (saved[i] > high_ms) { high_ms = saved[i]; } } } range = high_ms - low_ms; for (i = 0; i < NUM_FACTORS; i++) { scale[i] = low_ms + ((double) range * factors[i]); } } static void mtr_curses_init( void) { int i; int block_split; /* Initialize factors to a log scale. */ for (i = 0; i < NUM_FACTORS; i++) { factors[i] = ((double) 1 / NUM_FACTORS) * (i + 1); factors[i] *= factors[i]; /* Squared. */ } /* Initialize block_map. The block_split is always smaller than 9 */ block_split = (NUM_FACTORS - 2) / 2; for (i = 1; i <= block_split; i++) { block_map[i] = '0' + i; } for (i = block_split + 1; i < NUM_FACTORS - 1; i++) { block_map[i] = 'a' + i - block_split - 1; } block_map[0] = '.'; block_map[NUM_FACTORS - 1] = '>'; } static void mtr_print_scaled( int ms) { int i; for (i = 0; i < NUM_FACTORS; i++) { if (ms <= scale[i]) { attrset(block_col[i + 1]); printw("%c", block_map[i]); attrset(A_NORMAL); return; } } printw(">"); } static void mtr_fill_graph( struct mtr_ctl *ctl, int at, int cols) { int *saved; int i; saved = net_saved_pings(at); for (i = SAVED_PINGS - cols; i < SAVED_PINGS; i++) { if (saved[i] == -2) { printw(" "); } else if (saved[i] == -1) { attrset(block_col[0]); printw("%c", '?'); attrset(A_NORMAL); } else { if (ctl->display_mode == DisplayModeBlockmap) { if (saved[i] > scale[6]) { printw("%c", block_map[NUM_FACTORS - 1]); } else { printw("."); } } else { mtr_print_scaled(saved[i]); } } } } static void mtr_curses_graph( struct mtr_ctl *ctl, int startstat, int cols) { int max, at, y, err; ip_t *addr; char *name; int __unused_int ATTRIBUTE_UNUSED; max = net_max(ctl); for (at = ctl->display_offset; at < max; at++) { printw("%2d. ", at + 1); addr = net_addr(at); err = net_err(at); if (!addr) { printw("(%s)", host_error_to_string(err)); continue; } if (err == 0 && addrcmp((void *) addr, (void *) &ctl->unspec_addr, ctl->af)) { if (!net_up(at)) { attron(A_BOLD); } #ifdef HAVE_IPINFO if (is_printii(ctl)) printw(fmt_ipinfo(ctl, addr)); #endif name = dns_lookup(ctl, addr); printw("%s", name ? name : strlongip(ctl, addr)); } else { attron(A_BOLD); printw("(%s)", host_error_to_string(err)); } attroff(A_BOLD); getyx(stdscr, y, __unused_int); move(y, startstat); printw(" "); mtr_fill_graph(ctl, at, cols); printw("\n"); } } void mtr_curses_redraw( struct mtr_ctl *ctl) { int maxx; int startstat; int rowstat; time_t t; int __unused_int ATTRIBUTE_UNUSED; int i, j; int hd_len = 0; char buf[1024]; char fmt[16]; erase(); getmaxyx(stdscr, __unused_int, maxx); rowstat = 5; move(0, 0); attron(A_BOLD); snprintf(buf, sizeof(buf), "%s%s%s", "My traceroute [v", PACKAGE_VERSION, "]"); pwcenter(buf); attroff(A_BOLD); mvprintw(1, 0, "%s (%s)", ctl->LocalHostname, net_localaddr()); t = time(NULL); mvprintw(1, maxx - 25, iso_time(&t)); printw("\n"); printw("Keys: "); attron(A_BOLD); printw("H"); attroff(A_BOLD); printw("elp "); attron(A_BOLD); printw("D"); attroff(A_BOLD); printw("isplay mode "); attron(A_BOLD); printw("R"); attroff(A_BOLD); printw("estart statistics "); attron(A_BOLD); printw("O"); attroff(A_BOLD); printw("rder of fields "); attron(A_BOLD); printw("q"); attroff(A_BOLD); printw("uit\n"); if (ctl->display_mode == DisplayModeDefault) { for (i = 0; i < MAXFLD; i++) { j = ctl->fld_index[ctl->fld_active[i]]; if (j < 0) continue; snprintf(fmt, sizeof(fmt), "%%%ds", data_fields[j].length); snprintf(buf + hd_len, sizeof(buf) - hd_len, fmt, data_fields[j].title); hd_len += data_fields[j].length; } attron(A_BOLD); mvprintw(rowstat - 1, 0, " Host"); mvprintw(rowstat - 1, maxx - hd_len - 1, "%s", buf); mvprintw(rowstat - 2, maxx - hd_len - 1, " Packets Pings"); attroff(A_BOLD); move(rowstat, 0); mtr_curses_hosts(ctl, maxx - hd_len - 1); } else { char msg[80]; int padding = 30; int max_cols; #ifdef HAVE_IPINFO if (is_printii(ctl)) padding += get_iiwidth(ctl->ipinfo_no); #endif max_cols = maxx <= SAVED_PINGS + padding ? maxx - padding : SAVED_PINGS; startstat = padding - 2; snprintf(msg, sizeof(msg), " Last %3d pings", max_cols); mvprintw(rowstat - 1, startstat, msg); attroff(A_BOLD); move(rowstat, 0); mtr_gen_scale(ctl); mtr_curses_graph(ctl, startstat, max_cols); printw("\n"); attron(A_BOLD); printw("Scale:"); attroff(A_BOLD); for (i = 0; i < NUM_FACTORS - 1; i++) { printw(" "); attrset(block_col[i + 1]); printw("%c", block_map[i]); attrset(A_NORMAL); printw(":%d ms", scale[i] / 1000); } printw(" "); attrset(block_col[NUM_FACTORS]); printw("%c", block_map[NUM_FACTORS - 1]); attrset(A_NORMAL); } refresh(); } void mtr_curses_open( struct mtr_ctl *ctl) { int bg_col = 0; int i; initscr(); raw(); noecho(); start_color(); if (use_default_colors() == OK) bg_col = -1; for (i = 0; i < NUM_FACTORS; i++) init_pair(i + 1, i, bg_col); mtr_curses_init(); mtr_curses_redraw(ctl); } void mtr_curses_close( void) { printw("\n"); endwin(); } void mtr_curses_clear( struct mtr_ctl *ctl) { mtr_curses_close(); mtr_curses_open(ctl); }