a929bb8aa9ce37378203112a77c36cb379640272
[dragonfly.git] / usr.sbin / tcpdump / tcpslice / tcpslice.c
1 /*
2  * Copyright (c) 1987-1990 The Regents of the University of California.
3  * All rights reserved.
4  *
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.
20  *
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 $
24  */
25
26 /*
27  * tcpslice - extract pieces of and/or glue together tcpdump files
28  */
29
30 #include <err.h>
31 #include "tcpslice.h"
32
33 int tflag = 0;  /* global that util routines are sensitive to */
34 int fddipad;    /* XXX: libpcap needs this global */
35
36 /*
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.
40  */
41 enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE };
42 enum stamp_styles timestamp_style = TIMESTAMP_RAW;
43
44 #ifndef __DragonFly__
45 extern int getopt(int argc, char **argv, char *optstring);
46 #endif
47
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);
60
61
62 pcap_dumper_t *dumper = 0;
63
64 int
65 main(int argc, char **argv)
66 {
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;
75
76         opterr = 0;
77         while ((op = getopt(argc, argv, "dRrtw:")) != -1)
78                 switch (op) {
79
80                 case 'd':
81                         dump_flag = 1;
82                         break;
83
84                 case 'R':
85                         ++report_times;
86                         timestamp_style = TIMESTAMP_RAW;
87                         break;
88
89                 case 'r':
90                         ++report_times;
91                         timestamp_style = TIMESTAMP_READABLE;
92                         break;
93
94                 case 't':
95                         ++report_times;
96                         timestamp_style = TIMESTAMP_PARSEABLE;
97                         break;
98
99                 case 'w':
100                         write_file_name = optarg;
101                         break;
102
103                 default:
104                         usage();
105                         /* NOTREACHED */
106                 }
107
108         if (report_times > 1)
109                 error("only one of -R, -r, or -t can be specified");
110
111
112         if (optind < argc)
113                 /*
114                  * See if the next argument looks like a possible
115                  * start time, and if so assume it is one.
116                  */
117                 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
118                         start_time_string = argv[optind++];
119
120         if (optind < argc)
121                 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
122                         stop_time_string = argv[optind++];
123
124
125         if (optind >= argc)
126                 error("at least one input file must be given");
127
128
129         first_time = first_packet_time(argv[optind], &pcap);
130         pcap_close(pcap);
131
132
133         if (start_time_string)
134                 start_time = parse_time(start_time_string, first_time);
135         else
136                 start_time = first_time;
137
138         if (stop_time_string)
139                 stop_time = parse_time(stop_time_string, start_time);
140
141         else {
142                 stop_time = start_time;
143                 stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */
144         }
145
146
147         if (report_times) {
148                 for (; optind < argc; ++optind)
149                         dump_times(&pcap, argv[optind]);
150         }
151
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));
156         }
157
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");
162
163                 for (; optind < argc; ++optind)
164                         extract_slice(argv[optind], write_file_name,
165                                         &start_time, &stop_time);
166         }
167
168         return 0;
169 }
170
171
172 /*
173  * Returns non-zero if a string matches the format for a timestamp,
174  * 0 otherwise.
175  */
176 int
177 is_timestamp(char *str)
178 {
179         while (isdigit(*str) || *str == '.')
180                 ++str;
181
182         return *str == '\0';
183 }
184
185
186 /*
187  * Return the correction in seconds for the local time zone with respect
188  * to Greenwich time.
189  */
190 long
191 local_time_zone(long timestamp)
192 {
193         struct timeval now;
194         struct timezone tz;
195         long localzone;
196
197         if (gettimeofday(&now, &tz) < 0)
198                 err(1, "gettimeofday");
199         localzone = tz.tz_minuteswest * -60;
200
201         if (localtime((time_t *)&timestamp)->tm_isdst)
202                 localzone += 3600;
203
204         return localzone;
205 }
206
207 /*
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.
211  */
212 struct timeval
213 parse_time(char *time_string, struct timeval base_time)
214 {
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] == '+');
220
221         if (is_delta)
222                 ++time_string;  /* skip over '+' sign */
223
224         if (is_timestamp(time_string)) {
225                 /* interpret as a raw timestamp or timestamp offset */
226                 char *time_ptr;
227
228                 result.tv_sec = atoi(time_string);
229                 time_ptr = strchr(time_string, '.');
230
231                 if (time_ptr) {
232                         /* microseconds are specified, too */
233                         int num_digits;
234
235                         num_digits = strlen(time_ptr + 1);
236                         result.tv_usec = atoi(time_ptr + 1);
237
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;
243
244                 if (is_delta) {
245                         result.tv_sec += base_time.tv_sec;
246                         result.tv_usec += base_time.tv_usec;
247
248                         if (result.tv_usec >= 1000000) {
249                                 result.tv_usec -= 1000000;
250                                 ++result.tv_sec;
251                         }
252                 }
253
254                 return result;
255         }
256
257         if (is_delta) {
258                 t = *bt;
259                 usecs = base_time.tv_usec;
260         } else {
261                 /*
262                  * Zero struct (easy way around lack of tm_gmtoff/tm_zone
263                  * under older systems)
264                  */
265                 bzero((char *)&t, sizeof(t));
266
267                 /*
268                  * Set values to "not set" flag so we can later identify
269                  * and default them.
270                  */
271                 t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
272                         t.tm_year = -1;
273         }
274
275         fill_tm(time_string, is_delta, &t, &usecs);
276
277         /*
278          * Now until we reach a field that was specified, fill in the
279          * missing fields from the base time.
280          */
281 #define CHECK_FIELD(field_name)                 \
282         if (t.field_name < 0)                   \
283                 t.field_name = bt->field_name;  \
284         else                                    \
285                 break
286
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);
295
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
300
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);
308         }
309
310         result.tv_sec = gwtm2secs(&t);
311         result.tv_sec -= local_time_zone(result.tv_sec);
312         result.tv_usec = usecs;
313
314         return result;
315 }
316
317
318 /*
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.
323  */
324 void
325 fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr)
326 {
327         char *t_start, *t_stop, format_ch;
328         int val;
329
330 #define SET_VAL(lhs,rhs)        \
331         if (is_delta)           \
332                 lhs += rhs;     \
333         else                    \
334                 lhs = rhs
335
336         /*
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.
341          */
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);
346
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);
352
353                 val = atoi(t_start);
354
355                 format_ch = *t_stop;
356                 if (isupper(format_ch))
357                         format_ch = tolower(format_ch);
358
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;
366                                 }
367                                 SET_VAL(t->tm_year, val);
368                                 break;
369
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;
378
379                         case 'd':
380                                 SET_VAL(t->tm_mday, val);
381                                 break;
382
383                         case 'h':
384                                 SET_VAL(t->tm_hour, val);
385                                 break;
386
387                         case 's':
388                                 SET_VAL(t->tm_sec, val);
389                                 break;
390
391                         case 'u':
392                                 SET_VAL(*usecs_addr, val);
393                                 break;
394
395                         default:
396                                 error(
397                                 "bad date format %s, problem starting at %s",
398                                       time_string, t_start);
399                 }
400         }
401 }
402
403
404 /*
405  * Return in first_time and last_time the timestamps of the first and
406  * last packets in the given file.
407  */
408 void
409 get_file_range(char filename[], pcap_t **p,
410                struct timeval *first_time, struct timeval *last_time)
411 {
412         *first_time = first_packet_time(filename, p);
413
414         if (!sf_find_end(*p, first_time, last_time))
415                 error("couldn't find final packet in file %s", filename);
416 }
417
418 int snaplen;
419
420 /*
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.
424  */
425 struct timeval
426 first_packet_time(char filename[], pcap_t **p_addr)
427 {
428         struct pcap_pkthdr hdr;
429         pcap_t *p;
430         char errbuf[PCAP_ERRBUF_SIZE];
431
432         p = *p_addr = pcap_open_offline(filename, errbuf);
433         if (p == NULL)
434                 error("bad tcpdump file %s: %s", filename, errbuf);
435
436         snaplen = pcap_snapshot(p);
437
438         if (pcap_next(p, &hdr) == 0)
439                 error("bad status reading first packet in %s", filename);
440
441         return hdr.ts;
442 }
443
444
445 /*
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.
449  *
450  * Upon return, start_time is adjusted to reflect a time just after
451  * that of the last packet written to the output.
452  */
453 void
454 extract_slice(char filename[], char write_file_name[],
455               struct timeval *start_time, struct timeval *stop_time)
456 {
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];
462
463         p = pcap_open_offline(filename, errbuf);
464         if (p == NULL)
465                 error("bad tcpdump file %s: %s", filename, errbuf);
466
467         snaplen = pcap_snapshot(p);
468         start_pos = ftell(pcap_file(p));
469
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));
475         }
476
477         if (pcap_next(p, &hdr) == 0)
478                 error("error reading packet in %s: ",
479                         filename, pcap_geterr(p));
480
481         file_start_time = hdr.ts;
482
483
484         if (!sf_find_end(p, &file_start_time, &file_stop_time))
485                 error("problems finding end packet of file %s",
486                         filename);
487
488         stop_pos = ftell(pcap_file(p));
489
490
491         /*
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.
495          */
496         if (sf_timestamp_less_than(start_time, &file_start_time))
497                 *start_time = file_start_time;
498
499         if (sf_timestamp_less_than(&file_stop_time, start_time))
500                 return; /* there aren't any packets of interest in the file */
501
502
503         sf_find_packet(p, &file_start_time, start_pos,
504                         &file_stop_time, stop_pos,
505                         start_time);
506
507         for (;;) {
508                 struct timeval *timestamp;
509                 const u_char *pkt;
510
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;
520                 }
521
522                 timestamp = &hdr.ts;
523
524                 if (!sf_timestamp_less_than(timestamp, start_time)) {
525                         /* packet is recent enough */
526                         if (sf_timestamp_less_than(stop_time, timestamp)) {
527                                 /*
528                                  * We've gone beyond the end of the region
529                                  * of interest ... We're done with this file.
530                                  */
531                                 break;
532                         }
533
534                         pcap_dump((u_char *)dumper, &hdr, pkt);
535
536                         *start_time = *timestamp;
537
538                         /*
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.
543                          */
544                         ++start_time->tv_usec;
545                 }
546         }
547
548         pcap_close(p);
549 }
550
551
552 /*
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.
559  */
560 char *
561 timestamp_to_string(struct timeval *timestamp)
562 {
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;
568
569         buf = buffers[buffer_to_use];
570         buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
571
572         switch (timestamp_style) {
573         case TIMESTAMP_RAW:
574                 sprintf(buf, "%lu.%06lu", timestamp->tv_sec, timestamp->tv_usec);
575                 break;
576
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;
582
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;
591         }
592
593         return buf;
594 }
595
596
597 /*
598  * Given a tcpdump save filename, reports on the times of the first
599  * and last packets in the file.
600  */
601 void
602 dump_times(pcap_t **p, char filename[])
603 {
604         struct timeval first_time, last_time;
605
606         get_file_range(filename, p, &first_time, &last_time);
607
608         printf("%s\t%s\t%s\n",
609                 filename,
610                 timestamp_to_string(&first_time),
611                 timestamp_to_string(&last_time));
612 }
613
614 static void
615 usage(void)
616 {
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");
621
622         exit(1);
623 }
624