File Coverage

blib/lib/Net/Traceroute.pm
Criterion Covered Total %
statement 306 344 88.9
branch 88 134 65.6
condition 10 24 41.6
subroutine 53 54 98.1
pod 23 27 85.1
total 480 583 82.3


line stmt bran cond sub pod time code
1             ###
2             # Copyright 1998, 1999 Massachusetts Institute of Technology
3             # Copyright 2000-2005 Daniel Hagerty
4             #
5             # Permission to use, copy, modify, distribute, and sell this software and its
6             # documentation for any purpose is hereby granted without fee, provided that
7             # the above copyright notice appear in all copies and that both that
8             # copyright notice and this permission notice appear in supporting
9             # documentation, and that the name of M.I.T. not be used in advertising or
10             # publicity pertaining to distribution of the software without specific,
11             # written prior permission. M.I.T. makes no representations about the
12             # suitability of this software for any purpose. It is provided "as is"
13             # without express or implied warranty.
14              
15             ###
16             # File: traceroute.pm
17             # Author: Daniel Hagerty, hag@ai.mit.edu
18             # Date: Tue Mar 17 13:44:00 1998
19             # Description: Perl traceroute module for performing traceroute(1)
20             # functionality.
21              
22             # Currently attempts to parse the output of the system traceroute command,
23             # which it expects will behave like the standard LBL traceroute program.
24             # If it doesn't, (Windows, HPUX come to mind) you lose.
25             #
26              
27             # Could eventually be broken into several classes that know how to
28             # deal with various traceroutes; could attempt to auto-recognize the
29             # particular traceroute and parse it.
30             #
31             # Has a couple of random useful hooks for child classes to override.
32              
33             package Net::Traceroute;
34              
35 22     22   692406 use strict;
  22         86  
  22         963  
36 22     22   114 no strict qw(subs);
  22         45  
  22         764  
37              
38             #require 5.xxx; # We'll probably need this
39              
40 22     22   125 use vars qw(@EXPORT $VERSION @ISA);
  22         38  
  22         1836  
41              
42 22     22   187 use Exporter;
  22         46  
  22         1019  
43 22     22   24514 use IO::Pipe;
  22         272605  
  22         731  
44 22     22   25685 use IO::Select;
  22         49101  
  22         1756  
45 22     22   23633 use Socket;
  22         97939  
  22         15395  
46 22     22   214 use Symbol qw(qualify_to_ref);
  22         49  
  22         1675  
47 22     22   43895 use Time::HiRes qw(time);
  22         51330  
  22         132  
48 22     22   26270 use Errno qw(EAGAIN EINTR);
  22         39939  
  22         3578  
49 22     22   36982 use Data::Dumper; # Debugging
  22         272260  
  22         8014  
