static char rcsid[] = "$Id: main.cc 1082 2005-02-12 19:40:04Z bmah $"; // // $Id: main.cc 1082 2005-02-12 19:40:04Z bmah $ // // main.cc // Bruce A. Mah // // This work was first produced by an employee of Sandia National // Laboratories under a contract with the U.S. Department of Energy. // Sandia National Laboratories dedicates whatever right, title or // interest it may have in this software to the public. Although no // license from Sandia is needed to copy and use this software, // copying and using the software might infringe the rights of // others. This software is provided as-is. SANDIA DISCLAIMS ANY // WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. // // Main driver program // #include #ifdef STDC_HEADERS #include #else extern "C" { double atof(const char *); long random(void); } #endif /* STDC_HEADERS */ #ifdef HAVE_UNISTD_H #include #endif /* HAVE_UNISTD_H */ #include #include #include #include #include #include #include #include #ifdef HAVE_STRINGS_H #include #endif /* HAVE_STRINGS_H */ #include "pc.h" #include "Pctest.h" #include "PctestIpv4Udp.h" #include "PctestIpv4Raw.h" #include "PctestIpv4Tcp.h" #include "PctestIpv4Icmp.h" #include "PctestIpv4File.h" #ifdef HAVE_IPV6 #include "PctestIpv6Icmp.h" #include "PctestIpv6Tcp.h" #include "PctestIpv6Udp.h" #include "PctestIpv6File.h" #endif /* HAVE_IPV6 */ #include "ResultTable.h" #include "TestRecord.h" #ifdef HAVE_SNMP #include "GetIfInfo.h" #endif /* HAVE_SNMP */ // // Forward declarations // void DoPchar(Pctest *pct); void DoTrout(Pctest *pct); #ifdef HAVE_SNMP void PrintIfInfo(const GetIfInfo *); #endif /* HAVE_SNMP */ // // Default values // AnalysisType Analysis = AnalysisLeastSquares; unsigned int Burst = 1; bool ChangeFlag = false; bool PcapFlag = false; int DebugLevel = 0; double Gap = 0.25; GapType GapDist = GapFixed; unsigned int Hops = 30; char *Interface = NULL; unsigned int Increment = 32; char *OriginHost = NULL; ModeType Mode = ModePchar; unsigned int Mtu = 1500; NetworkProtocolType NetworkProtocol = NetworkProtocolNone; int NumericFlag = 0; unsigned int Port = 32768; char *ReadFilename = NULL; int QuietFlag = 0; unsigned int Repetitions = 32; #ifdef HAVE_SNMP bool SnmpFlag = false; #endif /* HAVE_SNMP */ unsigned int StartHop = 1; unsigned int Timeout = 3; unsigned int Tos = 0x0; int VerboseFlag = 0; char *WriteFilename = NULL; char *TargetHost = NULL; // // Globals // ResultTable **PartialResults; ResultTable **PartialMins; const int MaxAddressesPerHop = 10; const int MaxSocketAddressLength = 255; #ifdef HAVE_PCAP char PcapErrBuf[PCAP_ERRBUF_SIZE]; #endif /* HAVE_PCAP */ // // Version // // Input: None // // Output: None // // Print version/copyright/build information // void VersionInfo() { extern char *Version, *Copyright, *Build, *DFlags; fprintf(stderr, "%s\n", Version); fprintf(stderr, "%s\n", Copyright); fprintf(stderr, "%s\n", Build); fprintf(stderr, "Compilation flags: %s\n", DFlags); } // // Usage // // Input: program name (argv0) // // Output: None // // Print out invocation information // void Usage(char *argv0) { fprintf(stderr, "Usage: %s [-a analysis] [-b burst] [-c] " #ifdef HAVE_PCAP "[-C] " #endif /* HAVE_PCAP */ "[-d debuglevel] [-g gap] [-G gaptype] [-h] [-H hops] " #ifdef HAVE_PCAP "[-i interface] " #endif /* HAVE_PCAP */ "[-I increment] [-m mtu] [-n] [-p protocol] [-P port] [-q] [-R reps] [-s hop] " #ifdef HAVE_SNMP "[-S] " #endif /* HAVE_SNMP */ "[-t timeout] [-T tos] [-v] [-V] [-w file] -r file | host]\n", argv0); fprintf(stderr, "\t-a analysis\tSet analysis type (default is lsq)\n"); fprintf(stderr, "\t\t\tlsq\tLeast sum of squares linear fit\n"); fprintf(stderr, "\t\t\tkendall\tLinear fit using Kendall's test statistic\n"); fprintf(stderr, "\t\t\tlms\tLeast median of squares linear fit\n"); fprintf(stderr, "\t\t\tlmsint\tLeast median of squares linear fit (integer computations)\n"); fprintf(stderr, "\t-b\t\tBurst size (default = %d)\n", Burst); fprintf(stderr, "\t-c\t\tIgnore route changes\n"); #ifdef HAVE_PCAP fprintf(stderr, "\t-C\t\tUse pcap packet capture facilities\n"); #endif /* HAVE_PCAP */ fprintf(stderr, "\t-d debuglevel\tSet debugging output level\n"); fprintf(stderr, "\t-g gap\t\tInter-test gap in seconds (default = %0.2f)\n", Gap); fprintf(stderr, "\t-G gaptype\tInter-test gap type (default is fixed)\n"); fprintf(stderr, "\t\t\tfixed\tFixed gap\n"); fprintf(stderr, "\t\t\texp\tExponentially distributed random\n"); fprintf(stderr, "\t-H hops\t\tMaximum number of hops (default = %d)\n", Hops); fprintf(stderr, "\t-h\t\tPrint this help information\n"); #ifdef HAVE_PCAP fprintf(stderr, "\t-i interface\tpcap interface\n"); #endif /* HAVE_PCAP */ fprintf(stderr, "\t-I increment\tPacket size increment (default = %d)\n", Increment); fprintf(stderr, "\t-l host\t\tSet origin address of probes (defaults to hostname)\n"); fprintf(stderr, "\t-m mtu\t\tMaximum packet size to check (default = %d)\n", Mtu); fprintf(stderr, "\t-M mode\t\tOperational mode (defaults to pchar)\n"); fprintf(stderr, "\t\t\tpchar\tPath characterization\n"); fprintf(stderr, "\t\t\ttrout\tTiny traceroute\n"); fprintf(stderr, "\t-n\t\tDon't resolve addresses to hostnames\n"); fprintf(stderr, "\t-p protocol\tNetwork protocol (default is ipv4udp)\n"); fprintf(stderr, "\t\t\tipv4udp\t\tUDP over IPv4\n"); fprintf(stderr, "\t\t\tipv4raw\t\tUDP over IPv4 (raw sockets)\n"); #ifdef HAVE_PCAP fprintf(stderr, "\t\t\tipv4tcp\t\tTCP over IPv4 (raw sockets)\n"); #endif /* HAVE_PCAP */ fprintf(stderr, "\t\t\tipv4icmp\tICMP over IPv4 (raw sockets)\n"); #ifdef HAVE_IPV6 fprintf(stderr, "\t\t\tipv6icmp\tICMPv6 over IPv6 (raw sockets)\n"); fprintf(stderr, "\t\t\tipv6tcp\t\tTCP over IPv6\n"); fprintf(stderr, "\t\t\tipv6udp\t\tUDP over IPv6\n"); #endif /* HAVE_IPV6 */ fprintf(stderr, "\t-P port\t\tStarting port number (default = %d)\n", Port); fprintf(stderr, "\t-q\t\tQuiet output\n"); fprintf(stderr, "\t-r file\t\tRead data from a file (- for stdin)\n"); fprintf(stderr, "\t-R reps\t\tRepetitions per hop (default = %d)\n", Repetitions); fprintf(stderr, "\t-s hop\t\tStarting hop number (default = %d)\n", StartHop); #ifdef HAVE_SNMP fprintf(stderr, "\t-S\t\tDo SNMP queries per-hop\n"); #endif /* HAVE_SNMP */ fprintf(stderr, "\t-t timeout\tICMP timeout in seconds (default = %d)\n", Timeout); fprintf(stderr, "\t-T tos\t\tSet IP type-of-service field (default = %d)\n", Tos); fprintf(stderr, "\t-v\t\tVerbose output\n"); fprintf(stderr, "\t-V\t\tPrint version information\n"); fprintf(stderr, "\t-w file\t\tWrite data to a file (- for stdout)\n"); fprintf(stderr, "\n"); } // // GetAddressFamily // // Input: None // // Output: String describing the address family for the tracefile // named by ReadFilename. // // Open, scan, and close ReadFilename for an "addresses" line // and return the parameter associated with that line. // We use this to figure out what kind of a PctestIpv?File // object we need. // char *GetAddressFamily() { FILE *f; // file structure const unsigned int buflen = 1024; // maximum line length char buf[buflen]; // line buffer char *s; // return value from fgets char *af = NULL; // address family string bool done = false; // If the user didn't supply us with a command-line filename, // it's an error. if (!ReadFilename) { fprintf(stderr, "No filename specified for -r\n"); return NULL; } // Try to open the file f = fopen(ReadFilename, "r"); if (!f) { perror("fopen"); return NULL; } // Loop until finished while (!done) { s = fgets(buf, buflen, f); // See if we're done... if (!s) { if (ferror(f)) { // error condition perror("fgets"); exit(1); } done = true; } else { // Process a line. This is a simplified version of the // parser in PctestIpv4File::SetTargetName(). // if (strncasecmp(s, "addresses ", 10) == 0) { af = strdup(s+10); done = true; } } } // Done with file fclose(f); f = NULL; // Return the string we found after "addresses ", if any. return af; } // // GetPrintableNetworkProtocol // // Input: Network protocol // // Output: ASCII representation of network protocol name. // char *GetPrintableNetworkProtocol(NetworkProtocolType np) { switch (np) { case (NetworkProtocolIpv4Udp): return("UDP/IPv4"); break; case (NetworkProtocolIpv4Raw): return("UDP/IPv4 (raw sockets)"); break; case (NetworkProtocolIpv4Tcp): return("TCP/IPv4 (raw sockets)"); break; case (NetworkProtocolIpv4Icmp): return("ICMP/IPv4 (raw sockets)"); break; case (NetworkProtocolIpv4File): return("IPv4 save file"); break; #ifdef HAVE_IPV6 case (NetworkProtocolIpv6Icmp): return("ICMPv6/IPv6"); break; case (NetworkProtocolIpv6Tcp): return("TCP/IPv6"); break; case (NetworkProtocolIpv6Udp): return("UDP/IPv6"); break; case (NetworkProtocolIpv6File): return("IPv6 save file"); break; #endif /* HAVE_IPV6 */ default: return("unknown network protocol"); break; } } // // main // int main(int argc, char **argv) { int c; // getopt Pctest *pct = NULL; // test structure // Parse command-line arguments using getopt while ((c = getopt(argc, argv, "a:b:cCd:g:G:hH:i:I:l:m:M:np:P:qR:r:s:St:T:vVw:")) != -1) { // Check for the different command-line flags we accept switch (c) { // a: set analysis type case 'a': { if (strcasecmp(optarg, "lsq") == 0) { Analysis = AnalysisLeastSquares; } else if (strcasecmp(optarg, "kendall") == 0) { Analysis = AnalysisKendall; } else if (strcasecmp(optarg, "lms") == 0) { Analysis = AnalysisLeastMedianSquares; } else if (strcasecmp(optarg, "lmsint") == 0) { Analysis = AnalysisLeastMedianSquaresIntegers; } else { fprintf(stderr, "Invalid analysis type: %s\n", optarg); Usage(argv[0]); exit(1); } break; } // b: burst size case 'b': { Burst = atoi(optarg); if (Burst < 1) { fprintf(stderr, "Warning: burst size %d too small; resetting to 1\n"); Burst = 1; } break; } // c: ignore route changes case 'c': { ChangeFlag = true; break; } // C: Use pcap packet capture facilities case 'C': { #ifdef HAVE_PCAP PcapFlag = true; break; #else fprintf(stderr, "pcap unavailable in this build"); Usage(argv[0]); exit(1); #endif /* HAVE_PCAP */ } // d: set debugging level case 'd': { DebugLevel = atoi(optarg); break; } // g: inter-test gap case 'g': { Gap = atof(optarg); break; } // G: set gap type case 'G': { if (strcasecmp(optarg, "fixed") == 0) { GapDist = GapFixed; } else if (strcasecmp(optarg, "exp") == 0) { GapDist = GapExponential; } else { fprintf(stderr, "Invalid gap type: %s\n", optarg); Usage(argv[0]); exit(1); } break; } // h: print help info and usage case 'h': { Usage(argv[0]); exit(0); break; }; // H: Maximum hops case 'H': { Hops = atoi(optarg); if (Hops > 255) { fprintf(stdout, "Warning: Maximum hops %d too large, resetting to 30\n", Hops); Hops = 30; } break; } case 'i': { #ifdef HAVE_PCAP Interface = strdup(optarg); if (Interface == NULL) { fprintf(stderr, "Couldn't allocate space for interface name\n"); exit(1); } break; #else fprintf(stderr, "pcap unavailable in this build"); Usage(argv[0]); exit(1); #endif /* HAVE_PCAP */ } // I: set packet size increment case 'I': { Increment = atoi(optarg); break; } // l: set local origin of probes case 'l': { OriginHost = strdup(optarg); if (OriginHost == NULL) { fprintf(stderr, "Couldn't allocate space for origin hostname\n"); exit(1); } break; } // m: MTU size case 'm': { Mtu = atoi(optarg); if (!Mtu) { fprintf(stderr, "Mtu argument must be positive (was %d)\n", Mtu); exit(1); } break; } case 'M': { if ((strcasecmp(optarg, "pchar") == 0) || (strcasecmp(optarg, "pathchar") == 0)) { Mode = ModePchar; } else if (strcasecmp(optarg, "trout") == 0) { Mode = ModeTrout; } else { fprintf(stderr, "Invalid operational mode %s\n", optarg); Usage(argv[0]); exit(1); } break; } // n: don't resolve addresses to hostnames case 'n': { NumericFlag = 1; break; } // p: Network protocol case 'p': { if (strcasecmp(optarg, "ipv4udp") == 0) { NetworkProtocol = NetworkProtocolIpv4Udp; } else if (strcasecmp(optarg, "ipv4raw") == 0) { NetworkProtocol = NetworkProtocolIpv4Raw; } #ifdef HAVE_PCAP else if (strcasecmp(optarg, "ipv4tcp") == 0) { NetworkProtocol = NetworkProtocolIpv4Tcp; } #endif /* HAVE_PCAP */ else if (strcasecmp(optarg, "ipv4icmp") == 0) { NetworkProtocol = NetworkProtocolIpv4Icmp; } #ifdef HAVE_IPV6 else if (strcasecmp(optarg, "ipv6icmp") == 0) { NetworkProtocol = NetworkProtocolIpv6Icmp; } else if (strcasecmp(optarg, "ipv6tcp") == 0) { NetworkProtocol = NetworkProtocolIpv6Tcp; } else if (strcasecmp(optarg, "ipv6udp") == 0) { NetworkProtocol = NetworkProtocolIpv6Udp; } #endif /* HAVE_IPV6 */ else { fprintf(stderr, "Invalid network protocol: %s\n", optarg); Usage(argv[0]); exit(1); } break; } // P: starting port number case 'P': { Port = atoi(optarg); break; } // q: quiet output case 'q': { QuietFlag = 1; VerboseFlag = 0; break; } // r: read from file case 'r': { ReadFilename = strdup(optarg); if (ReadFilename == NULL) { fprintf(stderr, "Couldn't allocate space for statistics filename\n"); exit(1); } if (WriteFilename) { fprintf(stderr, "Warning: both -r and -w specified\n"); } break; } // R: Repetitions per hop case 'R': { Repetitions = atoi(optarg); break; } // s: Starting hop number case 's': { StartHop = atoi(optarg); if (StartHop < 1) { fprintf(stdout, "Warning: starting hop %d too small, resetting to 1\n", StartHop); StartHop = 1; } break; } // S: Enable SNMP queries case 'S': { #ifdef HAVE_SNMP SnmpFlag = true; break; #else fprintf(stderr, "SNMP unavailable in this build"); Usage(argv[0]); exit(1); #endif /* HAVE_SNMP */ } // t: ICMP timeout case 't': { Timeout = atoi(optarg); if (Timeout < 1) { fprintf(stdout, "Warning: timeout value %d too small, resetting to 1\n", Timeout); Timeout = 1; } break; } // T: IP TOS case 'T': { Tos = atoi(optarg); break; } // v: verbose case 'v': { VerboseFlag = 1; QuietFlag = 0; break; } // V: version information case 'V': { VersionInfo(); exit(0); } // w: write statistics to file case 'w': { WriteFilename = strdup(optarg); if (WriteFilename == NULL) { fprintf(stderr, "Couldn't allocate space for statistics filename\n"); exit(1); } if (ReadFilename) { fprintf(stderr, "Warning: both -r and -w specified\n"); } break; } // ? indicates an unrecognized option case '?': { Usage(argv[0]); exit(1); break; } // Didn't know how to handle this case default: { fprintf(stderr, "Received valid, but unknown flag %c\n", c); exit(1); } } } // If we're not reading from a file, we need to get a target host // name, which should be the last word on the command line. if (!ReadFilename) { if (optind != (argc - 1)) { Usage(argv[0]); exit(1); } } TargetHost = argv[optind]; IF_DEBUG(1, fprintf(stderr, "TargetHost %s\n", TargetHost)); // Other initialization before we go off to deal with protocol- // dependent things: Weakly seed process random number generator. srandom(time(NULL)); // Initialize a test structure. Note that reading from a file // is a special case. if (ReadFilename) { char *af; #ifdef WITH_SUID // If we're running SUID root, then we drop privileges // here. We don't want to read random files in the // filesystem, and the main reason we needed superuser // in the first place (raw sockets) doesn't apply. setuid(getuid()); #endif /* WITH_SUID */ // We need to read the file first to figure out what address // family it describes. af = GetAddressFamily(); if (!af) { fprintf(stderr, "Nonexistent address family in tracefile\n"); exit(1); } else { if (strcmp(af, "AF_INET\n") == 0) { NetworkProtocol = NetworkProtocolIpv4File; pct = new PctestIpv4File(); } #ifdef HAVE_IPV6 else if (strcmp(af, "AF_INET6\n") == 0) { NetworkProtocol = NetworkProtocolIpv6File; pct = new PctestIpv6File(); } #endif /* HAVE_IPV6 */ else { fprintf(stderr, "Unknown address family %s in tracefile\n", af); exit(1); } free(af); } } else { // If the user didn't specify a protocol, we have to figure // out a default. For IPv4-only, this trivial. if (NetworkProtocol == NetworkProtocolNone) { #ifdef HAVE_IPV6 // IPv6 available, so we need to do a little work here. // We want to resolve the hostname and figure out what // address family comes back first in the response. // Presumably if we have IPv6 available, we can do // getaddrinfo(). struct addrinfo *host = NULL; struct addrinfo hints; int error_num; // Setup for getaddrinfo() taken from PctestIpv6::SetTargetName() memset(&hints, 0, sizeof(hints)); error_num = getaddrinfo(TargetHost, NULL, &hints, &host); if (host == NULL) { // An error here implies there's going to be an error // later, so we might as well blow up now. fprintf(stderr, "%s: %s\n", TargetHost, gai_strerror(error_num)); exit(1); } // See which protocol family came back first from the // resolver query. We'll use that to determine the // default protocol to use. switch (host->ai_family) { case (AF_INET): NetworkProtocol = NetworkProtocolIpv4Udp; IF_DEBUG(1, fprintf(stderr, "Default protocol UDP/IPv4\n")); break; case (AF_INET6): NetworkProtocol = NetworkProtocolIpv6Udp; IF_DEBUG(1, fprintf(stderr, "Default protocol UDP/IPv6\n")); break; default: // We don't know what this protocol family is. Really // there's a better way to handle this. We should // traverse the linked list of host->ai_next until // we find a host->ai_family that we *do* recognize, // and then use that, and only snarl at the user if // we ran down the entire chain without finding // something. Get the simple and dumb behavior // working first. fprintf(stderr, "%s: Unknown protocol family default\n", TargetHost); exit(1); break; } freeaddrinfo(host); #else // IPv4 only, so use UDP/IPv4 NetworkProtocol = NetworkProtocolIpv4Udp; IF_DEBUG(1, fprintf(stderr, "Default protocol UDP/IPv4\n")); #endif /* HAVE_IPV6 */ } // Normal case is to make a new protocol-dependent structure switch (NetworkProtocol) { case (NetworkProtocolIpv4Udp): pct = new PctestIpv4Udp(Port); break; case (NetworkProtocolIpv4Raw): pct = new PctestIpv4Raw(Port); break; case (NetworkProtocolIpv4Tcp): pct = new PctestIpv4Tcp(Port); break; case (NetworkProtocolIpv4Icmp): pct = new PctestIpv4Icmp(); break; #ifdef HAVE_IPV6 case (NetworkProtocolIpv6Icmp): pct = new PctestIpv6Icmp(); break; case (NetworkProtocolIpv6Tcp): pct = new PctestIpv6Tcp(Port); break; case (NetworkProtocolIpv6Udp): pct = new PctestIpv6Udp(Port); break; #endif /* HAVE_IPV6 */ default: fprintf(stderr, "Unknown protocol type...exiting...\n"); exit(1); } } // Having figured out what the address family is, do the necessary // name resolution to determine the source and destination of // our probe packets. if (pct->SetTargetName(TargetHost) < 0) { exit(1); } if (pct->SetOriginName(OriginHost) < 0) { exit(1); } // Get sockets if (pct->GetSocketOut() < 0) { exit(1); } if (pct->GetSocketIn() < 0) { exit(1); } #ifdef WITH_SUID // If we were running SUID root, then drop privileges here // (assuming we didn't do this already). It's not so great to // run this far into the program with superuser privileges. // At least we give them up in any case before we write to // output files. setuid(getuid()); #endif /* WITH_SUID */ // With all arguments parsed, determine which set of tests // to run and go do it. switch (Mode) { case (ModePchar): DoPchar(pct); break; case (ModeTrout): DoTrout(pct); break; default: fprintf(stderr, "Unknown mode type...exiting...\n"); exit(1); } // Stick a fork in us, we're done. exit(0); } // // DoPchar // // Input: Pctest structure controlling the type of tests to run. // // Output: None. // // Run the original path characterization measurement and analysis // algorithm. // void DoPchar(Pctest *pct) { int i, j, k, l, m; // universal loop counters FILE *df = NULL; // output file // Generate set of packet sizes to test. We'll test packets // from Increment to the maximum multiple of Increment that // that will still fit in Mtu bytes. We weakly randomize // the packet sizes (we just don't want a sequence of // packet sizes that is *too* predictable). // // Note that if increment is small (in particular, if it's // smaller than a UDP/IP header), the protocol-specific code // will refuse to generate packets smaller than the minimum // possible. int testsPerRep = Mtu/Increment; int *packetSize = new int[testsPerRep]; for (i = 0; i < testsPerRep; i++) { packetSize[i] = Increment * (i+1); } for (i = 0; i < testsPerRep; i++) { int swapIndex = random() % testsPerRep; int temp; temp = packetSize[i]; packetSize[i] = packetSize[swapIndex]; packetSize[swapIndex] = temp; } for (i = 0; i < testsPerRep; i++) { IF_DEBUG(3, fprintf(stderr, "packetsize[%d] = %d\n", i, packetSize[i])); } typedef ResultTable *ResultTablePtr; PartialResults = (ResultTable **) calloc(Hops, sizeof(ResultTable *)); PartialMins = (ResultTable **) calloc(Hops, sizeof(ResultTable *)); for (i = 0; i < Hops; i++) { PartialResults[i] = NULL; PartialMins[i] = NULL; } // // Start output // if (!QuietFlag) { fprintf(stdout, "pchar to %s (%s) using %s\n", pct->GetTargetName(), pct->GetPrintableAddress(), GetPrintableNetworkProtocol(NetworkProtocol)); if (PcapFlag) { fprintf(stdout, "Using pcap capture on %s\n", Interface); } else { fprintf(stdout, "Using raw socket input\n"); } fprintf(stdout, "Packet size increments from %d to %d by %d\n", pct->GetMinSize(), Mtu, Increment); fprintf(stdout, "%d test(s) per repetition\n", testsPerRep); fprintf(stdout, "%d repetition(s) per hop\n", Repetitions); } // // If we're probing a real host (as opposed to reading a trace), // do a few pings (using whatever protocol we have selected) to // try to see if the destination host is really up or not. // if (!ReadFilename) { int rc; // syscall return code TestRecord *tr = new TestRecord; // dummy test record int timeouts; // number of timeouts received so far int maxtimeouts = 3; // we'll make three tries to "ping" timeouts = 0; tr->burst = 1; // only need one packet for pings tr->size = 128; // what packet size is best here? if (tr->size < pct->GetMinSize()) { tr->size = pct->GetMinSize(); } while (timeouts < maxtimeouts) { tr->hops = MAXTTL; rc = pct->Test(*tr); // Exit if there was an error. if (rc < 0) { exit(1); } if (tr->result == PctestActionTimeout) { timeouts++; } else { break; } } // If we didn't get a response to // our initial pings, then warn about this fact. if (timeouts >= maxtimeouts) { fprintf(stdout, "Warning: target host did not respond to initial test.\n"); } delete tr; } // Print first-hop output line. Prior versions // of this code were more complex because they used the returned // ICMP packets to figure out the source address. But now we // have that information up-front. if (!QuietFlag) { fprintf(stdout, "%2d: %s ", StartHop - 1, pct->GetPrintableAddress(pct->GetOriginAddress())); if (NumericFlag) { fprintf(stdout, "(%s)", pct->GetPrintableAddress(pct->GetOriginAddress())); } else { fprintf(stdout, "(%s)", pct->GetOriginName()); } fprintf(stdout, "\n"); #ifdef HAVE_SNMP if (!ReadFilename && SnmpFlag) { GetIfInfo *gifp; gifp = new GetIfInfo(pct->GetOriginAddress(), pct); PrintIfInfo(gifp); delete gifp; } #endif /* HAVE_SNMP */ } // Initialize statistics save file and write some stuff out if (WriteFilename) { if (strcmp(WriteFilename, "-") == 0) { df = stdout; } else { df = fopen(WriteFilename, "w"); if (df == NULL) { perror("fopen"); exit(1); } } // Write initial parameters to the file fprintf(df, "addresses %s\n", pct->GetAddressFamilyString()); fprintf(df, "targethost %s\n", pct->GetTargetName()); fprintf(df, "src %s\n", pct->GetPrintableAddress(pct->GetOriginAddress())); fprintf(df, "dest %s\n", pct->GetPrintableAddress()); fprintf(df, "hops %d\n", Hops); fprintf(df, "burst %d\n", Burst); fprintf(df, "minsize %d\n", pct->GetMinSize()); fprintf(df, "increment %d\n", Increment); fprintf(df, "mtu %d\n", Mtu); fprintf(df, "burst %d\n", Burst); fprintf(df, "repetitions %d\n", Repetitions); fprintf(df, "starthop %d\n", StartHop); } // // Begin testing. We increment the hop number, iterating over // the different packet sizes we have available. (Really, we // keep the hop number minus StartHop, and add it back before printing, // since the internal data structures we use start from zero.) // bool firstProbe = true; // first probe flag struct timeval timeFirst, timeLast; // time of first and last probes bool lastHopFlag = true; // *could* be the last hop double aCumulativeLast = 0.0, bCumulativeLast = 0.0, r2CumulativeLast = 0.0; double minBandwidth = -1.0; // minimum bandwidth found so far double queueingTime = 0.0; // total queueing time seen so far int queueingBytes = 0; // total queued bytes estimated so far char *checkAddress[MaxAddressesPerHop]; int checkAddressLength[MaxAddressesPerHop]; for (i = 0; i < MaxAddressesPerHop; i++) { checkAddress[i] = new char[MaxSocketAddressLength]; if (checkAddress[i] == NULL) { fprintf(stderr, "Couldn't allocate space for route change detection\n"); exit(1); } } for (i = 0; i < Hops; i++) { int packetsLost = 0; int packetsSent = 0; unsigned addressesSeen = 0; // for detecting routing changes bool realTimestamp = false; // set true once we've received // real timestamp that didn't need // adjustments PctestActionType pcta; // Initialize statistics structure PartialResults[i] = new ResultTable(Increment, Mtu, Burst, Repetitions); lastHopFlag = true; // Run the correct number of repetitions and iterate over the // packet sizes. for (j = 0; j < Repetitions; j++) { for (k = 0; k < testsPerRep; k++) { for (m = 1; m < Burst + 1; m++) { int rc; // syscall return code double rtt; TestRecord *tr; // test record for this test // Let the user know what we're doing if (VerboseFlag) { fprintf(stderr, " hop %2d rep %3d burst %2d size %5d\r", i+StartHop, j, m, packetSize[k]); } // Set up variables for a test tr = new TestRecord; tr->burst = m; tr->size = packetSize[k]; if (tr->size < pct->GetMinSize()) { // Skip this test if the packet size is too small // for this protocol. delete tr; continue; } tr->hops = i+StartHop; gettimeofday(&tr->tvstart, NULL); rc = pct->Test(*tr); packetsSent++; if (rc < 0) { exit(1); } // Update start and end timestamps for this session if (firstProbe) { timeFirst.tv_sec = tr->tvstart.tv_sec; timeFirst.tv_usec = tr->tvstart.tv_usec; firstProbe = false; } timeLast.tv_sec = tr->tvstart.tv_sec; timeLast.tv_usec = tr->tvstart.tv_usec; // Write to disk if requested if (WriteFilename) { fprintf(df, "%s\n", tr->htoa(pct)); } // Check for a timeout. If we got one, then increment // the appropriate counter and keep going. if (tr->result == PctestActionTimeout) { packetsLost++; delete tr; continue; } // Process the results we got back. rtt = tr->tv.tv_sec + (tr->tv.tv_usec/1000000.0); // Hack for OSF from Jeffrey Mogul . // Tweak null timestamps upwards, but keep track of whether // we've had to do this for every timestamp on a hop or // whether or not we've gotten a real one. if (rtt == 0.0) { rtt = 0.0000001; } else { realTimestamp = true; } IF_DEBUG(2, fprintf(stderr, "bytes = %d, rtt = %f, ip_src = %s, replbytes = %d\n", tr->size, rtt, pct->GetPrintableAddress(tr->icmpSourceAddress), tr->replsize)); // See if we've seen this address before or not. If so, // record it for posterity. for (l = 0; l < addressesSeen; l++) { if ((checkAddressLength[l] == tr->icmpSourceAddressLength) && (memcmp(tr->icmpSourceAddress, checkAddress[l], checkAddressLength[l]) == 0)) { break; } } if (l == addressesSeen) { if (addressesSeen == 1) { if (!QuietFlag) { fprintf(stdout, "Route change detected\n"); } if ((!ChangeFlag) && (ReadFilename)) { exit(1); } } if (addressesSeen < MaxAddressesPerHop) { memcpy(checkAddress[addressesSeen], tr->icmpSourceAddress, tr->icmpSourceAddressLength); checkAddressLength[addressesSeen] = tr->icmpSourceAddressLength; addressesSeen++; } } // Have the Pctest subclass figure out what the ICMP // type and code fields meant (they're kind of protocol- // independent). pcta = tr->result; // If we received a valid (to us) ICMP message, then // attempt to store timing information. if ((pcta == PctestActionValid) || (pcta == PctestActionValidLasthop)) { if ((PartialResults[i]->put( tr->size + tr->replsize, rtt)) < 0) { fprintf(stderr, "Couldn't store result\n"); abort(); } } else if (pcta == PctestActionFiltered) { // We hit a firewall or something that's going // to mess with our packets. Give up now. fprintf(stderr, "ICMP: packet filtered\n"); lastHopFlag = true; goto endreps; } else { // With certain types/codes returned by ICMP, we're also // in trouble. It'd be nice if the protocol-dependent // packet processing threw away everything except // time exceeded and port-unreachable. fprintf(stderr, "Unexpected response to probe\n"); } // If ICMP type was time exceeded, we know this is *not* // the last hop. if (pcta != PctestActionValidLasthop) { lastHopFlag = false; } // If we're not reading from a file, we need to set // the delay between network packets (otherwise, this // is kind of silly) if (!ReadFilename) { // Initialize inter-test gap and other structures struct timeval tvGap; if (GapDist == GapFixed) { tvGap.tv_sec = (long) Gap; tvGap.tv_usec = (long) ((Gap - ((long) Gap)) * 1000000.0); } else if (GapDist == GapExponential) { // Generate exponentially distributed random // value with a mean of Gap. long u; double udub; const double maxudub = pow(2.0, 31.0); // 2^31 double x; u = random(); udub = ((double) u / maxudub); x = - Gap * log(udub); IF_DEBUG(2, fprintf(stderr, "main: exponential gap %f\n", x)); tvGap.tv_sec = (long) x; tvGap.tv_usec = (long) ((x - ((long) x)) * 1000000.0); } else { fprintf(stderr, "Unknown gap type\n"); exit(1); } // Delay for some amount of time. rc = select(0, NULL, NULL, NULL, &tvGap); if (rc < 0) { perror("select"); } } delete tr; } } } endreps: // If we weren't in quiet mode, then clear the line we were on if (VerboseFlag) { fprintf(stdout, "%80s\r", ""); } // If every timestamp got tweaked (on an OSF machine with low // clock resolution, running on a fast link, perhaps), then // print a warning to this effect. if ((!realTimestamp) && (packetsSent > packetsLost)) { fprintf(stdout, "Warning: No non-zero timestamps measured, bumping up to 0.0000001\n"); } // Per-hop processing and statistics. PartialMins[i] = PartialResults[i]->getMin(); double aCumulative, bCumulative, r2Cumulative, aHop, bHop; double sa, sb; double bLower, bUpper; // Get cumulative delay and bandwidth if (Analysis == AnalysisLeastSquares) { PartialMins[i]->slr(aCumulative, bCumulative, r2Cumulative, sa, sb); } else if (Analysis == AnalysisKendall) { PartialMins[i]->tau(aCumulative, bCumulative, bLower, bUpper); } else if (Analysis == AnalysisLeastMedianSquares) { PartialMins[i]->lms(aCumulative, bCumulative, r2Cumulative); } else if (Analysis == AnalysisLeastMedianSquaresIntegers) { PartialMins[i]->lmsint(aCumulative, bCumulative, r2Cumulative); } else { fprintf(stderr, "Unknown statistical analysis type, exiting...\n"); exit(1); } // Figure the per-hop delay and bandwidth. This computation's // correctness relies on aCumulativeLast and bCumulativeLast // being initialized to 0.0. if (bCumulative > 0.0) { aHop = aCumulative - aCumulativeLast; bHop = bCumulative - bCumulativeLast; } else { aHop = 0.0; bHop = 0.0; } // Update our idea of the minimum bandwidth found so far. // Clearly we only take into account hop bandwidths that // make some sense (positive). double hopBandwidth; if (bHop != 0.0) { hopBandwidth = (1.0/bHop) * 8.0 / 1000.0; } else { hopBandwidth = 0.0; } if (((minBandwidth < 0.0) || (minBandwidth > hopBandwidth)) && (hopBandwidth > 0.0)) { minBandwidth = hopBandwidth; } // Compute queueing time statistic(s) double qTime; // estimate of queueing time along path double qTimeHop; // estimate of queueing time this hop int qBytes; // estimate of bytes queued along path qTime = PartialResults[i]->queueing(); qTimeHop = qTime - queueingTime; if ((hopBandwidth > 0.0) && (qTimeHop >= 0.0)) { qBytes = (int) (qTimeHop * (1.0/bHop)); } else { qBytes = 0; } queueingTime = qTime; queueingBytes += qBytes; // Per-hop output if (!QuietFlag) { fprintf(stdout, " Partial loss: %d / %d (%d%%)\n", packetsLost, packetsSent, packetsLost * 100 / packetsSent); if (Analysis == AnalysisLeastSquares) { fprintf(stdout, " Partial char: rtt = %f ms, (b = %f ms/B), r2 = %f\n", aCumulative*1000.0, bCumulative*1000.0, r2Cumulative); fprintf(stdout, " stddev rtt = %f, stddev b = %f\n", sa * 1000.0, sb * 1000.0); } else if (Analysis == AnalysisKendall) { fprintf(stdout, " Partial char: rtt = %f ms, (b = %f ms/B)\n", aCumulative*1000.0, bCumulative*1000.0); fprintf(stdout, " 90%% confidence interval is [%f,%f] ms/B\n", bLower * 1000.0, bUpper * 1000.0); } else if ((Analysis == AnalysisLeastMedianSquares) || (Analysis == AnalysisLeastMedianSquaresIntegers)) { fprintf(stdout, " Partial char: rtt = %f ms, (b = %f ms/B), r2 = %f\n", aCumulative*1000.0, bCumulative*1000.0, r2Cumulative); } else { fprintf(stderr, "Unknown statistical analysis type, exiting...\n"); exit(1); } fprintf(stdout, " Partial queueing: avg = %f ms (%d bytes)\n", qTime, queueingBytes); // Hop characterististics don't make any sense for the first // hop if we start with hop > 1 if ((i > 0) || (StartHop == 1)) { fprintf(stdout, " Hop char: rtt = "); if (aHop >= 0.0) { fprintf(stdout, "%f", aHop*1000.0); } else { fprintf(stdout, "--.---"); } fprintf(stdout, " ms, bw = "); if (hopBandwidth >= 0.0) { fprintf(stdout, "%f", hopBandwidth); } else { fprintf(stdout, "--.---"); } fprintf(stdout, " Kbps\n"); fprintf(stdout, " Hop queueing: avg = %f ms (%d bytes)\n", qTimeHop, qBytes); } if (addressesSeen > 0) { for (m = 0; m < addressesSeen; m++) { fprintf(stdout, "%2d: %s ", i+StartHop, pct->GetPrintableAddress(checkAddress[m])); if (NumericFlag) { fprintf(stdout, "(%s)\n", pct->GetPrintableAddress(checkAddress[m])); } else { fprintf(stdout, "(%s)\n", pct->GetName(checkAddress[m])); } #ifdef HAVE_SNMP if (!ReadFilename && SnmpFlag) { GetIfInfo *gifp; gifp = new GetIfInfo(checkAddress[m], pct); PrintIfInfo(gifp); delete gifp; } #endif /* HAVE_SNMP */ } } else { fprintf(stdout, "%2d: no probe responses\n", i+StartHop); lastHopFlag = false; } } // Update inter-hop state aCumulativeLast = aCumulative; bCumulativeLast = bCumulative; r2CumulativeLast = r2Cumulative; // If all ICMP messages received were "port unreachable", then // we're done. if (lastHopFlag) { break; } } int pathLength = i + StartHop; // End-of-run processing if (!QuietFlag) { if (lastHopFlag) { // fprintf(stdout, " Partial loss: %d / %d (%d%%)\n", packetsLost, packetsSent, packetsLost * 100 / packetsSent); double aPath, bPath, r2Path, qPath; double saPath, sbPath; double bLowerPath, bUpperPath; if (Analysis == AnalysisLeastSquares) { PartialMins[i]->slr(aPath, bPath, r2Path, saPath, sbPath); } else if (Analysis == AnalysisKendall) { PartialMins[i]->tau(aPath, bPath, bLowerPath, bUpperPath); } else if (Analysis == AnalysisLeastMedianSquares) { PartialMins[i]->lms(aPath, bPath, r2Path); } else if (Analysis == AnalysisLeastMedianSquaresIntegers) { PartialMins[i]->lmsint(aPath, bPath, r2Path); } else { fprintf(stderr, "Unknown statistical analysis type, exiting...\n"); exit(1); } qPath = PartialResults[i]->queueing(); fprintf(stdout, " Path length: %d hops\n", pathLength); if (Analysis == AnalysisLeastSquares) { fprintf(stdout, " Path char: rtt = %f ms r2 = %f\n", aPath*1000.0, r2Path); } else if (Analysis == AnalysisKendall) { fprintf(stdout, " Path char: rtt = %f ms\n", aPath*1000.0); } else if ((Analysis == AnalysisLeastMedianSquares) || (Analysis == AnalysisLeastMedianSquaresIntegers)) { fprintf(stdout, " Path char: rtt = %f ms, r2 = %f\n", aPath*1000.0, r2Path); } else { fprintf(stderr, "Unknown statistical analysis type, exiting...\n"); exit(1); } if (minBandwidth > 0.0) { fprintf(stdout, " Path bottleneck: %f Kbps\n", minBandwidth); fprintf(stdout, " Path pipe: %d bytes\n", (int) (aPath * (minBandwidth * 1000.0 / 8.0))); } fprintf(stdout, " Path queueing: average = %f ms (%d bytes)\n", qPath, queueingBytes); } else { fprintf(stdout, " End of path not reached after %d hops\n", pathLength); } // We don't know for sure that a time_t is the same size as // one of the members of a struct timeval. So to make ctime(3) // happy, we'll grab the value we want out of the timeval and // put it in a real time_t before calling. time_t temptime; temptime = timeFirst.tv_sec; fprintf(stdout, " Start time: %s", ctime(&temptime)); temptime = timeLast.tv_sec; fprintf(stdout, " End time: %s", ctime(&temptime)); } // // Free memory, close files, etc. // for (i = 0; i < MaxAddressesPerHop; i++) { delete [] checkAddress[i]; checkAddress[i] = NULL; } if (WriteFilename) { fclose(df); } } // // DoTrout // // Input: Pctest structure controlling the type of tests to run. // // Output: None. // // Run a tiny traceroute (intended for use as a part of a larger // measurement infrastructure). // void DoTrout(Pctest *pct) { int i; // universal loop counters FILE *df = NULL; // output file // // Some of the original pchar parameters may need adjusting... Repetitions = 1; // force repetitions to 1 // // Start output // if (!QuietFlag) { fprintf(stdout, "trout to %s (%s) using %s\n", pct->GetTargetName(), pct->GetPrintableAddress(), GetPrintableNetworkProtocol(NetworkProtocol)); fprintf(stdout, "Packet size increments from %d to %d by %d\n", pct->GetMinSize(), Mtu, Increment); } // Print first-hop output line. if (!QuietFlag) { fprintf(stdout, "%2d: %s ", StartHop - 1, pct->GetPrintableAddress(pct->GetOriginAddress())); if (NumericFlag) { fprintf(stdout, "(%s)", pct->GetPrintableAddress(pct->GetOriginAddress())); } else { fprintf(stdout, "(%s)", pct->GetOriginName()); } fprintf(stdout, "\n"); #ifdef HAVE_SNMP if (!ReadFilename && SnmpFlag) { GetIfInfo *gifp; gifp = new GetIfInfo(pct->GetOriginAddress(), pct); PrintIfInfo(gifp); delete gifp; } #endif /* HAVE_SNMP */ } // Initialize statistics save file and write some stuff out if (WriteFilename) { if (strcmp(WriteFilename, "-") == 0) { df = stdout; } else { df = fopen(WriteFilename, "w"); if (df == NULL) { perror("fopen"); exit(1); } } // Write initial parameters to the file fprintf(df, "addresses %s\n", pct->GetAddressFamilyString()); fprintf(df, "targethost %s\n", pct->GetTargetName()); fprintf(df, "src %s\n", pct->GetPrintableAddress(pct->GetOriginAddress())); fprintf(df, "dest %s\n", pct->GetPrintableAddress()); fprintf(df, "hops %d\n", Hops); fprintf(df, "minsize %d\n", pct->GetMinSize()); fprintf(df, "increment %d\n", Increment); fprintf(df, "mtu %d\n", Mtu); fprintf(df, "repetitions %d\n", Repetitions); fprintf(df, "starthop %d\n", StartHop); } // // Begin testing. We increment the hop number, iterating over // the different packet sizes we have available. (Really, we // keep the hop number minus StartHop, and add it back before printing, // since the internal data structures we use start from zero.) // bool lastHopFlag; // *could* be the last hop int packetsLost = 0; int packetsSent = 0; for (i = 0; i < Hops; i++) { unsigned int packetSize; PctestActionType pcta; lastHopFlag = true; int rc; // syscall return code double rtt; TestRecord *tr; // test record for this test // Generate packet size packetSize = Increment * (random() % (Mtu/Increment)); if (packetSize < pct->GetMinSize()) { packetSize = pct->GetMinSize(); } // Let the user know what we're doing if (VerboseFlag) { fprintf(stderr, " hop %2d size %5d\r", i+StartHop, packetSize); } // Set up variables for a test tr = new TestRecord; tr->burst = 1; tr->size = packetSize; tr->hops = i+StartHop; gettimeofday(&tr->tvstart, NULL); tr->tv.tv_sec = 255; tr->tv.tv_usec = 0; tr->replsize = 0; rc = pct->Test(*tr); packetsSent++; if (rc < 0) { exit(1); } // Write to disk if requested. if (WriteFilename) { fprintf(df, "%s\n", tr->htoa(pct)); } // Check for a timeout. If we got one, then increment // the appropriate counter and keep going. if (tr->result == PctestActionTimeout) { packetsLost++; delete tr; continue; // XXX Do we want to retry this probe? } // Process the results we got back. rtt = tr->tv.tv_sec + (tr->tv.tv_usec/1000000.0); IF_DEBUG(2, fprintf(stderr, "bytes = %d, rtt = %f, ip_src = %s, replbytes = %d\n", tr->size, rtt, pct->GetPrintableAddress(tr->icmpSourceAddress), tr->replsize)); // Have the Pctest subclass figure out what the ICMP // type and code fields meant (they're kind of protocol- // independent). pcta = tr->result; // If we received a valid (to us) ICMP message, then // attempt to store timing information. if ((pcta == PctestActionValid) || (pcta == PctestActionValidLasthop)) { // XXX Do we write out something here? } else if (pcta == PctestActionFiltered) { // We hit a firewall or something that's going // to mess with our packets. Give up now. fprintf(stderr, "ICMP: packet filtered\n"); lastHopFlag = true; goto endreps; } else { // With certain types/codes returned by ICMP, we're also // in trouble. It'd be nice if the protocol-dependent // packet processing threw away everything except // time exceeded and port-unreachable. fprintf(stderr, "Unexpected response to probe\n"); abort(); } // If ICMP type was time exceeded, we know this is *not* // the last hop. if (pcta != PctestActionValidLasthop) { lastHopFlag = false; } // Delay between packets struct timeval tvGap; if (GapDist == GapFixed) { tvGap.tv_sec = (long) Gap; tvGap.tv_usec = (long) ((Gap - ((long) Gap)) * 1000000.0); } else if (GapDist == GapExponential) { // Generate exponentially distributed random // value with a mean of Gap. long u; double udub; const double maxudub = pow(2.0, 31.0); // 2^31 double x; u = random(); udub = ((double) u / maxudub); x = - Gap * log(udub); IF_DEBUG(2, fprintf(stderr, "main: exponential gap %f\n", x)); tvGap.tv_sec = (long) x; tvGap.tv_usec = (long) ((x - ((long) x)) * 1000000.0); } else { fprintf(stderr, "Unknown gap type\n"); exit(1); } // Delay for some amount of time. rc = select(0, NULL, NULL, NULL, &tvGap); if (rc < 0) { perror("select"); } endreps: // If we weren't in quiet mode, then clear the line we were on if (VerboseFlag) { fprintf(stdout, "%80s\r", ""); } // Per-hop output if (!QuietFlag) { fprintf(stdout, "%2d: %s ", i + StartHop, pct->GetPrintableAddress(tr->icmpSourceAddress)); if (NumericFlag) { fprintf(stdout, "(%s)", pct->GetPrintableAddress(tr->icmpSourceAddress)); } else { fprintf(stdout, "(%s)", pct->GetName(tr->icmpSourceAddress)); } fprintf(stdout, " %d -> %d bytes: %0.3f ms\n", tr->size, tr->replsize, rtt * 1000.0); } #ifdef HAVE_SNMP if (!ReadFilename && SnmpFlag) { GetIfInfo *gifp; gifp = new GetIfInfo(tr->icmpSourceAddress, pct); PrintIfInfo(gifp); delete gifp; } #endif /* HAVE_SNMP */ delete tr; // If all ICMP messages received were "port unreachable", then // we're done. if (lastHopFlag) { break; } } // End-of-run processing if (!QuietFlag) { } if (WriteFilename) { fclose(df); } } #ifdef HAVE_SNMP // // PrintIfInfo // void PrintIfInfo(const GetIfInfo *gifp) { if (gifp == NULL) { return; } fprintf(stdout, " Description: %s\n", gifp->GetDescription()); fprintf(stdout, " Name: %s\n", gifp->GetName()); fprintf(stdout, " Contact: %s\n", gifp->GetContact()); fprintf(stdout, " Location: %s\n", gifp->GetLocation()); fprintf(stdout, " IfDescription: %s\n", gifp->GetIfDescription()); fprintf(stdout, " Interface: type = %s(%lu)\n", gifp->GetIfTypeString(), gifp->GetIfType()); fprintf(stdout, " speed = %lu bps, MTU = %lu\n", gifp->GetIfSpeed(), gifp->GetIfMtu()); fflush(stdout); } #endif /* HAVE_SNMP */