/* 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mtr.h" #include "mtr-curses.h" #include #include "display.h" #include "dns.h" #include "report.h" #include "net.h" #include "asn.h" #include "version.h" #ifdef ENABLE_IPV6 #define DEFAULT_AF AF_UNSPEC #else #define DEFAULT_AF AF_INET #endif #ifdef NO_HERROR #define herror(str) fprintf(stderr, str ": error looking up \"%s\"\n", Hostname); #endif int DisplayMode; int display_mode; int Interactive = 1; int PrintVersion = 0; int PrintHelp = 0; int MaxPing = 10; int ForceMaxPing = 0; float WaitTime = 1.0; char *Hostname = NULL; char *InterfaceAddress = NULL; char LocalHostname[128]; int dns = 1; int show_ips = 0; int enablempls = 0; int cpacketsize = 64; /* default packet size */ int bitpattern = 0; int tos = 0; #ifdef SO_MARK int mark = -1; #endif int reportwide = 0; int af = DEFAULT_AF; int mtrtype = IPPROTO_ICMP; /* Use ICMP as default packet type */ /* begin ttl windows addByMin */ int fstTTL = 1; /* default start at first hop */ /*int maxTTL = MaxHost-1; */ /* max you can go is 255 hops */ int maxTTL = 30; /* inline with traceroute */ /* end ttl window stuff. */ int remoteport = 80; /* for TCP tracing */ int timeout = 10 * 1000000; /* for TCP tracing */ /* default display field(defined by key in net.h) and order */ unsigned char fld_active[2*MAXFLD] = "LS NABWV"; int fld_index[256]; char available_options[MAXFLD]; struct fields data_fields[MAXFLD] = { /* key, Remark, Header, Format, Width, CallBackFunc */ {' ', ": Space between fields", " ", " ", 1, &net_drop }, {'L', "L: Loss Ratio", "Loss%", " %4.1f%%", 6, &net_loss }, {'D', "D: Dropped Packets", "Drop", " %4d", 5, &net_drop }, {'R', "R: Received Packets", "Rcv", " %5d", 6, &net_returned}, {'S', "S: Sent Packets", "Snt", " %5d", 6, &net_xmit }, {'N', "N: Newest RTT(ms)", "Last", " %5.1f", 6, &net_last }, {'B', "B: Min/Best RTT(ms)", "Best", " %5.1f", 6, &net_best }, {'A', "A: Average RTT(ms)", "Avg", " %5.1f", 6, &net_avg }, {'W', "W: Max/Worst RTT(ms)", "Wrst", " %5.1f", 6, &net_worst }, {'V', "V: Standard Deviation", "StDev", " %5.1f", 6, &net_stdev }, {'G', "G: Geometric Mean", "Gmean", " %5.1f", 6, &net_gmean }, {'J', "J: Current Jitter", "Jttr", " %4.1f", 5, &net_jitter}, {'M', "M: Jitter Mean/Avg.", "Javg", " %4.1f", 5, &net_javg }, {'X', "X: Worst Jitter", "Jmax", " %4.1f", 5, &net_jworst}, {'I', "I: Interarrival Jitter", "Jint", " %4.1f", 5, &net_jinta }, {'\0', NULL, NULL, NULL, 0, NULL} }; typedef struct names { char* name; struct names* next; } names_t; static names_t *names = NULL; char * trim(char * s) { char * p = s; int l = strlen(p); while(isspace(p[l-1]) && l) p[--l] = 0; while(*p && isspace(*p) && l) ++p, --l; return p; } static void append_to_names(const char* progname, const char* item) { names_t* name = calloc(1, sizeof(names_t)); if (name == NULL) { fprintf(stderr, "%s: memory allocation failure\n", progname); exit(EXIT_FAILURE); } name->name = strdup(item); name->next = names; names = name; } static void read_from_file(const char* progname, const char *filename) { FILE *in; char line[512]; if (! filename || strcmp(filename, "-") == 0) { clearerr(stdin); in = stdin; } else { in = fopen(filename, "r"); if (! in) { fprintf(stderr, "%s: fopen: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } } while (fgets(line, sizeof(line), in)) { char* name = trim(line); append_to_names(progname, name); } if (ferror(in)) { fprintf(stderr, "%s: ferror: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if (in != stdin) fclose(in); } /* * If the file stream is associated with a regular file, lock the file * in order coordinate writes to a common file from multiple mtr * instances. This is useful if, for example, multiple mtr instances * try to append results to a common file. */ static void lock(const char* progname, FILE *f) { int fd; struct stat buf; static struct flock lock; assert(f); lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_END; lock.l_len = 0; lock.l_pid = getpid(); fd = fileno(f); if ((fstat(fd, &buf) == 0) && S_ISREG(buf.st_mode)) { if (fcntl(fd, F_SETLKW, &lock) == -1) { fprintf(stderr, "%s: fcntl: %s (ignored)\n", progname, strerror(errno)); } } } /* * If the file stream is associated with a regular file, unlock the * file (which presumably has previously been locked). */ static void unlock(const char* progname, FILE *f) { int fd; struct stat buf; static struct flock lock; assert(f); lock.l_type = F_UNLCK; lock.l_start = 0; lock.l_whence = SEEK_END; lock.l_len = 0; lock.l_pid = getpid(); fd = fileno(f); if ((fstat(fd, &buf) == 0) && S_ISREG(buf.st_mode)) { if (fcntl(fd, F_SETLKW, &lock) == -1) { fprintf(stderr, "%s: fcntl: %s (ignored)\n", progname, strerror(errno)); } } } void init_fld_options (void) { int i; for (i=0;i < 256;i++) fld_index[i] = -1; for (i=0;data_fields[i].key != 0;i++) { available_options[i] = data_fields[i].key; fld_index[data_fields[i].key] = i; } available_options[i] = 0; } void parse_arg (int argc, char **argv) { int opt; int i; /* IMPORTANT: when adding or modifying an option: 1/ mind the order of options, there is some logic; 2/ update the getopt_long call below; 3/ update the man page (use the same order); 4/ update the help message showed when using --help. */ static struct option long_options[] = { { "help", 0, 0, 'h' }, { "version", 0, 0, 'v' }, { "inet", 0, 0, '4' }, /* IPv4 only */ { "inet6", 0, 0, '6' }, /* IPv6 only */ { "filename", 1, 0, 'F' }, { "report", 0, 0, 'r' }, { "report-wide", 0, 0, 'w' }, { "xml", 0, 0, 'x' }, { "curses", 0, 0, 't' }, { "gtk", 0, 0, 'g' }, { "raw", 0, 0, 'l' }, { "csv", 0, 0, 'C' }, { "split", 0, 0, 'p' }, /* BL */ /* maybe above should change to -d 'x' */ { "no-dns", 0, 0, 'n' }, { "show-ips", 0, 0, 'b' }, { "order", 1, 0, 'o' }, /* fields to display & their order */ #ifdef IPINFO { "ipinfo", 1, 0, 'y' }, /* IP info lookup */ { "aslookup", 0, 0, 'z' }, /* Do AS lookup (--ipinfo 0) */ #endif { "interval", 1, 0, 'i' }, { "report-cycles", 1, 0, 'c' }, { "psize", 1, 0, 's' }, /* changed 'p' to 's' to match ping option overload psize<0, ->rand(min,max) */ { "bitpattern", 1, 0, 'B' },/* overload b>255, ->rand(0,255) */ { "tos", 1, 0, 'Q' }, /* typeof service (0,255) */ { "mpls", 0, 0, 'e' }, { "address", 1, 0, 'a' }, { "first-ttl", 1, 0, 'f' }, /* -f & -m are borrowed from traceroute */ { "max-ttl", 1, 0, 'm' }, { "udp", 0, 0, 'u' }, /* UDP (default is ICMP) */ { "tcp", 0, 0, 'T' }, /* TCP (default is ICMP) */ { "port", 1, 0, 'P' }, /* target port number for TCP */ { "timeout", 1, 0, 'Z' }, /* timeout for TCP sockets */ #ifdef SO_MARK { "mark", 1, 0, 'M' }, /* use SO_MARK */ #endif { 0, 0, 0, 0 } }; opt = 0; while(1) { opt = getopt_long(argc, argv, "hv46F:rwxtglCpnbo:y:zi:c:s:B:Q:ea:f:m:uTP:Z:M:", long_options, NULL); if(opt == -1) break; switch(opt) { case 'v': PrintVersion = 1; break; case 'h': PrintHelp = 1; break; case 'r': DisplayMode = DisplayReport; break; case 'w': reportwide = 1; DisplayMode = DisplayReport; break; case 't': DisplayMode = DisplayCurses; break; case 'g': DisplayMode = DisplayGTK; break; case 'p': /* BL */ DisplayMode = DisplaySplit; break; case 'l': DisplayMode = DisplayRaw; break; case 'C': DisplayMode = DisplayCSV; break; case 'x': DisplayMode = DisplayXML; break; case 'c': MaxPing = atoi (optarg); ForceMaxPing = 1; break; case 's': cpacketsize = atoi (optarg); break; case 'a': InterfaceAddress = optarg; break; case 'e': enablempls = 1; break; case 'n': dns = 0; break; case 'i': WaitTime = atof (optarg); if (WaitTime <= 0.0) { fprintf (stderr, "mtr: wait time must be positive\n"); exit (1); } if (getuid() != 0 && WaitTime < 1.0) { fprintf (stderr, "non-root users cannot request an interval < 1.0 seconds\r\n"); exit (1); } break; case 'f': fstTTL = atoi (optarg); if (fstTTL > maxTTL) { fstTTL = maxTTL; } if (fstTTL < 1) { /* prevent 0 hop */ fstTTL = 1; } break; case 'F': read_from_file(argv[0], optarg); break; case 'm': maxTTL = atoi (optarg); if (maxTTL > (MaxHost - 1)) { maxTTL = MaxHost-1; } if (maxTTL < 1) { /* prevent 0 hop */ maxTTL = 1; } if (fstTTL > maxTTL) { /* don't know the pos of -m or -f */ fstTTL = maxTTL; } break; case 'o': /* Check option before passing it on to fld_active. */ if (strlen (optarg) > MAXFLD) { fprintf (stderr, "Too many fields: %s\n", optarg); exit (1); } for (i=0; optarg[i]; i++) { if(!strchr (available_options, optarg[i])) { fprintf (stderr, "Unknown field identifier: %c\n", optarg[i]); exit (1); } } strcpy ((char*)fld_active, optarg); break; case 'B': bitpattern = atoi (optarg); if (bitpattern > 255) bitpattern = -1; break; case 'Q': tos = atoi (optarg); if (tos > 255 || tos < 0) { /* error message, should do more checking for valid values, * details in rfc2474 */ tos = 0; } break; case 'u': if (mtrtype != IPPROTO_ICMP) { fprintf(stderr, "-u and -T are mutually exclusive.\n"); exit(EXIT_FAILURE); } mtrtype = IPPROTO_UDP; break; case 'T': if (mtrtype != IPPROTO_ICMP) { fprintf(stderr, "-u and -T are mutually exclusive.\n"); exit(EXIT_FAILURE); } mtrtype = IPPROTO_TCP; break; case 'b': show_ips = 1; break; case 'P': remoteport = atoi(optarg); if (remoteport > 65535 || remoteport < 1) { fprintf(stderr, "Illegal port number.\n"); exit(EXIT_FAILURE); } break; case 'Z': timeout = atoi(optarg); timeout *= 1000000; break; case '4': af = AF_INET; break; case '6': #ifdef ENABLE_IPV6 af = AF_INET6; break; #else fprintf( stderr, "IPv6 not enabled.\n" ); break; #endif #ifdef IPINFO case 'y': ipinfo_no = atoi (optarg); if (ipinfo_no < 0) ipinfo_no = 0; break; case 'z': ipinfo_no = 0; break; #else case 'y': case 'z': fprintf( stderr, "IPINFO not enabled.\n" ); break; #endif #ifdef SO_MARK case 'M': mark = atoi (optarg); if (mark < 0) { fprintf( stderr, "SO_MARK must be positive.\n" ); exit(EXIT_FAILURE); } break; #else case 'M': fprintf( stderr, "SO_MARK not enabled.\n" ); break; #endif } } if (DisplayMode == DisplayReport || DisplayMode == DisplayTXT || DisplayMode == DisplayXML || DisplayMode == DisplayRaw || DisplayMode == DisplayCSV) Interactive = 0; if (optind > argc - 1) return; } void parse_mtr_options (char *string) { int argc; char *argv[128], *p; if (!string) return; argv[0] = "mtr"; argc = 1; p = strtok (string, " \t"); while (p != NULL && ((size_t) argc < (sizeof(argv)/sizeof(argv[0])))) { argv[argc++] = p; p = strtok (NULL, " \t"); } if (p != NULL) { fprintf (stderr, "Warning: extra arguments ignored: %s", p); } parse_arg (argc, argv); optind = 0; } int main(int argc, char **argv) { struct hostent * host = NULL; int net_preopen_result; #ifdef ENABLE_IPV6 struct addrinfo hints, *res; int error; struct hostent trhost; char * alptr[2]; struct sockaddr_in * sa4; struct sockaddr_in6 * sa6; #endif /* Get the raw sockets first thing, so we can drop to user euid immediately */ if ( ( net_preopen_result = net_preopen () ) ) { fprintf( stderr, "mtr: unable to get raw sockets.\n" ); exit( EXIT_FAILURE ); } /* Now drop to user permissions */ if (setgid(getgid()) || setuid(getuid())) { fprintf (stderr, "mtr: Unable to drop permissions.\n"); exit(1); } /* Double check, just in case */ if ((geteuid() != getuid()) || (getegid() != getgid())) { fprintf (stderr, "mtr: Unable to drop permissions.\n"); exit(1); } /* reset the random seed */ srand (getpid()); display_detect(&argc, &argv); /* The field options are now in a static array all together, but that requires a run-time initialization. */ init_fld_options (); parse_mtr_options (getenv ("MTR_OPTIONS")); parse_arg (argc, argv); while (optind < argc) { char* name = argv[optind++]; append_to_names(argv[0], name); } /* Now that we know mtrtype we can select which socket to use */ if (net_selectsocket() != 0) { fprintf( stderr, "mtr: Couldn't determine raw socket type.\n" ); exit( EXIT_FAILURE ); } if (PrintVersion) { printf ("mtr " MTR_VERSION "\n"); exit(0); } if (PrintHelp) { printf("usage: %s [--help] [--version] [-4|-6] [-F FILENAME]\n" "\t\t[--report] [--report-wide]\n" "\t\t[--xml] [--gtk] [--curses] [--raw] [--csv] [--split]\n" "\t\t[--no-dns] [--show-ips] [-o FIELDS] [-y IPINFO] [--aslookup]\n" "\t\t[-i INTERVAL] [-c COUNT] [-s PACKETSIZE] [-B BITPATTERN]\n" "\t\t[-Q TOS] [--mpls]\n" "\t\t[-a ADDRESS] [-f FIRST-TTL] [-m MAX-TTL]\n" "\t\t[--udp] [--tcp] [-P PORT] [-Z TIMEOUT]\n" "\t\t[-M MARK] HOSTNAME\n", argv[0]); printf("See the man page for details.\n"); exit(0); } time_t now = time(NULL); if (!names) append_to_names (argv[0], "localhost"); // default: localhost. names_t* head = names; while (names != NULL) { Hostname = names->name; // if (Hostname == NULL) Hostname = "localhost"; // no longer necessary. if (gethostname(LocalHostname, sizeof(LocalHostname))) { strcpy(LocalHostname, "UNKNOWNHOST"); } if (net_preopen_result != 0) { fprintf(stderr, "mtr: Unable to get raw socket. (Executable not suid?)\n"); if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE); else { names = names->next; continue; } } #ifdef ENABLE_IPV6 /* gethostbyname2() is deprecated so we'll use getaddrinfo() instead. */ bzero( &hints, sizeof hints ); hints.ai_family = af; hints.ai_socktype = SOCK_DGRAM; error = getaddrinfo( Hostname, NULL, &hints, &res ); if ( error ) { if (error == EAI_SYSTEM) perror ("Failed to resolve host"); else fprintf (stderr, "Failed to resolve host: %s\n", gai_strerror(error)); if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE); else { names = names->next; continue; } } /* Convert the first addrinfo into a hostent. */ host = &trhost; bzero( host, sizeof trhost ); host->h_name = res->ai_canonname; host->h_aliases = NULL; host->h_addrtype = res->ai_family; af = res->ai_family; host->h_length = res->ai_addrlen; host->h_addr_list = alptr; switch ( af ) { case AF_INET: sa4 = (struct sockaddr_in *) res->ai_addr; alptr[0] = (void *) &(sa4->sin_addr); break; case AF_INET6: sa6 = (struct sockaddr_in6 *) res->ai_addr; alptr[0] = (void *) &(sa6->sin6_addr); break; default: fprintf( stderr, "mtr unknown address type\n" ); if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE); else { names = names->next; continue; } } alptr[1] = NULL; #else host = gethostbyname(Hostname); if (host == NULL) { herror("mtr gethostbyname"); if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE); else { names = names->next; continue; } } af = host->h_addrtype; #endif if (net_open(host) != 0) { fprintf(stderr, "mtr: Unable to start net module.\n"); if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE); else { names = names->next; continue; } } if (net_set_interfaceaddress (InterfaceAddress) != 0) { fprintf( stderr, "mtr: Couldn't set interface address.\n" ); if ( DisplayMode != DisplayCSV ) exit(EXIT_FAILURE); else { names = names->next; continue; } } lock(argv[0], stdout); display_open(); dns_open(); display_mode = 0; display_loop(); net_end_transit(); display_close(now); unlock(argv[0], stdout); if ( DisplayMode != DisplayCSV ) break; else names = names->next; } net_close(); while (head != NULL) { names_t* item = head; free(item->name); item->name = NULL; head = head->next; free(item); item = NULL; } head=NULL; return 0; }