/******************************************* Active Probing Package: tcpdumptodt-rtt.c - Calculates the round trip time based on two tcpdump outputs, one for the packets sent and one for the packets received. - This is based off tcpdump from DAG family cards using the dagsnap utility, ie. for DAG 3.2E SENDER: dagbpf -e < tracefile.d3h | tcpdump -r - | grep udp <packetsize> > sent_tcpdumpfile.txt RECEIVER: dagbpf -e < tracefile.d3h | tcpdump icmp -r - > recv_tcpdumpfile.txt - However it can be used for ANY tcpdumpfile which is formatted so that the sent and received packet entries are matched in the tcpdumpfile. - Converts the tcpdump files to a roundtriptime value and then stores that in the general timestamp file format '*.dt' Created by: Attila Pasztor, Campbell Skene and David Smith Version: 0.1 Date: 19.3.2003 Copyright (C) 2003 EMULab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ********************************************/ #include <stdio.h> #include <string.h> #include <unistd.h> /* getopt stuff */ #include <stdlib.h> #define MAXDT 10000 /* Magic number: can compute up to XXXX round trip times */ #define MAXFILENAMELEN 200 /* Maximum filename length */ char* format_timestamp (char *); void usage(char *); int readin_timestamps (unsigned long long *, FILE *); int post_process(long long *, long long *, int, int, int); int str2val(const char *, const char *, int, int); char fname[1000]; unsigned long long *dt_ptr; char linebuff[1000]; short int raw_ts; /* are we using raw tcpdump timestamps? (via -tt option)*/ short int debug; /* print out debugging information? */ short int averages = 0; /* are we calculating the average rtts? */ extern int optind; extern int opterr; extern char *optarg; int main(int argc, char *argv[]) { register int op; int recv_pcount, sent_pcount, lost_pcount, hops; unsigned long long dt_recv[MAXDT]; unsigned long long dt_sent[MAXDT]; char i; FILE *recv_datafile = NULL; FILE *sent_datafile = NULL; /* Syntax check */ if (argc < 5 || argc > 6 ) { usage(argv[0]); } opterr = 0; while ((op = getopt(argc, argv, "dt")) != EOF) switch (op) { case 'd': debug++; break; case 't': raw_ts++; break; default: usage(argv[0]); } if ((argc - optind) > 5) { usage(argv[0]); } /* read data from files */ recv_datafile = fopen(argv[optind++], "r"); fprintf(stderr,"recv_datafile is %s\n",argv[optind-1]); sent_datafile = fopen(argv[optind++], "r"); fprintf(stderr,"sent_datafile is %s\n",argv[optind-1]); if (recv_datafile == NULL) { fprintf(stderr,"%s: Error - couldn't open recv file: %s\n",argv[0],argv[1]); exit(-1); } if (sent_datafile == NULL) { fprintf(stderr,"%s: Error - couldn't open sent file: %s\n",argv[0],argv[2]); exit(-1); } /* open output file */ strcpy(fname, argv[optind++]); if (!strstr(fname,".dt")) { fprintf(stderr,"Error: output filename: '%s' must end in .dt\n", fname); exit(-1); } /* Number of hops to dt_file, defaults to 1 if there aren't 4 args */ if (argc - optind == 1) hops = str2val(argv[optind], "hops", 0, 255); else hops = 1; if (debug) fprintf(stdout,"Hops are: %d", hops); i = '\0'; /* Read received tcpdump file */ recv_pcount=readin_timestamps(dt_recv,recv_datafile); /* Read sent tcpdump file */ sent_pcount=readin_timestamps(dt_sent,sent_datafile); /* Remove lost packets */ lost_pcount=post_process(dt_sent, dt_recv, recv_pcount, sent_pcount, hops); fprintf(stderr,"\n%d Round trip time values were found, with %d lost packets and written to %s\n", recv_pcount, lost_pcount, fname); fclose(recv_datafile); fclose(sent_datafile); return 0; } /* Function: format_timestamp */ /* Removes the hours and minutes from the timestamp generated by tcpdump */ char* format_timestamp (char *dtbuff) { int j = 0; /* Remove hour */ while ( (dtbuff[j] != ':') && (dtbuff[j] != '\0')) { dtbuff[j] = ' '; j++; } /* Remove ':' */ dtbuff[j] = ' '; j++; /* Remove Minutes */ while ( (dtbuff[j] != ':') && (dtbuff[j] != '\0')) { dtbuff[j] = ' '; j++; } /* Remove ':' */ dtbuff[j] = ' '; j++; /* Remove decimal point */ while ( (dtbuff[j] != '.') && (dtbuff[j] != '\0') ) j++; dtbuff[j] = ' '; return dtbuff; } /* print a simple usage statement to the commandline */ void usage(char * program_name) { fprintf(stderr,"Usage: \n"); fprintf(stderr,"\t%s -tt <received_filename> <sent filename> <dt_filename.dt> [num_hops]\n", program_name); fprintf(stderr,"\tie. To generate tcpdumpfile for received/sent packets monitored with DAG 3.2E card use:\n"); fprintf(stderr,"\tReceived file: dagbpf -e < dagsnap.d3h | tcpdump icmp -r - > recv_filename.txt\n"); fprintf(stderr,"\tSent file: dagbpf -e < dagsnap.d3h | tcpdump -r - | grep udp <packetsize> > sent_filename.txt\n"); fprintf(stderr,"\t** If you use tcpdump -tt (raw timestamps) then use -tt\n"); exit(-1); } /* read in the time stamps for sent and received packets */ int readin_timestamps (unsigned long long * dt_ptr, FILE * datafile) { int pcount = 0; char * pbuff; pbuff = fgets(linebuff, 999, datafile); while (!feof(datafile) && pbuff != NULL) { int n; char i; char dtbuff[100]; char tmpbuff[1000]; double dtmp; unsigned long sec, usec; n = sscanf(linebuff,"%s %s %c",dtbuff,tmpbuff,&i); if(debug) fprintf(stdout,"dtbuff: %s, tmpbuff: %s, i: %c\n",dtbuff,tmpbuff,i); /* read the time stamp in the receiver file */ if(!raw_ts) /* ie. tcpdump format is: 15:18:24.108722 <sent machine name>... */ format_timestamp(dtbuff); else { /* tcpdump has used raw timestamps, no need to format it */ int j=0; while ( (dtbuff[j] != '.') && (dtbuff[j] != '\0') ) j++; if ( dtbuff[j] == '.') { dtbuff[j] = ' '; } } n = sscanf(dtbuff,"%lu %lu",&sec,&usec); if(debug) { fprintf(stdout,"sec: %lu, usec: %lu\n",sec,usec); } dt_ptr[pcount] = sec; dtmp = usec*(1.0e-6); dtmp *= (1 << 16); dtmp *= (1 << 16); usec = dtmp; dt_ptr[pcount] = ( dt_ptr[pcount] << 32 ) | usec; if ( i == '>' || ((tmpbuff[0] == '<') && tmpbuff[1] == '\0') ) i = '<'; if ( n == 2 && i == '<' ) { /* this is really a time stamp */ if (debug) { fprintf(stdout,"Read in timestamp: %Lu %c\n",dt_ptr[pcount],i); } } else { fprintf(stderr,"Parse error, check if the receiver input file was generated with the\n"); fprintf(stderr,"proper tcpdump options.\n"); exit(-1); } pcount++; /* find the next packet header */ i = '.'; while ( i!= '<' && !feof(datafile) ) { pbuff = fgets(linebuff, 999, datafile); if ( pbuff != 0 ) { sscanf(linebuff,"%s %s %c",dtbuff,tmpbuff,&i); if ( i == '>' || ((tmpbuff[0] == '<') && tmpbuff[1] == '\0') ) i = '<'; } } } return pcount; } /* Function: post_process ** Searches for and removes sent packets that have received no correspond ICMP ** reply, outputs computed round trip times in the double .dt file format */ int post_process(long long *dt_sent, long long *dt_recv, int recv_pcount, int sent_pcount, int hops) { FILE *dt_file = NULL; int j, lost_pcount=0; unsigned long long dt = 0; unsigned long long hops_num = (unsigned long long) hops; int flag = 0; dt_file = fopen(fname, "w"); /* save the number of hops as the first value of the output file */ fprintf(stdout,"\n%d hops written\n",hops); fwrite(&hops_num, sizeof(double), 1, dt_file); if (sent_pcount != recv_pcount) { fprintf(stdout,"Different amount of sent (%d) and received (%d) packets counted.\n", sent_pcount, recv_pcount); } if(debug) fprintf(stdout, "***Round trip times are***\n"); /* process both send and receiver arrays of timestamps */ for (j=0; j < recv_pcount; j++) { /* if there is a lost packet, ie. there is a sent packet but no ** corresponding received packet then write -1 in the correct position ** into the .dt file indicating a lost packet, and remove the sent ** packet from the array as it cannot be used to calculate a rtt. */ if ( ( j != recv_pcount-1 && dt_recv[j] > dt_sent[j+1] ) || ( j == recv_pcount-1 && dt_recv[j] < dt_sent[sent_pcount-1] ) ) { unsigned long long lost_dt = -1; int k; if ( j == recv_pcount-1) flag++; else fwrite(&lost_dt, sizeof(long long), 1, dt_file); /* Don't remove sent packet entry if you're at the end of the recv_pcount array */ if (j != recv_pcount -1) { /* Remove sent packet entry that has no reply */ for (k=j; k < sent_pcount; k++) { if (k != (sent_pcount - 1) ) { /* sent_pcount -1 */ dt_sent[k] = dt_sent[k+1]; } } } j--; sent_pcount--; lost_pcount++; } else { /* No lost packets, just compute rtt */ dt = dt_recv[j]-dt_sent[j]; if(debug) fprintf(stdout,"%d: %Lu\n", j, dt); fwrite(&dt, sizeof(long long), 1, dt_file); } } /* Write lost last packet at the end if flag is set */ if (flag) { unsigned long long lost_dt = -1; fwrite(&lost_dt, sizeof(long long), 1, dt_file); } fclose(dt_file); return lost_pcount; } /* String to value with optional min and max. Handles decimal and hex. */ int str2val(register const char *str, register const char *what, register int mi, register int ma) { register const char *cp; register int val; char *ep; if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { cp = str + 2; val = (int)strtol(cp, &ep, 16); } else val = (int)strtol(str, &ep, 10); if (*ep != '\0') { fprintf(stderr, "%s bad value for %s \n", str, what); exit(1); } if (val < mi && mi >= 0) { if (mi == 0) fprintf(stderr, "%s must be >= %d\n", what, mi); else fprintf(stderr, "%s must be > %d\n", what, mi - 1); exit(1); } if (val > ma && ma >= 0) { fprintf(stderr, "%s must be <= %d\n", what, ma); exit(1); } return (val); }