/* tm.cpp, Matt Mahoney, mmahoney@cs.fit.edu Copyright (C) 2002, Matt Mahoney. This program is distributed without warranty under terms of the GNU general public license. See http://www.gnu.org/licenses/gpl.txt Usage: tm file1 file2 file3... (2 or more tcpdump files) tm merges tcpdump files, shifting the time stamps for file2, file3... to match the ranges of times in file1. The output is created as file tm.out. Output is truncated at the end of file1 or file2, file3... whichever comes first. */ #include #include #include #include #include using namespace std; // Return the time (seconds since 1970 UT) in a readable format const char* print_time(double seconds) { static char s[30]; time_t t=time_t(seconds); tm* local=localtime(&t); if (local) { strftime(s, 30, "%m/%d/%Y %H:%M:%S", local); sprintf(s+19, ".%06d", int(1000000*(seconds-t))); } else s[0]=0; return s; } /* PacketReader - a class for reading, writing, and time shifting packets from a series of tcpdump files. PacketReader pr(int argc, char** argv); Prepares pr to read packets from a list of files named in argv[0..argc-1]. Files must be tcpdump files. double pr.read() Reads one packet and returns its timestamp as the number of seconds since 0000 Jan. 1, 1970 as stored in the packet. The first call reads the first packet from argv[0]. At the end of each file, the file is closed and read() returns the first packet from the next file. At the end of the last file, read() returns 0.0. double pr.write(FILE* f, double t) Writes the most recently read packet to f with new timestamp t. Then read another packet and return its timestamp. double duration() const Returns the time from the first packet to the most recently read, not including gaps longer than 60 seconds. */ class PacketReader { private: enum {MAX_PACKET=1600}; // Max packet size including tcpdump header unsigned char* buf; // Current input packet, MAX_PACKET bytes double last_time; // Timestamps of previous packet double dur; // Duration not including gaps int argc; // Number of files remaining to be read const char* const* argv; // Names of files remaining to be read FILE* f; // Currently open file, or 0 if all are closed static unsigned long i4(const unsigned char* p) { // Convert 4 bytes to int return (((((p[0]<<8)|p[1])<<8)|p[2])<<8)|p[3]; } static void i4put(unsigned char* p, unsigned long x){ // Write x to p[0..3] p[0]=x>>24; p[1]=x>>16; p[2]=x>>8; p[3]=x; } void close(const char* msg=0); // Close file, print msg if any public: double duration() const {return dur;} PacketReader(int ac, const char* const* av): buf(new unsigned char[MAX_PACKET]), last_time(0), dur(0), argc(ac), argv(av), f(0) {} ~PacketReader() {delete[] buf;} double read(); double write(FILE* f, double t); }; // Close f and go to next file. If msg is not 0, print error message void PacketReader::close(const char* msg) { if (f) { fclose(f); f=0; } if (msg) printf("%s: %s at %s\n", argv[0], msg, print_time(last_time)); --argc; ++argv; } // Read a packet and return its timestamp, or 0 at EOF double PacketReader::read() { while (true) { if (f) { if (fread(buf, 1, 16, f)!=16) close("end of file"); // EOF else { unsigned long len1=i4(buf+8); // Recorded length <= len2 unsigned long len2=i4(buf+12); // Original length <= MAX_PACKET-16 if (len1>len2 || len2>MAX_PACKET-16) close("bad tcpdump packet header"); else if (fread(buf+16, 1, len1, f)!=len1) close("truncated packet"); else { double now=i4(buf)+0.000001*i4(buf+4); // Timestamp if (last_time==0) printf("Start at %s\n", print_time(now)); else if (now-last_time>60 || now-last_time<0) { printf("Gap from %s", print_time(last_time)); printf(" to %s\n", print_time(now)); } else dur+=now-last_time; last_time=now; return now; } } } else { // Open file if (argc<1) return 0.0; // No file to open printf("%s\n", argv[0]); f=fopen(argv[0], "rb"); if (!f) close("file not found"); else if (fread(buf, 1, 24, f)!=24) close("file is too small"); else if (i4(buf)!=0xa1b2c3d4) close("not in tcpdump format"); } } } // Write the last read packet to f, changing the time to t, then read. double PacketReader::write(FILE* out, double t) { unsigned long len1=i4(buf+8); i4put(buf, (unsigned long)t); i4put(buf+4, (unsigned long)((t-(unsigned long)t)*1000000.0)); fwrite(buf, 1, len1+16, out); return read(); } int main(int argc, const char* const* argv) { if (argc<2) { printf("To merge tcpdump files: tm file1 file2 ...\n"); return 1; } // Merge to tm.out PacketReader pr1(1, argv+1), pr2(argc-2, argv+2); double t1=pr1.read(); double t2=pr2.read(); if (t1<=0 || t2<=0) return 1; int count1=0, count2=0; FILE* f=fopen("tm.out", "wb"); if (!f) { perror("tm.out"); return 1; } printf("Creating merged file tm.out\n"); fwrite("\xa1\xb2\xc3\xd4\x00\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00" "\x00\x01\x01\xd0\x00\x00\x00\x01", 1, 24, f); // tcpdump header while (t1>0) { double diff=pr1.duration()-pr2.duration(); if (t2==0 || diff<0) { t1=pr1.write(f, t1); ++count1; } else { t2=pr2.write(f, t1-diff); ++count2; } } fclose(f); printf("%d pkts in %f sec from %s\nmerged with %d pkts in %f seconds\n" "until %s to tm.out\n", count1, pr1.duration(), argv[1], count2, pr2.duration(), print_time(t1+t2)); return 0; }