Initial import from FreeBSD RELENG_4:
[games.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
22 #ifndef lint
23 static const char copyright[] =
24 "@(#) Copyright (c) 1987-1990\n\
25         The Regents of the University of California. All rights reserved.\n";
26 #endif /* not lint */
27
28 #ifndef lint
29 static const char rcsid[] =
30   "$FreeBSD: src/usr.sbin/tcpdump/tcpslice/tcpslice.c,v 1.9.2.1 2000/07/01 01:34:11 ps Exp $";
31 #endif /* not lint */
32
33 /*
34  * tcpslice - extract pieces of and/or glue together tcpdump files
35  */
36
37 #include <err.h>
38 #include "tcpslice.h"
39
40 int tflag = 0;  /* global that util routines are sensitive to */
41 int fddipad;    /* XXX: libpcap needs this global */
42
43 /* Style in which to print timestamps; RAW is "secs.usecs"; READABLE is
44  * ala the Unix "date" tool; and PARSEABLE is tcpslice's custom format,
45  * designed to be easy to parse.  The default is RAW.
46  */
47 enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE };
48 enum stamp_styles timestamp_style = TIMESTAMP_RAW;
49
50 #ifndef __FreeBSD__
51 extern int getopt( int argc, char **argv, char *optstring );
52 #endif
53
54 int is_timestamp( char *str );
55 long local_time_zone(long timestamp);
56 struct timeval parse_time(char *time_string, struct timeval base_time);
57 void fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr);
58 void get_file_range( char filename[], pcap_t **p,
59                         struct timeval *first_time, struct timeval *last_time );
60 struct timeval first_packet_time(char filename[], pcap_t **p_addr);
61 void extract_slice(char filename[], char write_file_name[],
62                         struct timeval *start_time, struct timeval *stop_time);
63 char *timestamp_to_string(struct timeval *timestamp);
64 void dump_times(pcap_t **p, char filename[]);
65 static void usage(void);
66
67
68 pcap_dumper_t *dumper = 0;
69
70 int
71 main(int argc, char **argv)
72 {
73         int op;
74         int dump_flag = 0;
75         int report_times = 0;
76         char *start_time_string = 0;
77         char *stop_time_string = 0;
78         char *write_file_name = "-";    /* default is stdout */
79         struct timeval first_time, start_time, stop_time;
80         pcap_t *pcap;
81
82         opterr = 0;
83         while ((op = getopt(argc, argv, "dRrtw:")) != -1)
84                 switch (op) {
85
86                 case 'd':
87                         dump_flag = 1;
88                         break;
89
90                 case 'R':
91                         ++report_times;
92                         timestamp_style = TIMESTAMP_RAW;
93                         break;
94
95                 case 'r':
96                         ++report_times;
97                         timestamp_style = TIMESTAMP_READABLE;
98                         break;
99
100                 case 't':
101                         ++report_times;
102                         timestamp_style = TIMESTAMP_PARSEABLE;
103                         break;
104
105                 case 'w':
106                         write_file_name = optarg;
107                         break;
108
109                 default:
110                         usage();
111                         /* NOTREACHED */
112                 }
113
114         if ( report_times > 1 )
115                 error( "only one of -R, -r, or -t can be specified" );
116
117
118         if (optind < argc)
119                 /* See if the next argument looks like a possible
120                  * start time, and if so assume it is one.
121                  */
122                 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
123                         start_time_string = argv[optind++];
124
125         if (optind < argc)
126                 if (isdigit(argv[optind][0]) || argv[optind][0] == '+')
127                         stop_time_string = argv[optind++];
128
129
130         if (optind >= argc)
131                 error("at least one input file must be given");
132
133
134         first_time = first_packet_time(argv[optind], &pcap);
135         pcap_close(pcap);
136
137
138         if (start_time_string)
139                 start_time = parse_time(start_time_string, first_time);
140         else
141                 start_time = first_time;
142
143         if (stop_time_string)
144                 stop_time = parse_time(stop_time_string, start_time);
145
146         else
147                 {
148                 stop_time = start_time;
149                 stop_time.tv_sec += 86400*3660; /* + 10 years; "forever" */
150                 }
151
152
153         if (report_times) {
154                 for (; optind < argc; ++optind)
155                         dump_times(&pcap, argv[optind]);
156         }
157
158         if (dump_flag) {
159                 printf( "start\t%s\nstop\t%s\n",
160                         timestamp_to_string( &start_time ),
161                         timestamp_to_string( &stop_time ) );
162         }
163
164         if (! report_times && ! dump_flag) {
165                 if ( ! strcmp( write_file_name, "-" ) &&
166                      isatty( fileno(stdout) ) )
167                         error("stdout is a terminal; redirect or use -w");
168
169                 for (; optind < argc; ++optind)
170                         extract_slice(argv[optind], write_file_name,
171                                         &start_time, &stop_time);
172         }
173
174         return 0;
175 }
176
177
178 /* Returns non-zero if a string matches the format for a timestamp,
179  * 0 otherwise.
180  */
181 int is_timestamp( char *str )
182         {
183         while ( isdigit(*str) || *str == '.' )
184                 ++str;
185
186         return *str == '\0';
187         }
188
189
190 /* Return the correction in seconds for the local time zone with respect
191  * to Greenwich time.
192  */
193 long local_time_zone(long timestamp)
194 {
195         struct timeval now;
196         struct timezone tz;
197         long localzone;
198
199         if (gettimeofday(&now, &tz) < 0)
200                 err(1, "gettimeofday");
201         localzone = tz.tz_minuteswest * -60;
202
203         if (localtime((time_t *) &timestamp)->tm_isdst)
204                 localzone += 3600;
205
206         return localzone;
207 }
208
209 /* Given a string specifying a time (or a time offset) and a "base time"
210  * from which to compute offsets and fill in defaults, returns a timeval
211  * containing the specified time.
212  */
213
214 struct timeval
215 parse_time(char *time_string, struct timeval base_time)
216 {
217         struct tm *bt = localtime((time_t *) &base_time.tv_sec);
218         struct tm t;
219         struct timeval result;
220         time_t usecs = 0;
221         int is_delta = (time_string[0] == '+');
222
223         if ( is_delta )
224                 ++time_string;  /* skip over '+' sign */
225
226         if ( is_timestamp( time_string ) )
227                 { /* interpret as a raw timestamp or timestamp offset */
228                 char *time_ptr;
229
230                 result.tv_sec = atoi( time_string );
231                 time_ptr = strchr( time_string, '.' );
232
233                 if ( time_ptr )
234                         { /* microseconds are specified, too */
235                         int 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                         }
242
243                 else
244                         result.tv_usec = 0;
245
246                 if ( is_delta )
247                         {
248                         result.tv_sec += base_time.tv_sec;
249                         result.tv_usec += base_time.tv_usec;
250
251                         if ( result.tv_usec >= 1000000 )
252                                 {
253                                 result.tv_usec -= 1000000;
254                                 ++result.tv_sec;
255                                 }
256                         }
257
258                 return result;
259                 }
260
261         if (is_delta) {
262                 t = *bt;
263                 usecs = base_time.tv_usec;
264         } else {
265                 /* Zero struct (easy way around lack of tm_gmtoff/tm_zone
266                  * under older systems) */
267                 bzero((char *)&t, sizeof(t));
268
269                 /* Set values to "not set" flag so we can later identify
270                  * and default them.
271                  */
272                 t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
273                         t.tm_year = -1;
274         }
275
276         fill_tm(time_string, is_delta, &t, &usecs);
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 /* Fill in (or add to, if is_delta is true) the time values in the
319  * tm struct "t" as specified by the time specified in the string
320  * "time_string".  "usecs_addr" is updated with the specified number
321  * of microseconds, if any.
322  */
323 void
324 fill_tm(char *time_string, int is_delta, struct tm *t, time_t *usecs_addr)
325 {
326         char *t_start, *t_stop, format_ch;
327         int val;
328
329 #define SET_VAL(lhs,rhs)        \
330         if (is_delta)           \
331                 lhs += rhs;     \
332         else                    \
333                 lhs = rhs
334
335         /* Loop through the time string parsing one specification at
336          * a time.  Each specification has the form <number><letter>
337          * where <number> indicates the amount of time and <letter>
338          * the units.
339          */
340         for (t_stop = t_start = time_string; *t_start; t_start = ++t_stop) {
341                 if (! isdigit(*t_start))
342                         error("bad date format %s, problem starting at %s",
343                               time_string, t_start);
344
345                 while (isdigit(*t_stop))
346                         ++t_stop;
347                 if (! t_stop)
348                         error("bad date format %s, problem starting at %s",
349                               time_string, t_start);
350
351                 val = atoi(t_start);
352
353                 format_ch = *t_stop;
354                 if ( isupper( format_ch ) )
355                         format_ch = tolower( format_ch );
356
357                 switch (format_ch) {
358                         case 'y':
359                                 if ( val >= 1900 )
360                                         val -= 1900;
361                                 else if (val < 100 && !is_delta) {
362                                         if (val < 69)   /* Same hack as date */
363                                                 val += 100;
364                                 }
365                                 SET_VAL(t->tm_year, val);
366                                 break;
367
368                         case 'm':
369                                 if (strchr(t_stop+1, 'D') ||
370                                     strchr(t_stop+1, 'd'))
371                                         /* it's months */
372                                         SET_VAL(t->tm_mon, val - 1);
373                                 else    /* it's minutes */
374                                         SET_VAL(t->tm_min, val);
375                                 break;
376
377                         case 'd':
378                                 SET_VAL(t->tm_mday, val);
379                                 break;
380
381                         case 'h':
382                                 SET_VAL(t->tm_hour, val);
383                                 break;
384
385                         case 's':
386                                 SET_VAL(t->tm_sec, val);
387                                 break;
388
389                         case 'u':
390                                 SET_VAL(*usecs_addr, val);
391                                 break;
392
393                         default:
394                                 error(
395                                 "bad date format %s, problem starting at %s",
396                                       time_string, t_start);
397                 }
398         }
399 }
400
401
402 /* Return in first_time and last_time the timestamps of the first and
403  * last packets in the given file.
404  */
405 void
406 get_file_range( char filename[], pcap_t **p,
407                 struct timeval *first_time, struct timeval *last_time )
408 {
409         *first_time = first_packet_time( filename, p );
410
411         if ( ! sf_find_end( *p, first_time, last_time ) )
412                 error( "couldn't find final packet in file %s", filename );
413 }
414
415 int snaplen;
416
417 /* Returns the timestamp of the first packet in the given tcpdump save
418  * file, which as a side-effect is initialized for further save-file
419  * reading.
420  */
421
422 struct timeval
423 first_packet_time(char filename[], pcap_t **p_addr)
424 {
425         struct pcap_pkthdr hdr;
426         pcap_t *p;
427         char errbuf[PCAP_ERRBUF_SIZE];
428
429         p = *p_addr = pcap_open_offline(filename, errbuf);
430         if (! p)
431                 error( "bad tcpdump file %s: %s", filename, errbuf );
432
433         snaplen = pcap_snapshot( p );
434
435         if (pcap_next(p, &hdr) == 0)
436                 error( "bad status reading first packet in %s", filename );
437
438         return hdr.ts;
439 }
440
441
442 /* Extract from the given file all packets with timestamps between
443  * the two time values given (inclusive).  These packets are written
444  * to the save file given by write_file_name.
445  *
446  * Upon return, start_time is adjusted to reflect a time just after
447  * that of the last packet written to the output.
448  */
449
450 void
451 extract_slice(char filename[], char write_file_name[],
452                 struct timeval *start_time, struct timeval *stop_time)
453 {
454         long start_pos, stop_pos;
455         struct timeval file_start_time, file_stop_time;
456         struct pcap_pkthdr hdr;
457         pcap_t *p;
458         char errbuf[PCAP_ERRBUF_SIZE];
459
460         p = pcap_open_offline(filename, errbuf);
461         if (! p)
462                 error( "bad tcpdump file %s: %s", filename, errbuf );
463
464         snaplen = pcap_snapshot( p );
465         start_pos = ftell( pcap_file( p ) );
466
467         if ( ! dumper )
468                 {
469                 dumper = pcap_dump_open(p, write_file_name);
470                 if ( ! dumper )
471                         error( "error creating output file %s: ",
472                                 write_file_name, pcap_geterr( p ) );
473                 }
474
475         if (pcap_next(p, &hdr) == 0)
476                 error( "error reading packet in %s: ",
477                         filename, pcap_geterr( p ) );
478
479         file_start_time = hdr.ts;
480
481
482         if ( ! sf_find_end( p, &file_start_time, &file_stop_time ) )
483                 error( "problems finding end packet of file %s",
484                         filename );
485
486         stop_pos = ftell( pcap_file( p ) );
487
488
489         /* sf_find_packet() requires that the time it's passed as its last
490          * argument be in the range [min_time, max_time], so we enforce
491          * that constraint here.
492          */
493         if ( sf_timestamp_less_than( start_time, &file_start_time ) )
494                 *start_time = file_start_time;
495
496         if ( sf_timestamp_less_than( &file_stop_time, start_time ) )
497                 return; /* there aren't any packets of interest in the file */
498
499
500         sf_find_packet( p, &file_start_time, start_pos,
501                         &file_stop_time, stop_pos,
502                         start_time );
503
504         for ( ; ; )
505                 {
506                 struct timeval *timestamp;
507                 const u_char *pkt = pcap_next( p, &hdr );
508
509                 if ( pkt == 0 )
510                         {
511 #ifdef notdef
512                         int status;
513                         if ( status != SFERR_EOF )
514                                 error( "bad status %d reading packet in %s",
515                                         status, filename );
516 #endif
517                         break;
518                         }
519
520                 timestamp = &hdr.ts;
521
522                 if ( ! sf_timestamp_less_than( timestamp, start_time ) )
523                         { /* packet is recent enough */
524                         if ( sf_timestamp_less_than( stop_time, timestamp ) )
525                                 /* We've gone beyond the end of the region
526                                  * of interest ... We're done with this file.
527                                  */
528                                 break;
529
530                         pcap_dump((u_char *) dumper, &hdr, pkt);
531
532                         *start_time = *timestamp;
533
534                         /* We know that each packet is guaranteed to have
535                          * a unique timestamp, so we push forward the
536                          * allowed minimum time to weed out duplicate
537                          * packets.
538                          */
539                         ++start_time->tv_usec;
540                         }
541                 }
542
543         pcap_close( p );
544 }
545
546
547 /* Translates a timestamp to the time format specified by the user.
548  * Returns a pointer to the translation residing in a static buffer.
549  * There are two such buffers, which are alternated on subseqeuent
550  * calls, so two calls may be made to this routine without worrying
551  * about the results of the first call being overwritten by the
552  * results of the second.
553  */
554
555 char *
556 timestamp_to_string(struct timeval *timestamp)
557 {
558         struct tm *t;
559 #define NUM_BUFFERS 2
560         static char buffers[NUM_BUFFERS][128];
561         static int buffer_to_use = 0;
562         char *buf;
563
564         buf = buffers[buffer_to_use];
565         buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
566
567         switch ( timestamp_style )
568             {
569             case TIMESTAMP_RAW:
570                 sprintf(buf, "%lu.%06lu", timestamp->tv_sec, timestamp->tv_usec);
571                 break;
572
573             case TIMESTAMP_READABLE:
574                 t = localtime((time_t *) &timestamp->tv_sec);
575                 strcpy( buf, asctime( t ) );
576                 buf[24] = '\0'; /* nuke final newline */
577                 break;
578
579             case TIMESTAMP_PARSEABLE:
580                 t = localtime((time_t *) &timestamp->tv_sec);
581                 if (t->tm_year >= 100)
582                         t->tm_year += 1900;
583                 sprintf( buf, "%02dy%02dm%02dd%02dh%02dm%02ds%06ldu",
584                         t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour,
585                         t->tm_min, t->tm_sec, timestamp->tv_usec );
586                 break;
587
588             }
589
590         return buf;
591 }
592
593
594 /* Given a tcpdump save filename, reports on the times of the first
595  * and last packets in the file.
596  */
597
598 void
599 dump_times(pcap_t **p, char filename[])
600 {
601         struct timeval first_time, last_time;
602
603         get_file_range( filename, p, &first_time, &last_time );
604
605         printf( "%s\t%s\t%s\n",
606                 filename,
607                 timestamp_to_string( &first_time ),
608                 timestamp_to_string( &last_time ) );
609 }
610
611 static void
612 usage(void)
613 {
614         (void)fprintf(stderr, "tcpslice for tcpdump version %d.%d\n",
615                       VERSION_MAJOR, VERSION_MINOR);
616         (void)fprintf(stderr,
617 "usage: tcpslice [-dRrt] [-w file] [start-time [end-time]] file ... \n");
618
619         exit(1);
620 }
621