diff --git a/usr.sbin/tcpdump/tcpslice/gwtm2secs.c b/usr.sbin/tcpdump/tcpslice/gwtm2secs.c index 249454e6f044..f8d2e91c024e 100644 --- a/usr.sbin/tcpdump/tcpslice/gwtm2secs.c +++ b/usr.sbin/tcpdump/tcpslice/gwtm2secs.c @@ -1,72 +1,79 @@ /* * Copyright (c) 1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that: (1) source code distributions * retain the above copyright notice and this paragraph in its entirety, (2) * distributions including binary code include the above copyright notice and * this paragraph in its entirety in the documentation or other materials * provided with the distribution, and (3) all advertising materials mentioning * features or use of this software display the following acknowledgement: * ``This product includes software developed by the University of California, * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of * the University nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior * written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #if !defined(lint) && !defined(__GNUC__) static char rcsid[] = "@(#)$FreeBSD$ (LBL)"; #endif /* * gwtm2secs.c - convert "tm" structs for Greenwich time to Unix timestamp */ #include "tcpslice.h" static int days_in_month[] = /* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec */ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; #define IS_LEAP_YEAR(year) \ (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) time_t gwtm2secs( struct tm *tm ) { int i, days, year; year = tm->tm_year; /* Allow for year being specified with either 2 digits or 4 digits. * 2-digit years are either 19xx or 20xx - a simple heuristic * distinguishes them, since we can't represent any time < 1970. */ if ( year < 100 ) if ( year >= 70 ) year += 1900; else year += 2000; + /* Make sure our year is still >= 1970. We fix 3-digit years + * this way, because localtime(3) can return tm_year >= 100, + * starting in year 2000. + */ + if ( year < 1970 ) + year += 1900; + days = 0; for ( i = 1970; i < year; ++i ) { days += 365; if ( IS_LEAP_YEAR(i) ) ++days; } for ( i = 0; i < tm->tm_mon; ++i ) days += days_in_month[i]; if ( IS_LEAP_YEAR(year) && tm->tm_mon > 1 ) /* 1 is February */ ++days; days += tm->tm_mday - 1; /* -1 since days are numbered starting at 1 */ return days * 86400 + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec; } diff --git a/usr.sbin/tcpdump/tcpslice/search.c b/usr.sbin/tcpdump/tcpslice/search.c index a460ffab2f7e..75db36001ab6 100644 --- a/usr.sbin/tcpdump/tcpslice/search.c +++ b/usr.sbin/tcpdump/tcpslice/search.c @@ -1,566 +1,566 @@ /* * Copyright (c) 1990, 1991, 1992, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that: (1) source code distributions * retain the above copyright notice and this paragraph in its entirety, (2) * distributions including binary code include the above copyright notice and * this paragraph in its entirety in the documentation or other materials * provided with the distribution, and (3) all advertising materials mentioning * features or use of this software display the following acknowledgement: * ``This product includes software developed by the University of California, * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of * the University nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior * written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #if !defined(lint) && !defined(__GNUC__) static char rcsid[] = "@(#)$FreeBSD$ (LBL)"; #endif /* * search.c - supports fast searching through tcpdump files for timestamps */ #include "tcpslice.h" /* Maximum number of seconds that we can conceive of a dump file spanning. */ #define MAX_REASONABLE_FILE_SPAN (3600*24*366) /* one year */ /* Maximum packet length we ever expect to see. */ #define MAX_REASONABLE_PACKET_LENGTH 65535 /* Size of a packet header in bytes; easier than typing the sizeof() all * the time ... */ #define PACKET_HDR_LEN (sizeof( struct pcap_pkthdr )) extern int snaplen; /* The maximum size of a packet, including its header. */ #define MAX_PACKET_SIZE (PACKET_HDR_LEN + snaplen) /* Number of contiguous bytes from a dumpfile in which there's guaranteed * to be enough information to find a "definite" header if one exists * therein. This takes 3 full packets - the first to be just misaligned * (one byte short of a full packet), missing its timestamp; the second * to have the legitimate timestamp; and the third to provide confirmation * that the second is legit, making it a "definite" header. We could * scrimp a bit here since not the entire third packet is required, but * it doesn't seem worth it */ #define MAX_BYTES_FOR_DEFINITE_HEADER (3 * MAX_PACKET_SIZE) /* Maximum number of seconds that might reasonably separate two headers. */ #define MAX_REASONABLE_HDR_SEPARATION (3600 * 24 * 7) /* one week */ /* When searching a file for a packet, if we think we're within this many * bytes of the packet we just search linearly. Since linear searches are * probably much faster than random ones (random ones require searching for * the beginning of the packet, which may be unaligned in memory), we make * this value pretty hefty. */ #define STRAIGHT_SCAN_THRESHOLD (100 * MAX_PACKET_SIZE) /* Given a header and an acceptable first and last time stamp, returns non-zero * if the header looks reasonable and zero otherwise. */ static int reasonable_header( struct pcap_pkthdr *hdr, long first_time, long last_time ) { if ( last_time == 0 ) last_time = first_time + MAX_REASONABLE_FILE_SPAN; return hdr->ts.tv_sec >= first_time && hdr->ts.tv_sec <= last_time && hdr->len > 0 && hdr->len <= MAX_REASONABLE_PACKET_LENGTH && hdr->caplen > 0 && hdr->caplen <= MAX_REASONABLE_PACKET_LENGTH; } #define SWAPLONG(y) \ ((((y)&0xff)<<24) | (((y)&0xff00)<<8) | (((y)&0xff0000)>>8) | (((y)>>24)&0xff)) #define SWAPSHORT(y) \ ( (((y)&0xff)<<8) | (((y)&0xff00)>>8) ) /* Given a buffer, extracts a (properly aligned) packet header from it. */ static void extract_header( pcap_t *p, u_char *buf, struct pcap_pkthdr *hdr ) { bcopy((char *) buf, (char *) hdr, sizeof(struct pcap_pkthdr)); if ( pcap_is_swapped( p ) ) { hdr->ts.tv_sec = SWAPLONG(hdr->ts.tv_sec); hdr->ts.tv_usec = SWAPLONG(hdr->ts.tv_usec); hdr->len = SWAPLONG(hdr->len); hdr->caplen = SWAPLONG(hdr->caplen); } /* * From bpf/libpcap/savefile.c: * * We interchanged the caplen and len fields at version 2.3, * in order to match the bpf header layout. But unfortunately * some files were written with version 2.3 in their headers * but without the interchanged fields. */ if ( pcap_minor_version( p ) < 3 || (pcap_minor_version( p ) == 3 && hdr->caplen > hdr->len) ) { int t = hdr->caplen; hdr->caplen = hdr->len; hdr->len = t; } } /* Search a buffer to locate the first header within it. Return values * are HEADER_NONE, HEADER_CLASH, HEADER_PERHAPS, and HEADER_DEFINITELY. * The first indicates that no evidence of a header was found; the second * that two or more possible headers were found, neither more convincing * than the other(s); the third that exactly one "possible" header was * found; and the fourth that exactly one "definite" header was found. * * Headers are detected by looking for positions in the buffer which have * reasonable timestamps and lengths. If there is enough room in the buffer * for another header to follow a candidate header, a check is made for * that following header. If it is present then the header is *definite* * (unless another "perhaps" or "definite" header is found); if not, then * the header is discarded. If there is not enough room in the buffer for * another header then the candidate is *perhaps* (unless another header * is subsequently found). A "tie" between a "definite" header and a * "perhaps" header is resolved in favor of the definite header. Any * other tie leads to HEADER_CLASH. * * The buffer position of the header is returned in hdrpos_addr and * for convenience the corresponding header in return_hdr. * * first_time is the earliest possible acceptable timestamp in the * header. last_time, if non-zero, is the last such timestamp. If * zero, then up to MAX_REASONABLE_FILE_SPAN seconds after first_time * is acceptable. */ #define HEADER_NONE 0 #define HEADER_CLASH 1 #define HEADER_PERHAPS 2 #define HEADER_DEFINITELY 3 static int find_header( pcap_t *p, u_char *buf, int buf_len, long first_time, long last_time, u_char **hdrpos_addr, struct pcap_pkthdr *return_hdr ) { u_char *bufptr, *bufend, *last_pos_to_try; struct pcap_pkthdr hdr, hdr2; int status = HEADER_NONE; int saw_PERHAPS_clash = 0; /* Initially, try each buffer position to see whether it looks like * a valid packet header. We may later restrict the positions we look * at to avoid seeing a sequence of legitimate headers as conflicting * with one another. */ bufend = buf + buf_len; last_pos_to_try = bufend - PACKET_HDR_LEN; for ( bufptr = buf; bufptr < last_pos_to_try; ++bufptr ) { extract_header( p, bufptr, &hdr ); if ( reasonable_header( &hdr, first_time, last_time ) ) { u_char *next_header = bufptr + PACKET_HDR_LEN + hdr.caplen; if ( next_header + PACKET_HDR_LEN < bufend ) { /* check for another good header */ extract_header( p, next_header, &hdr2 ); if ( reasonable_header( &hdr2, hdr.ts.tv_sec, hdr.ts.tv_sec + MAX_REASONABLE_HDR_SEPARATION ) ) { /* a confirmed header */ switch ( status ) { case HEADER_NONE: case HEADER_PERHAPS: status = HEADER_DEFINITELY; *hdrpos_addr = bufptr; *return_hdr = hdr; /* Make sure we don't demote this "definite" * to a "clash" if we stumble across its * successor. */ last_pos_to_try = next_header - PACKET_HDR_LEN; break; case HEADER_DEFINITELY: return HEADER_CLASH; default: error( "bad status in find_header()" ); } } /* ... else the header is bogus - we've verified that it's * not followed by a reasonable header. */ } else { /* can't check for another good header */ switch ( status ) { case HEADER_NONE: status = HEADER_PERHAPS; *hdrpos_addr = bufptr; *return_hdr = hdr; break; case HEADER_PERHAPS: /* We don't immediately turn this into a * clash because perhaps we'll later see a * "definite" which will save us ... */ saw_PERHAPS_clash = 1; break; case HEADER_DEFINITELY: /* Keep the definite in preference to this one. */ break; default: error( "bad status in find_header()" ); } } } } if ( status == HEADER_PERHAPS && saw_PERHAPS_clash ) status = HEADER_CLASH; return status; } /* Positions the sf_readfile stream such that the next sf_read() will * read the final full packet in the file. Returns non-zero if * successful, zero if unsuccessful. If successful, returns the * timestamp of the last packet in last_timestamp. * * Note that this routine is a special case of sf_find_packet(). In * order to use sf_find_packet(), one first must use this routine in * order to give sf_find_packet() an upper bound on the timestamps * present in the dump file. */ int sf_find_end( pcap_t *p, struct timeval *first_timestamp, struct timeval *last_timestamp ) { long first_time = first_timestamp->tv_sec; u_int num_bytes; u_char *buf, *bufpos, *bufend; u_char *hdrpos; struct pcap_pkthdr hdr, successor_hdr; int status; /* Allow enough room for at least two full (untruncated) packets, * perhaps followed by a truncated packet, so we have a shot at * finding a "definite" header and following its chain to the * end of the file. */ num_bytes = MAX_BYTES_FOR_DEFINITE_HEADER; - if ( fseek( pcap_file( p ), (long) -num_bytes, 2 ) < 0 ) + if ( fseeko( pcap_file( p ), (off_t)-num_bytes, 2 ) < 0 ) return 0; buf = (u_char *)malloc((u_int) num_bytes); if ( ! buf ) return 0; status = 0; bufpos = buf; bufend = buf + num_bytes; if ( fread( (char *) bufpos, num_bytes, 1, pcap_file( p ) ) != 1 ) goto done; if ( find_header( p, bufpos, num_bytes, first_time, 0L, &hdrpos, &hdr ) != HEADER_DEFINITELY ) goto done; /* Okay, we have a definite header in our hands. Follow its * chain till we find the last valid packet in the file ... */ for ( ; ; ) { /* move to the next header position */ bufpos = hdrpos + PACKET_HDR_LEN + hdr.caplen; /* bufpos now points to a candidate packet, which if valid * should replace the current packet pointed to by hdrpos as * the last valid packet ... */ if ( bufpos >= bufend - PACKET_HDR_LEN ) /* not enough room for another header */ break; extract_header( p, bufpos, &successor_hdr ); first_time = hdr.ts.tv_sec; if ( ! reasonable_header( &successor_hdr, first_time, 0L ) ) /* this bodes ill - it means bufpos is perhaps a * bogus packet header after all ... */ break; /* Note that the following test is for whether the next * packet starts at a position > bufend, *not* for a * position >= bufend. If this is the last packet in the * file and there isn't a subsequent partial packet, then * we expect the first buffer position beyond this packet * to be just beyond the end of the buffer, i.e., at bufend * itself. */ if ( bufpos + PACKET_HDR_LEN + successor_hdr.caplen > bufend ) /* the packet is truncated */ break; /* Accept this packet as fully legit. */ hdrpos = bufpos; hdr = successor_hdr; } /* Success! Last valid packet is at hdrpos. */ *last_timestamp = hdr.ts; status = 1; /* Seek so that the next read will start at last valid packet. */ - if ( fseek( pcap_file( p ), (long) -(bufend - hdrpos), 2 ) < 0 ) - error( "final fseek() failed in sf_find_end()" ); + if ( fseeko( pcap_file( p ), (off_t) -(bufend - hdrpos), 2 ) < 0 ) + error( "final fseeko() failed in sf_find_end()" ); done: free( (char *) buf ); return status; } /* Takes two timeval's and returns the difference, tv2 - tv1, as a double. */ static double timeval_diff( struct timeval *tv1, struct timeval *tv2 ) { double result = (tv2->tv_sec - tv1->tv_sec); result += (tv2->tv_usec - tv1->tv_usec) / 1000000.0; return result; } /* Returns true if timestamp t1 is chronologically less than timestamp t2. */ int sf_timestamp_less_than( struct timeval *t1, struct timeval *t2 ) { return t1->tv_sec < t2->tv_sec || (t1->tv_sec == t2->tv_sec && t1->tv_usec < t2->tv_usec); } /* Given two timestamps on either side of desired_time and their positions, * returns the interpolated position of the desired_time packet. Returns a * negative value if the desired_time is outside the given range. */ static long interpolated_position( struct timeval *min_time, long min_pos, struct timeval *max_time, long max_pos, struct timeval *desired_time ) { double full_span = timeval_diff( max_time, min_time ); double desired_span = timeval_diff( desired_time, min_time ); long full_span_pos = max_pos - min_pos; double fractional_offset = desired_span / full_span; if ( fractional_offset < 0.0 || fractional_offset > 1.0 ) return -1; return min_pos + (long) (fractional_offset * (double) full_span_pos); } /* Reads packets linearly until one with a time >= the given desired time * is found; positions the dump file so that the next read will start * at the given packet. Returns non-zero on success, 0 if an EOF was * first encountered. */ static int read_up_to( pcap_t *p, struct timeval *desired_time ) { struct pcap_pkthdr hdr; const u_char *buf; - long pos; + fpos_t pos; int status; for ( ; ; ) { struct timeval *timestamp; - pos = ftell( pcap_file( p ) ); + fgetpos( pcap_file( p ), &pos ); buf = pcap_next( p, &hdr ); if ( buf == 0 ) { if ( feof( pcap_file( p ) ) ) { status = 0; clearerr( pcap_file( p ) ); break; } error( "bad status in read_up_to()" ); } timestamp = &hdr.ts; if ( ! sf_timestamp_less_than( timestamp, desired_time ) ) { status = 1; break; } } - if ( fseek( pcap_file( p ), pos, 0 ) < 0 ) - error( "fseek() failed in read_up_to()" ); + if ( fsetpos( pcap_file( p ), &pos ) < 0 ) + error( "fsetpos() failed in read_up_to()" ); return (status); } /* Positions the sf_readfile stream so that the next sf_read() will * return the first packet with a time greater than or equal to * desired_time. desired_time must be greater than min_time and less * than max_time, which should correspond to actual packets in the * file. min_pos is the file position (byte offset) corresponding to * the min_time packet and max_pos is the same for the max_time packet. * * Returns non-zero on success, 0 if the given position is beyond max_pos. * * NOTE: when calling this routine, the sf_readfile stream *must* be * already aligned so that the next call to sf_next_packet() will yield * a valid packet. */ int sf_find_packet( pcap_t *p, struct timeval *min_time, long min_pos, struct timeval *max_time, long max_pos, struct timeval *desired_time ) { int status = 1; struct timeval min_time_copy, max_time_copy; u_int num_bytes = MAX_BYTES_FOR_DEFINITE_HEADER; int num_bytes_read; - long desired_pos, present_pos; + fpos_t desired_pos, present_pos; u_char *buf, *hdrpos; struct pcap_pkthdr hdr; buf = (u_char *) malloc( num_bytes ); if ( ! buf ) error( "malloc() failured in sf_find_packet()" ); min_time_copy = *min_time; min_time = &min_time_copy; max_time_copy = *max_time; max_time = &max_time_copy; for ( ; ; ) /* loop until positioned correctly */ { desired_pos = interpolated_position( min_time, min_pos, max_time, max_pos, desired_time ); if ( desired_pos < 0 ) { status = 0; break; } - present_pos = ftell( pcap_file( p ) ); + fgetpos( pcap_file( p ), &present_pos ); if ( present_pos <= desired_pos && desired_pos - present_pos < STRAIGHT_SCAN_THRESHOLD ) { /* we're close enough to just blindly read ahead */ status = read_up_to( p, desired_time ); break; } /* Undershoot the target a little bit - it's much easier to * then scan straight forward than to try to read backwards ... */ desired_pos -= STRAIGHT_SCAN_THRESHOLD / 2; if ( desired_pos < min_pos ) desired_pos = min_pos; - if ( fseek( pcap_file( p ), desired_pos, 0 ) < 0 ) - error( "fseek() failed in sf_find_packet()" ); + if ( fsetpos( pcap_file( p ), &desired_pos ) < 0 ) + error( "fsetpos() failed in sf_find_packet()" ); num_bytes_read = fread( (char *) buf, 1, num_bytes, pcap_file( p ) ); if ( num_bytes_read == 0 ) /* This shouldn't ever happen because we try to * undershoot, unless the dump file has only a * couple packets in it ... */ error( "fread() failed in sf_find_packet()" ); if ( find_header( p, buf, num_bytes, min_time->tv_sec, max_time->tv_sec, &hdrpos, &hdr ) != HEADER_DEFINITELY ) error( "can't find header at position %ld in dump file", desired_pos ); /* Correct desired_pos to reflect beginning of packet. */ desired_pos += (hdrpos - buf); /* Seek to the beginning of the header. */ - if ( fseek( pcap_file( p ), desired_pos, 0 ) < 0 ) - error( "fseek() failed in sf_find_packet()" ); + if ( fsetpos( pcap_file( p ), &desired_pos ) < 0 ) + error( "fsetpos() failed in sf_find_packet()" ); if ( sf_timestamp_less_than( &hdr.ts, desired_time ) ) { /* too early in the file */ *min_time = hdr.ts; min_pos = desired_pos; } else if ( sf_timestamp_less_than( desired_time, &hdr.ts ) ) { /* too late in the file */ *max_time = hdr.ts; max_pos = desired_pos; } else /* got it! */ break; } free( (char *) buf ); return status; } diff --git a/usr.sbin/tcpdump/tcpslice/tcpslice.c b/usr.sbin/tcpdump/tcpslice/tcpslice.c index adca2f547a88..ab5de7bc2edf 100644 --- a/usr.sbin/tcpdump/tcpslice/tcpslice.c +++ b/usr.sbin/tcpdump/tcpslice/tcpslice.c @@ -1,626 +1,626 @@ /* * Copyright (c) 1987-1990 The Regents of the University of California. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that: (1) source code distributions * retain the above copyright notice and this paragraph in its entirety, (2) * distributions including binary code include the above copyright notice and * this paragraph in its entirety in the documentation or other materials * provided with the distribution, and (3) all advertising materials mentioning * features or use of this software display the following acknowledgement: * ``This product includes software developed by the University of California, * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of * the University nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior * written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1987-1990\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ /* * tcpslice - extract pieces of and/or glue together tcpdump files */ #include #include "tcpslice.h" int tflag = 0; /* global that util routines are sensitive to */ int fddipad; /* XXX: libpcap needs this global */ /* Style in which to print timestamps; RAW is "secs.usecs"; READABLE is * ala the Unix "date" tool; and PARSEABLE is tcpslice's custom format, * designed to be easy to parse. The default is RAW. */ enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE }; enum stamp_styles timestamp_style = TIMESTAMP_RAW; #ifndef __FreeBSD__ extern int getopt( int argc, char **argv, char *optstring ); #endif int is_timestamp( char *str ); long local_time_zone(long timestamp); struct timeval parse_time(char *time_string, struct timeval base_time); void fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr); void get_file_range( char filename[], pcap_t **p, struct timeval *first_time, struct timeval *last_time ); struct timeval first_packet_time(char filename[], pcap_t **p_addr); void extract_slice(char filename[], char write_file_name[], struct timeval *start_time, struct timeval *stop_time); char *timestamp_to_string(struct timeval *timestamp); void dump_times(pcap_t **p, char filename[]); static void usage(void); pcap_dumper_t *dumper = 0; int main(int argc, char **argv) { int op; int dump_flag = 0; int report_times = 0; char *start_time_string = 0; char *stop_time_string = 0; char *write_file_name = "-"; /* default is stdout */ struct timeval first_time, start_time, stop_time; pcap_t *pcap; opterr = 0; while ((op = getopt(argc, argv, "dRrtw:")) != -1) switch (op) { case 'd': dump_flag = 1; break; case 'R': ++report_times; timestamp_style = TIMESTAMP_RAW; break; case 'r': ++report_times; timestamp_style = TIMESTAMP_READABLE; break; case 't': ++report_times; timestamp_style = TIMESTAMP_PARSEABLE; break; case 'w': write_file_name = optarg; break; default: usage(); /* NOTREACHED */ } if ( report_times > 1 ) error( "only one of -R, -r, or -t can be specified" ); if (optind < argc) /* See if the next argument looks like a possible * start time, and if so assume it is one. */ if (isdigit(argv[optind][0]) || argv[optind][0] == '+') start_time_string = argv[optind++]; if (optind < argc) if (isdigit(argv[optind][0]) || argv[optind][0] == '+') stop_time_string = argv[optind++]; if (optind >= argc) error("at least one input file must be given"); first_time = first_packet_time(argv[optind], &pcap); pcap_close(pcap); if (start_time_string) start_time = parse_time(start_time_string, first_time); else start_time = first_time; if (stop_time_string) stop_time = parse_time(stop_time_string, start_time); else { stop_time = start_time; stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */ } if (report_times) { for (; optind < argc; ++optind) dump_times(&pcap, argv[optind]); } if (dump_flag) { printf( "start\t%s\nstop\t%s\n", timestamp_to_string( &start_time ), timestamp_to_string( &stop_time ) ); } if (! report_times && ! dump_flag) { if ( ! strcmp( write_file_name, "-" ) && isatty( fileno(stdout) ) ) error("stdout is a terminal; redirect or use -w"); for (; optind < argc; ++optind) extract_slice(argv[optind], write_file_name, &start_time, &stop_time); } return 0; } /* Returns non-zero if a string matches the format for a timestamp, * 0 otherwise. */ int is_timestamp( char *str ) { while ( isdigit(*str) || *str == '.' ) ++str; return *str == '\0'; } /* Return the correction in seconds for the local time zone with respect * to Greenwich time. */ long local_time_zone(long timestamp) { struct timeval now; struct timezone tz; long localzone; time_t t = _long_to_time(timestamp); if (gettimeofday(&now, &tz) < 0) err(1, "gettimeofday"); localzone = tz.tz_minuteswest * -60; if (localtime(&t)->tm_isdst) localzone += 3600; return localzone; } /* Given a string specifying a time (or a time offset) and a "base time" * from which to compute offsets and fill in defaults, returns a timeval * containing the specified time. */ struct timeval parse_time(char *time_string, struct timeval base_time) { time_t tt = _long_to_time(base_time.tv_sec); struct tm *bt = localtime(&tt); struct tm t; struct timeval result; time_t usecs = 0; int is_delta = (time_string[0] == '+'); if ( is_delta ) ++time_string; /* skip over '+' sign */ if ( is_timestamp( time_string ) ) { /* interpret as a raw timestamp or timestamp offset */ char *time_ptr; result.tv_sec = atoi( time_string ); time_ptr = strchr( time_string, '.' ); if ( time_ptr ) { /* microseconds are specified, too */ int num_digits = strlen( time_ptr + 1 ); result.tv_usec = atoi( time_ptr + 1 ); /* turn 123.456 into 123 seconds plus 456000 usec */ while ( num_digits++ < 6 ) result.tv_usec *= 10; } else result.tv_usec = 0; if ( is_delta ) { result.tv_sec += base_time.tv_sec; result.tv_usec += base_time.tv_usec; if ( result.tv_usec >= 1000000 ) { result.tv_usec -= 1000000; ++result.tv_sec; } } return result; } if (is_delta) { t = *bt; usecs = base_time.tv_usec; } else { /* Zero struct (easy way around lack of tm_gmtoff/tm_zone * under older systems) */ bzero((char *)&t, sizeof(t)); /* Set values to "not set" flag so we can later identify * and default them. */ t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon = t.tm_year = -1; } fill_tm(time_string, is_delta, &t, &usecs); /* Now until we reach a field that was specified, fill in the * missing fields from the base time. */ #define CHECK_FIELD(field_name) \ if (t.field_name < 0) \ t.field_name = bt->field_name; \ else \ break do { /* bogus do-while loop so "break" in CHECK_FIELD will work */ CHECK_FIELD(tm_year); CHECK_FIELD(tm_mon); CHECK_FIELD(tm_mday); CHECK_FIELD(tm_hour); CHECK_FIELD(tm_min); CHECK_FIELD(tm_sec); } while ( 0 ); /* Set remaining unspecified fields to 0. */ #define ZERO_FIELD_IF_NOT_SET(field_name,zero_val) \ if (t.field_name < 0) \ t.field_name = zero_val if (! is_delta) { ZERO_FIELD_IF_NOT_SET(tm_year,90); /* should never happen */ ZERO_FIELD_IF_NOT_SET(tm_mon,0); ZERO_FIELD_IF_NOT_SET(tm_mday,1); ZERO_FIELD_IF_NOT_SET(tm_hour,0); ZERO_FIELD_IF_NOT_SET(tm_min,0); ZERO_FIELD_IF_NOT_SET(tm_sec,0); } result.tv_sec = gwtm2secs(&t); result.tv_sec -= local_time_zone(result.tv_sec); result.tv_usec = usecs; return result; } /* Fill in (or add to, if is_delta is true) the time values in the * tm struct "t" as specified by the time specified in the string * "time_string". "usecs_addr" is updated with the specified number * of microseconds, if any. */ void fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr) { char *t_start, *t_stop, format_ch; int val; #define SET_VAL(lhs,rhs) \ if (is_delta) \ lhs += rhs; \ else \ lhs = rhs /* Loop through the time string parsing one specification at * a time. Each specification has the form * where indicates the amount of time and * the units. */ for (t_stop = t_start = time_string; *t_start; t_start = ++t_stop) { if (! isdigit(*t_start)) error("bad date format %s, problem starting at %s", time_string, t_start); while (isdigit(*t_stop)) ++t_stop; if (! t_stop) error("bad date format %s, problem starting at %s", time_string, t_start); val = atoi(t_start); format_ch = *t_stop; if ( isupper( format_ch ) ) format_ch = tolower( format_ch ); switch (format_ch) { case 'y': if ( val >= 1900 ) val -= 1900; else if (val < 100 && !is_delta) { if (val < 69) /* Same hack as date */ val += 100; } SET_VAL(t->tm_year, val); break; case 'm': if (strchr(t_stop+1, 'D') || strchr(t_stop+1, 'd')) /* it's months */ SET_VAL(t->tm_mon, val - 1); else /* it's minutes */ SET_VAL(t->tm_min, val); break; case 'd': SET_VAL(t->tm_mday, val); break; case 'h': SET_VAL(t->tm_hour, val); break; case 's': SET_VAL(t->tm_sec, val); break; case 'u': SET_VAL(*usecs_addr, val); break; default: error( "bad date format %s, problem starting at %s", time_string, t_start); } } } /* Return in first_time and last_time the timestamps of the first and * last packets in the given file. */ void get_file_range( char filename[], pcap_t **p, struct timeval *first_time, struct timeval *last_time ) { *first_time = first_packet_time( filename, p ); if ( ! sf_find_end( *p, first_time, last_time ) ) error( "couldn't find final packet in file %s", filename ); } int snaplen; /* Returns the timestamp of the first packet in the given tcpdump save * file, which as a side-effect is initialized for further save-file * reading. */ struct timeval first_packet_time(char filename[], pcap_t **p_addr) { struct pcap_pkthdr hdr; pcap_t *p; char errbuf[PCAP_ERRBUF_SIZE]; p = *p_addr = pcap_open_offline(filename, errbuf); if (! p) error( "bad tcpdump file %s: %s", filename, errbuf ); snaplen = pcap_snapshot( p ); if (pcap_next(p, &hdr) == 0) error( "bad status reading first packet in %s", filename ); return hdr.ts; } /* Extract from the given file all packets with timestamps between * the two time values given (inclusive). These packets are written * to the save file given by write_file_name. * * Upon return, start_time is adjusted to reflect a time just after * that of the last packet written to the output. */ void extract_slice(char filename[], char write_file_name[], struct timeval *start_time, struct timeval *stop_time) { - long start_pos, stop_pos; + off_t start_pos, stop_pos; struct timeval file_start_time, file_stop_time; struct pcap_pkthdr hdr; pcap_t *p; char errbuf[PCAP_ERRBUF_SIZE]; p = pcap_open_offline(filename, errbuf); if (! p) error( "bad tcpdump file %s: %s", filename, errbuf ); snaplen = pcap_snapshot( p ); - start_pos = ftell( pcap_file( p ) ); + fgetpos( pcap_file( p ), &start_pos ); if ( ! dumper ) { dumper = pcap_dump_open(p, write_file_name); if ( ! dumper ) error( "error creating output file %s: ", write_file_name, pcap_geterr( p ) ); } if (pcap_next(p, &hdr) == 0) error( "error reading packet in %s: ", filename, pcap_geterr( p ) ); file_start_time = hdr.ts; if ( ! sf_find_end( p, &file_start_time, &file_stop_time ) ) error( "problems finding end packet of file %s", filename ); - stop_pos = ftell( pcap_file( p ) ); + fgetpos( pcap_file( p ), &stop_pos ); /* sf_find_packet() requires that the time it's passed as its last * argument be in the range [min_time, max_time], so we enforce * that constraint here. */ if ( sf_timestamp_less_than( start_time, &file_start_time ) ) *start_time = file_start_time; if ( sf_timestamp_less_than( &file_stop_time, start_time ) ) return; /* there aren't any packets of interest in the file */ sf_find_packet( p, &file_start_time, start_pos, &file_stop_time, stop_pos, start_time ); for ( ; ; ) { struct timeval *timestamp; const u_char *pkt = pcap_next( p, &hdr ); if ( pkt == 0 ) { #ifdef notdef int status; if ( status != SFERR_EOF ) error( "bad status %d reading packet in %s", status, filename ); #endif break; } timestamp = &hdr.ts; if ( ! sf_timestamp_less_than( timestamp, start_time ) ) { /* packet is recent enough */ if ( sf_timestamp_less_than( stop_time, timestamp ) ) /* We've gone beyond the end of the region * of interest ... We're done with this file. */ break; pcap_dump((u_char *) dumper, &hdr, pkt); *start_time = *timestamp; /* We know that each packet is guaranteed to have * a unique timestamp, so we push forward the * allowed minimum time to weed out duplicate * packets. */ ++start_time->tv_usec; } } pcap_close( p ); } /* Translates a timestamp to the time format specified by the user. * Returns a pointer to the translation residing in a static buffer. * There are two such buffers, which are alternated on subseqeuent * calls, so two calls may be made to this routine without worrying * about the results of the first call being overwritten by the * results of the second. */ char * timestamp_to_string(struct timeval *timestamp) { struct tm *t; time_t tt; #define NUM_BUFFERS 2 static char buffers[NUM_BUFFERS][128]; static int buffer_to_use = 0; char *buf; buf = buffers[buffer_to_use]; buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS; switch ( timestamp_style ) { case TIMESTAMP_RAW: sprintf(buf, "%lu.%06lu", timestamp->tv_sec, timestamp->tv_usec); break; case TIMESTAMP_READABLE: tt = _long_to_time(timestamp->tv_sec); t = localtime(&tt); strcpy( buf, asctime( t ) ); buf[24] = '\0'; /* nuke final newline */ break; case TIMESTAMP_PARSEABLE: tt = _long_to_time(timestamp->tv_sec); t = localtime(&tt); if (t->tm_year >= 100) t->tm_year += 1900; sprintf( buf, "%02dy%02dm%02dd%02dh%02dm%02ds%06ldu", t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, timestamp->tv_usec ); break; } return buf; } /* Given a tcpdump save filename, reports on the times of the first * and last packets in the file. */ void dump_times(pcap_t **p, char filename[]) { struct timeval first_time, last_time; get_file_range( filename, p, &first_time, &last_time ); printf( "%s\t%s\t%s\n", filename, timestamp_to_string( &first_time ), timestamp_to_string( &last_time ) ); } static void usage(void) { (void)fprintf(stderr, "tcpslice for tcpdump version %d.%d\n", VERSION_MAJOR, VERSION_MINOR); (void)fprintf(stderr, "usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n"); exit(1); }