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