1 /*
2 
3 Copyright (C) 2002 - 2007 Christian Kreibich
4 
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to
7 deal in the Software without restriction, including without limitation the
8 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 sell copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11 
12 The above copyright notice and this permission notice shall be included in
13 all copies of the Software and its documentation and acknowledgment shall be
14 given in the documentation and software packages that this Software was
15 used.
16 
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 
24 */
25 #if HAVE_CONFIG_H
26 # include <config.h>
27 #endif
28 
29 #include <string.h>
30 #include <errno.h>
31 #include "pcapnav_private.h"
32 #include "pcapnav_debug.h"
33 #include "pcapnav_private.h"
34 #include "pcapnav_macros.h"
35 #include "pcapnav_header.h"
36 #include "pcapnav_util.h"
37 #include "pcapnav_trace.h"
38 
39 
40 /**
41  * trace_get_interpolated_position - estimates offset of a timestamp in trace.
42  * @min_time: start of time frame.
43  * @min_pos: offset in file of @min_time packet.
44  * @max_time: end of time frame.
45  * @max_pos: offset in file of @max_time packet.
46  * @desired_time: timestamp of packet whose offset is interpolated.
47  *
48  * Given a timestamp on each side of desired_time and their offsets
49  * in the file, the function returns the interpolated position of the
50  * @desired_time packet. Nothing is looked at on disk, the value is
51  * calculated only.
52  *
53  * Returns: a negative value if @desired_time is outside the [@min_time, @max_time]
54  * interval.
55  */
56 static off_t
trace_get_interpolated_position(struct bpf_timeval * min_time,off_t min_pos,struct bpf_timeval * max_time,off_t max_pos,struct bpf_timeval * desired_time)57 trace_get_interpolated_position( struct bpf_timeval *min_time, off_t min_pos,
58 				 struct bpf_timeval *max_time, off_t max_pos,
59 				 struct bpf_timeval *desired_time )
60 {
61   double full_span    = __pcapnav_util_timeval_diff(max_time, min_time);
62   double desired_span = __pcapnav_util_timeval_diff(desired_time, min_time);
63   off_t full_span_pos = max_pos - min_pos;
64   double fractional_offset = desired_span / full_span;
65 
66   if ( fractional_offset + 0.0000005 < 0.0 || fractional_offset - 0.0000005 > 1.0 )
67     return -1;
68 
69   return (min_pos + (off_t) (fractional_offset * (double) full_span_pos));
70 }
71 
72 
73 /**
74  * trace_read_up_to_timestamp - reads packets until timestamp is found.
75  * @pn: pcapnav handle.
76  * @desired_time: timestamp that stops the read.
77  *
78  * The function reads packets linearly until one with a timestamp equal
79  * or larger than @desired_time is found. Then, it positions the stream
80  * so that the next read will start at that packet.
81  *
82  * Returns: PCAPNAV_DEFINITELY if timestamp found, or PCAPNAV_ERROR.
83  */
84 static pcapnav_result_t
trace_read_up_to_timestamp(pcapnav_t * pn,struct bpf_timeval * desired_time)85 trace_read_up_to_timestamp(pcapnav_t *pn, struct bpf_timeval *desired_time)
86 {
87   struct pcap_pkthdr hdr;
88   const u_char *buf;
89   off_t pos;
90   int status = PCAPNAV_NONE;
91 
92   for ( ; ; )
93     {
94       struct bpf_timeval *timestamp;
95 
96       pos = FTELL(pn->fp);
97       buf = pcapnav_next(pn, &hdr);
98 
99       if (buf == NULL)
100 	{
101 	  if (feof(pn->fp))
102 	    {
103 	      status = PCAPNAV_ERROR;
104 	      clearerr(pn->fp);
105 	    }
106 
107 	  break;
108 	}
109 
110       timestamp = &hdr.ts;
111 
112       if ( ! __pcapnav_util_timeval_less_than(timestamp, desired_time))
113 	{
114 	  status = PCAPNAV_DEFINITELY;
115 	  break;
116 	}
117     }
118 
119   if (FSEEK(pn->fp, pos, SEEK_SET) < 0)
120     status = PCAPNAV_ERROR;
121 
122   return status;
123 }
124 
125 
126 pcapnav_result_t
__pcapnav_trace_find_packet_at_timestamp(pcapnav_t * pn,struct bpf_timeval * desired_time)127 __pcapnav_trace_find_packet_at_timestamp(pcapnav_t *pn,
128 					 struct bpf_timeval *desired_time)
129 {
130   struct bpf_timeval min_time, max_time;
131   off_t desired_pos, present_pos, min_pos, max_pos;
132   u_char *hdrpos;
133   struct pcap_pkthdr hdr;
134   pcapnav_result_t   status = PCAPNAV_NONE;
135 
136   min_time  = pn->start_time;
137   min_pos   = pn->start_offset;
138   max_time  = pn->end_time;
139   max_pos   = pn->end_offset;
140 
141   /* Handle the special cases -- requested timestamp beyond
142    * end of trace or below start of it.
143    */
144 
145   if (__pcapnav_util_timeval_less_than(&max_time, desired_time))
146     {
147       if (FSEEK(pn->fp, max_pos, SEEK_SET) < 0)
148 	return PCAPNAV_ERROR;
149 
150       return PCAPNAV_NONE;
151     }
152 
153   if (__pcapnav_util_timeval_less_than(desired_time, &min_time))
154     {
155       if (FSEEK(pn->fp, min_pos, SEEK_SET) < 0)
156 	return PCAPNAV_ERROR;
157 
158       return PCAPNAV_NONE;
159     }
160 
161   /* We actually need to look for the right spot in the trace.
162    * Interpolate the position in the trace based upon the
163    * timestamps, and loop until positioned correctly. During
164    * each iteration, the interpolation interval shrinks and
165    * thus becomes more accurate, reflecting local values.
166    */
167 
168   for ( ; ; )
169     {
170       D(("find_packet iteration ...\n"));
171       desired_pos =
172 	trace_get_interpolated_position(&min_time, min_pos,
173 					&max_time, max_pos,
174 					desired_time);
175 
176       if (desired_pos < 0)
177 	{
178 	  status = PCAPNAV_ERROR;
179 	  D(("Negative desired_pos\n"));
180 	  break;
181 	}
182 
183       present_pos = FTELL(pn->fp);
184 
185       if ((present_pos <= desired_pos) &&
186 	  (desired_pos - present_pos < (off_t)STRAIGHT_SCAN_THRESHOLD(pn)))
187 	{
188 	  /* we're close enough to just blindly read ahead */
189 
190 	  status = trace_read_up_to_timestamp(pn, desired_time);
191 	  D(("Blind read-ahead\n"));
192 	  break;
193 	}
194 
195       /* Undershoot the target a little bit - it's much easier to
196        * then scan straight forward than to try to read backwards ...
197        */
198       desired_pos -= STRAIGHT_SCAN_THRESHOLD(pn) / 2;
199 
200       if (desired_pos < min_pos)
201 	desired_pos = min_pos;
202 
203       if (FSEEK(pn->fp, desired_pos, SEEK_SET) < 0)
204 	{
205 	  D(("fseek() failed: %s\n",strerror(errno)));
206 	  status = PCAPNAV_ERROR;
207 	  break;
208 	}
209 
210       D(("SEEK AT BUFFER START: %lu\n", (long unsigned) desired_pos));
211 
212       if (__pcapnav_buf_fill(pn->search_buf, pn->fp, 0, 0, pn->search_buf->size) == 0)
213 	{
214 	  /* This shouldn't ever happen because we try to
215 	   * undershoot, unless the dump file has only a
216 	   * couple packets in it ...
217 	   */
218 
219 	  status = PCAPNAV_ERROR;
220 	  D(("Buffer fill failed.\n"));
221 	  break;
222 	}
223 
224 
225       if ( (status = __pcapnav_header_search(pn, &hdrpos, &hdr)) !=
226 	  PCAPNAV_DEFINITELY)
227 	{
228 	  D(("can't find header at position %lu in dump file -- result is %i\n",
229 	     (long unsigned) desired_pos, status));
230 
231 	  break;
232 	}
233 
234       /* desired_pos is the beginning of the buffer that was
235        * filled above. hdrpos is the actual beginning of a header
236        * in that chunk, so adjust desired_pos to match the
237        * actual beginning of the packet.
238        */
239       desired_pos += (hdrpos - pn->search_buf->buf);
240 
241       /* Seek to the beginning of the header. */
242       if (FSEEK(pn->fp, desired_pos, SEEK_SET) < 0)
243 	{
244 	  D(("fseek() failed: %s\n", strerror(errno)));
245 	  status = PCAPNAV_ERROR;
246 	  break;
247 	}
248 
249       if (__pcapnav_util_timeval_less_than(&hdr.ts, desired_time))
250 	{
251 	  /* We're too early in the file. */
252 	  min_time = hdr.ts;
253 	  min_pos = desired_pos;
254 	}
255       else if (__pcapnav_util_timeval_less_than(desired_time, &hdr.ts))
256 	{
257 	  /* We're too late in the file. */
258 	  max_time = hdr.ts;
259 	  max_pos = desired_pos;
260 	}
261       else
262 	{
263 	  /* got it! */
264 	  D(("Success!\n"));
265 	  break;
266 	}
267     }
268 
269   D(("Return from find_packet, %i\n", status));
270   return status;
271 }
272 
273 
274 /* NOTE -- offset 0 means first packet -- offsets are relative to
275  * the end of the pcap file header.
276  */
277 pcapnav_result_t
__pcapnav_trace_find_packet_at_offset(pcapnav_t * pn,off_t offset,pcapnav_cmp_t boundary)278 __pcapnav_trace_find_packet_at_offset(pcapnav_t *pn,
279 				      off_t offset,
280 				      pcapnav_cmp_t boundary)
281 {
282   off_t               current, next_off;
283   pcapnav_result_t    status = PCAPNAV_NONE;
284   struct pcap_pkthdr  hdr;
285   u_char             *hdrpos = NULL;
286 
287   current = offset;
288   D(("Trying to find packet at %lu with policy %i\n",
289      (long unsigned) offset, boundary));
290 
291   /* Handle the special cases -- requested offset beyond
292    * end of trace or below beginning of actual packets.
293    */
294   if (offset + (off_t) sizeof(struct pcap_file_header) <= pn->start_offset)
295     {
296       D(("Setting to offset 0 as given offset too small\n"));
297       pcapnav_set_offset(pn, 0);
298       return PCAPNAV_DEFINITELY;
299     }
300 
301   /* If an offset is requested that is too close to the end of the
302    * trace, we return the offset of the last valid packet.
303    */
304   if (offset + (off_t) sizeof(struct pcap_file_header) >= pn->size)
305     {
306       D(("Setting to last valid offset (%llu) as given offset %llu too large\n",
307 	 pn->end_offset, offset));
308 
309       pcapnav_set_offset(pn, pn->end_offset);
310       return PCAPNAV_DEFINITELY;
311     }
312 
313   for ( ; ; )
314     {
315       /* Undershoot the target a little bit - it's much easier to
316        * then scan straight forward than to try to read backwards ...
317        */
318       current -= STRAIGHT_SCAN_THRESHOLD(pn);
319 
320       if (current + (off_t) sizeof(struct pcap_file_header) < pn->start_offset)
321 	current = 0;
322 
323       D(("Offset seek iteration: %lu\n", (long unsigned) current));
324 
325       if (FSEEK(pn->fp, current + sizeof(struct pcap_file_header), SEEK_SET) < 0)
326 	{
327 	  D(("fseek() failed: %s\n", strerror(errno)));
328 	  status = PCAPNAV_ERROR;
329 	  break;
330 	}
331 
332       if (__pcapnav_buf_fill(pn->search_buf, pn->fp, 0, 0, pn->search_buf->size) == 0)
333 	{
334 	  /* This shouldn't ever happen because we try to
335 	   * undershoot, unless the dump file has only a
336 	   * couple packets in it ...
337 	   */
338 
339 	  status = PCAPNAV_ERROR;
340 	  D(("Buffer fill failed.\n"));
341 	  break;
342 	}
343 
344       if ( (status = __pcapnav_header_search(pn, &hdrpos, &hdr)) !=
345 	   PCAPNAV_DEFINITELY)
346 	{
347 	  D(("Can't find header at position %lu in dump file -- result is %i\n",
348 	     (long unsigned) current, status));
349 
350 	  return PCAPNAV_NONE;
351 	}
352 
353       if (current + (hdrpos - pn->search_buf->buf) <= offset)
354 	{
355 	  current += (hdrpos - pn->search_buf->buf);
356 	  break;
357 	}
358     }
359 
360   /* Now follow the chain up as close as
361    * possible to the desired offset.
362    */
363   pcapnav_set_offset(pn, current);
364   D(("Starting scan from packet at %lu, aiming at %lu\n",
365      (long unsigned) current, (long unsigned) offset));
366 
367   switch (boundary)
368     {
369     case PCAPNAV_CMP_LEQ:
370       while (pcapnav_get_offset(pn) <= offset)
371 	{
372 	  current = pcapnav_get_offset(pn);
373 
374 	  if (!pcapnav_next(pn, &hdr))
375 	    break;
376 
377 	  D(("Packet at %lu, next one at %lu\n",
378 	     (long unsigned) current, (long unsigned) pcapnav_get_offset(pn)));
379 	}
380       break;
381 
382     case PCAPNAV_CMP_GEQ:
383       while (current < offset)
384 	{
385 	  if (!pcapnav_next(pn, &hdr))
386 	    break;
387 
388 	  current = pcapnav_get_offset(pn);
389 	  D(("Packet at %lu\n", (long unsigned) current));
390 	}
391       break;
392 
393     case PCAPNAV_CMP_ANY:
394     default:
395 
396       while (pcapnav_get_offset(pn) <= offset)
397 	{
398 	  current = pcapnav_get_offset(pn);
399 
400 	  if (!pcapnav_next(pn, &hdr))
401 	    break;
402 
403 	  /* If we scan past the goal, check if the offset we get
404 	   * to is closer to the goal as the one we found below
405 	   * the goal. Then use whichever packet is closer.
406 	   */
407 	  if ( (next_off = pcapnav_get_offset(pn)) > offset)
408 	    {
409 	      if ((next_off - offset) < (offset - current))
410 		{
411 		  current = next_off;
412 		  break;
413 		}
414 	    }
415 
416 	  D(("Packet at %lu, next one at %lu\n",
417 	     (long unsigned) current, (long unsigned) pcapnav_get_offset(pn)));
418 	}
419     }
420 
421   pcapnav_set_offset(pn, current);
422 
423   return status;
424 }
425 
426 
427 void
__pcapnav_trace_find_start(pcapnav_t * pn)428 __pcapnav_trace_find_start(pcapnav_t *pn)
429 {
430   struct pcap_pkthdr hdr, hdr2;
431   off_t old_pos;
432 
433   memset(&pn->start_time, 0, sizeof(struct bpf_timeval));
434 
435   if ((old_pos = FTELL(pn->fp)) < 0)
436     {
437       D(("ftell() failed: %s\n", strerror(errno)));
438       return;
439     }
440 
441   if (FSEEK(pn->fp, sizeof(struct pcap_file_header), SEEK_SET) < 0)
442     {
443       D(("fseek() failed: %s\n", strerror(errno)));
444       return;
445     }
446 
447   if (fread((void *) &hdr, sizeof(struct pcap_pkthdr), 1, pn->fp) != 1)
448     {
449       D(("fread() failed: %s\n", strerror(errno)));
450       return;
451     }
452 
453   if (FSEEK(pn->fp, old_pos, SEEK_SET) < 0)
454     {
455       D(("fseek() to old position failed: %s\n", strerror(errno)));
456       return;
457     }
458 
459   pn->start_offset = sizeof(struct pcap_file_header);
460 
461   __pcapnav_header_extract(pn, (u_char*) &hdr, &hdr2);
462   pn->start_time.tv_sec = hdr2.ts.tv_sec;
463   pn->start_time.tv_usec = hdr2.ts.tv_usec;
464 }
465 
466 
467 void
__pcapnav_trace_find_end(pcapnav_t * pn)468 __pcapnav_trace_find_end(pcapnav_t *pn)
469 {
470   off_t   num_bytes;
471   u_char *hdrpos;
472   struct  pcap_pkthdr hdr;
473   off_t   offset_orig, tmp_offset;
474 
475   if ((pn->start_time.tv_sec == 0)  &&
476       (pn->start_time.tv_usec == 0))
477     __pcapnav_trace_find_start(pn);
478 
479   pn->end_offset = 0;
480   memset(&pn->end_time, 0, sizeof(struct bpf_timeval));
481 
482   /* We go back in the trace far enough to see MAX_CHAIN_LENGTH
483    * consecutive packets, but still use only the (smaller) search_buf.
484    * Once we've found a valid packet, we use pcap to iterate to
485    * the last valid header. This'll involve disk I/O, but is a
486    * safer method than jumping near the end of the trace were we
487    * cannot scan enough packets to be really sure.
488    */
489 
490   /* Remember current position */
491   offset_orig = pcapnav_get_offset(pn);
492 
493   if (pn->trace.length < (int) MAX_PACKET_SIZE(pn) * MAX_CHAIN_LENGTH)
494     num_bytes = pn->trace.length;
495   else
496     num_bytes = MAX_PACKET_SIZE(pn) * MAX_CHAIN_LENGTH;
497 
498   __pcapnav_buf_fill(pn->search_buf, pn->fp, -num_bytes, SEEK_END, pn->search_buf->size);
499   if (__pcapnav_header_search(pn, &hdrpos, &hdr) != PCAPNAV_DEFINITELY)
500     {
501       D(("Header search failed\n"));
502       goto cleanup_return;
503     }
504 
505   tmp_offset = pn->search_buf->offset + (hdrpos - pn->search_buf->buf);
506   D(("Definite header at offset %lu\n", (long unsigned) tmp_offset));
507   pcapnav_set_offset(pn, tmp_offset - sizeof(struct pcap_file_header));
508 
509   pn->end_time   = hdr.ts;
510   pn->end_caplen = hdr.caplen;
511   pn->end_offset = pcapnav_get_offset(pn);
512 
513   /* Select last packet so that the offset is pointing
514    * AT the last packet, not to the offset following it!
515    */
516 
517  for ( ; ; )
518     {
519       tmp_offset = pcapnav_get_offset(pn) + sizeof(struct pcap_file_header);
520 
521       if (!pcap_next(pn->pcap, &hdr))
522 	break;
523 
524       pn->end_time   = hdr.ts;
525       pn->end_caplen = hdr.caplen;
526       pn->end_offset = tmp_offset;
527     }
528 
529   D(("Finished -- last valid packet is at %lu, at %u.%u, captured %u bytes.\n",
530      (long unsigned) pn->end_offset, (unsigned) pn->end_time.tv_sec,
531      (unsigned) pn->end_time.tv_usec, pn->end_caplen));
532 
533  cleanup_return:
534   /* Rewind to old position */
535   pcapnav_set_offset(pn, offset_orig);
536 }
537