50              
51             $VERSION = "1.15"; # Version number is only incremented by
52             # hand.
53              
54             @ISA = qw(Exporter);
55              
56             @EXPORT = qw(
57             TRACEROUTE_OK
58             TRACEROUTE_TIMEOUT
59             TRACEROUTE_UNKNOWN
60             TRACEROUTE_BSDBUG
61             TRACEROUTE_UNREACH_NET
62             TRACEROUTE_UNREACH_HOST
63             TRACEROUTE_UNREACH_PROTO
64             TRACEROUTE_UNREACH_NEEDFRAG
65             TRACEROUTE_UNREACH_SRCFAIL
66             TRACEROUTE_UNREACH_FILTER_PROHIB
67             TRACEROUTE_UNREACH_ADDR
68             TRACEROUTE_UNREACH_PORT
69             TRACEROUTE_SOURCE_QUENCH
70             TRACEROUTE_INTERRUPTED
71             );
72              
73             ###
74              
75             ## Exported functions.
76              
77             # Perl's facist mode gets very grumbly if a few things aren't declared
78             # first.
79              
80 505     505 1 1714 sub TRACEROUTE_OK { 0 }
81 66     66 1 387 sub TRACEROUTE_TIMEOUT { 1 }
82 36     36 1 238 sub TRACEROUTE_UNKNOWN { 2 }
83 22     22 1 138 sub TRACEROUTE_BSDBUG { 3 }
84 69     69 1 274 sub TRACEROUTE_UNREACH_NET { 4 }
85 44     44 1 120 sub TRACEROUTE_UNREACH_HOST { 5 }
86 44     44 1 139 sub TRACEROUTE_UNREACH_PROTO { 6 }
87 22     22 1 74 sub TRACEROUTE_UNREACH_NEEDFRAG { 7 }
88 22     22 1 73 sub TRACEROUTE_UNREACH_SRCFAIL { 8 }
89 72     72 1 374 sub TRACEROUTE_UNREACH_FILTER_PROHIB { 9 }
90 22     22 1 68 sub TRACEROUTE_UNREACH_ADDR { 10 }
91 44     44 1 120 sub TRACEROUTE_UNREACH_PORT { 11 }
92 22     22 1 63 sub TRACEROUTE_SOURCE_QUENCH { 12 }
93 22     22 1 56 sub TRACEROUTE_INTERRUPTED { 13 }
94              
95             ## Internal data used throughout the module
96              
97             # Instance variables that are nothing special, and have an obvious
98             # corresponding accessor/mutator method.
99             my @public_instance_vars =
100             qw(
101             base_port
102             debug
103             host
104             max_ttl
105             packetlen
106             queries
107             query_timeout
108             source_address
109             text
110             trace_program
111             timeout
112             no_fragment
113             use_icmp
114             use_tcp
115             tos
116             );
117              
118             my @simple_instance_vars = (
119             qw(
120             pathmtu
121             stat
122             ),
123             @public_instance_vars,
124             );
125              
126             # Field offsets for query info array
127 22     22   206 use constant query_stat_offset => 0;
  22         43  
  22         1540  
128 22     22   270 use constant query_host_offset => 1;
  22         78  
  22         1086  
129 22     22   214 use constant query_time_offset => 2;
  22         38  
  22         891  
130              
131             # We keep track of the most recently seen chunk of the traceroute for
132             # parsing purposes.
133 22     22   107 use constant token_addr => 0;
  22         44  
  22         853  
134 22     22   198 use constant token_time => 1;
  22         62  
  22         2252  
135 22     22   1345 use constant token_flag => 2;
  22         1352  
  22         116684  
