Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[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.2 2003/06/17 04:30:03 dillon 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 /* 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.
39  */
40 enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE };
41 enum stamp_styles timestamp_style = TIMESTAMP_RAW;
42
43 #ifndef __FreeBSD__
44 extern int getopt( int argc, char **argv, char *optstring );
45 #endif
46
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);
59
60
61 pcap_dumper_t *dumper = 0;
62
63 int
64 main(int argc, char **argv)
65 {
66         int op;
67         int dump_flag = 0;
68         int report_times = 0;
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;
73         pcap_t *pcap;
74
75         opterr = 0;
76         while ((op = getopt(argc, argv, "dRrtw:")) != -1)
77                 switch (op) {
78
79                 case 'd':
80                         dump_flag = 1;
81                         break;
82
83                 case 'R':
84                         ++report_times;
85                         timestamp_style = TIMESTAMP_RAW;
86                         break;
87
88                 case 'r':
89                         ++report_times;
90                         timestamp_style = TIMESTAMP_READABLE;
91                         break;
92
93                 case 't':
94                         ++report_times;
95                         timestamp_style = TIMESTAMP_PARSEABLE;
96                         break;
97
98                 case 'w':
99                         write_file_name = optarg;
100                         break;
101
102                 default:
103                         usage();
104                         /* NOTREACHED */
105                 }
106
107         if ( report_times > 1 )
108                 error( "only one of -R, -r, or -t can be specified" );
109
110
111         if (optind < argc)
112                 /* See if the next argument looks like a possible
113                  * start time, and if so assume it is one.
114                  */
115                 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
116                         start_time_string = argv[optind++];
117
118         if (optind < argc)
119                 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
120                         stop_time_string = argv[optind++];
121
122
123         if (optind >= argc)
124                 error("at least one input file must be given");
125
126
127         first_time = first_packet_time(argv[optind], &pcap);
128         pcap_close(pcap);
129
130
131         if (start_time_string)
132                 start_time = parse_time(start_time_string, first_time);
133         else
134                 start_time = first_time;
135
136         if (stop_time_string)
137                 stop_time = parse_time(stop_time_string, start_time);
138
139         else
140                 {
141                 stop_time = start_time;
142                 stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */
143                 }
144
145
146         if (report_times) {
147                 for (; optind < argc; ++optind)
148                         dump_times(&pcap, argv[optind]);
149         }
150
151         if (dump_flag) {
152                 printf( "start\t%s\nstop\t%s\n",
153                         timestamp_to_string( &start_time ),
154                         timestamp_to_string( &stop_time ) );
155         }
156
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");
161
162                 for (; optind < argc; ++optind)
163                         extract_slice(argv[optind], write_file_name,
164                                         &start_time, &stop_time);
165         }
166
167         return 0;
168 }
169
170
171 /* Returns non-zero if a string matches the format for a timestamp,
172  * 0 otherwise.
173  */
174 int is_timestamp( char *str )
175         {
176         while ( isdigit(*str) || *str == '.' )
177                 ++str;
178
179         return *str == '\0';
180         }
181
182
183 /* Return the correction in seconds for the local time zone with respect
184  * to Greenwich time.
185  */
186 long local_time_zone(long timestamp)
187 {
188         struct timeval now;
189         struct timezone tz;
190         long localzone;
191
192         if (gettimeofday(&now, &tz) < 0)
193                 err(1, "gettimeofday");
194         localzone = tz.tz_minuteswest * -60;
195
196         if (localtime((time_t *) &timestamp)->tm_isdst)
197                 localzone += 3600;
198
199         return localzone;
200 }
201
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.
205  */
206
207 struct timeval
208 parse_time(char *time_string, struct timeval base_time)
209 {
210         struct tm *bt = localtime((time_t *) &base_time.tv_sec);
211         struct tm t;
212         struct timeval result;
213         time_t usecs = 0;
214         int is_delta = (time_string[0] == '+');
215
216         if ( is_delta )
217                 ++time_string;  /* skip over '+' sign */
218
219         if ( is_timestamp( time_string ) )
220                 { /* interpret as a raw timestamp or timestamp offset */
221                 char *time_ptr;
222
223                 result.tv_sec = atoi( time_string );
224                 time_ptr = strchr( time_string, '.' );
225
226                 if ( time_ptr )
227                         { /* microseconds are specified, too */
228                         int num_digits = strlen( time_ptr + 1 );
229                         result.tv_usec = atoi( time_ptr + 1 );
230
231                         /* turn 123.456 into 123 seconds plus 456000 usec */
232                         while ( num_digits++ < 6 )
233                                 result.tv_usec *= 10;
234                         }
235
236                 else
237                         result.tv_usec = 0;
238
239                 if ( is_delta )
240                         {
241                         result.tv_sec += base_time.tv_sec;
242                         result.tv_usec += base_time.tv_usec;
243
244                         if ( result.tv_usec >= 1000000 )
245                                 {
246                                 result.tv_usec -= 1000000;
247                                 ++result.tv_sec;
248                                 }
249                         }
250
251                 return result;
252                 }
253
254         if (is_delta) {
255                 t = *bt;
256                 usecs = base_time.tv_usec;
257         } else {
258                 /* Zero struct (easy way around lack of tm_gmtoff/tm_zone
259                  * under older systems) */
260                 bzero((char *)&t, sizeof(t));
261
262                 /* Set values to "not set" flag so we can later identify
263                  * and default them.
264                  */
265                 t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
266                         t.tm_year = -1;
267         }
268
269         fill_tm(time_string, is_delta, &t, &usecs);
270
271         /* Now until we reach a field that was specified, fill in the
272          * missing fields from the base time.
273          */
274 #define CHECK_FIELD(field_name)                 \
275         if (t.field_name < 0)                   \
276                 t.field_name = bt->field_name;  \
277         else                                    \
278                 break
279
280         do {    /* bogus do-while loop so "break" in CHECK_FIELD will work */
281                 CHECK_FIELD(tm_year);
282                 CHECK_FIELD(tm_mon);
283                 CHECK_FIELD(tm_mday);
284                 CHECK_FIELD(tm_hour);
285                 CHECK_FIELD(tm_min);
286                 CHECK_FIELD(tm_sec);
287         } while ( 0 );
288
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
293
294         if (! is_delta) {
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);
301         }
302
303         result.tv_sec = gwtm2secs(&t);
304         result.tv_sec -= local_time_zone(result.tv_sec);
305         result.tv_usec = usecs;
306
307         return result;
308 }
309
310
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.
315  */
316 void
317 fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr)
318 {
319         char *t_start, *t_stop, format_ch;
320         int val;
321
322 #define SET_VAL(lhs,rhs)        \
323         if (is_delta)           \
324                 lhs += rhs;     \
325         else                    \
326                 lhs = rhs
327
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>
331          * the units.
332          */
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);
337
338                 while (isdigit(*t_stop))
339                         ++t_stop;
340                 if (! t_stop)
341                         error("bad date format %s, problem starting at %s",
342                               time_string, t_start);
343
344                 val = atoi(t_start);
345
346                 format_ch = *t_stop;
347                 if ( isupper( format_ch ) )
348                         format_ch = tolower( format_ch );
349
350                 switch (format_ch) {
351                         case 'y':
352                                 if ( val >= 1900 )
353                                         val -= 1900;
354                                 else if (val < 100 && !is_delta) {
355                                         if (val < 69)   /* Same hack as date */
356                                                 val += 100;
357                                 }
358                                 SET_VAL(t->tm_year, val);
359                                 break;
360
361                         case 'm':
362                                 if (strchr(t_stop+1, 'D') ||
363                                     strchr(t_stop+1, 'd'))
364                                         /* it's months */
365                                         SET_VAL(t->tm_mon, val - 1);
366                                 else    /* it's minutes */
367                                         SET_VAL(t->tm_min, val);
368                                 break;
369
370                         case 'd':
371                                 SET_VAL(t->tm_mday, val);
372                                 break;
373
374                         case 'h':
375                                 SET_VAL(t->tm_hour, val);
376                                 break;
377
378                         case 's':
379                                 SET_VAL(t->tm_sec, val);
380                                 break;
381
382                         case 'u':
383                                 SET_VAL(*usecs_addr, val);
384                                 break;
385
386                         default:
387                                 error(
388                                 "bad date format %s, problem starting at %s",
389                                       time_string, t_start);
390                 }
391         }
392 }
393
394
395 /* Return in first_time and last_time the timestamps of the first and
396  * last packets in the given file.
397  */
398 void
399 get_file_range( char filename[], pcap_t **p,
400                 struct timeval *first_time, struct timeval *last_time )
401 {
402         *first_time = first_packet_time( filename, p );
403
404         if ( ! sf_find_end( *p, first_time, last_time ) )
405                 error( "couldn't find final packet in file %s", filename );
406 }
407
408 int snaplen;
409
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
412  * reading.
413  */
414
415 struct timeval
416 first_packet_time(char filename[], pcap_t **p_addr)
417 {
418         struct pcap_pkthdr hdr;
419         pcap_t *p;
420         char errbuf[PCAP_ERRBUF_SIZE];
421
422         p = *p_addr = pcap_open_offline(filename, errbuf);
423         if (! p)
424                 error( "bad tcpdump file %s: %s", filename, errbuf );
425
426         snaplen = pcap_snapshot( p );
427
428         if (pcap_next(p, &hdr) == 0)
429                 error( "bad status reading first packet in %s", filename );
430
431         return hdr.ts;
432 }
433
434
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.
438  *
439  * Upon return, start_time is adjusted to reflect a time just after
440  * that of the last packet written to the output.
441  */
442
443 void
444 extract_slice(char filename[], char write_file_name[],
445                 struct timeval *start_time, struct timeval *stop_time)
446 {
447         long start_pos, stop_pos;
448         struct timeval file_start_time, file_stop_time;
449         struct pcap_pkthdr hdr;
450         pcap_t *p;
451         char errbuf[PCAP_ERRBUF_SIZE];
452
453         p = pcap_open_offline(filename, errbuf);
454         if (! p)
455                 error( "bad tcpdump file %s: %s", filename, errbuf );
456
457         snaplen = pcap_snapshot( p );
458         start_pos = ftell( pcap_file( p ) );
459
460         if ( ! dumper )
461                 {
462                 dumper = pcap_dump_open(p, write_file_name);
463                 if ( ! dumper )
464                         error( "error creating output file %s: ",
465                                 write_file_name, pcap_geterr( p ) );
466                 }
467
468         if (pcap_next(p, &hdr) == 0)
469                 error( "error reading packet in %s: ",
470                         filename, pcap_geterr( p ) );
471
472         file_start_time = hdr.ts;
473
474
475         if ( ! sf_find_end( p, &file_start_time, &file_stop_time ) )
476                 error( "problems finding end packet of file %s",
477                         filename );
478
479         stop_pos = ftell( pcap_file( p ) );
480
481
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.
485          */
486         if ( sf_timestamp_less_than( start_time, &file_start_time ) )
487                 *start_time = file_start_time;
488
489         if ( sf_timestamp_less_than( &file_stop_time, start_time ) )
490                 return; /* there aren't any packets of interest in the file */
491
492
493         sf_find_packet( p, &file_start_time, start_pos,
494                         &file_stop_time, stop_pos,
495                         start_time );
496
497         for ( ; ; )
498                 {
499                 struct timeval *timestamp;
500                 const u_char *pkt = pcap_next( p, &hdr );
501
502                 if ( pkt == 0 )
503                         {
504 #ifdef notdef
505                         int status;
506                         if ( status != SFERR_EOF )
507                                 error( "bad status %d reading packet in %s",
508                                         status, filename );
509 #endif
510                         break;
511                         }
512
513                 timestamp = &hdr.ts;
514
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.
520                                  */
521                                 break;
522
523                         pcap_dump((u_char *) dumper, &hdr, pkt);
524
525                         *start_time = *timestamp;
526
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
530                          * packets.
531                          */
532                         ++start_time->tv_usec;
533                         }
534                 }
535
536         pcap_close( p );
537 }
538
539
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.
546  */
547
548 char *
549 timestamp_to_string(struct timeval *timestamp)
550 {
551         struct tm *t;
552 #define NUM_BUFFERS 2
553         static char buffers[NUM_BUFFERS][128];
554         static int buffer_to_use = 0;
555         char *buf;
556
557         buf = buffers[buffer_to_use];
558         buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
559
560         switch ( timestamp_style )
561             {
562             case TIMESTAMP_RAW:
563                 sprintf(buf, "%lu.%06lu", timestamp->tv_sec, timestamp->tv_usec);
564                 break;
565
566             case TIMESTAMP_READABLE:
567                 t = localtime((time_t *) &timestamp->tv_sec);
568                 strcpy( buf, asctime( t ) );
569                 buf[24] = '\0'; /* nuke final newline */
570                 break;
571
572             case TIMESTAMP_PARSEABLE:
573                 t = localtime((time_t *) &timestamp->tv_sec);
574                 if (t->tm_year >= 100)
575                         t->tm_year += 1900;
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 );
579                 break;
580
581             }
582
583         return buf;
584 }
585
586
587 /* Given a tcpdump save filename, reports on the times of the first
588  * and last packets in the file.
589  */
590
591 void
592 dump_times(pcap_t **p, char filename[])
593 {
594         struct timeval first_time, last_time;
595
596         get_file_range( filename, p, &first_time, &last_time );
597
598         printf( "%s\t%s\t%s\n",
599                 filename,
600                 timestamp_to_string( &first_time ),
601                 timestamp_to_string( &last_time ) );
602 }
603
604 static void
605 usage(void)
606 {
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");
611
612         exit(1);
613 }
614