File Coverage

blib/lib/Net/Traceroute6.pm
Criterion Covered Total %
statement 25 27 92.5
branch n/a
condition n/a
subroutine 9 9 100.0
pod n/a
total 34 36 94.4


line stmt bran cond sub pod time code
1             ###
2             # Copyright 1998, 1999 Massachusetts Institute of Technology
3             # Copyright 2001 Janos Mohacsi
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.
10             # It is provided "as is" without express or implied warranty.
11              
12             ###
13             # File: Traceroute6.pm
14             # Author: Janos Mohacsi mohacsi@ik.bme.hu
15             # Original Traceroute.pm Author: Daniel Hagerty, hag@ai.mit.edu
16             # Date: Thu Aug 30 19:07:59 CEST 2001
17             # Description: Perl traceroute module for performing traceroute6(1)
18             # functionality. Most of the code is imported from the
19             # Net::Traceroute perl module. Added to support IPv6 and
20             # IPv6/IPv4 selection. You need Socket6 perl module for
21             # low-level IPv6 support
22             #
23              
24             # Currently attempts to parse the output of the system traceroute command,
25             # which it expects will behave like the standard LBL traceroute program.
26             # If it doesn't, (Windows, HPUX come to mind) you may lose.
27             #
28             # It tries figure out the particular traceroute from the configuration
29             # and parse it.
30             #
31             # The IPv6 traceroute makes the situation worse, becuase no standard systax
32             # IPv6 traceroute. Some system prefer separate traceroute (understandable
33             # since the ICMPv6 error options are so different.
34             # For IPv6 currently only on some system is known to work:
35             # *BSD (FreeBSD 4.0 or later, NetBSD 1.5 or later. OpenBSD 2.8 and later)
36             # + any KAME patched BSD
37             # Solaris 8 or later
38             #
39             # BUGS: currently only few ICMPv6 error options are recognised.
40             #
41             # Changelog:
42             # Version 0.01 : inital version
43             # Version 0.02 : Solaris support
44             # Version 0.03 : removing some debugging information
45             #
46             # Has a couple of random useful hooks for child classes to override.
47              
48             package Net::Traceroute6;
49              
50 1     1   720 use Config;
  1         2  
  1         45  
51 1     1   5 use strict;
  1         1  
  1         27  
52 1     1   4 no strict qw(subs);
  1         4  
  1         29  
53              
54             #require 5.xxx; # We'll probably need this
55              
56 1     1   4 use vars qw(@EXPORT $VERSION @ISA);
  1         2  
  1         57  
57              
58 1     1   4 use Exporter;
  1         2  
  1         31  
59 1     1   766 use IO::Pipe;
  1         20174  
  1         35  
60 1     1   1970 use IO::Select;
  1         2057  
  1         67  
61 1     1   979 use Socket;
  1         4611  
  1         893  
62 1     1   10724 use Socket6;
  0            
  0            
