]> The Tcpdump Group git mirrors - tcpslice/blob - tcpslice.c
Use TS_RAW_US_MAX_DIGITS in parse_time().
[tcpslice] / tcpslice.c
1 /*
2 * Copyright (c) 1991, 1992, 1993, 1995, 1996, 1997, 1999, 2000
3 * The Regents of the University of California. 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 /*
23 * tcpslice - extract pieces of and/or glue together pcap files
24 */
25
26 #include <config.h>
27
28 // For fileno() and getopt().
29 #if defined(__SUNPRO_C) && ! defined(__EXTENSIONS__)
30 #define __EXTENSIONS__
31 #endif
32
33 #include <sys/types.h>
34 #include <sys/file.h>
35 #include <sys/stat.h>
36 #include <sys/time.h>
37
38 #include <ctype.h>
39 #include <fcntl.h>
40 #include <memory.h>
41 #include <pcap.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <time.h>
46 #include <unistd.h>
47 #include <errno.h>
48 #include <stdint.h>
49
50 #ifdef HAVE_OS_PROTO_H
51 #include "os-proto.h"
52 #endif
53
54 #ifdef HAVE_LIBNIDS
55 #include <nids.h>
56 # ifdef HAVE_LIBOOH323C
57 # include <ootypes.h>
58 # endif /* HAVE_LIBOOH323C */
59 #endif /* HAVE_LIBNIDS */
60
61 #include "tcpslice.h"
62 #include "gmt2local.h"
63 #include "sessions.h"
64
65 /* For Solaris before 11. */
66 /* compute a + b, store in c */
67 #ifndef timeradd
68 #define timeradd(a, b, c) { \
69 (c)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
70 (c)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
71 if ((c)->tv_usec > 1000000) { \
72 (c)->tv_usec -= 1000000; \
73 (c)->tv_sec += 1; \
74 } \
75 }
76 #endif /* timeradd */
77 /* compute a - b, store in c */
78 #ifndef timersub
79 #define timersub(a, b, c) { \
80 (c)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
81 if ((a)->tv_usec < (b)->tv_usec) { \
82 (c)->tv_sec -= 1; /* need to borrow */ \
83 (c)->tv_usec = ((a)->tv_usec + 1000000) - (b)->tv_usec; \
84 } else { \
85 (c)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
86 } \
87 }
88 #endif /* timersub */
89
90 /* The structure used to keep track of files being merged. */
91 struct state {
92 int64_t start_pos, /* seek position corresponding to start time */
93 stop_pos; /* seek position corresponding to stop time */
94 struct timeval
95 file_start_time, /* time of first pkt in file */
96 file_stop_time, /* time of last pkt in file */
97 last_pkt_time; /* time of most recently read pkt */
98 pcap_t *p;
99 struct pcap_pkthdr hdr;
100 const u_char *pkt;
101 char *filename;
102 int done;
103 };
104
105 /* Style in which to print timestamps; RAW is "secs.usecs"; READABLE is
106 * ala the Unix "date" tool; and PARSEABLE is tcpslice's custom format,
107 * designed to be easy to parse. The default is RAW.
108 */
109 enum stamp_styles { TIMESTAMP_RAW, TIMESTAMP_READABLE, TIMESTAMP_PARSEABLE };
110 static enum stamp_styles timestamp_style = TIMESTAMP_RAW;
111
112 /* Let's for now define that as far as tcpslice command-line argument parsing
113 * of raw timestamps goes, valid Unix time is the non-negative range of a
114 * 32-bit signed integer. This way it is possible to validate input without
115 * knowing the size and signedness of the local time_t type. Someone please
116 * invent a better solution before year 2038.
117 */
118 #define TS_RAW_S_MAX_VALUE INT32_MAX
119 #define TS_RAW_US_MAX_DIGITS 6 /* 000000~999999 */
120 #define TS_PARSEABLE_MAX_TOKENS 7 /* ymdhmsu */
121
122 struct parseable_token_t {
123 unsigned amount;
124 /* Bigger units must have bigger integer values for validation. */
125 enum {
126 MICROSECOND,
127 SECOND,
128 MINUTE, /* the default for "m" */
129 HOUR,
130 DAY,
131 MONTH, /* potentially after disambiguation */
132 YEAR,
133 } unit;
134 };
135
136
137 static unsigned char timestamp_input_format_correct(const char *str);
138 static struct timeval parse_time(const char *time_string, struct timeval base_time);
139 static void fill_tm(const char *time_string, const int is_delta, struct tm *t, time_t *usecs_addr);
140 static struct timeval lowest_start_time(const struct state *states, int numfiles);
141 static struct timeval latest_end_time(const struct state *states, int numfiles);
142 static struct state *open_files(char *filenames[], const int numfiles);
143 static u_char validate_files(struct state[], const int);
144 static void close_files(struct state[], const int);
145 static void extract_slice(struct state *states, const int numfiles,
146 const char *write_file_name,
147 const struct timeval *start_time, struct timeval *stop_time,
148 const int keep_dups, const int relative_time_merge,
149 const struct timeval *base_time);
150 static void dump_times(const struct state *states, int numfiles);
151 static void print_usage(FILE *);
152
153
154 pcap_dumper_t *global_dumper = 0;
155
156 extern char *optarg;
157 extern int optind, opterr;
158
159 extern int snaplen; /* needed by search.c, extract_slice() */
160 int snaplen = 0;
161
162 int
163 main(int argc, char **argv)
164 {
165 int op;
166 int dump_flag = 0;
167 int keep_dups = 0;
168 int report_times = 0;
169 int relative_time_merge = 0;
170 int numfiles;
171 char *start_time_string = NULL;
172 char *stop_time_string = NULL;
173 const char *write_file_name = "-"; /* default is stdout */
174 struct timeval first_time, start_time, stop_time;
175 struct state *states;
176
177 opterr = 0;
178 while ((op = getopt(argc, argv, "dDe:f:hlRrs:tvw:")) != EOF)
179 switch (op) {
180
181 case 'd':
182 dump_flag = 1;
183 break;
184
185 case 'D':
186 keep_dups = 1;
187 break;
188
189 case 'e':
190 sessions_expiration_delay = atoi(optarg);
191 break;
192
193 case 'f':
194 sessions_file_format = optarg;
195 break;
196
197 case 'h':
198 print_usage(stdout);
199 exit(0);
200 /* NOTREACHED */
201
202 case 'l':
203 relative_time_merge = 1;
204 break;
205
206 case 'R':
207 ++report_times;
208 timestamp_style = TIMESTAMP_RAW;
209 break;
210
211 case 'r':
212 ++report_times;
213 timestamp_style = TIMESTAMP_READABLE;
214 break;
215
216 case 's':
217 timestamp_style = TIMESTAMP_PARSEABLE;
218 sessions_init(optarg);
219 break;
220
221 case 't':
222 ++report_times;
223 timestamp_style = TIMESTAMP_PARSEABLE;
224 break;
225
226 case 'v':
227 ++verbose;
228 break;
229
230 case 'w':
231 write_file_name = optarg;
232 break;
233
234 default:
235 (void)fprintf(stderr, "Error: invalid command-line option and/or argument!\n");
236 print_usage(stderr);
237 exit(-1);
238 /* NOTREACHED */
239 }
240
241 if ( report_times > 1 )
242 error( "only one of -R, -r, or -t can be specified" );
243
244 /* As far as command-line argument parsing is concerned, iff a string
245 * conforms to a timestamp format, it is a time argument no matter
246 * which format and what value. Whether a parseable time argument
247 * produces a valid date and a valid time is a different test a bit
248 * later with an additional input (base time) and a different error
249 * reporting. This way, the argument "25h70m80s" should always make
250 * tcpslice exit with an invalid time argument error instead of trying
251 * to open a file with that name.
252 */
253 if (optind < argc)
254 if (timestamp_input_format_correct(argv[optind]))
255 start_time_string = argv[optind++];
256
257 if (optind < argc)
258 if (timestamp_input_format_correct(argv[optind]))
259 stop_time_string = argv[optind++];
260
261 if (optind >= argc)
262 error("at least one input file must be given");
263
264 numfiles = argc - optind;
265
266 if ( numfiles == 1 )
267 keep_dups = 1; /* no dups can occur, so don't do the work */
268
269 states = open_files(&argv[optind], numfiles);
270 /* validate_files() might identify multiple issues before returning. */
271 if (validate_files(states, numfiles))
272 exit(1);
273 first_time = lowest_start_time(states, numfiles);
274
275 if (start_time_string)
276 start_time = parse_time(start_time_string, first_time);
277 else
278 start_time = first_time;
279
280 if (stop_time_string)
281 stop_time = parse_time(stop_time_string, start_time);
282 else
283 stop_time = latest_end_time(states, numfiles);
284
285 if (report_times) {
286 dump_times(states, numfiles);
287 }
288
289 if (dump_flag) {
290 printf( "start\t%s\nstop\t%s\n",
291 timestamp_to_string( &start_time ),
292 timestamp_to_string( &stop_time ) );
293 }
294
295 if (! report_times && ! dump_flag) {
296 if ( ! strcmp( write_file_name, "-" ) &&
297 isatty( fileno(stdout) ) )
298 error("stdout is a terminal; redirect or use -w");
299
300 extract_slice(states, numfiles, write_file_name,
301 &start_time, &stop_time, keep_dups, relative_time_merge,
302 &first_time);
303 }
304
305 close_files (states, numfiles);
306 return 0;
307 }
308
309 /* Test if the string has a form of "sssssssss" or "sssssssss.uuuuuu" (as
310 * discussed in the man page) and the integer part does not exceed the upper
311 * limit and the fractional part (if any) does not try to specify more
312 * precision than the format allows. Return 1 on good and 0 on bad.
313 */
314 static unsigned char
315 timestamp_raw_format_correct(const char *str)
316 {
317 enum { START, SECONDS, POINT, MICROSECONDS } fsm_state = START;
318 uint64_t s_value = 0; /* Initialize to squelch a warning. */
319 unsigned us_digits = 0; /* Initialize to squelch a warning. */
320
321 while (1) {
322 switch (fsm_state) {
323 case START: /* Have not seen anything yet. */
324 if (! isdigit((u_char)*str))
325 return 0;
326 s_value = *str - '0';
327 fsm_state = SECONDS;
328 break;
329 case SECONDS: /* Have seen one or more digits for the seconds. */
330 if (*str == '\0')
331 return 1; /* "uuuuuuuuu" */
332 if (*str == '.') {
333 fsm_state = POINT;
334 break;
335 }
336 if (! isdigit((u_char)*str) ||
337 (s_value = s_value * 10 + *str - '0') > TS_RAW_S_MAX_VALUE)
338 return 0;
339 break;
340 case POINT: /* Have seen the decimal point. */
341 if (! isdigit((u_char)*str))
342 return 0;
343 us_digits = 1;
344 fsm_state = MICROSECONDS;
345 break;
346 case MICROSECONDS: /* Have seen one or more digits for the microseconds. */
347 if (*str == '\0')
348 return 1; /* "uuuuuuuuu.ssssss" */
349 if (! isdigit((u_char)*str) || ++us_digits > TS_RAW_US_MAX_DIGITS)
350 return 0;
351 break;
352 default:
353 error("invalid FSM state in %s()", __func__);
354 } /* switch (fsm_state) */
355 str++;
356 } /* while (1) */
357 }
358
359 /* Try to read one complete token (amount, unit) from the given string into
360 * the provided structure by advancing the pointer and consuming characters.
361 * Accept the unit characters in both lowercase and uppercase to be consistent
362 * with the undocumented behaviour of fill_tm(). Do not check whether the
363 * amount is valid for the unit. Return the advanced pointer on success or
364 * NULL otherwise.
365 */
366 static const char *
367 parse_token(const char *str, struct parseable_token_t *token)
368 {
369 enum { START, AMOUNT, UNIT } fsm_state = START;
370 uint64_t amount = 0; /* Initialize to squelch a warning. */
371 char char_unit = 0; /* Initialize to squelch a warning. */
372
373 while (1) {
374 switch (fsm_state) {
375 case START: /* Have not seen anything yet. */
376 if (! isdigit((u_char)*str))
377 return NULL;
378 amount = *str - '0';
379 fsm_state = AMOUNT;
380 break;
381 case AMOUNT: /* Have seen one or more digits for the amount. */
382 if (isalpha((u_char)*str)) {
383 token->amount = amount;
384 char_unit = tolower((u_char)*str);
385 fsm_state = UNIT;
386 break;
387 }
388 if (! isdigit((u_char)*str) ||
389 (amount = amount * 10 + *str - '0') > INT32_MAX)
390 return NULL;
391 break;
392 case UNIT: /* Have seen a character, could be a valid unit. */
393 switch (char_unit) {
394 case 'y':
395 token->unit = YEAR;
396 break;
397 /* no month */
398 case 'd':
399 token->unit = DAY;
400 break;
401 case 'h':
402 token->unit = HOUR;
403 break;
404 case 'm':
405 token->unit = MINUTE;
406 break;
407 case 's':
408 token->unit = SECOND;
409 break;
410 case 'u':
411 token->unit = MICROSECOND;
412 break;
413 default:
414 return NULL;
415 } /* switch (char_unit) */
416 return str;
417 default:
418 error("invalid FSM state in %s()", __func__);
419 } /* switch (fsm_state) */
420 str++;
421 } /* while (1) */
422 }
423
424 /* Test if the string conforms to the "ymdhmsu" format (as discussed in the
425 * man page). Do not test individual amounts to be valid for their time units
426 * or the date to be a valid date or the time to be a valid time. Return 1 on
427 * good and 0 on bad.
428 */
429 static unsigned char
430 timestamp_parseable_format_correct(const char *str)
431 {
432 struct parseable_token_t token[TS_PARSEABLE_MAX_TOKENS];
433 unsigned numtokens = 0;
434
435 /* Try to tokenize the full string, fail as early as possible. */
436 while (*str != '\0') {
437 if (numtokens == TS_PARSEABLE_MAX_TOKENS ||
438 (str = parse_token(str, token + numtokens)) == NULL)
439 return 0;
440 numtokens++;
441 }
442
443 if (numtokens > 1) {
444 unsigned i;
445 /* Disambiguate "m". */
446 for (i = 0; i < numtokens - 1; i++)
447 if (token[i].unit == MINUTE && token[i + 1].unit == DAY)
448 token[i].unit = MONTH;
449 /* Require each time unit in the vector to appear at most once
450 * and in the order of strictly decreasing magnitude.
451 */
452 for (i = 0; i < numtokens - 1; i++)
453 if (token[i].unit <= token[i + 1].unit)
454 return 0;
455 }
456
457 return numtokens > 0;
458 }
459
460 /* Test if the string conforms to a valid input time format (with an optional
461 * leading "+"). Return 1 on good and 0 on bad.
462 */
463 static unsigned char
464 timestamp_input_format_correct(const char *str)
465 {
466 if (*str == '+')
467 str++;
468 return timestamp_parseable_format_correct(str) ||
469 timestamp_raw_format_correct(str);
470 }
471
472 /* No-op iff the date and the time in the given broken-down time are valid
473 * and the year is within the [1970, 2069] range declared in the man page.
474 */
475 static void
476 assert_valid_tm(const struct tm t)
477 {
478 int year, maxdays;
479
480 /* yy: [70, 99] in tm_year means [1970, 1999]
481 * yy: [0, 69] in tm_year means [2000, 2069]
482 * yyyy: [70, 99] in tm_year means [1970, 1999].
483 * yyyy: [100, 169] in tm_year means [2000, 2069].
484 */
485 year = 1900 + t.tm_year;
486 if (year < 1970)
487 year += 100;
488 if (year < 1970 || year > 2069)
489 error("year %d is not valid\n", year);
490
491 if (t.tm_mon < 0 || t.tm_mon > 11) /* 11 is December */
492 error("month %d is not valid\n", t.tm_mon + 1);
493
494 maxdays = days_in_month[t.tm_mon];
495 if (t.tm_mon == 1 && IS_LEAP_YEAR(year)) /* 1 is February */
496 maxdays++;
497 if (t.tm_mday < 1 || t.tm_mday > maxdays)
498 error("day %d is not valid\n", t.tm_mday);
499
500 if (t.tm_hour < 0 || t.tm_hour > 23)
501 error("hour %d is not valid\n", t.tm_hour);
502
503 if (t.tm_min < 0 || t.tm_min > 59)
504 error("minute %d is not valid\n", t.tm_min);
505
506 if (t.tm_sec < 0 || t.tm_sec > 59)
507 error("second %d is not valid\n", t.tm_sec);
508 }
509
510 /* Given a string specifying a time (or a time offset) and a "base time"
511 * from which to compute offsets and fill in defaults, returns a timeval
512 * containing the specified time.
513 */
514 static struct timeval
515 parse_time(const char *time_string, struct timeval base_time)
516 {
517 struct tm *bt = localtime((time_t *) &base_time.tv_sec);
518 struct tm t;
519 struct timeval result;
520 time_t usecs = 0;
521 int is_delta = (time_string[0] == '+');
522
523 if ( is_delta )
524 ++time_string; /* skip over '+' sign */
525
526 if (timestamp_raw_format_correct(time_string))
527 { /* interpret as a raw timestamp or timestamp offset */
528 char *time_ptr;
529
530 result.tv_sec = atoi( time_string );
531 time_ptr = strchr( time_string, '.' );
532
533 if ( time_ptr )
534 { /* microseconds are specified, too */
535 int num_digits = strlen( time_ptr + 1 );
536 result.tv_usec = atoi( time_ptr + 1 );
537
538 /* turn 123.456 into 123 seconds plus 456000 usec */
539 while ( num_digits++ < TS_RAW_US_MAX_DIGITS )
540 result.tv_usec *= 10;
541 }
542
543 else
544 result.tv_usec = 0;
545
546 if ( is_delta )
547 timeradd(&result, &base_time, &result);
548
549 return result;
550 }
551
552 if (is_delta) {
553 t = *bt;
554 usecs = base_time.tv_usec;
555 } else {
556 /* Zero struct (easy way around lack of tm_gmtoff/tm_zone
557 * under older systems) */
558 memset((char *)&t, 0, sizeof(t));
559
560 /* Set values to "not set" flag so we can later identify
561 * and default them.
562 */
563 t.tm_sec = t.tm_min = t.tm_hour = t.tm_mday = t.tm_mon =
564 t.tm_year = -1;
565 }
566
567 /* Terminate the program on error. */
568 fill_tm(time_string, is_delta, &t, &usecs);
569
570 /* Now until we reach a field that was specified, fill in the
571 * missing fields from the base time.
572 */
573 #define CHECK_FIELD(field_name) \
574 if (t.field_name < 0) \
575 t.field_name = bt->field_name; \
576 else \
577 break
578
579 do { /* bogus do-while loop so "break" in CHECK_FIELD will work */
580 CHECK_FIELD(tm_year);
581 CHECK_FIELD(tm_mon);
582 CHECK_FIELD(tm_mday);
583 CHECK_FIELD(tm_hour);
584 CHECK_FIELD(tm_min);
585 CHECK_FIELD(tm_sec);
586 } while ( 0 );
587
588 /* Set remaining unspecified fields to 0. */
589 #define ZERO_FIELD_IF_NOT_SET(field_name,zero_val) \
590 if (t.field_name < 0) \
591 t.field_name = zero_val
592
593 if (! is_delta) {
594 ZERO_FIELD_IF_NOT_SET(tm_year,90); /* should never happen */
595 ZERO_FIELD_IF_NOT_SET(tm_mon,0);
596 ZERO_FIELD_IF_NOT_SET(tm_mday,1);
597 ZERO_FIELD_IF_NOT_SET(tm_hour,0);
598 ZERO_FIELD_IF_NOT_SET(tm_min,0);
599 ZERO_FIELD_IF_NOT_SET(tm_sec,0);
600 }
601
602 assert_valid_tm(t); /* Terminate the program on error. */
603 result.tv_sec = gwtm2secs(&t);
604 result.tv_sec -= gmt2local(result.tv_sec);
605 result.tv_usec = usecs;
606
607 return result;
608 }
609
610 /* Fill in (or add to, if is_delta is true) the time values in the
611 * tm struct "t" as specified by the time specified in the string
612 * "time_string". "usecs_addr" is updated with the specified number
613 * of microseconds, if any.
614 */
615 static void
616 fill_tm(const char *time_string, const int is_delta, struct tm *t, time_t *usecs_addr)
617 {
618 const char *t_start, *t_stop;
619
620 #define SET_VAL(lhs,rhs) \
621 if (is_delta) \
622 lhs += rhs; \
623 else \
624 lhs = rhs
625
626 /* Loop through the time string parsing one specification at
627 * a time. Each specification has the form <number><letter>
628 * where <number> indicates the amount of time and <letter>
629 * the units.
630 */
631 for (t_stop = t_start = time_string; *t_start; t_start = ++t_stop) {
632 if (! isdigit((u_char)*t_start))
633 error("bad date format %s, problem starting at %s",
634 time_string, t_start);
635
636 while (isdigit((u_char)*t_stop))
637 ++t_stop;
638 if (! (*t_stop))
639 error("bad date format %s, problem starting at %s",
640 time_string, t_start);
641
642 int val = atoi(t_start);
643
644 char format_ch = *t_stop;
645 if (isupper((u_char)format_ch))
646 format_ch = tolower((u_char)format_ch);
647
648 switch (format_ch) {
649 case 'y':
650 if ( val >= 100 && val < 1970)
651 error("Can't handle year %d\n", val);
652 if ( val > 1900 )
653 val -= 1900;
654 SET_VAL(t->tm_year, val);
655 break;
656
657 case 'm':
658 if (strchr(t_stop+1, 'D') ||
659 strchr(t_stop+1, 'd'))
660 /* it's months */
661 SET_VAL(t->tm_mon, val - 1);
662 else /* it's minutes */
663 SET_VAL(t->tm_min, val);
664 break;
665
666 case 'd':
667 SET_VAL(t->tm_mday, val);
668 break;
669
670 case 'h':
671 SET_VAL(t->tm_hour, val);
672 break;
673
674 case 's':
675 SET_VAL(t->tm_sec, val);
676 break;
677
678 case 'u':
679 SET_VAL(*usecs_addr, val);
680 break;
681
682 default:
683 error(
684 "bad date format %s, problem starting at %s",
685 time_string, t_start);
686 }
687 }
688 }
689
690 /* Of all the files, what is the lowest start time. */
691 static struct timeval
692 lowest_start_time(const struct state *states, int numfiles)
693 {
694 struct timeval min_time = states->file_start_time;
695
696 while (numfiles--) {
697 if (sf_timestamp_less_than(&states->file_start_time, &min_time)) {
698 min_time = states->file_start_time;
699 }
700 ++states;
701 }
702 return min_time;
703 }
704
705 /* Of all the files, what is the latest end time. */
706 static struct timeval
707 latest_end_time(const struct state *states, int numfiles)
708 {
709 struct timeval max_time = states->file_stop_time;
710
711 while (numfiles--) {
712 if (sf_timestamp_less_than(&max_time, &states->file_stop_time)) {
713 max_time = states->file_stop_time;
714 }
715 ++states;
716 }
717 return max_time;
718 }
719
720 /* Get the next record in a file. Deal with end of file.
721 *
722 * This routine also prevents time from going "backwards"
723 * within a single file.
724 */
725 static void
726 get_next_packet(struct state *s)
727 {
728 struct timeval tvbuf;
729
730 do {
731 s->pkt = pcap_next(s->p, &s->hdr);
732 if (! s->pkt) {
733 s->done = 1;
734 if (track_sessions)
735 sessions_exit();
736 pcap_close(s->p);
737 }
738 TIMEVAL_FROM_PKTHDR_TS(tvbuf, s->hdr.ts);
739 } while ((! s->done) &&
740 sf_timestamp_less_than(&tvbuf, &s->last_pkt_time));
741
742 s->last_pkt_time = tvbuf;
743 }
744
745 static struct state *
746 open_files(char *filenames[], const int numfiles)
747 {
748 struct state *states;
749 struct state *s;
750 char errbuf[PCAP_ERRBUF_SIZE];
751 int i;
752
753 if (numfiles == 0)
754 error("no input files specified");
755
756 /* allocate memory for all the files */
757 states = (struct state *) calloc(numfiles, sizeof(struct state));
758 if (! states)
759 error("unable to allocate memory for %d input files", numfiles);
760
761 for (i = 0; i < numfiles; ++i) {
762 s = &states[i];
763 s->filename = filenames[i];
764 s->p = pcap_open_offline(s->filename, errbuf);
765 if (! s->p)
766 error( "bad pcap file %s: %s", s->filename, errbuf );
767
768 #ifdef HAVE_POSIX_FADVISE
769 FILE *pf = pcap_file(s->p);
770 if (pf == NULL)
771 error("pcap_file() failed");
772 int fd = fileno(pf);
773 if (fd == -1)
774 error("fileno() failed: %s", strerror(errno));
775 int padv_err;
776 if (0 != (padv_err = posix_fadvise(fd, 0, 0, POSIX_FADV_RANDOM)))
777 warning("warning: posix_fadvise() failed: %s", strerror(padv_err));
778 if (0 != (padv_err = posix_fadvise(fd, 0, 0, POSIX_FADV_NOREUSE)))
779 warning("warning: posix_fadvise() failed: %s", strerror(padv_err));
780 #endif
781
782 if (track_sessions)
783 sessions_nids_init(s->p);
784
785 int this_snap = pcap_snapshot( s->p );
786 if (this_snap > snaplen) {
787 snaplen = this_snap;
788 }
789
790 s->start_pos = ftell64( pcap_file( s->p ) );
791
792 if (pcap_next(s->p, &s->hdr) == NULL)
793 error( "error reading packet in %s: %s",
794 s->filename, pcap_geterr( s->p ) );
795
796 TIMEVAL_FROM_PKTHDR_TS(s->file_start_time, s->hdr.ts);
797
798 if ( ! sf_find_end( s->p, &s->file_start_time,
799 &s->file_stop_time ) )
800 error( "problems finding end packet of file %s",
801 s->filename );
802
803 s->stop_pos = ftell64( pcap_file( s->p ) );
804 }
805
806 return states;
807 }
808
809 /* Return 0 on no errors. */
810 static u_char
811 validate_files(struct state states[], const int numfiles)
812 {
813 u_char ret = 0;
814 int i, first_dlt, this_dlt;
815
816 for (i = 0; i < numfiles; i++) {
817 this_dlt = pcap_datalink(states[i].p);
818 if (i == 0)
819 first_dlt = this_dlt;
820 else if (first_dlt != this_dlt) {
821 warning("file '%s' uses DLT %d, and the first file '%s' uses DLT %d",
822 states[i].filename, this_dlt, states[0].filename, first_dlt);
823 ret = 1;
824 }
825
826 /* Do a minimal sanity check of the timestamps. */
827 if (sf_timestamp_less_than(&states[i].file_stop_time,
828 &states[i].file_start_time)) {
829 warning("'%s' has the last timestamp before the first timestamp",
830 states[i].filename);
831 ret = 1;
832 }
833 }
834 return ret;
835 }
836
837 static void
838 close_files(struct state states[], const int numfiles)
839 {
840 int i;
841
842 for (i = 0; i < numfiles; i++)
843 if (!states[i].done)
844 pcap_close(states[i].p);
845 free(states);
846 }
847
848 /*
849 * Extract from a given set of files all packets with timestamps between
850 * the two time values given (inclusive). These packets are written
851 * to the save file given by write_file_name.
852 *
853 * Upon return, start_time is adjusted to reflect a time just after
854 * that of the last packet written to the output.
855 */
856 static void
857 extract_slice(struct state *states, const int numfiles, const char *write_file_name,
858 const struct timeval *start_time, struct timeval *stop_time,
859 const int keep_dups, const int relative_time_merge,
860 const struct timeval *base_time)
861 {
862 struct state *s, *min_state;
863 struct timeval temp1, temp2, relative_start, relative_stop;
864 int i;
865
866 struct state *last_state; /* remember the last packet */
867 struct pcap_pkthdr last_hdr; /* in order to remove duplicates */
868 u_char* last_pkt;
869
870 if (numfiles == 0)
871 error("no input files specified");
872
873 last_state = 0;
874 last_hdr.ts.tv_sec = last_hdr.ts.tv_usec = 0;
875 last_hdr.caplen = last_hdr.len = 0;
876 last_pkt = (u_char *) calloc(1, snaplen);
877
878 if (! last_pkt)
879 error("out of memory");
880
881 timersub(start_time, base_time, &relative_start);
882 timersub(stop_time, base_time, &relative_stop);
883
884 /* Always write the output file, use the first input file's DLT. */
885 global_dumper = pcap_dump_open(states[0].p, write_file_name);
886 if (!global_dumper) {
887 error("error creating output file '%s': %s",
888 write_file_name, pcap_geterr(states[0].p));
889 }
890
891 for (i = 0; i < numfiles; ++i) {
892 s = &states[i];
893
894 /* compute the first packet time within *this* file */
895 if (relative_time_merge) {
896 /* relative time within this file */
897 timeradd(&s->file_start_time, &relative_start, &temp1);
898 } else {
899 /* absolute time */
900 temp1 = *start_time;
901 }
902
903 /* check if this file has *anything* for us ... */
904 if (sf_timestamp_less_than(&s->file_stop_time, &temp1)) {
905 /* there aren't any packets of interest in this file */
906 s->done = 1;
907 pcap_close(s->p);
908 continue;
909 }
910
911 /*
912 * sf_find_packet() requires that the time it's passed as
913 * its last argument be in the range [min_time, max_time],
914 * so we enforce that constraint here.
915 */
916
917 if (sf_timestamp_less_than(&temp1, &s->file_start_time)){
918 temp1 = s->file_start_time;
919 }
920
921 sf_find_packet(s->p, &s->file_start_time, s->start_pos,
922 &s->file_stop_time, s->stop_pos,
923 &temp1);
924
925 /* get first packet for this file */
926 get_next_packet(s);
927 }
928
929
930 /*
931 * Now, loop through all the packets in all the files,
932 * putting packets out in timestamp order.
933 *
934 * Quite often, the files will not have overlapping
935 * timestamps, so it would be nice to try to deal
936 * efficiently with that situation. (XXX)
937 */
938
939 while (1) {
940 struct timeval tvbuf;
941
942 min_state = 0;
943 for (i = 0; i < numfiles; ++i) {
944 s = &states[i];
945 if (! s->done) {
946 if (! min_state)
947 min_state = s;
948
949 if (relative_time_merge) {
950 /* compare *relative* times */
951 timersub(&s->hdr.ts,
952 &s->file_start_time, &temp1);
953 timersub(&min_state->hdr.ts,
954 &min_state->file_start_time, &temp2);
955 } else {
956 /* compare *absolute* times */
957 TIMEVAL_FROM_PKTHDR_TS(temp1, s->hdr.ts);
958 TIMEVAL_FROM_PKTHDR_TS(temp2, min_state->hdr.ts);
959 }
960 if (sf_timestamp_less_than( &temp1, &temp2))
961 min_state = s;
962 }
963 }
964
965 if (! min_state)
966 break; /* didn't find any !done files */
967
968 if (relative_time_merge) {
969 /* relative time w/in this file */
970 timeradd(&min_state->file_start_time, &relative_stop, &temp1);
971 } else
972 /* take absolute times */
973 temp1 = *stop_time;
974
975 TIMEVAL_FROM_PKTHDR_TS(tvbuf, min_state->hdr.ts);
976 if (sf_timestamp_less_than(&temp1, &tvbuf)) {
977 if (!sessions_count) {
978 /* We've gone beyond the end of the region
979 * of interest ... We're done with this file.
980 */
981 if (track_sessions)
982 sessions_exit();
983 min_state->done = 1;
984 pcap_close(min_state->p);
985 break;
986 } else {
987 /* We need to wait for the sessions to close */
988 bonus_time = 1;
989 *stop_time = min_state->file_stop_time;
990 }
991 }
992
993 if (relative_time_merge) {
994 timersub(&min_state->hdr.ts, &min_state->file_start_time, &temp1);
995 timeradd(&temp1, base_time, &min_state->hdr.ts);
996 }
997
998 #ifdef HAVE_LIBNIDS
999 /* Keep track of sessions, if specified by the user */
1000 if (track_sessions && min_state->hdr.caplen) {
1001 /*
1002 * Copy the packet buffer to deconstify it for the function.
1003 */
1004 u_char *pkt_copy = malloc(min_state->hdr.caplen);
1005
1006 if (!pkt_copy)
1007 error("malloc() failed in %s()", __func__);
1008 memcpy(pkt_copy, min_state->pkt, min_state->hdr.caplen);
1009 nids_pcap_handler((u_char *)min_state->p, &min_state->hdr, pkt_copy);
1010 free(pkt_copy);
1011 }
1012 #endif
1013
1014 /* Dump it, unless it's a duplicate. */
1015 if (!bonus_time)
1016 if ( keep_dups ||
1017 min_state == last_state ||
1018 memcmp(&last_hdr, &min_state->hdr, sizeof(last_hdr)) ||
1019 memcmp(last_pkt, min_state->pkt, last_hdr.caplen) ) {
1020 pcap_dump((u_char *) global_dumper, &min_state->hdr, min_state->pkt);
1021
1022 if ( ! keep_dups ) {
1023 last_state = min_state;
1024 last_hdr = min_state->hdr;
1025 memcpy(last_pkt, min_state->pkt, min_state->hdr.caplen);
1026 }
1027 }
1028
1029 get_next_packet(min_state);
1030 }
1031
1032 pcap_dump_close(global_dumper);
1033 free(last_pkt);
1034 }
1035
1036 /* Translates a timestamp to the time format specified by the user.
1037 * Returns a pointer to the translation residing in a static buffer.
1038 * There are two such buffers, which are alternated on subsequent
1039 * calls, so two calls may be made to this routine without worrying
1040 * about the results of the first call being overwritten by the
1041 * results of the second.
1042 */
1043 char *
1044 timestamp_to_string(const struct timeval *timestamp)
1045 {
1046 struct tm *t;
1047 #define NUM_BUFFERS 2
1048 #define DATEBUFSIZE 128
1049 static char buffers[NUM_BUFFERS][DATEBUFSIZE];
1050 static int buffer_to_use = 0;
1051 char *buf;
1052
1053 buf = buffers[buffer_to_use];
1054 buffer_to_use = (buffer_to_use + 1) % NUM_BUFFERS;
1055
1056 switch ( timestamp_style ) {
1057
1058 case TIMESTAMP_RAW:
1059 snprintf( buf, DATEBUFSIZE, "%u.%06u",
1060 (u_int32_t)timestamp->tv_sec,
1061 (u_int32_t)timestamp->tv_usec );
1062 break;
1063
1064 case TIMESTAMP_READABLE:
1065 t = localtime((const time_t *) &timestamp->tv_sec);
1066 /* Mimic asctime() with C99 format specifiers. */
1067 strftime(buf, DATEBUFSIZE, "%a %b %e %T %Y", t);
1068 break;
1069
1070 case TIMESTAMP_PARSEABLE:
1071 t = localtime((const time_t *) &timestamp->tv_sec);
1072 snprintf( buf, DATEBUFSIZE, "%04dy%02dm%02dd%02dh%02dm%02ds%06uu",
1073 t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
1074 t->tm_hour, t->tm_min, t->tm_sec,
1075 (u_int32_t)timestamp->tv_usec );
1076 break;
1077
1078 }
1079
1080 return buf;
1081 }
1082
1083 /* Given a pcap save filename, reports on the times of the first
1084 * and last packets in the file.
1085 */
1086 static void
1087 dump_times(const struct state *states, int numfiles)
1088 {
1089 for (; numfiles--; states++) {
1090 printf( "%s\t%s\t%s\n",
1091 states->filename,
1092 timestamp_to_string( &states->file_start_time ),
1093 timestamp_to_string( &states->file_stop_time ) );
1094 }
1095 }
1096
1097 static void
1098 print_usage(FILE *f)
1099 {
1100 #ifndef HAVE_PCAP_LIB_VERSION
1101 #ifdef HAVE_PCAP_VERSION
1102 extern char pcap_version[];
1103 #else /* HAVE_PCAP_VERSION */
1104 static char pcap_version[] = "unknown";
1105 #endif /* HAVE_PCAP_VERSION */
1106 #endif /* HAVE_PCAP_LIB_VERSION */
1107
1108 (void)fprintf(f, "tcpslice version %s\n", PACKAGE_VERSION);
1109 #ifdef HAVE_PCAP_LIB_VERSION
1110 (void)fprintf(f, "%s\n", pcap_lib_version());
1111 #else /* HAVE_PCAP_LIB_VERSION */
1112 (void)fprintf(f, "libpcap version %s\n", pcap_version);
1113 #endif /* HAVE_PCAP_LIB_VERSION */
1114
1115 #ifdef HAVE_LIBNIDS
1116 (void)fprintf(f, "libnids version %u.%u\n", NIDS_MAJOR, NIDS_MINOR);
1117
1118 #ifdef HAVE_LIBOSIPPARSER2
1119 (void)fprintf(f, "libosip2 unknown version\n");
1120 #endif /* HAVE_LIBOSIPPARSER2 */
1121
1122 #ifdef HAVE_LIBOOH323C
1123 (void)fprintf(f, "libooh323c %s\n", OOH323C_VERSION);
1124 #endif /* HAVE_LIBOOH323C */
1125
1126 #endif /* HAVE_LIBNIDS */
1127
1128 #if defined(SIZEOF_VOID_P) && defined(SIZEOF_TIME_T)
1129 (void)fprintf (f, "%u-bit build, %u-bit time_t\n",
1130 SIZEOF_VOID_P * 8, SIZEOF_TIME_T * 8);
1131 #endif
1132
1133 (void)fprintf(f,
1134 "Usage: tcpslice [-DdhlRrtv] [-w file]\n"
1135 " [ -s types [ -e seconds ] [ -f format ] ]\n"
1136 " [start-time [end-time]] file ... \n");
1137 }