Clarify that the argument to -t is in seconds.
[dragonfly/netmp.git] / usr.sbin / tcpdump / tcpslice / tcpslice.c
bloba929bb8aa9ce37378203112a77c36cb379640272
1 /*
2 * Copyright (c) 1987-1990 The Regents of the University of California.
3 * All rights reserved.
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
16 * written permission.
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
30 #include <err.h>
31 #include "tcpslice.h"
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;
44 #ifndef __DragonFly__
45 extern int getopt(int argc, char **argv, char *optstring);
46 #endif
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;
64 int
65 main(int argc, char **argv)
67 int op;
68 int dump_flag = 0;
69 int report_times = 0;
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;
74 pcap_t *pcap;
76 opterr = 0;
77 while ((op = getopt(argc, argv, "dRrtw:")) != -1)
78 switch (op) {
80 case 'd':
81 dump_flag = 1;
82 break;
84 case 'R':
85 ++report_times;
86 timestamp_style = TIMESTAMP_RAW;
87 break;
89 case 'r':
90 ++report_times;
91 timestamp_style = TIMESTAMP_READABLE;
92 break;
94 case 't':
95 ++report_times;
96 timestamp_style = TIMESTAMP_PARSEABLE;
97 break;
99 case 'w':
100 write_file_name = optarg;
101 break;
103 default:
104 usage();
105 /* NOTREACHED */
108 if (report_times > 1)
109 error("only one of -R, -r, or -t can be specified");
112 if (optind < argc)
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++];
120 if (optind < argc)
121 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
122 stop_time_string = argv[optind++];
125 if (optind >= argc)
126 error("at least one input file must be given");
129 first_time = first_packet_time(argv[optind], &pcap);
130 pcap_close(pcap);
133 if (start_time_string)
134 start_time = parse_time(start_time_string, first_time);
135 else
136 start_time = first_time;
138 if (stop_time_string)
139 stop_time = parse_time(stop_time_string, start_time);
141 else {
142 stop_time = start_time;
143 stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */
147 if (report_times) {
148 for (; optind < argc; ++optind)
149 dump_times(&pcap, argv[optind]);
152 if (dump_flag) {
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);
168 return 0;
173 * Returns non-zero if a string matches the format for a timestamp,
174 * 0 otherwise.
177 is_timestamp(char *str)
179 while (isdigit(*str) || *str == '.')
180 ++str;
182 return *str == '\0';
187 * Return the correction in seconds for the local time zone with respect
188 * to Greenwich time.
190 long
191 local_time_zone(long timestamp)
193 struct timeval now;
194 struct timezone tz;
195 long localzone;
197 if (gettimeofday(&now, &tz) < 0)
198 err(1, "gettimeofday");
199 localzone = tz.tz_minuteswest * -60;
201 if (localtime((time_t *)&timestamp)->tm_isdst)
202 localzone += 3600;
204 return localzone;
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.
212 struct timeval
213 parse_time(char *time_string, struct timeval base_time)
215 struct tm *bt = localtime((time_t *)&base_time.tv_sec);
216 struct tm t;
217 struct timeval result;
218 time_t usecs = 0;
219 int is_delta = (time_string[0] == '+');
221 if (is_delta)
222 ++time_string; /* skip over '+' sign */
224 if (is_timestamp(time_string)) {
225 /* interpret as a raw timestamp or timestamp offset */
226 char *time_ptr;
228 result.tv_sec = atoi(time_string);
229 time_ptr = strchr(time_string, '.');
231 if (time_ptr) {
232 /* microseconds are specified, too */
233 int num_digits;
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;
241 } else
242 result.tv_usec = 0;
244 if (is_delta) {
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;
250 ++result.tv_sec;
254 return result;
257 if (is_delta) {
258 t = *bt;
259 usecs = base_time.tv_usec;
260 } else {
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
269 * and default them.
271 t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
272 t.tm_year = -1;
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; \
284 else \
285 break
287 do { /* bogus do-while loop so "break" in CHECK_FIELD will work */
288 CHECK_FIELD(tm_year);
289 CHECK_FIELD(tm_mon);
290 CHECK_FIELD(tm_mday);
291 CHECK_FIELD(tm_hour);
292 CHECK_FIELD(tm_min);
293 CHECK_FIELD(tm_sec);
294 } while (0);
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
301 if (!is_delta) {
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;
314 return result;
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.
324 void
325 fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr)
327 char *t_start, *t_stop, format_ch;
328 int val;
330 #define SET_VAL(lhs,rhs) \
331 if (is_delta) \
332 lhs += rhs; \
333 else \
334 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>
340 * the units.
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))
348 ++t_stop;
349 if (!t_stop)
350 error("bad date format %s, problem starting at %s",
351 time_string, t_start);
353 val = atoi(t_start);
355 format_ch = *t_stop;
356 if (isupper(format_ch))
357 format_ch = tolower(format_ch);
359 switch (format_ch) {
360 case 'y':
361 if (val >= 1900)
362 val -= 1900;
363 else if (val < 100 && !is_delta) {
364 if (val < 69) /* Same hack as date */
365 val += 100;
367 SET_VAL(t->tm_year, val);
368 break;
370 case 'm':
371 if (strchr(t_stop+1, 'D') ||
372 strchr(t_stop+1, 'd'))
373 /* it's months */
374 SET_VAL(t->tm_mon, val - 1);
375 else /* it's minutes */
376 SET_VAL(t->tm_min, val);
377 break;
379 case 'd':
380 SET_VAL(t->tm_mday, val);
381 break;
383 case 'h':
384 SET_VAL(t->tm_hour, val);
385 break;
387 case 's':
388 SET_VAL(t->tm_sec, val);
389 break;
391 case 'u':
392 SET_VAL(*usecs_addr, val);
393 break;
395 default:
396 error(
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.
408 void
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);
418 int snaplen;
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
423 * reading.
425 struct timeval
426 first_packet_time(char filename[], pcap_t **p_addr)
428 struct pcap_pkthdr hdr;
429 pcap_t *p;
430 char errbuf[PCAP_ERRBUF_SIZE];
432 p = *p_addr = pcap_open_offline(filename, errbuf);
433 if (p == NULL)
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);
441 return hdr.ts;
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.
453 void
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;
460 pcap_t *p;
461 char errbuf[PCAP_ERRBUF_SIZE];
463 p = pcap_open_offline(filename, errbuf);
464 if (p == NULL)
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);
472 if (dumper == NULL)
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",
486 filename);
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,
505 start_time);
507 for (;;) {
508 struct timeval *timestamp;
509 const u_char *pkt;
511 pkt = pcap_next(p, &hdr);
512 if (pkt == NULL) {
513 #ifdef notdef
514 int status;
515 if (status != SFERR_EOF)
516 error("bad status %d reading packet in %s",
517 status, filename);
518 #endif
519 break;
522 timestamp = &hdr.ts;
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.
531 break;
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
542 * packets.
544 ++start_time->tv_usec;
548 pcap_close(p);
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.
560 char *
561 timestamp_to_string(struct timeval *timestamp)
563 struct tm *t;
564 #define NUM_BUFFERS 2
565 static char buffers[NUM_BUFFERS][128];
566 static int buffer_to_use = 0;
567 char *buf;
569 buf = buffers[buffer_to_use];
570 buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
572 switch (timestamp_style) {
573 case TIMESTAMP_RAW:
574 sprintf(buf, "%lu.%06lu", timestamp->tv_sec, timestamp->tv_usec);
575 break;
577 case TIMESTAMP_READABLE:
578 t = localtime((time_t *)&timestamp->tv_sec);
579 strcpy(buf, asctime(t));
580 buf[24] = '\0'; /* nuke final newline */
581 break;
583 case TIMESTAMP_PARSEABLE:
584 t = localtime((time_t *)&timestamp->tv_sec);
585 if (t->tm_year >= 100)
586 t->tm_year += 1900;
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);
590 break;
593 return buf;
598 * Given a tcpdump save filename, reports on the times of the first
599 * and last packets in the file.
601 void
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",
609 filename,
610 timestamp_to_string(&first_time),
611 timestamp_to_string(&last_time));
614 static void
615 usage(void)
617 fprintf(stderr, "tcpslice for tcpdump version %d.%d\n",
618 VERSION_MAJOR, VERSION_MINOR);
619 fprintf(stderr,
620 "usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n");
622 exit(1);