63             use Data::Dumper; # Debugging
64              
65             $VERSION = "0.03"; # Version number is only incremented by
66             # hand.
67              
68             @ISA = qw(Exporter);
69              
70             @EXPORT = qw(TRACEROUTE_OK
71             TRACEROUTE_TIMEOUT
72             TRACEROUTE_UNKNOWN
73             TRACEROUTE_BSDBUG
74             TRACEROUTE_UNREACH_NET
75             TRACEROUTE_UNREACH_HOST
76             TRACEROUTE_UNREACH_PROTO
77             TRACEROUTE_UNREACH_NEEDFRAG
78             TRACEROUTE_UNREACH_SRCFAIL
79             TRACEROUTE_UNREACH_ADDR
80             TRACEROUTE_UNREACH_FILTER_PROHIB);
81              
82             ###
83              
84             ## Exported functions.
85              
86             # Perl's facist mode gets very grumbly if a few things aren't declared
87             # first.
88              
89             sub TRACEROUTE_OK { 0 }
90             sub TRACEROUTE_TIMEOUT { 1 }
91             sub TRACEROUTE_UNKNOWN { 2 }
92             sub TRACEROUTE_BSDBUG { 3 }
93             sub TRACEROUTE_UNREACH_NET { 4 }
94             sub TRACEROUTE_UNREACH_HOST { 5 }
95             sub TRACEROUTE_UNREACH_PROTO { 6 }
96             sub TRACEROUTE_UNREACH_NEEDFRAG { 7 }
97             sub TRACEROUTE_UNREACH_SRCFAIL { 8 }
98             sub TRACEROUTE_UNREACH_FILTER_PROHIB { 9 }
99             sub TRACEROUTE_UNREACH_ADDR { 10 }
100              
101             ## Internal data used throughout the module
102              
103             # Instance variables that are nothing special, and have an obvious
104             # corresponding accessor/mutator method.
105             my @simple_instance_vars = qw(base_port
106             debug
107             host
108             max_ttl
109             queries
110             query_timeout
111             timeout
112             af
113             host_address);
114              
115             # Field offsets for query info array
116             my $query_stat_offset = 0;
117             my $query_host_offset = 1;
118             my $query_time_offset = 2;
119              
120             #real address family if you specify PF_UNSPEC
121             my $real_af = -1;
122              
123             # check whether we have IPv6 support;
124             my $inet6 = defined(eval 'PF_INET6');
125              
126             ###
127             # Public methods
128              
129             # Constructor
130              
131             sub new {
132             my $self = shift;
133             my $type = ref($self) || $self;
134              
135             my %hash = ();
136              
137             my %arg = @_;
138              
139             my $me = bless \%hash, $type;
140              
141             # If we've been called through an object, use that one as a template.
142             # Does a shallow copy of the hash key/values to the new hash.
143             if(ref($self)) {
144             my($key, $val);
145             while(($key, $val) = each %{$self}) {
146             $me->{$key} = $val;
147             }
148             }
149              
150             # Take our constructer arguments and initialize the attributes with
151             # them.
152             my $var;
153             foreach $var (@simple_instance_vars) {
154             if(defined($arg{$var})) {
155             $me->$var($arg{$var});
156             }
157             }
158              
159             # Initialize debug if it isn't already.
160             $me->debug(0) if(!defined($me->debug));
161              
162             $me->debug_print(1, "Running in debug mode\n");
163              
164             # Initialize status
165             $me->stat(TRACEROUTE_UNKNOWN);
166              
167             # Initialize address family
168             $me->af(PF_UNSPEC) if (!defined($me->af));
169              
170              
171             if(defined($me->host)) {
172             $me->traceroute;
173             }
174              
175             $me->debug_print(9, Dumper($me));
176              
177             $me;
178             }
179              
180             ##
181             # Methods
182              
183             # Do the actual work. Not really a published interface; completely
184             # useable from the constructor.
185             sub traceroute {
186             my $self = shift;
187             my $host = shift || $self->host();
188             my $af = shift || $self->af();
189              
190             $self->debug_print(1, "Want to perform traceroute for $host on address family $af\n");
191             die "No host provided!" unless $host;
192              
193             # get socket information for $host for service=0, specified address
194             # family ,
195             # should be raw socket type, ICMPv6 protocol (but SOCK_STREAM is the
196             # only working at the moment )
197             # and return canonical name
198              
199             my @tmp = getaddrinfo($host, 0, $af, SOCK_STREAM, 0, AI_CANONNAME);
200             if (scalar(@tmp) >= 5) {
201             my($family, $socktype, $protocol, $sin, $canonname)=splice(@tmp, $[, 5);
202             my($addr, $port)=getnameinfo($sin, NI_NUMERICHOST |NI_NUMERICSERV);
203            
204             # print the real information
205             $self->debug_print(1, "In reality performing traceroute for canonnical name: $canonname adress: $addr on address family $family\n");
206              
207             $self->af($family);
208             $self->host_address($addr);
209              
210             } else {
211             die "No Address for specified host: $host, address family: $af!";
212             }
213              
214              
215             # Sit in a select loop on the incoming text from traceroute,
216             # waiting for a timeout if we need to. Accumulate the text for
217             # parsing later in one fell swoop.
218              
219             # Note time
220             my $start_time = time();
221             my $total_wait = $self->timeout();
222             my @this_wait = defined($total_wait) ? ($total_wait) : ();
223              
224             my $tr_pipe = $self->_make_pipe();
225             my $select = new IO::Select($tr_pipe);
226              
227             $self->_zero_text_accumulator();
228             $self->_zero_hops();
229              
230             my @ready;
231             out:
232             while( @ready = $select->can_read(@this_wait)) {
233             my $fh;
234             foreach $fh (@ready) {
235             my $buf;
236             my $len = $fh->read($buf, 2048);
237              
238             die "read error: $!" unless(defined($len));
239              
240             last out if(!$len); # EOF
241              
242             $self->_text_accumulator($buf);
243             }
244              
245             # Check for timeout
246             my $now = time();
247             if(defined($total_wait)) {
248             if($now > ($start_time + $total_wait)) {
249             $self->stat(TRACEROUTE_TIMEOUT);
250             last out;
251             }
252             $this_wait[0] = ($start_time + $total_wait) - $now;
253             }
254             }
255              
256             $tr_pipe->close();
257              
258             # Do the grunt parsing work
259             $self->_parse($self->_text_accumulator());
260              
261             if($self->stat() != TRACEROUTE_TIMEOUT) {
262             $self->stat(TRACEROUTE_OK);
263             }
264              
265             $self;
266             }
267              
268             ##
269             # Accesssor/mutators for ordinary instance variables. (Read/Write)
270              
271             sub base_port {
272             my $self = shift;
273             my $elem = "base_port";
274              
275             my $old = $self->{$elem};
276             $self->{$elem} = $_[0] if @_;
277             return $old;
278             }
279              
280             sub debug {
281             my $self = shift;
282             my $elem = "debug";
283              
284             my $old = $self->{$elem};
285             $self->{$elem} = $_[0] if @_;
286             return $old;
287             }
288              
289             sub max_ttl {
290             my $self = shift;
291             my $elem = "max_ttl";
292              
293             my $old = $self->{$elem};
294             $self->{$elem} = $_[0] if @_;
295             return $old;
296             }
297              
298             sub queries {
299             my $self = shift;
300             my $elem = "queries";
301              
302             my $old = $self->{$elem};
303             $self->{$elem} = $_[0] if @_;
304             return $old;
305             }
306              
307             sub query_timeout {
308             my $self = shift;
309             my $elem = "query_timeout";
310              
311             my $old = $self->{$elem};
312             $self->{$elem} = $_[0] if @_;
313             return $old;
314             }
315              
316             sub host {
317             my $self = shift;
318             my $elem = "host";
319              
320             my $old = $self->{$elem};
321             $self->{$elem} = $_[0] if @_;
322             return $old;
323             }
324              
325             sub host_address {
326             my $self = shift;
327             my $elem = "host_address";
328              
329             my $old = $self->{$elem};
330              
331             # Internal representation always uses IP address in string form.
332             if(@_) {
333             $self->{$elem} = $_[0];
334             }
335             return $old;
336             }
337              
338             sub timeout {
339             my $self = shift;
340             my $elem = "timeout";
341              
342             my $old = $self->{$elem};
343             $self->{$elem} = $_[0] if @_;
344             return $old;
345             }
346              
347             sub af {
348             my $self = shift;
349             my $elem = "af";
350              
351             my $old = $self->{$elem};
352             if (@_) {
353            
354             $self->{$elem} = PF_UNSPEC if ($_[0]== PF_UNSPEC) ;
355             $self->{$elem} = PF_INET if ($_[0]== PF_INET) ;
356             $self->{$elem} = PF_INET6 if ($inet6 && ($_[0] == PF_INET6));
357             }
358             return $old;
359             }
360              
361             # Accessor for status of this traceroute object. Externally read only
362             # (not enforced).
363             sub stat {
364             my $self = shift;
365             my $elem = "stat";
366              
367             my $old = $self->{$elem};
368             $self->{$elem} = $_[0] if @_;
369             return $old;
370             }
371              
372             ##
373             # Hop and query functions
374              
375             sub hops {
376             my $self = shift;
377              
378             my $hop_ary = $self->{"hops"};
379              
380             return() unless $hop_ary;
381              
382             return(int(@{$hop_ary}));
383             }
384              
385             sub hop_queries {
386             my $self = shift;
387             my $hop = (shift) - 1;
388              
389             $self->{"hops"} && $self->{"hops"}->[$hop] &&
390             int(@{$self->{"hops"}->[$hop]});
391             }
392              
393             sub found {
394             my $self = shift;
395             my $hops = $self->hops();
396              
397             if($hops) {
398             my $host_address = $self->host_address;
399              
400             my $last_hop = $self->hop_query_host($hops, 0);
401             my $stat = $self->hop_query_stat($hops, 0);
402              
403             if( $last_hop eq $host_address &&
404             (($stat == TRACEROUTE_OK) || ($stat == TRACEROUTE_BSDBUG) ||
405             ($stat == TRACEROUTE_UNREACH_PROTO))) {
406             return(1);
407             }
408             }
409             return(undef);
410             }
411              
412             sub hop_query_stat {
413             _query_accessor_common(@_,$query_stat_offset);
414             }
415              
416             sub hop_query_host {
417             _query_accessor_common(@_,$query_host_offset);
418             }
419              
420             sub hop_query_time {
421             _query_accessor_common(@_,$query_time_offset);
422             }
423              
424             ###
425             # Various internal methods
426              
427             # Many of these would be useful to override in a derived class.
428              
429             # Build and return the pipe that talks to our child traceroute.
430             sub _make_pipe {
431             my $self = shift;
432              
433             my @tr_args = $self->_tr_program_name();
434              
435             push(@tr_args, $self->_tr_cmd_args());
436             push(@tr_args, $self->host_address());
437              
438             # XXX we probably shouldn't throw stderr away.
439             open(SAVESTDERR, ">&STDERR");
440             #open(STDERR, ">/tmp/log");
441             open(STDERR, ">/dev/null");
442              
443             my $pipe = new IO::Pipe;
444              
445             # IO::Pipe is very unhelpful about error catching. It calls die
446             # in the child program, but returns a reasonable looking object in
447             # the parent. This is really a standard unix fork/exec issue, but
448             # the library really doesn't help us.
449             my $result = $pipe->reader(@tr_args);
450              
451             open(STDERR, ">& SAVESTDERR");
452             close(SAVESTDERR);
453              
454             # XXX We're going to assume that an eof right after fork/exec is
455             # actually a failure. This is quite dubious.
456             if($result->eof) {
457             die "No output from traceroute. Exec failure?";
458             }
459              
460             $result;
461             }
462              
463             # Return the name of the traceroute executable itself
464             sub _tr_program_name {
465             my $self = shift;
466             my @args; # collector of arguments
467             my $os=$Config{'osname'};
468             my $prg_sw;
469              
470             OSNAMESW: {
471             # here comes Solaris
472             if ($os = ~ /solaris/) {
473             push (@args, "traceroute");
474             push (@args, "-A");
475             $prg_sw = ($self->af == PF_INET6) ? "inet6" : "inet";
476             push (@args, $prg_sw);
477             last OSNAMESW;
478             }
479             # here comes AIX
480             #
481             # here comes Tru64 UNIX
482             #
483             # here comes W2K
484             #
485             # for the rest we assume traceroute6/traceroute
486            
487             $prg_sw = ($self->af == PF_INET6) ? "traceroute6" : "traceroute";
488             push (@args, $prg_sw);
489             last OSNAMESW;
490             }
491              
492             @args;
493             }
494              
495             # How to map some of the instance variables to command line arguments
496             my %cmdline_map = ("base_port" => "-p",
497             "max_ttl" => "-m",
498             "queries" => "-q",
499             "query_timeout" => "-w");
500              
501             # Build a list of command line arguments
502             sub _tr_cmd_args {
503             my $self = shift;
504              
505             my @result;
506              
507             push(@result, "-n");
508              
509             my($key, $flag);
510             while(($key, $flag) = each %cmdline_map) {
511             my $val = $self->$key();
512             if(defined $val) {
513             push(@result, $flag, $val);
514             }
515             }
516              
517             @result;
518             }
519              
520             # Map ! notation traceroute uses for various icmp packet types
521             # it may receive.
522             my %icmp_map = (N => TRACEROUTE_UNREACH_NET,
523             H => TRACEROUTE_UNREACH_HOST,
524             P => TRACEROUTE_UNREACH_PROTO,
525             F => TRACEROUTE_UNREACH_NEEDFRAG,
526             S => TRACEROUTE_UNREACH_SRCFAIL,
527             A => TRACEROUTE_UNREACH_ADDR,
528             X => TRACEROUTE_UNREACH_FILTER_PROHIB);
529              
530             # Do the grunt work of parsing the output.
531             sub _parse {
532             my $self = shift;
533             my $tr_output = shift;
534              
535             ttl:
536             foreach $_ (split(/\n/, $tr_output)) {
537              
538             # Some traceroutes appear to print informational line to stdout,
539             # and we don't care.
540             /^traceroute to / && next;
541              
542             # Each line starts with the ttl (space padded to two characters)
543             # and a space.
544             /^([0-9 ][0-9]) / || die "Unable to traceroute output: $_";
545             my $ttl = $1 + 0;
546              
547             my $query = 1;
548             my $addr;
549             my $time;
550              
551             $_ = substr($_,length($&));
552              
553             # Munch through the line
554             query:
555             while($_) {
556             # ip address of a response
557             /^ (\d+\.\d+\.\d+\.\d+)/ && do {
558             $addr = $1;
559             $_ = substr($_, length($&));
560             next query;
561             };
562             # ipv6 address of a response
563             /^ ([0-9a-fA-F:]+)/ && do {
564             $addr = $1;
565             $_ = substr($_, length($&));
566             next query;
567             };
568             # round trip time of query
569             /^ ([0-9.]+) ms/ && do {
570             $time = $1 + 0;
571              
572             $self->_add_hop_query($ttl, $query,
573             TRACEROUTE_OK, $addr, $time);
574             $query++;
575             $_ = substr($_, length($&));
576             next query;
577             };
578             # query timed out
579             /^ +\*/ && do {
580             $self->_add_hop_query($ttl, $query,
581             TRACEROUTE_TIMEOUT,
582             inet_ntoa(INADDR_NONE), 0);
583             $query++;
584             $_ = substr($_, length($&));
585             next query;
586             };
587             # extra information from the probe (random ICMP info
588             # and such).
589             /^ (![NHPFSAX]?|!<\d+>)/ && do {
590             my $flag = $1;
591             my $matchlen = length($&);
592              
593             # Flip the counter back one; this flag only appears
594             # optionally and by now we've already incremented the
595             # query counter.
596             my $query = $query - 1;
597              
598             if($flag =~ /^!<\d>$/) {
599             $self->_change_hop_query_stat($ttl, $query,
600             TRACEROUTE_UNKNOWN);
601             } elsif($flag =~ /^!$/) {
602             $self->_change_hop_query_stat($ttl, $query,
603             TRACEROUTE_BSDBUG);
604             } elsif($flag =~ /^!([NHPFSAX])$/) {
605             my $icmp = $1;
606              
607             # Shouldn't happen
608             die "Unable to traceroute output (flag $icmp)!"
609             unless(defined($icmp_map{$icmp}));
610              
611             $self->_change_hop_query_stat($ttl, $query,
612             $icmp_map{$icmp});
613             }
614             $_ = substr($_, $matchlen);
615             next query;
616             };
617             # Nothing left, next line.
618             /^$/ && do {
619             next ttl;
620             };
621             # Some LBL derived traceroutes print ttl stuff
622             /^ \(ttl ?= ?\d+!\)/ && do {
623             $_ = substr($_, length($&));
624             next query;
625             };
626              
627             die "Unable to parse traceroute output: $_";
628             }
629             }
630             }
631              
632             sub _text_accumulator {
633             my $self = shift;
634             my $elem = "_text_accumulator";
635              
636             my $old = $self->{$elem};
637             $self->{$elem} .= $_[0] if @_;
638             return $old;
639             }
640              
641             sub _zero_text_accumulator {
642             my $self = shift;
643             my $elem = "_text_accumulator";
644              
645             delete $self->{$elem};
646             }
647              
648             # Hop stuff
649             sub _zero_hops {
650             my $self = shift;
651              
652             delete $self->{"hops"};
653             }
654              
655             sub _add_hop_query {
656             my $self = shift;
657              
658             my $hop = (shift) - 1;
659             my $query = (shift) - 1;
660              
661             my $stat = shift;
662             my $host = shift;
663             my $time = shift;
664              
665             $self->{"hops"}->[$hop]->[$query] = [ $stat, $host, $time ];
666             }
667              
668             sub _change_hop_query_stat {
669             my $self = shift;
670              
671             # Zero base these
672             my $hop = (shift) - 1;
673             my $query = (shift) - 1;
674              
675             my $stat = shift;
676              
677             $self->{"hops"}->[$hop]->[$query]->[ $query_stat_offset ] = $stat;
678             }
679              
680             sub _query_accessor_common {
681             my $self = shift;
682              
683             # Zero base these
684             my $hop = (shift) - 1;
685             my $query = (shift) - 1;
686              
687             my $which_one = shift;
688              
689             # Deal with wildcard
690             if($query == -1) {
691             my $query_stat;
692              
693             my $aref;
694             query:
695             foreach $aref (@{$self->{"hops"}->[$hop]}) {
696             $query_stat = $aref->[$query_stat_offset];
697             $query_stat == TRACEROUTE_TIMEOUT && do { next query };
698             $query_stat == TRACEROUTE_UNKNOWN && do { next query };
699             do { return $aref->[$which_one] };
700             }
701             return undef;
702             } else {
703             $self->{"hops"}->[$hop]->[$query]->[$which_one];
704             }
705             }
706              
707             sub debug_print {
708             my $self = shift;
709             my $level = shift;
710             my $fmtstring = shift;
711              
712             return unless $self->debug() >= $level;
713              
714             my($package, $filename, $line, $subroutine,
715             $hasargs, $wantarray, $evaltext, $is_require) = caller(0);
716              
717             my $caller_line = $line;
718             my $caller_name = $subroutine;
719             my $caller_file = $filename;
720              
721             my $string = sprintf($fmtstring, @_);
722              
723             my $caller = "${caller_file}:${caller_name}:${caller_line}";
724              
725             print STDERR "$caller: $string";
726             }
727              
728             1;
729              
730             __END__