136              
137             # Map ! notation traceroute uses for various icmp packet types
138             # it may receive.
139             my %icmp_map_v4 = (
140             N => TRACEROUTE_UNREACH_NET,
141             H => TRACEROUTE_UNREACH_HOST,
142             P => TRACEROUTE_UNREACH_PROTO,
143             F => TRACEROUTE_UNREACH_NEEDFRAG,
144             S => TRACEROUTE_UNREACH_SRCFAIL,
145             X => TRACEROUTE_UNREACH_FILTER_PROHIB,
146             '!' => TRACEROUTE_BSDBUG,
147             );
148              
149             my %icmp_map_v6 = (
150             N => TRACEROUTE_UNREACH_NET,
151             P => TRACEROUTE_UNREACH_FILTER_PROHIB,
152             # Unlikely to be seen in the wild:
153             # S => unreach notneighbor,
154             A => TRACEROUTE_UNREACH_ADDR,
155             '!' => TRACEROUTE_UNREACH_PORT,
156             );
157              
158             # Entries Q, I, T, and U have never been tested. For the most part, I
159             # don't know how to produce them or they're so rare I couldn't be
160             # bothered.
161             my %icmp_map_cisco = (
162             A => TRACEROUTE_UNREACH_FILTER_PROHIB,
163             Q => TRACEROUTE_SOURCE_QUENCH,
164             I => TRACEROUTE_INTERRUPTED,
165             U => TRACEROUTE_UNREACH_PORT,
166             H => TRACEROUTE_UNREACH_HOST,
167             N => TRACEROUTE_UNREACH_NET,
168             P => TRACEROUTE_UNREACH_PROTO,
169             T => TRACEROUTE_TIMEOUT,
170             # Handled elsehow:
171             # ? => unknown packet type,
172             );
173              
174             ###
175             # Public methods
176              
177             # Constructor
178              
179             sub new ($;%) {
180 24     24 0 30182 my $self = shift;
181 24   66     224 my $type = ref($self) || $self;
182              
183 24         96 my %arg = @_;
184              
185             # We implement a goofy UI so that all programmers can use
186             # Net::Traceroute as a constructor for all types of object.
187 24 100       117 if(exists($arg{backend})) {
188 3         5 my $backend = $arg{backend};
189 3 100       13 if($backend ne "Parser") {
190 2         5 my $module = "Net::Traceroute::$backend";
191 2         124 eval "require $module";
192              
193             # Ignore error on the possibility that they just defined
194             # the module at runtime, rather than an actual module in
195             # the filesystem.
196 2         18 my $newref = qualify_to_ref("new", $module);
197 2         49 my $newcode = *{$newref}{CODE};
  2         5  
198 2 100       8 if(!defined($newcode)) {
199 1         9 die "Backend implementation $backend has no new";
200             }
201 1         3 return(&{$newcode}($module, @_));
  1         5  
202             }
203             }
204              
205 22 100       130 if(!ref($self)) {
206 21         297 $self = bless {}, $type;
207             }
208              
209 22         148 $self->init(%arg);
210 22         321 $self;
211             }
212              
213             sub init {
214 22     22 0 51 my $self = shift;
215 22         53 my %arg = @_;
216              
217             # Take our constructer arguments and initialize the attributes with
218             # them.
219 22         40 my $var;
220 22         70 foreach $var (@public_instance_vars) {
221 330 100       696 if(defined($arg{$var})) {
222 6         27 $self->$var($arg{$var});
223             }
224             }
225              
226             # Initialize debug if it isn't already.
227 22 50       122 $self->debug(0) if(!defined($self->debug));
228 22 100       98 $self->trace_program("traceroute") if(!defined($self->trace_program));
229              
230 22         125 $self->debug_print(1, "Running in debug mode\n");
231              
232             # Initialize status
233 22         87 $self->stat(TRACEROUTE_UNKNOWN);
234              
235 22 100       92 if(defined($self->host)) {
    50          
236 2         8 $self->traceroute;
237             } elsif(defined($self->text)) {
238 0         0 $self->_parse($self->text)
239             }
240              
241 22         172 $self->debug_print(9, Dumper($self));
242             }
243              
244             sub clone ($;%) {
245 1     1 0 2 my $self = shift;
246 1         17 my $type = ref($self);
247              
248 1         4 my %arg = @_;
249              
250 1 50       4 die "Can't clone a non-object!" unless($type);
251              
252 1         4 my $clone = bless {}, $type;
253              
254             # Does a shallow copy of the hash key/values to the new hash.
255 1 50       6 if(ref($self)) {
256 1         1 my($key, $val);
257 1         3 while(($key, $val) = each %{$self}) {
  5         17  
258 4         9 $clone->{$key} = $val;
259             }
260             }
261              
262             # Take our constructer arguments and initialize the attributes with
263             # them.
264 1         3 my $var;
265 1         3 foreach $var (@public_instance_vars) {
266 15 100       32 if(defined($arg{$var})) {
267 1         5 $clone->$var($arg{$var});
268             }
269             }
270              
271             # Initialize status
272 1         5 $clone->stat(TRACEROUTE_UNKNOWN);
273              
274 1 50       4 if(defined($clone->host)) {
    50          
275 0         0 $clone->traceroute;
276             } elsif(defined($clone->text)) {
277 0         0 $clone->_parse($clone->text)
278             }
279              
280 1         5 $clone->debug_print(9, Dumper($clone));
281              
282 1         11 return($clone);
283             }
284              
285             ##
286             # Methods
287              
288             # Do the actual work. Not really a published interface; completely
289             # useable from the constructor.
290             sub traceroute ($) {
291 2     2 1 4 my $self = shift;
292 2         6 my $host = $self->host();
293              
294 2         14 $self->debug_print(1, "Performing traceroute\n");
295              
296 2 50       8 die "No host provided!" unless $host;
297              
298             # Sit in a select loop on the incoming text from traceroute,
299             # waiting for a timeout if we need to. Accumulate the text for
300             # parsing later in one fell swoop.
301              
302             # Note time. Time::HiRes will give us floating point.
303 2         5 my $start_time;
304             my $end_time;
305 2         14 my $total_wait = $self->timeout();
306 2         4 my @this_wait;
307 2 100       7 if(defined($total_wait)) {
308 1         6 $start_time = time();
309 1         10 push(@this_wait, $total_wait);
310 1         4 $end_time = $start_time + $total_wait;
311             }
312              
313 2         7 my $tr_pipe = $self->_make_pipe();
314 2         96 my $select = new IO::Select($tr_pipe);
315              
316 2         397 $self->_zero_text_accumulator();
317 2         38 $self->_zero_hops();
318              
319 2         4 my @ready;
320             out:
321 2         21 while( @ready = $select->can_read(@this_wait)) {
322 3         14019 my $fh;
323 3         27 foreach $fh (@ready) {
324 3         12 my $buf;
325 3         165 my $len = $fh->sysread($buf, 2048);
326              
327             # XXX Linux is fond of returning EAGAIN, which we'll need
328             # to check for here. Still true for sysread?
329 3 50       280 if(!defined($len)) {
330 0         0 my $errno = int($!);
331 0 0 0     0 next out if(($errno == EAGAIN) || ($errno == EINTR));
332 0         0 die "read error: $!";
333             }
334 3 100       19 last out if(!$len); # EOF
335              
336 2         47 $self->text($self->text() . $buf);
337             }
338              
339             # Adjust select timer if we need to.
340 2 100       57 if(defined($total_wait)) {
341 1         14 my $now = time();
342 1 50       8 last out if($now >= $end_time);
343 1         10 $this_wait[0] = $end_time - $now;
344             }
345             }
346 2 100       1990508 if(defined($total_wait)) {
347 1         8 my $now = time();
348 1 50       19 $self->stat(TRACEROUTE_TIMEOUT) if($now >= $end_time);
349              
350             # This is exceedingly dubious. Crawl into IO::Pipe::End's
351             # innards, and nuke the pid connected to our pipe. Otherwise,
352             # close will call waitpid, which we certainly don't wait for a
353             # timeout.
354 1         2 delete ${*$tr_pipe}{io_pipe_pid};
  1         10  
355             }
356              
357 2         19 $tr_pipe->close();
358              
359 2         142 my $accum = $self->text();
360 2 50       27 die "No output from traceroute. Exec failure?" if($accum eq "");
361              
362             # Do the grunt parsing work
363 2         18 $self->_parse($accum);
364              
365             # XXX are you really sure you want to do it like this??
366 2 100       15 if($self->stat() != TRACEROUTE_TIMEOUT) {
367 1         4 $self->stat(TRACEROUTE_OK);
368             }
369              
370 2         58 $self;
371             }
372              
373             sub parse {
374 16     16 1 88 my $self = shift;
375              
376 16         52 $self->_parse($self->text());
377             }
378              
379             sub argv {
380 2     2 1 6 my $self = shift;
381              
382 2         4 my @tr_args;
383 2         7 push(@tr_args, $self->trace_program());
384 2         20 push(@tr_args, $self->_tr_cmd_args());
385 2         8 push(@tr_args, $self->host());
386 2   33     17 my @plen = ($self->packetlen) || (); # Sigh.
387 2         4 push(@tr_args, @plen);
388              
389 2         13 return(@tr_args);
390             }
391              
392             ##
393             # Hop and query functions
394              
395             sub hops ($) {
396 3     3 1 30 my $self = shift;
397              
398 3         8 my $hop_ary = $self->{"hops"};
399              
400 3 50       11 return() unless $hop_ary;
401              
402 3         12 return(int(@{$hop_ary}));
  3         30  
403             }
404              
405             sub hop_queries ($$) {
406 15     15 1 7208 my $self = shift;
407 15         26 my $hop = (shift) - 1;
408              
409 15         108 $self->{"hops"} && $self->{"hops"}->[$hop] &&
410 15 50 33     118 int(@{$self->{"hops"}->[$hop]});
411             }
412              
413             sub found ($) {
414 0     0 1 0 my $self = shift;
415 0         0 my $hops = $self->hops();
416              
417 0 0       0 if($hops) {
418 0         0 my $last_hop = $self->hop_query_host($hops, 0);
419 0         0 my $stat = $self->hop_query_stat($hops, 0);
420              
421             # Is this the correct thing to be doing? This gap in
422             # semantics missed me, and wasn't caught until post 1.5 It
423             # would be a good to audit the semantics here. It's possible
424             # that a prior version change broke this.
425              
426             # Getting good regression tests would be nice, but traceroute
427             # is an annoying thing to do regression on -- you usually
428             # don't have enough control over the network. If I was good,
429             # I would be collecting my bug reports, and saving the
430             # traceroute output produced there.
431 0 0       0 return(undef) if(!defined($last_hop));
432              
433             # Ugh, what to do here?
434             # In IPv4, a host may send the port-unreachable ICMP from an
435             # address other than the one we sent to. (and in fact, I use
436             # this feature quite a bit to map out networks)
437             # IIRC, IPv6 mandates that the unreachable comes from the address we
438             # sent to, so we don't have this problem.
439              
440             # This assumption will that any last hop answer that wasn't an
441             # error may bite us.
442 0 0 0     0 if(
      0        
443             (($stat == TRACEROUTE_OK) || ($stat == TRACEROUTE_BSDBUG) ||
444             ($stat == TRACEROUTE_UNREACH_PROTO))) {
445 0         0 return(1);
446             }
447             }
448 0         0 return(undef);
449             }
450              
451             sub hop_query_stat ($$) {
452 39     39 1 1200 _query_accessor_common(@_,query_stat_offset);
453             }
454              
455             sub hop_query_host ($$) {
456 83     83 1 8715 _query_accessor_common(@_,query_host_offset);
457             }
458              
459             sub hop_query_time ($$) {
460 32     32 1 1267 _query_accessor_common(@_,query_time_offset);
461             }
462              
463             ##
464             # Accesssor/mutators for ordinary instance variables. (Read/Write)
465             # We generate these.
466              
467             foreach my $name (@simple_instance_vars) {
468             my $sym = qualify_to_ref($name);
469             my $code = sub {
470 284     284   1319 my $self = shift;
471 284         566 my $old = $self->{$name};
472 284 100       672 $self->{$name} = $_[0] if @_;
473 284         1142 return $old;
474             };
475             *{$sym} = $code;
476             }
477              
478             ###
479             # Various internal methods
480              
481             # Many of these would be useful to override in a derived class.
482              
483             # Build and return the pipe that talks to our child traceroute.
484             sub _make_pipe ($) {
485 2     2   10 my $self = shift;
486              
487 2         17 $self->debug_print(9, Dumper($self));
488              
489             # XXX we probably shouldn't throw stderr away.
490 2         69 open(my $savestderr, ">&", STDERR);
491 2         113 open(STDERR, ">", "/dev/null");
492              
493 2         22 my $pipe = new IO::Pipe;
494              
495             # IO::Pipe is very unhelpful about error catching. It calls die
496             # in the child program, but returns a reasonable looking object in
497             # the parent. This is really a standard unix fork/exec issue, but
498             # the library doesn't help us.
499 2         244 my $result = $pipe->reader($self->argv());
500              
501 2         3543 open(STDERR, ">&", $savestderr);
502 2         32 close($savestderr);
503              
504             # Long standing bug; the pipe needs to be marked non blocking.
505 2         90 $result->blocking(0);
506              
507 2         83 $result;
508             }
509              
510             # Map some instance variables to command line arguments that take
511             # arguments.
512             my %cmdline_valuemap =
513             ( "base_port" => "-p",
514             "max_ttl" => "-m",
515             "queries" => "-q",
516             "query_timeout" => "-w",
517             "source_address" => "-s",
518             "tos" => "-t",
519             );
520              
521             # Map more instance variables to command line arguments that are
522             # flags.
523             my %cmdline_flagmap =
524             ( "no_fragment" => "-F",
525             "use_icmp" => "-I",
526             "use_tcp" => "-T"
527             );
528              
529             # Build a list of command line arguments
530             sub _tr_cmd_args ($) {
531 2     2   5 my $self = shift;
532              
533 2         5 my @result;
534              
535 2         4 push(@result, "-n");
536              
537 2         4 my($key, $flag);
538              
539 2         13 while(($key, $flag) = each %cmdline_flagmap) {
540 6 50       64 push(@result, $flag) if($self->$key());;
541             }
542              
543 2         10 while(($key, $flag) = each %cmdline_valuemap) {
544 12         40 my $val = $self->$key();
545 12 50       46 if(defined $val) {
546 0         0 push(@result, $flag, $val);
547             }
548             }
549              
550 2         6 @result;
551             }
552              
553             # Do the grunt work of parsing the output.
554             sub _parse ($$) {
555 18     18   45 my $self = shift;
556 18         39 my $tr_output = shift;
557              
558 18         62 my $hopno;
559             my $query;
560              
561 0         0 my $icmp_map;
562 0         0 my $icmp_map_re;
563              
564             my $set_icmp_map = sub {
565 202 100   202   406 $icmp_map = shift if(!defined($icmp_map));;
566 202         205 $icmp_map_re = join("", keys(%{$icmp_map}));
  202         867  
567 18         122 };
568              
569             # This is a crufty hand coded parser that does its job well
570             # enough. The approach of regular expressions without state is
571             # far from perfect, but it gets the job done.
572             line:
573 18         208 foreach $_ (split(/\n/, $tr_output)) {
574              
575             # Some traceroutes appear to print informational line to stdout,
576             # and we don't care.
577 225 100       881 /^traceroute to / && next;
578              
579             # AIX 5L has to be different.
580 224 50       549 /^trying to get / && next;
581 224 50       426 /^source should be / && next;
582              
583             # NetBSD's traceroute emits info about path MTU discovery if
584             # you want, don't know who else does this.
585 224 50       392 /^message too big, trying new MTU = (\d+)/ && do {
586 0         0 $self->pathmtu($1);
587 0         0 next;
588             };
589              
590             # For now, discard MPLS label stack information emitted by
591             # some vendor's traceroutes. Once I'm sure I'm sure I
592             # understand the semantics offered by both the underlying MPLS
593             # and whatever crazy limits the MPLS patch has, I can think
594             # about an interface. My reading of the code is that you will
595             # get the label stack of the last query. If this isn't
596             # representative of all of the queries, it sucks to be you.
597             # You can still get what you need, but it would be nice if the
598             # tool didn't throw information away...
599             # possibilities.
600 224 50       702 /^\s+MPLS Label=(\d+) CoS=(\d) TTL=(\d+) S=(\d+)/ && next;
601              
602             # Cisco chatter. We use the "Type escape sequence..." line to
603             # set the icmp_map to cisco.
604 224 100       470 /^Type escape sequence to abort/ && do {
605 9         19 &{$set_icmp_map}(\%icmp_map_cisco);
  9         30  
606 9         24 next;
607             };
608 215 100       382 /^Tracing the route to/ && next;
609              
610             # XXX there's one like this in the query loop, too.
611             # Can we eliminate one?
612 206 100       528 /^$/ && next;
613              
614             # Cisco marks ECMP paths very differently from LBL. LBL
615             # outputs the changing addresses in one line, whereas cisco
616             # will output a line with no hop count.
617             # XXX we probably need to possibly match DNS in here.
618 188 100       697 s/^\s{4}(\d+\.\d+\.\d+\.\d+ )/$1/ && goto query;
619 180 100       480 s/^\s{4}([0-9a-fA-F:]*:[0-9a-fA-F]*(?:\.\d+\.\d+\.\d+)?)/$1/ &&
620             goto query;
621              
622             # Each line starts with the hopno (space padded to two characters)
623             # and a space.
624 174 50       2498 s/^ ?([0-9 ][0-9]) // || die "Can't find hop number in output: $_";
625              
626 174         385 $hopno = $1 + 0;
627 174         189 $query = 1;
628              
629 174         195 my $addr;
630             my $time;
631              
632 0         0 my $last_token;
633              
634             query:
635 188         440 while($_) {
636             # dns name and address; rewrite as just an address
637             # XXX should keep dns name
638 721         928 s/^ ?[-A-Za-z0-9.]+ \((\d+\.\d+\.\d+\.\d+)\)/$1/;
639 721         849 s/^ ?[-A-Za-z0-9.]+ \(([0-9a-fA-F:]*:[0-9a-fA-F]*(?:\.\d+\.\d+\.\d+)?)\)/$1/;
640              
641             # ip address of a response
642 721 100       3018 s/^ ?(\d+\.\d+\.\d+\.\d+)// && do {
643 115         224 $last_token = token_addr;
644 115         195 $addr = $1;
645 115         150 &{$set_icmp_map}(\%icmp_map_v4);
  115         258  
646 115         717 next query;
647             };
648             # ipv6 address of a response. This regexp is sleazy.
649 606 100       1791 s/^ ?([0-9a-fA-F:]*:[0-9a-fA-F]*(?:\.\d+\.\d+\.\d+)?)// && do {
650 78         102 $last_token = token_addr;
651 78         126 $addr = $1;
652 78         108 &{$set_icmp_map}(\%icmp_map_v6);
  78         149  
653 78         219 next query;
654             };
655             # Redhat FC5 traceroute does this; it's redundant.
656 528 50       1277 s/^ \((\d+\.\d+\.\d+\.\d+)\)// && next query;
657              
658             # round trip time of query
659 528 100       2423 s/^ ? ?([0-9.]+) ms(?:ec)?// && do {
660 495         575 $last_token = token_time;
661 495         1322 $time = $1 + 0;
662              
663 495         893 $self->_add_hop_query($hopno, $query,
664             TRACEROUTE_OK, $addr, $time);
665 495         587 $query++;
666 495         1280 next query;
667             };
668             # query timed out
669 33 100       127 s/^ +\*// && do {
670 16         21 $last_token = token_time;
671 16         42 $self->_add_hop_query($hopno, $query,
672             TRACEROUTE_TIMEOUT,
673             inet_ntoa(INADDR_NONE), 0);
674 16         20 $query++;
675 16         40 next query;
676             };
677              
678             # extra information from the probe (random ICMP info
679             # and such).
680              
681             # There was a bug in this regexp prior to 1.09; reorder
682             # the clauses and everything gets better.
683              
684             # Note that this is actually a very subtle DWIM on perl's
685             # part: in "pure" regular expression theory, order of
686             # expression doesn't matter; the resultant DFA has no
687             # order concept. Without perl DWIMing on our regexp, we'd
688             # write the regexp and code to perform a token lookahead:
689             # the transitions after ! would be < for digits, the keys
690             # of icmp map, and finally whitespace or end of string
691             # indicate a lone "!".
692              
693 17 50       371 s/^ (!<\d+>|\?|![$icmp_map_re]?) ?// && do {
694 17         35 my $flag = $1;
695              
696             # If the prior token was a time sample, it incremented
697             # query. Undo that locally.
698 17         24 my $lquery = $query;
699 17 100 100     99 $lquery-- if(defined($last_token) && $last_token == token_time);
700              
701 17         22 my $stat;
702 17 50       182 if($flag =~ /^!<\d>$/) {
    50          
    100          
    50          
703 0         0 $stat = TRACEROUTE_UNKNOWN;
704             } elsif($flag =~ /^!$/) {
705 0         0 $stat = $icmp_map->{"!"};
706             } elsif($flag =~ /^!([$icmp_map_re])$/) {
707 12         27 my $icmp = $1;
708              
709             # Shouldn't happen
710 12 50       34 die "Unable to parse traceroute output (flag $icmp)!"
711             unless(defined($icmp_map->{$icmp}));
712              
713 12         22 $stat = $icmp_map->{$icmp};
714             } elsif($flag eq "?") {
715             # Cisco does this.
716 5         7 $stat = TRACEROUTE_UNKNOWN;
717             } else {
718 0         0 die "unrecognized flag: $flag";
719             }
720              
721 17 100 100     92 if(defined($last_token) && ($last_token == token_time)) {
722 7         26 $self->_change_hop_query_stat($hopno, $lquery, $stat);
723             } else {
724 10         32 $self->_add_hop_query($hopno, $lquery, $stat, $addr, 0);
725 10         15 $query++;
726             }
727 17         19 $last_token = token_flag;
728              
729 17         82 next query;
730             };
731              
732             # Nothing left, next line.
733 0 0       0 /^$/ && next line;
734              
735             # Cisco ASN data.
736             # XXX we should keep this.
737 0 0       0 s/^ \[AS \d+\]// && next query;
738              
739 0 0       0 s/ \[MPLS: Label \d+ Exp \d+\]// && next query;
740 0 0       0 s, \[MPLS: Labels \d+(?:/\d+)* Exp \d+\],, && next query;
741              
742             # Some LBL derived traceroutes print ttl stuff
743 0 0       0 s/^ \(ttl ?= ?\d+!\)// && next query;
744              
745 0         0 die "Unable to parse traceroute output: $_";
746             }
747             }
748             }
749              
750             sub _zero_text_accumulator ($) {
751 2     2   15 my $self = shift;
752 2         31 my $elem = "text";
753              
754 2         73 $self->{$elem} = "";
755             }
756              
757             # Hop stuff
758             sub _zero_hops ($) {
759 2     2   18 my $self = shift;
760              
761 2         20 delete $self->{"hops"};
762             }
763              
764             sub _add_hop_query ($$$$$$) {
765 521     521   1243 my $self = shift;
766              
767 521         607 my $hop = (shift) - 1;
768 521         585 my $query = (shift) - 1;
769              
770 521         486 my $stat = shift;
771 521         583 my $host = shift;
772 521         512 my $time = shift;
773              
774 521         1998 $self->{"hops"}->[$hop]->[$query] = [ $stat, $host, $time ];
775             }
776              
777             sub _change_hop_query_stat ($$$$) {
778 7     7   9 my $self = shift;
779              
780             # Zero base these
781 7         14 my $hop = (shift) - 1;
782 7         11 my $query = (shift) - 1;
783              
784 7         9 my $stat = shift;
785              
786 7         20 $self->{"hops"}->[$hop]->[$query]->[ query_stat_offset ] = $stat;
787             }
788              
789             sub _query_accessor_common ($$$) {
790 154     154   225 my $self = shift;
791              
792             # Zero base these
793 154         273 my $hop = (shift) - 1;
794 154         192 my $query = (shift) - 1;
795              
796 154         182 my $which_one = shift;
797              
798             # Deal with wildcard
799 154 100       319 if($query == -1) {
800 5         11 my $query_stat;
801              
802             my $aref;
803 5         21 query:
804 5         10 foreach $aref (@{$self->{"hops"}->[$hop]}) {
805 11         18 $query_stat = $aref->[query_stat_offset];
806 11 100       22 $query_stat == TRACEROUTE_TIMEOUT && do { next query };
  8         17  
807 3 50       49 $query_stat == TRACEROUTE_UNKNOWN && do { next query };
  0         0  
808 3         5 do { return $aref->[$which_one] };
  3         18  
809             }
810 2         12 return undef;
811             } else {
812 149         848 $self->{"hops"}->[$hop]->[$query]->[$which_one];
813             }
814             }
815              
816             sub debug_print ($$$;@) {
817 49     49 0 4454 my $self = shift;
818 49         76 my $level = shift;
819 49         78 my $fmtstring = shift;
820              
821 49 50       129 return unless $self->debug() >= $level;
822              
823 0           my($package, $filename, $line, $subroutine,
824             $hasargs, $wantarray, $evaltext, $is_require) = caller(0);
825              
826 0           my $caller_line = $line;
827 0           my $caller_name = $subroutine;
828 0           my $caller_file = $filename;
829              
830 0           my $string = sprintf($fmtstring, @_);
831              
832 0           my $caller = "${caller_file}:${caller_name}:${caller_line}";
833              
834 0           print STDERR "$caller: $string";
835             }
836              
837             1;
838              
839             __END__