File Coverage

blib/lib/Traceroute/Similar.pm
Criterion Covered Total %
statement 65 124 52.4
branch 13 54 24.0
condition 5 24 20.8
subroutine 10 13 76.9
pod 4 4 100.0
total 97 219 44.2


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             #
3             # vim:ts=4:sw=4:expandtab
4              
5             package Traceroute::Similar;
6              
7 3     3   84641 use 5.000000;
  3         11  
  3         112  
8 3     3   16 use strict;
  3         6  
  3         107  
9 3     3   15 use warnings;
  3         20  
  3         144  
10 3     3   15 use Carp;
  3         5  
  3         5030  
11              
12             our $VERSION = '0.18';
13              
14             =head1 NAME
15              
16             Traceroute::Similar - calculate common route for a bunch of hosts
17              
18             =head1 SYNOPSIS
19              
20             use Traceroute::Similar;
21             my $ts = Traceroute::Similar->new();
22             print $ts->get_last_common_hop('host1.com', 'host2.org');
23              
24             =head1 DESCRIPTION
25              
26             This module calculates the furthest common hop from a list of host. The backend
27             will be Net::Traceroute:PurePerl or Net::Traceroute or system
28             tracerroute (which may require root or sudo permissions).
29              
30             =head1 CONSTRUCTOR
31              
32             =over 4
33              
34             =item new ( [ARGS] )
35              
36             Creates an C object. All arguments are optional.
37              
38             backend 'Net::Traceroute' or 'Net::Traceroute::PurePerl'
39             verbose verbose mode
40              
41             =back
42              
43             =cut
44              
45             ########################################
46             sub new {
47 3     3 1 951 my($class,%options) = @_;
48 3         14 my $self = {
49             "verbose" => 0,
50             "backend" => undef,
51             };
52 3         11 bless $self, $class;
53              
54 3 50       26 $self->{'verbose'} = $options{'verbose'} if defined $options{'verbose'};
55              
56             # which backend do we use?
57 3 50       13 $self->{'backend'} = $options{'backend'} if defined $options{'backend'};
58 3 50       24 $self->{'backend'} = $self->_detect_backend() unless defined $self->{'backend'};
59              
60 3 50       53 if(!defined $self->{'backend'}) {
61 3         1555 carp("No backend found, please install one of Net::Traceroute or Net::Traceroute::PurePerl. Or make sure your traceroute binary is in your path.");
62             }
63              
64 3         54 return $self;
65             }
66              
67              
68             ########################################
69              
70             =head1 METHODS
71              
72             =over 4
73              
74             =item get_backend ( )
75              
76             returns the used backend or undef if none found
77              
78             =cut
79              
80             sub get_backend {
81 2     2 1 2128 my $self = shift;
82 2         27 return($self->{'backend'});
83             }
84              
85             =item get_last_common_hop ( host 1, host 2, [ host x...] )
86              
87             return the last hop which is part of all given hosts
88              
89             =cut
90              
91             sub get_last_common_hop {
92 0     0 1 0 my $self = shift;
93 0 0       0 return if !defined $self->{'backend'};
94 0         0 my $routes;
95 0         0 while(my $host = shift) {
96 0         0 $routes->{$host} = $self->_get_route_for_host($host);
97             }
98              
99 0         0 return($self->_calculate_last_common_hop($routes))
100             }
101              
102              
103             ########################################
104              
105             =item get_common_hops ( host 1, host 2, [ host x...] )
106              
107             return an array ref of the common hops from this list of hosts
108              
109             =cut
110              
111             sub get_common_hops {
112 0     0 1 0 my $self = shift;
113 0 0       0 return if !defined $self->{'backend'};
114 0         0 my $routes;
115 0         0 while(my $host = shift) {
116 0         0 $routes->{$host} = $self->_get_route_for_host($host);
117             }
118              
119 0         0 return($self->_calculate_common_hops($routes))
120             }
121              
122              
123             ########################################
124             # internal subs
125             ########################################
126             sub _calculate_last_common_hop {
127 1     1   11 my $self = shift;
128 1         2 my $routes = shift;
129 1         2 my $last_common_addr;
130 1         7 my $common = $self->_calculate_common_hops($routes);
131              
132 1 50 33     14 if(defined $common and scalar @{$common} >= 1) {
  1         4  
133 1         1 $last_common_addr = pop @{$common};
  1         3  
134             }
135              
136 1         4 return($last_common_addr);
137             }
138              
139             ########################################
140             sub _calculate_common_hops {
141 2     2   882 my $self = shift;
142 2         4 my $routes = shift;
143 2         3 my $common;
144              
145 2 50       9 return if !defined $routes;
146              
147 2         5 my @hostnames = keys %{$routes};
  2         13  
148 2 50       8 if(scalar @hostnames <= 1) { croak("need at least 2 hosts to calculate similiar routes"); }
  0         0  
149              
150 2         3 my $last_common_addr = undef;
151 2         5 for(my $x = 0; $x <= scalar(@{$routes->{$hostnames[0]}}); $x++) {
  12         30  
152 12         38 my $current_hop = $routes->{$hostnames[0]}->[$x]->{'addr'};
153 12         17 for my $host (@hostnames) {
154 24 100 66     116 if(!defined $routes->{$host}->[$x]->{'addr'} or $current_hop ne $routes->{$host}->[$x]->{'addr'}) {
155 2         7 return $common;
156             }
157             }
158 10         15 $last_common_addr = $current_hop;
159 10         13 push @{$common}, $last_common_addr;
  10         22  
160             }
161              
162 0         0 return($common);
163             }
164              
165             ########################################
166             sub _get_route_for_host {
167 0     0   0 my $self = shift;
168 0         0 my $host = shift;
169 0         0 my $routes = [];
170              
171 0 0       0 print "DEBUG: _get_route_for_host('".$host."')\n" if $self->{'verbose'};
172              
173 0 0       0 if($self->{'backend'} eq 'traceroute') {
    0          
    0          
174 0         0 my $cmd = "traceroute $host";
175 0 0       0 print "DEBUG: cmd: $cmd\n" if $self->{'verbose'};
176 0 0       0 open(my $ph, "-|", "$cmd 2>&1") or confess("cmd failed: $!");
177 0         0 my $output;
178 0         0 while(<$ph>) {
179 0         0 my $line = $_;
180 0         0 $output .= $line;
181 0 0       0 print "DEBUG: traceroute: $line" if $self->{'verbose'};
182             }
183 0         0 close($ph);
184 0         0 my $rt = $?>>8;
185 0 0       0 print "DEBUG: return code from traceroute: $rt\n" if $self->{'verbose'};
186              
187 0 0       0 if($rt == 0) {
188 0         0 $routes = $self->_extract_routes_from_traceroute($output);
189             }
190             }
191             elsif($self->{'backend'} eq 'Net::Traceroute') {
192 0         0 my $tr = Net::Traceroute->new(host=> $host);
193 0         0 my $hops = $tr->hops;
194 0         0 my $last_hop;
195 0         0 for(my $x = 0; $x <= $hops; $x++) {
196 0         0 my $cur_hop = $tr->hop_query_host($x, 0);
197 0 0 0     0 if(defined $cur_hop and (!defined $last_hop or $last_hop ne $cur_hop)) {
      0        
198 0         0 push @{$routes}, { 'addr' => $cur_hop, 'name' => '' };
  0         0  
199 0         0 $last_hop = $cur_hop;
200             }
201             }
202             }
203             elsif($self->{'backend'} eq 'Net::Traceroute::PurePerl') {
204 0         0 my $tr = new Net::Traceroute::PurePerl( host => $host );
205 0         0 $tr->traceroute;
206 0         0 my $hops = $tr->hops;
207 0         0 my $last_hop;
208 0         0 for(my $x = 0; $x <= $hops; $x++) {
209 0         0 my $cur_hop = $tr->hop_query_host($x, 0);
210 0 0 0     0 if(defined $cur_hop and (!defined $last_hop or $last_hop ne $cur_hop)) {
      0        
211 0         0 push @{$routes}, { 'addr' => $cur_hop, 'name' => '' };
  0         0  
212 0         0 $last_hop = $cur_hop;
213             }
214             }
215             }
216             else {
217 0         0 carp("unknown backend: ".$self->{'backend'});
218             }
219              
220 0         0 return $routes;
221             }
222              
223             ########################################
224             sub _extract_routes_from_traceroute {
225 2     2   3959 my $self = shift;
226 2         5 my $output = shift;
227 2         3 my @routes;
228              
229 2         21 for my $line (split /\n/xm, $output) {
230 31 100       209 if($line =~ m/(\d+)\s+(.*?)\s+\((\d+\.\d+\.\d+\.\d+)\)/xm) {
231 19         107 push @routes, { 'addr' => $3, 'name' => $2 };
232             }
233             }
234              
235 2         15 return(\@routes);
236             }
237              
238             ########################################
239             sub _detect_backend {
240 3     3   8 my $self = shift;
241              
242 3 50       11 print "DEBUG: detecting backend\n" if $self->{'verbose'};
243              
244             # try to load Net::Traceroute:PurePerl
245 3         7 eval {
246 3         1285 require Net::Traceroute::PurePerl;
247 0 0       0 print "DEBUG: using Net::Traceroute::PurePerl as backend\n" if $self->{'verbose'};
248 0         0 return("Net::Traceroute::PurePerl");
249             };
250              
251             # try to load Net::Traceroute
252 3         13 eval {
253 3         1192 require Net::Traceroute;
254 0 0       0 print "DEBUG: using Net::Traceroute as backend\n" if $self->{'verbose'};
255 0         0 return("Net::Traceroute");
256             };
257              
258             # try to use traceroute
259 3         20660 chomp(my $traceroute_bin = qx{which traceroute});
260 3 50 33     132 if(defined $traceroute_bin and $traceroute_bin ne '' and -x $traceroute_bin) {
      33        
261 0 0       0 print "DEBUG: found traceroute in path: $traceroute_bin\n" if $self->{'verbose'};
262 0         0 return('traceroute');
263             }
264              
265 3         61 return;
266             }
267              
268             ########################################
269              
270             1;
271              
272             =head1 AUTHOR
273              
274             Sven Nierlein, Enierlein@cpan.orgE
275              
276             =head1 COPYRIGHT AND LICENSE
277              
278             Copyright (C) 2009 by Sven Nierlein
279              
280             This library is free software; you can redistribute it and/or modify
281             it under the same terms as Perl itself.
282              
283             =cut
284              
285             __END__