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.4 2004/04/23 17:55:11 cpressey 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 */
37 * Style in which to print timestamps; RAW is "secs.usecs"; READABLE is
38 * ala the Unix "date" tool; and PARSEABLE is tcpslice's custom format,
39 * designed to be easy to parse. The default is RAW.
41 enum stamp_styles
{ TIMESTAMP_RAW
, TIMESTAMP_READABLE
, TIMESTAMP_PARSEABLE
};
42 enum stamp_styles timestamp_style
= TIMESTAMP_RAW
;
45 extern int getopt(int argc
, char **argv
, char *optstring
);
48 int is_timestamp(char *str
);
49 long local_time_zone(long timestamp
);
50 struct timeval
parse_time(char *time_string
, struct timeval base_time
);
51 void fill_tm(char *time_string
, int is_delta
, struct tm
*t
, time_t *usecs_addr
);
52 void get_file_range(char filename
[], pcap_t
**p
,
53 struct timeval
*first_time
, struct timeval
*last_time
);
54 struct timeval
first_packet_time(char filename
[], pcap_t
**p_addr
);
55 void extract_slice(char filename
[], char write_file_name
[],
56 struct timeval
*start_time
, struct timeval
*stop_time
);
57 char *timestamp_to_string(struct timeval
*timestamp
);
58 void dump_times(pcap_t
**p
, char filename
[]);
59 static void usage(void);
62 pcap_dumper_t
*dumper
= 0;
65 main(int argc
, char **argv
)
70 char *start_time_string
= 0;
71 char *stop_time_string
= 0;
72 char *write_file_name
= "-"; /* default is stdout */
73 struct timeval first_time
, start_time
, stop_time
;
77 while ((op
= getopt(argc
, argv
, "dRrtw:")) != -1)
86 timestamp_style
= TIMESTAMP_RAW
;
91 timestamp_style
= TIMESTAMP_READABLE
;
96 timestamp_style
= TIMESTAMP_PARSEABLE
;
100 write_file_name
= optarg
;
108 if (report_times
> 1)
109 error("only one of -R, -r, or -t can be specified");
114 * See if the next argument looks like a possible
115 * start time, and if so assume it is one.
117 if (isdigit(argv
[optind
][0]) || argv
[optind
][0] == '+')
118 start_time_string
= argv
[optind
++];
121 if (isdigit(argv
[optind
][0]) || argv
[optind
][0] == '+')
122 stop_time_string
= argv
[optind
++];
126 error("at least one input file must be given");
129 first_time
= first_packet_time(argv
[optind
], &pcap
);
133 if (start_time_string
)
134 start_time
= parse_time(start_time_string
, first_time
);
136 start_time
= first_time
;
138 if (stop_time_string
)
139 stop_time
= parse_time(stop_time_string
, start_time
);
142 stop_time
= start_time
;
143 stop_time
.tv_sec
+= 86400*3660; /* + 10 years; "forever" */
148 for (; optind
< argc
; ++optind
)
149 dump_times(&pcap
, argv
[optind
]);
153 printf("start\t%s\nstop\t%s\n",
154 timestamp_to_string(&start_time
),
155 timestamp_to_string(&stop_time
));
158 if (!report_times
&& !dump_flag
) {
159 if (!strcmp(write_file_name
, "-") &&
160 isatty(fileno(stdout
)))
161 error("stdout is a terminal; redirect or use -w");
163 for (; optind
< argc
; ++optind
)
164 extract_slice(argv
[optind
], write_file_name
,
165 &start_time
, &stop_time
);
173 * Returns non-zero if a string matches the format for a timestamp,
177 is_timestamp(char *str
)
179 while (isdigit(*str
) || *str
== '.')
187 * Return the correction in seconds for the local time zone with respect
191 local_time_zone(long timestamp
)
197 if (gettimeofday(&now
, &tz
) < 0)
198 err(1, "gettimeofday");
199 localzone
= tz
.tz_minuteswest
* -60;
201 if (localtime((time_t *)×tamp
)->tm_isdst
)
208 * Given a string specifying a time (or a time offset) and a "base time"
209 * from which to compute offsets and fill in defaults, returns a timeval
210 * containing the specified time.
213 parse_time(char *time_string
, struct timeval base_time
)
215 struct tm
*bt
= localtime((time_t *)&base_time
.tv_sec
);
217 struct timeval result
;
219 int is_delta
= (time_string
[0] == '+');
222 ++time_string
; /* skip over '+' sign */
224 if (is_timestamp(time_string
)) {
225 /* interpret as a raw timestamp or timestamp offset */
228 result
.tv_sec
= atoi(time_string
);
229 time_ptr
= strchr(time_string
, '.');
232 /* microseconds are specified, too */
235 num_digits
= strlen(time_ptr
+ 1);
236 result
.tv_usec
= atoi(time_ptr
+ 1);
238 /* turn 123.456 into 123 seconds plus 456000 usec */
239 while (num_digits
++ < 6)
240 result
.tv_usec
*= 10;
245 result
.tv_sec
+= base_time
.tv_sec
;
246 result
.tv_usec
+= base_time
.tv_usec
;
248 if (result
.tv_usec
>= 1000000) {
249 result
.tv_usec
-= 1000000;
259 usecs
= base_time
.tv_usec
;
262 * Zero struct (easy way around lack of tm_gmtoff/tm_zone
263 * under older systems)
265 bzero((char *)&t
, sizeof(t
));
268 * Set values to "not set" flag so we can later identify
271 t
.tm_sec
= t
.tm_min
= t
.tm_hour
= t
.tm_mday
= t
.tm_mon
=
275 fill_tm(time_string
, is_delta
, &t
, &usecs
);
278 * Now until we reach a field that was specified, fill in the
279 * missing fields from the base time.
281 #define CHECK_FIELD(field_name) \
282 if (t.field_name < 0) \
283 t.field_name = bt->field_name; \
287 do { /* bogus do-while loop so "break" in CHECK_FIELD will work */
288 CHECK_FIELD(tm_year
);
290 CHECK_FIELD(tm_mday
);
291 CHECK_FIELD(tm_hour
);
296 /* Set remaining unspecified fields to 0. */
297 #define ZERO_FIELD_IF_NOT_SET(field_name,zero_val) \
298 if (t.field_name < 0) \
299 t.field_name = zero_val
302 ZERO_FIELD_IF_NOT_SET(tm_year
, 90); /* should never happen */
303 ZERO_FIELD_IF_NOT_SET(tm_mon
, 0);
304 ZERO_FIELD_IF_NOT_SET(tm_mday
, 1);
305 ZERO_FIELD_IF_NOT_SET(tm_hour
, 0);
306 ZERO_FIELD_IF_NOT_SET(tm_min
, 0);
307 ZERO_FIELD_IF_NOT_SET(tm_sec
, 0);
310 result
.tv_sec
= gwtm2secs(&t
);
311 result
.tv_sec
-= local_time_zone(result
.tv_sec
);
312 result
.tv_usec
= usecs
;
319 * Fill in (or add to, if is_delta is true) the time values in the
320 * tm struct "t" as specified by the time specified in the string
321 * "time_string". "usecs_addr" is updated with the specified number
322 * of microseconds, if any.
325 fill_tm(char *time_string
, int is_delta
, struct tm
*t
, time_t *usecs_addr
)
327 char *t_start
, *t_stop
, format_ch
;
330 #define SET_VAL(lhs,rhs) \
337 * Loop through the time string parsing one specification at
338 * a time. Each specification has the form <number><letter>
339 * where <number> indicates the amount of time and <letter>
342 for (t_stop
= t_start
= time_string
; *t_start
; t_start
= ++t_stop
) {
343 if (!isdigit(*t_start
))
344 error("bad date format %s, problem starting at %s",
345 time_string
, t_start
);
347 while (isdigit(*t_stop
))
350 error("bad date format %s, problem starting at %s",
351 time_string
, t_start
);
356 if (isupper(format_ch
))
357 format_ch
= tolower(format_ch
);
363 else if (val
< 100 && !is_delta
) {
364 if (val
< 69) /* Same hack as date */
367 SET_VAL(t
->tm_year
, val
);
371 if (strchr(t_stop
+1, 'D') ||
372 strchr(t_stop
+1, 'd'))
374 SET_VAL(t
->tm_mon
, val
- 1);
375 else /* it's minutes */
376 SET_VAL(t
->tm_min
, val
);
380 SET_VAL(t
->tm_mday
, val
);
384 SET_VAL(t
->tm_hour
, val
);
388 SET_VAL(t
->tm_sec
, val
);
392 SET_VAL(*usecs_addr
, val
);
397 "bad date format %s, problem starting at %s",
398 time_string
, t_start
);
405 * Return in first_time and last_time the timestamps of the first and
406 * last packets in the given file.
409 get_file_range(char filename
[], pcap_t
**p
,
410 struct timeval
*first_time
, struct timeval
*last_time
)
412 *first_time
= first_packet_time(filename
, p
);
414 if (!sf_find_end(*p
, first_time
, last_time
))
415 error("couldn't find final packet in file %s", filename
);
421 * Returns the timestamp of the first packet in the given tcpdump save
422 * file, which as a side-effect is initialized for further save-file
426 first_packet_time(char filename
[], pcap_t
**p_addr
)
428 struct pcap_pkthdr hdr
;
430 char errbuf
[PCAP_ERRBUF_SIZE
];
432 p
= *p_addr
= pcap_open_offline(filename
, errbuf
);
434 error("bad tcpdump file %s: %s", filename
, errbuf
);
436 snaplen
= pcap_snapshot(p
);
438 if (pcap_next(p
, &hdr
) == 0)
439 error("bad status reading first packet in %s", filename
);
446 * Extract from the given file all packets with timestamps between
447 * the two time values given (inclusive). These packets are written
448 * to the save file given by write_file_name.
450 * Upon return, start_time is adjusted to reflect a time just after
451 * that of the last packet written to the output.
454 extract_slice(char filename
[], char write_file_name
[],
455 struct timeval
*start_time
, struct timeval
*stop_time
)
457 long start_pos
, stop_pos
;
458 struct timeval file_start_time
, file_stop_time
;
459 struct pcap_pkthdr hdr
;
461 char errbuf
[PCAP_ERRBUF_SIZE
];
463 p
= pcap_open_offline(filename
, errbuf
);
465 error("bad tcpdump file %s: %s", filename
, errbuf
);
467 snaplen
= pcap_snapshot(p
);
468 start_pos
= ftell(pcap_file(p
));
470 if (dumper
== NULL
) {
471 dumper
= pcap_dump_open(p
, write_file_name
);
473 error("error creating output file %s: ",
474 write_file_name
, pcap_geterr(p
));
477 if (pcap_next(p
, &hdr
) == 0)
478 error("error reading packet in %s: ",
479 filename
, pcap_geterr(p
));
481 file_start_time
= hdr
.ts
;
484 if (!sf_find_end(p
, &file_start_time
, &file_stop_time
))
485 error("problems finding end packet of file %s",
488 stop_pos
= ftell(pcap_file(p
));
492 * sf_find_packet() requires that the time it's passed as its last
493 * argument be in the range [min_time, max_time], so we enforce
494 * that constraint here.
496 if (sf_timestamp_less_than(start_time
, &file_start_time
))
497 *start_time
= file_start_time
;
499 if (sf_timestamp_less_than(&file_stop_time
, start_time
))
500 return; /* there aren't any packets of interest in the file */
503 sf_find_packet(p
, &file_start_time
, start_pos
,
504 &file_stop_time
, stop_pos
,
508 struct timeval
*timestamp
;
511 pkt
= pcap_next(p
, &hdr
);
515 if (status
!= SFERR_EOF
)
516 error("bad status %d reading packet in %s",
524 if (!sf_timestamp_less_than(timestamp
, start_time
)) {
525 /* packet is recent enough */
526 if (sf_timestamp_less_than(stop_time
, timestamp
)) {
528 * We've gone beyond the end of the region
529 * of interest ... We're done with this file.
534 pcap_dump((u_char
*)dumper
, &hdr
, pkt
);
536 *start_time
= *timestamp
;
539 * We know that each packet is guaranteed to have
540 * a unique timestamp, so we push forward the
541 * allowed minimum time to weed out duplicate
544 ++start_time
->tv_usec
;
553 * Translates a timestamp to the time format specified by the user.
554 * Returns a pointer to the translation residing in a static buffer.
555 * There are two such buffers, which are alternated on subseqeuent
556 * calls, so two calls may be made to this routine without worrying
557 * about the results of the first call being overwritten by the
558 * results of the second.
561 timestamp_to_string(struct timeval
*timestamp
)
564 #define NUM_BUFFERS 2
565 static char buffers
[NUM_BUFFERS
][128];
566 static int buffer_to_use
= 0;
569 buf
= buffers
[buffer_to_use
];
570 buffer_to_use
= (buffer_to_use
+ 1) % NUM_BUFFERS
;
572 switch (timestamp_style
) {
574 sprintf(buf
, "%lu.%06lu", timestamp
->tv_sec
, timestamp
->tv_usec
);
577 case TIMESTAMP_READABLE
:
578 t
= localtime((time_t *)×tamp
->tv_sec
);
579 strcpy(buf
, asctime(t
));
580 buf
[24] = '\0'; /* nuke final newline */
583 case TIMESTAMP_PARSEABLE
:
584 t
= localtime((time_t *)×tamp
->tv_sec
);
585 if (t
->tm_year
>= 100)
587 sprintf(buf
, "%02dy%02dm%02dd%02dh%02dm%02ds%06ldu",
588 t
->tm_year
, t
->tm_mon
+ 1, t
->tm_mday
, t
->tm_hour
,
589 t
->tm_min
, t
->tm_sec
, timestamp
->tv_usec
);
598 * Given a tcpdump save filename, reports on the times of the first
599 * and last packets in the file.
602 dump_times(pcap_t
**p
, char filename
[])
604 struct timeval first_time
, last_time
;
606 get_file_range(filename
, p
, &first_time
, &last_time
);
608 printf("%s\t%s\t%s\n",
610 timestamp_to_string(&first_time
),
611 timestamp_to_string(&last_time
));
617 fprintf(stderr
, "tcpslice for tcpdump version %d.%d\n",
618 VERSION_MAJOR
, VERSION_MINOR
);
620 "usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n");