2 * Copyright (c) 1987-1990 The Regents of the University of California.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that: (1) source code distributions
7 * retain the above copyright notice and this paragraph in its entirety, (2)
8 * distributions including binary code include the above copyright notice and
9 * this paragraph in its entirety in the documentation or other materials
10 * provided with the distribution, and (3) all advertising materials mentioning
11 * features or use of this software display the following acknowledgement:
12 * ``This product includes software developed by the University of California,
13 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
14 * the University nor the names of its contributors may be used to endorse
15 * or promote products derived from this software without specific prior
17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21 * @(#) Copyright (c) 1987-1990 The Regents of the University of California. All rights reserved.
22 * $FreeBSD: src/usr.sbin/tcpdump/tcpslice/tcpslice.c,v 1.9.2.1 2000/07/01 01:34:11 ps Exp $
23 * $DragonFly: src/usr.sbin/tcpdump/tcpslice/tcpslice.c,v 1.2 2003/06/17 04:30:03 dillon Exp $
27 * tcpslice - extract pieces of and/or glue together tcpdump files
33 int tflag = 0; /* global that util routines are sensitive to */
34 int fddipad; /* XXX: libpcap needs this global */
36 /* Style in which to print timestamps; RAW is "secs.usecs"; READABLE is
37 * ala the Unix "date" tool; and PARSEABLE is tcpslice's custom format,
38 * designed to be easy to parse. The default is RAW.
40 enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE };
41 enum stamp_styles timestamp_style = TIMESTAMP_RAW;
44 extern int getopt( int argc, char **argv, char *optstring );
47 int is_timestamp( char *str );
48 long local_time_zone(long timestamp);
49 struct timeval parse_time(char *time_string, struct timeval base_time);
50 void fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr);
51 void get_file_range( char filename[], pcap_t **p,
52 struct timeval *first_time, struct timeval *last_time );
53 struct timeval first_packet_time(char filename[], pcap_t **p_addr);
54 void extract_slice(char filename[], char write_file_name[],
55 struct timeval *start_time, struct timeval *stop_time);
56 char *timestamp_to_string(struct timeval *timestamp);
57 void dump_times(pcap_t **p, char filename[]);
58 static void usage(void);
61 pcap_dumper_t *dumper = 0;
64 main(int argc, char **argv)
69 char *start_time_string = 0;
70 char *stop_time_string = 0;
71 char *write_file_name = "-"; /* default is stdout */
72 struct timeval first_time, start_time, stop_time;
76 while ((op = getopt(argc, argv, "dRrtw:")) != -1)
85 timestamp_style = TIMESTAMP_RAW;
90 timestamp_style = TIMESTAMP_READABLE;
95 timestamp_style = TIMESTAMP_PARSEABLE;
99 write_file_name = optarg;
107 if ( report_times > 1 )
108 error( "only one of -R, -r, or -t can be specified" );
112 /* See if the next argument looks like a possible
113 * start time, and if so assume it is one.
115 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
116 start_time_string = argv[optind++];
119 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
120 stop_time_string = argv[optind++];
124 error("at least one input file must be given");
127 first_time = first_packet_time(argv[optind], &pcap);
131 if (start_time_string)
132 start_time = parse_time(start_time_string, first_time);
134 start_time = first_time;
136 if (stop_time_string)
137 stop_time = parse_time(stop_time_string, start_time);
141 stop_time = start_time;
142 stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */
147 for (; optind < argc; ++optind)
148 dump_times(&pcap, argv[optind]);
152 printf( "start\t%s\nstop\t%s\n",
153 timestamp_to_string( &start_time ),
154 timestamp_to_string( &stop_time ) );
157 if (! report_times && ! dump_flag) {
158 if ( ! strcmp( write_file_name, "-" ) &&
159 isatty( fileno(stdout) ) )
160 error("stdout is a terminal; redirect or use -w");
162 for (; optind < argc; ++optind)
163 extract_slice(argv[optind], write_file_name,
164 &start_time, &stop_time);
171 /* Returns non-zero if a string matches the format for a timestamp,
174 int is_timestamp( char *str )
176 while ( isdigit(*str) || *str == '.' )
183 /* Return the correction in seconds for the local time zone with respect
186 long local_time_zone(long timestamp)
192 if (gettimeofday(&now, &tz) < 0)
193 err(1, "gettimeofday");
194 localzone = tz.tz_minuteswest * -60;
196 if (localtime((time_t *) ×tamp)->tm_isdst)
202 /* Given a string specifying a time (or a time offset) and a "base time"
203 * from which to compute offsets and fill in defaults, returns a timeval
204 * containing the specified time.
208 parse_time(char *time_string, struct timeval base_time)
210 struct tm *bt = localtime((time_t *) &base_time.tv_sec);
212 struct timeval result;
214 int is_delta = (time_string[0] == '+');
217 ++time_string; /* skip over '+' sign */
219 if ( is_timestamp( time_string ) )
220 { /* interpret as a raw timestamp or timestamp offset */
223 result.tv_sec = atoi( time_string );
224 time_ptr = strchr( time_string, '.' );
227 { /* microseconds are specified, too */
228 int num_digits = strlen( time_ptr + 1 );
229 result.tv_usec = atoi( time_ptr + 1 );
231 /* turn 123.456 into 123 seconds plus 456000 usec */
232 while ( num_digits++ < 6 )
233 result.tv_usec *= 10;
241 result.tv_sec += base_time.tv_sec;
242 result.tv_usec += base_time.tv_usec;
244 if ( result.tv_usec >= 1000000 )
246 result.tv_usec -= 1000000;
256 usecs = base_time.tv_usec;
258 /* Zero struct (easy way around lack of tm_gmtoff/tm_zone
259 * under older systems) */
260 bzero((char *)&t, sizeof(t));
262 /* Set values to "not set" flag so we can later identify
265 t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
269 fill_tm(time_string, is_delta, &t, &usecs);
271 /* Now until we reach a field that was specified, fill in the
272 * missing fields from the base time.
274 #define CHECK_FIELD(field_name) \
275 if (t.field_name < 0) \
276 t.field_name = bt->field_name; \
280 do { /* bogus do-while loop so "break" in CHECK_FIELD will work */
281 CHECK_FIELD(tm_year);
283 CHECK_FIELD(tm_mday);
284 CHECK_FIELD(tm_hour);
289 /* Set remaining unspecified fields to 0. */
290 #define ZERO_FIELD_IF_NOT_SET(field_name,zero_val) \
291 if (t.field_name < 0) \
292 t.field_name = zero_val
295 ZERO_FIELD_IF_NOT_SET(tm_year,90); /* should never happen */
296 ZERO_FIELD_IF_NOT_SET(tm_mon,0);
297 ZERO_FIELD_IF_NOT_SET(tm_mday,1);
298 ZERO_FIELD_IF_NOT_SET(tm_hour,0);
299 ZERO_FIELD_IF_NOT_SET(tm_min,0);
300 ZERO_FIELD_IF_NOT_SET(tm_sec,0);
303 result.tv_sec = gwtm2secs(&t);
304 result.tv_sec -= local_time_zone(result.tv_sec);
305 result.tv_usec = usecs;
311 /* Fill in (or add to, if is_delta is true) the time values in the
312 * tm struct "t" as specified by the time specified in the string
313 * "time_string". "usecs_addr" is updated with the specified number
314 * of microseconds, if any.
317 fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr)
319 char *t_start, *t_stop, format_ch;
322 #define SET_VAL(lhs,rhs) \
328 /* Loop through the time string parsing one specification at
329 * a time. Each specification has the form <number><letter>
330 * where <number> indicates the amount of time and <letter>
333 for (t_stop = t_start = time_string; *t_start; t_start = ++t_stop) {
334 if (! isdigit(*t_start))
335 error("bad date format %s, problem starting at %s",
336 time_string, t_start);
338 while (isdigit(*t_stop))
341 error("bad date format %s, problem starting at %s",
342 time_string, t_start);
347 if ( isupper( format_ch ) )
348 format_ch = tolower( format_ch );
354 else if (val < 100 && !is_delta) {
355 if (val < 69) /* Same hack as date */
358 SET_VAL(t->tm_year, val);
362 if (strchr(t_stop+1, 'D') ||
363 strchr(t_stop+1, 'd'))
365 SET_VAL(t->tm_mon, val - 1);
366 else /* it's minutes */
367 SET_VAL(t->tm_min, val);
371 SET_VAL(t->tm_mday, val);
375 SET_VAL(t->tm_hour, val);
379 SET_VAL(t->tm_sec, val);
383 SET_VAL(*usecs_addr, val);
388 "bad date format %s, problem starting at %s",
389 time_string, t_start);
395 /* Return in first_time and last_time the timestamps of the first and
396 * last packets in the given file.
399 get_file_range( char filename[], pcap_t **p,
400 struct timeval *first_time, struct timeval *last_time )
402 *first_time = first_packet_time( filename, p );
404 if ( ! sf_find_end( *p, first_time, last_time ) )
405 error( "couldn't find final packet in file %s", filename );
410 /* Returns the timestamp of the first packet in the given tcpdump save
411 * file, which as a side-effect is initialized for further save-file
416 first_packet_time(char filename[], pcap_t **p_addr)
418 struct pcap_pkthdr hdr;
420 char errbuf[PCAP_ERRBUF_SIZE];
422 p = *p_addr = pcap_open_offline(filename, errbuf);
424 error( "bad tcpdump file %s: %s", filename, errbuf );
426 snaplen = pcap_snapshot( p );
428 if (pcap_next(p, &hdr) == 0)
429 error( "bad status reading first packet in %s", filename );
435 /* Extract from the given file all packets with timestamps between
436 * the two time values given (inclusive). These packets are written
437 * to the save file given by write_file_name.
439 * Upon return, start_time is adjusted to reflect a time just after
440 * that of the last packet written to the output.
444 extract_slice(char filename[], char write_file_name[],
445 struct timeval *start_time, struct timeval *stop_time)
447 long start_pos, stop_pos;
448 struct timeval file_start_time, file_stop_time;
449 struct pcap_pkthdr hdr;
451 char errbuf[PCAP_ERRBUF_SIZE];
453 p = pcap_open_offline(filename, errbuf);
455 error( "bad tcpdump file %s: %s", filename, errbuf );
457 snaplen = pcap_snapshot( p );
458 start_pos = ftell( pcap_file( p ) );
462 dumper = pcap_dump_open(p, write_file_name);
464 error( "error creating output file %s: ",
465 write_file_name, pcap_geterr( p ) );
468 if (pcap_next(p, &hdr) == 0)
469 error( "error reading packet in %s: ",
470 filename, pcap_geterr( p ) );
472 file_start_time = hdr.ts;
475 if ( ! sf_find_end( p, &file_start_time, &file_stop_time ) )
476 error( "problems finding end packet of file %s",
479 stop_pos = ftell( pcap_file( p ) );
482 /* sf_find_packet() requires that the time it's passed as its last
483 * argument be in the range [min_time, max_time], so we enforce
484 * that constraint here.
486 if ( sf_timestamp_less_than( start_time, &file_start_time ) )
487 *start_time = file_start_time;
489 if ( sf_timestamp_less_than( &file_stop_time, start_time ) )
490 return; /* there aren't any packets of interest in the file */
493 sf_find_packet( p, &file_start_time, start_pos,
494 &file_stop_time, stop_pos,
499 struct timeval *timestamp;
500 const u_char *pkt = pcap_next( p, &hdr );
506 if ( status != SFERR_EOF )
507 error( "bad status %d reading packet in %s",
515 if ( ! sf_timestamp_less_than( timestamp, start_time ) )
516 { /* packet is recent enough */
517 if ( sf_timestamp_less_than( stop_time, timestamp ) )
518 /* We've gone beyond the end of the region
519 * of interest ... We're done with this file.
523 pcap_dump((u_char *) dumper, &hdr, pkt);
525 *start_time = *timestamp;
527 /* We know that each packet is guaranteed to have
528 * a unique timestamp, so we push forward the
529 * allowed minimum time to weed out duplicate
532 ++start_time->tv_usec;
540 /* Translates a timestamp to the time format specified by the user.
541 * Returns a pointer to the translation residing in a static buffer.
542 * There are two such buffers, which are alternated on subseqeuent
543 * calls, so two calls may be made to this routine without worrying
544 * about the results of the first call being overwritten by the
545 * results of the second.
549 timestamp_to_string(struct timeval *timestamp)
552 #define NUM_BUFFERS 2
553 static char buffers[NUM_BUFFERS][128];
554 static int buffer_to_use = 0;
557 buf = buffers[buffer_to_use];
558 buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
560 switch ( timestamp_style )
563 sprintf(buf, "%lu.%06lu", timestamp->tv_sec, timestamp->tv_usec);
566 case TIMESTAMP_READABLE:
567 t = localtime((time_t *) ×tamp->tv_sec);
568 strcpy( buf, asctime( t ) );
569 buf[24] = '\0'; /* nuke final newline */
572 case TIMESTAMP_PARSEABLE:
573 t = localtime((time_t *) ×tamp->tv_sec);
574 if (t->tm_year >= 100)
576 sprintf( buf, "%02dy%02dm%02dd%02dh%02dm%02ds%06ldu",
577 t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour,
578 t->tm_min, t->tm_sec, timestamp->tv_usec );
587 /* Given a tcpdump save filename, reports on the times of the first
588 * and last packets in the file.
592 dump_times(pcap_t **p, char filename[])
594 struct timeval first_time, last_time;
596 get_file_range( filename, p, &first_time, &last_time );
598 printf( "%s\t%s\t%s\n",
600 timestamp_to_string( &first_time ),
601 timestamp_to_string( &last_time ) );
607 (void)fprintf(stderr, "tcpslice for tcpdump version %d.%d\n",
608 VERSION_MAJOR, VERSION_MINOR);
609 (void)fprintf(stderr,
610 "usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n");