/* * 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. * * @(#) Copyright (c) 1987-1990 The Regents of the University of California. All rights reserved. * $FreeBSD: src/usr.sbin/tcpdump/tcpslice/tcpslice.c,v 1.9.2.1 2000/07/01 01:34:11 ps Exp $ * $DragonFly: src/usr.sbin/tcpdump/tcpslice/tcpslice.c,v 1.4 2004/04/23 17:55:11 cpressey Exp $ */ /* * 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 __DragonFly__ 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; if (gettimeofday(&now, &tz) < 0) err(1, "gettimeofday"); localzone = tz.tz_minuteswest * -60; if (localtime((time_t *)×tamp)->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) { struct tm *bt = localtime((time_t *)&base_time.tv_sec); 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; 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 == NULL) 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; 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 == NULL) error("bad tcpdump file %s: %s", filename, errbuf); snaplen = pcap_snapshot(p); start_pos = ftell(pcap_file(p)); if (dumper == NULL) { dumper = pcap_dump_open(p, write_file_name); if (dumper == NULL) 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)); /* * 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; pkt = pcap_next(p, &hdr); if (pkt == NULL) { #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; #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: t = localtime((time_t *)×tamp->tv_sec); strcpy(buf, asctime(t)); buf[24] = '\0'; /* nuke final newline */ break; case TIMESTAMP_PARSEABLE: t = localtime((time_t *)×tamp->tv_sec); 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) { fprintf(stderr, "tcpslice for tcpdump version %d.%d\n", VERSION_MAJOR, VERSION_MINOR); fprintf(stderr, "usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n"); exit(1); }