File Coverage

blib/lib/Net/Traceroute/PurePerl.pm
Criterion Covered Total %
statement 313 370 84.5
branch 94 164 57.3
condition 47 105 44.7
subroutine 42 44 95.4
pod 2 3 66.6
total 498 686 72.5


line stmt bran cond sub pod time code
1             package Net::Traceroute::PurePerl;
2              
3 1     1   917 use vars qw(@ISA $VERSION $AUTOLOAD %net_traceroute_native_var %protocols);
  1         2  
  1         107  
4 1     1   6 use strict;
  1         1  
  1         32  
5 1     1   5 use warnings;
  1         5  
  1         25  
6 1     1   1158 use Net::Traceroute;
  1         43809  
  1         139  
7 1     1   13 use Socket;
  1         2  
  1         757  
8 1     1   1220 use FileHandle;
  1         5064  
  1         7  
9 1     1   536 use Carp qw(carp croak);
  1         3  
  1         66  
10 1     1   7 use Time::HiRes qw(time);
  1         2  
  1         11  
11              
12             @ISA = qw(Net::Traceroute);
13             $VERSION = '0.10';
14              
15             # Constants from header files or RFCs
16              
17 1     1   150 use constant SO_BINDTODEVICE => 25; # from asm/socket.h
  1         2  
  1         71  
18 1     1   6 use constant IPPROTO_IP => 0; # from netinet/in.h
  1         3  
  1         70  
19              
20             # Windows winsock2 uses 4 for IP_TTL instead of 2
21 1 50   1   5 use constant IP_TTL => ($^O eq "MSWin32") ? 4 : 2;
  1         2  
  1         80  
22              
23 1     1   7 use constant IP_HEADERS => 20; # Length of IP headers
  1         2  
  1         67  
24 1     1   6 use constant ICMP_HEADERS => 8; # Length of ICMP headers
  1         2  
  1         57  
25 1     1   19 use constant UDP_HEADERS => 8; # Length of UDP headers
  1         3  
  1         43  
26              
27 1     1   5 use constant IP_PROTOCOL => 9; # Position of protocol number
  1         2  
  1         70  
28              
29 1     1   5 use constant UDP_DATA => IP_HEADERS + UDP_HEADERS;
  1         8  
  1         64  
30 1     1   5 use constant ICMP_DATA => IP_HEADERS + ICMP_HEADERS;
  1         12  
  1         52  
31              
32 1     1   5 use constant UDP_SPORT => IP_HEADERS + 0; # Position of sport
  1         2  
  1         49  
33 1     1   4 use constant UDP_DPORT => IP_HEADERS + 2; # Position of dport
  1         2  
  1         53  
34              
35 1     1   5 use constant ICMP_TYPE => IP_HEADERS + 0; # Position of type
  1         1  
  1         55  
36 1     1   5 use constant ICMP_CODE => IP_HEADERS + 2; # Position of code
  1         1  
  1         52  
37 1     1   5 use constant ICMP_ID => IP_HEADERS + 4; # Position of ID
  1         2  
  1         46  
38 1     1   4 use constant ICMP_SEQ => IP_HEADERS + 6; # Position of seq
  1         1  
  1         47  
39              
40 1     1   4 use constant ICMP_PORT => 0; # ICMP has no port
  1         1  
  1         46  
41              
42 1     1   5 use constant ICMP_TYPE_TIMEEXCEED => 11; # ICMP Type
  1         2  
  1         45  
43 1     1   6 use constant ICMP_TYPE_ECHO => 8; # ICMP Type
  1         1  
  1         43  
44 1     1   5 use constant ICMP_TYPE_UNREACHABLE => 3; # ICMP Type
  1         2  
  1         43  
45 1     1   5 use constant ICMP_TYPE_ECHOREPLY => 0; # ICMP Type
  1         1  
  1         39  
46              
47 1     1   5 use constant ICMP_CODE_ECHO => 0; # ICMP Echo has no code
  1         2  
  1         82  
48              
49             # Perl 5.8.6 under Windows has a bug in the socket code, this env variable
50             # works around the bug. It may effect other versions as well, and they should
51             # be added here
52             BEGIN
53             {
54 1 50 33 1   4250 if ($^O eq "MSWin32" and $^V eq v5.8.6)
55             {
56 0         0 $ENV{PERL_ALLOW_NON_IFS_LSP} = 1;
57             }
58             }
59              
60             # The list of currently accepted protocols
61             %protocols =
62             (
63             'icmp' => 1,
64             'udp' => 1,
65             );
66              
67             my @icmp_unreach_code =
68             (
69             TRACEROUTE_UNREACH_NET,
70             TRACEROUTE_UNREACH_HOST,
71             TRACEROUTE_UNREACH_PROTO,
72             0,
73             TRACEROUTE_UNREACH_NEEDFRAG,
74             TRACEROUTE_UNREACH_SRCFAIL,
75             );
76              
77             # set up allowed autoload attributes we need
78             my @net_traceroute_native_vars = qw(use_alarm concurrent_hops protocol
79             first_hop device);
80              
81             @net_traceroute_native_var{@net_traceroute_native_vars} = ();
82              
83             # Methods
84              
85             # AUTOLOAD (perl internal)
86             # Used to create the methods for the object dynamically from
87             # net_traceroute_naive_vars.
88             sub AUTOLOAD
89             {
90 1034     1034   4679 my $self = shift;
91 1034         1678 my $attr = $AUTOLOAD;
92 1034         6384 $attr =~ s/.*:://;
93 1034 50       5552 return unless $attr =~ /[^A-Z]/; # skip DESTROY and all-cap methods
94 1034 50       2989 carp "invalid attribute method: ->$attr()"
95             unless exists $net_traceroute_native_var{$attr};
96 1034 100       2566 $self->{$attr} = shift if @_;
97 1034         7005 return $self->{$attr};
98             }
99              
100             # new (public method)
101             # Creates a new blessed object of type Net::Traceroute::PurePerl.
102             # Accepts many options as arguments, and initilizes the new object with
103             # their values.
104             # Croaks on bad arguments.
105             sub new
106             {
107 1     1 0 20 my $self = shift;
108 1   33     9 my $type = ref($self) || $self;
109 1         15 my %arg = @_;
110              
111 1         4 $self = bless {}, $type;
112              
113             # keep a loop from happening when calling super::new
114 1         3 my $backend = delete $arg{'backend'};
115              
116             # used to get around the real traceroute running the trace
117 1         2 my $host = delete $arg{'host'};
118              
119             # Old method to use ICMP for traceroutes, using `protocol' is preferred
120 1         3 my $useicmp = delete $arg{'useicmp'};
121              
122 1 50       4 $self->debug_print(1,
123             "The useicmp parameter is depreciated, use `protocol'\n") if ($useicmp);
124              
125             # Initialize blessed hash with passed arguments
126 1         9 $self->_init(%arg);
127              
128             # Set protocol to ICMP if useicmp was set;
129 1 50       531 if ($useicmp)
130             {
131 0 0 0     0 carp ("Protocol already set, useicmp is overriding")
132             if (defined $self->protocol and $self->protocol ne "icmp");
133 0 0       0 $self->protocol('icmp') if ($useicmp);
134             }
135              
136             # put our host back in and set defaults for undefined options
137 1 50       7 $self->host($host) if (defined $host);
138 1 50       8 $self->max_ttl(30) unless (defined $self->max_ttl);
139 1 50       11 $self->queries(3) unless (defined $self->queries);
140 1 50       10 $self->base_port(33434) unless (defined $self->base_port);
141 1 50       9 $self->query_timeout(5) unless (defined $self->query_timeout);
142 1 50       15 $self->packetlen(40) unless (defined $self->packetlen);
143 1 50       12 $self->first_hop(1) unless (defined $self->first_hop);
144 1 50       6 $self->concurrent_hops(6) unless (defined $self->concurrent_hops);
145            
146             # UDP is the UNIX default for traceroute
147 1 50       4 $self->protocol('udp') unless (defined $self->protocol);
148              
149             # Depreciated: we no longer use libpcap, so the alarm is no longer
150             # required. Kept for backwards compatibility but not used.
151 1 50       9 $self->use_alarm(0) unless (defined $self->use_alarm);
152              
153             # Validates all of the parameters.
154 1         6 $self->_validate();
155            
156 1         5 return $self;
157             }
158              
159             # _init (private initialization method)
160             # Overrides Net::Traceroutes init to set PurePerl specific parameters.
161             sub _init
162             {
163 1     1   2 my $self = shift;
164 1         6 my %arg = @_;
165              
166 1         7 foreach my $var (@net_traceroute_native_vars)
167             {
168 5 100       18 if(defined($arg{$var})) {
169 3         31 $self->$var($arg{$var});
170             }
171             }
172              
173 1         11 $self->SUPER::init(@_);
174             }
175              
176             # pretty_print (public method)
177             # The output of pretty_print tries to match the output of traceroute(1) as
178             # close as possible, with two excpetions. First, I cleaned up the columns to
179             # make it easier to read, and second, I start a new line if the host IP changes
180             # instead of printing the new IP inline. The first column stays the same hop
181             # number, only the host changes.
182             sub pretty_print
183             {
184 0     0 1 0 my $self = shift;
185 0         0 my $resolve = shift;
186              
187 0         0 print "traceroute to " . $self->host;
188 0         0 print " (" . inet_ntoa($self->{'_destination'}) . "), ";
189 0         0 print $self->max_ttl . " hops max, " . $self->packetlen ." byte packets\n";
190              
191 0         0 my $lasthop = $self->first_hop + $self->hops - 1;
192            
193 0         0 for (my $hop=$self->first_hop; $hop <= $lasthop; $hop++)
194             {
195 0         0 my $lasthost = '';
196              
197 0         0 printf '%2s ', $hop;
198              
199 0 0       0 if (not $self->hop_queries($hop))
200             {
201 0         0 print "error: no responses\n";
202 0         0 next;
203             }
204              
205 0         0 for (my $query=1; $query <= $self->hop_queries($hop); $query++) {
206 0         0 my $host = $self->hop_query_host($hop,$query);
207 0 0 0     0 if ($host and $resolve)
208             {
209 0         0 my $ip = $host;
210 0   0     0 $host = (gethostbyaddr(inet_aton($ip),AF_INET))[0] || $ip;
211             }
212 0 0 0     0 if ($host and ( not $lasthost or $host ne $lasthost ))
      0        
213             {
214 0 0 0     0 printf "\n%2s ", $hop if ($lasthost and $host ne $lasthost);
215 0         0 printf '%-15s ', $host;
216 0         0 $lasthost = $host;
217             }
218 0         0 my $time = $self->hop_query_time($hop, $query);
219 0 0 0     0 if (defined $time and $time > 0)
220             {
221 0         0 printf '%7s ms ', $time;
222             }
223             else
224             {
225 0         0 print "* ";
226             }
227             }
228              
229 0         0 print "\n";
230             }
231              
232 0         0 return;
233             }
234              
235             # traceroute (public method)
236             # Starts a new traceroute. This is a blocking call and it will either croak on
237             # error, or return 0 if the host wasn't reached, or 1 if it was.
238             sub traceroute
239             {
240 4     4 1 1976 my $self = shift;
241              
242             # Revalidate parameters incase they were changed by calling $t->parameter()
243             # since the object was created.
244 4         13 $self->_validate();
245              
246 3 50       13 carp "No host provided!" && return undef unless (defined $self->host);
247            
248 3         31 $self->debug_print(1, "Performing traceroute\n");
249              
250             # Lookup the destination IP inside of a local scope
251             {
252 3         29 my $destination = inet_aton($self->host);
  3         10  
253            
254 3 100       57762 croak "Could not resolve host " . $self->host
255             unless (defined $destination);
256              
257 2         16 $self->{_destination} = $destination;
258             }
259            
260             # release any old hop structure
261 2         36 $self->_zero_hops();
262              
263             # Create the ICMP socket, used to send ICMP messages and receive ICMP errors
264             # Under windows, the ICMP socket doesn't get the ICMP errors unless the
265             # sending socket was ICMP, or the interface is in promiscuous mode, which
266             # is why ICMP is the only supported protocol under windows.
267 2         81 my $icmpsocket = FileHandle->new();
268              
269 2 50       1522 socket($icmpsocket, PF_INET, SOCK_RAW, getprotobyname('icmp')) ||
270             croak("ICMP Socket error - $!");
271              
272 2         18 $self->debug_print(2, "Created ICMP socket to receive errors\n");
273              
274 2         45 $self->{'_icmp_socket'} = $icmpsocket;
275 2         33 $self->{'_trace_socket'} = $self->_create_tracert_socket();
276              
277             # _run_traceroute is the event loop that actually does the work.
278 2         25 my $success = $self->_run_traceroute();
279              
280 2         10 return $success;
281             }
282              
283             # Private methods
284              
285             # _validate (private method)
286             # Normalizes and validates all parameters, croaks on error
287             sub _validate
288             {
289 5     5   9 my $self = shift;
290              
291             # Normalize values;
292              
293 5         24 $self->protocol( lc $self->protocol);
294              
295 5         22 $self->max_ttl( sprintf('%i',$self->max_ttl));
296 5         82 $self->queries( sprintf('%i',$self->queries));
297 5         67 $self->base_port( sprintf('%i',$self->base_port));
298 5         78 $self->query_timeout( sprintf('%i',$self->query_timeout));
299 5         65 $self->packetlen( sprintf('%i',$self->packetlen));
300 5         74 $self->first_hop( sprintf('%i',$self->first_hop));
301 5         26 $self->concurrent_hops( sprintf('%i',$self->concurrent_hops));
302              
303             # Check to see if values are sane
304              
305 5 100       24 croak "Parameter `protocol' value is not supported : " . $self->protocol
306             if (not exists $protocols{$self->protocol});
307              
308 4 50 33     16 croak "Parameter `first_hop' must be an integer between 1 and 255"
309             if ($self->first_hop < 1 or $self->first_hop > 255);
310              
311 4 50 33     22 croak "Parameter `max_ttl' must be an integer between 1 and 255"
312             if ($self->max_ttl < 1 or $self->max_ttl > 255);
313              
314 4 50 33     73 croak "Parameter `base_port' must be an integer between 1 and 65280"
315             if ($self->base_port < 1 or $self->base_port > 65280);
316              
317 4 50 33     79 croak "Parameter `packetlen' must be an integer between 40 and 1492"
318             if ($self->packetlen < 40 or $self->packetlen > 1492);
319              
320 4 50       77 croak "Parameter `first_hop' must be less than or equal to `max_ttl'"
321             if ($self->first_hop > $self->max_ttl);
322              
323 4 50 33     36 croak "parameter `queries' must be an interger between 1 and 255"
324             if ($self->queries < 1 or $self->queries > 255);
325            
326 4 50 33     75 croak "parameter `concurrent_hops' must be an interger between 1 and 255"
327             if ($self->concurrent_hops < 1 or $self->concurrent_hops > 255);
328              
329 4 50 66     18 croak "protocol " . $self->protocol . " not supported under Windows"
330             if ($self->protocol ne 'icmp' and $^O eq 'MSWin32');
331              
332 4         8 return;
333             }
334              
335             # _run_traceroute (private method)
336             # The heart of the traceroute method. Sends out packets with incrementing
337             # ttls per hop. Recieves responses, validates them, and updates the hops
338             # hash with the time and host. Processes timeouts and returns when the host
339             # is reached, or the last packet on the last hop sent has been received
340             # or has timed out. Returns 1 if the host was reached, or 0.
341             sub _run_traceroute
342             {
343 2     2   5 my $self = shift;
344              
345 2         4 my ( $end, # Counter for endhop to wait until all queries return
346             $endhop, # The hop that the host was reached on
347             $stop, # Tells the main loop to exit
348             $sentpackets, # Number of packets sent
349             $currenthop, # Current hop
350             $currentquery, # Current query within the hop
351             $nexttimeout, # Next time a packet will timeout
352             $rbits, # select() bits
353             $nfound, # Number of ready sockets from select()
354             %packets, # Hash of packets sent but without a response
355             %pktids, # Hash of packet port or seq numbers to packet ids
356             );
357              
358 2         5 $stop = $end = $endhop = $sentpackets = 0;
359              
360 2         6 %packets = ();
361 2         4 %pktids = ();
362              
363 2         10 $currenthop = $self->first_hop;
364 2         3 $currentquery = 0;
365              
366 2         5 $rbits = "";
367 2         13 vec($rbits,$self->{'_icmp_socket'}->fileno(), 1) = 1;
368              
369 2         25 while (not $stop)
370             {
371             # Reset the variable
372 134         10288 $nfound = 0;
373              
374             # Send packets so long as there are packets to send, there is less than
375             # conncurrent_hops packets currently outstanding, there is no packets
376             # waiting to be read on the socket and we haven't reached the host yet.
377 134   100     1741 while (scalar keys %packets < $self->concurrent_hops and
      66        
      66        
      66        
378             $currenthop <= $self->max_ttl and
379             (not $endhop or $currenthop <= $endhop) and
380             not $nfound = select((my $rout = $rbits),undef,undef,0))
381             {
382             # sentpackets is used as an uid in the %packets hash.
383 90         3404 $sentpackets++;
384              
385 90         409 $self->debug_print(1,"Sending packet $currenthop $currentquery\n");
386 90         1419 my $start_time = $self->_send_packet($currenthop,$currentquery);
387 90         177 my $id = $self->{'_last_id'};
388 90         378 my $localport = $self->{'_local_port'};
389              
390 90         378 $packets{$sentpackets} =
391             {
392             'id' => $id,
393             'hop' => $currenthop,
394             'query' => $currentquery,
395             'localport' => $localport,
396             'starttime' => $start_time,
397             'timeout' => $start_time+$self->query_timeout,
398             };
399              
400 90         1432 $pktids{$id} = $sentpackets;
401              
402 90 100       220 $nexttimeout = $packets{$sentpackets}{'timeout'}
403             unless ($nexttimeout);
404              
405             # Current query and current hop increments
406 90         283 $currentquery = ($currentquery + 1) % $self->queries;
407 90 100       1388 if ($currentquery == 0)
408             {
409 30         195 $currenthop++;
410             }
411             }
412              
413             # If $nfound is nonzero than data is waiting to be read, no need to
414             # call select again.
415 134 100       1452 if (not $nfound) # No data waiting to be read yet
416             {
417             # This sets the timeout for select to no more than .1 seconds
418 121         919 my $timeout = $nexttimeout - time;
419 121 100       495 $timeout = .1 if ($timeout > .1);
420 121         8992116 $nfound = select((my $rout = $rbits),undef,undef,$timeout);
421             }
422              
423             # While data is waiting to be read, read it.
424 134   66     2337 while ($nfound and keys %packets)
425             {
426 75         108 my ( $recv_msg, # The packet read by recv()
427             $from_saddr, # The saddr returned by recv()
428             $from_port, # The port the packet came from
429             $from_ip, # The IP the packet came from
430             $from_id, # The dport / seq of the received packet
431             $from_proto, # The protocol of the packet
432             $from_type, # The ICMP type of the packet
433             $from_code, # The ICMP code of the packet
434             $icmp_data, # The data portion of the ICMP packet
435             $local_port, # The local port the packet is a reply to
436             $end_time, # The time the packet arrived
437             $last_hop, # Set to 1 if this packet came from the host
438             );
439              
440 75         396 $end_time = time;
441              
442 75         1997 $from_saddr = recv($self->{'_icmp_socket'},$recv_msg,1500,0);
443 75 50       205 if (defined $from_saddr)
444             {
445 75         315 ($from_port,$from_ip) = sockaddr_in($from_saddr);
446 75         1352 $from_ip = inet_ntoa($from_ip);
447 75         794 $self->debug_print(1,"Received packet from $from_ip\n");
448             }
449             else
450             {
451 0         0 $self->debug_print(1,"No packet?\n");
452 0         0 $nfound = 0;
453 0         0 last;
454             }
455              
456 75         1728 $from_proto = unpack('C',substr($recv_msg,IP_PROTOCOL,1));
457              
458 75 50       9522 if ($from_proto != getprotobyname('icmp'))
459             {
460 0         0 my $protoname = getprotobynumber($from_proto);
461 0         0 $self->debug_print(1,"Packet not ICMP $from_proto($protoname)\n");
462 0         0 last;
463             }
464              
465 75         295 ($from_type,$from_code) = unpack('CC',substr($recv_msg,ICMP_TYPE,2));
466 75         168 $icmp_data = substr($recv_msg,ICMP_DATA);
467              
468 75 50       204 if (not $icmp_data)
469             {
470 0         0 $self->debug_print(1,
471             "No data in packet ($from_type,$from_code)\n");
472 0         0 last;
473             }
474              
475             # TODO This code does not decode ICMP codes, only ICMP types, which can lead
476             # to false results if a router sends, for instance, a Network Unreachable
477             # or Fragmentation Needed packet.
478 75 50 100     265 if ( $from_type == ICMP_TYPE_TIMEEXCEED or
      33        
      66        
479             $from_type == ICMP_TYPE_UNREACHABLE or
480             ($self->protocol eq "icmp" and
481             $from_type == ICMP_TYPE_ECHOREPLY) )
482             {
483              
484 75 100       1663 if ($self->protocol eq 'udp')
    50          
485             {
486             # The local port is used to verify the packet was sent from
487             # This process.
488 36         112 $local_port = unpack('n',substr($icmp_data,UDP_SPORT,2));
489              
490             # The ID for UDP is the destination port number of the packet
491 36         80 $from_id = unpack('n',substr($icmp_data,UDP_DPORT,2));
492              
493             # The target system will send ICMP port unreachable, routers
494             # along the path will send ICMP Time Exceeded messages.
495 36 100       114 $last_hop = ($from_type == ICMP_TYPE_UNREACHABLE) ? 1 : 0;
496             }
497             elsif ($self->protocol eq 'icmp')
498             {
499 39 100       76 if ($from_type == ICMP_TYPE_ECHOREPLY)
500             {
501             # The ICMP ID is used to verify the packet was sent from
502             # this process.
503 3         7 my $icmp_id = unpack('n',substr($recv_msg,ICMP_ID,2));
504 3 50       10 last unless ($icmp_id == $$);
505              
506 3         7 my $seq = unpack('n',substr($recv_msg,ICMP_SEQ,2));
507 3         4 $from_id = $seq; # The ID for ICMP is the seq number
508 3         5 $last_hop = 1;;
509             }
510             else
511             {
512             # The ICMP ID is used to verify the packet was sent from
513             # this process.
514 36         179 my $icmp_id = unpack('n',substr($icmp_data,ICMP_ID,2));
515 36 50       117 last unless ($icmp_id == $$);
516              
517 36         69 my $ptype = unpack('C',substr($icmp_data,ICMP_TYPE,1));
518 36         143 my $pseq = unpack('n',substr($icmp_data,ICMP_SEQ,2));
519 36 50       85 if ($ptype eq ICMP_TYPE_ECHO)
520             {
521 36         66 $from_id = $pseq; # The ID for ICMP is the seq number
522             }
523             }
524             }
525             }
526              
527             # If we got and decoded the packet to get an ID, process it.
528 75 50 33     473 if ($from_ip and $from_id)
529             {
530 75         206 my $id = $pktids{$from_id};
531 75 50       166 if (not $id)
532             {
533 0         0 $self->debug_print(1,"No packet sent matches the reply\n");
534 0         0 last;
535             }
536 75 50       197 if (not exists $packets{$id})
537             {
538 0         0 $self->debug_print(1,"Packet $id received after ID deleted");
539 0         0 last;
540             }
541 75 50       523 if ($packets{$id}{'id'} == $from_id)
542             {
543 75 50 66     804 last if ($self->protocol eq 'udp' and
544             $packets{$id}{'localport'} != $local_port);
545              
546 75         207 my $total_time = $end_time - $packets{$id}{'starttime'};
547 75         171 my $hop = $packets{$id}{'hop'};
548 75         146 my $query = $packets{$id}{'query'};
549              
550 75 50 66     306 if (not $endhop or $hop <= $endhop)
551             {
552 75         1409 $self->debug_print(1,"Recieved response for $hop $query\n");
553 75         1614 $self->_add_hop_query($hop, $query+1, TRACEROUTE_OK,
554             $from_ip, sprintf("%.2f", 1000 * $total_time) );
555              
556             # Sometimes a route will change and last_hop won't be set
557             # causing the traceroute to hang. Therefore if hop = endhop
558             # we set $end to the number of query responses for the
559             # hop recieved so far.
560              
561 75 100 66     13663 if ($last_hop or ($endhop and $hop == $endhop))
      66        
562             {
563 6         35 $end = $self->hop_queries($hop);
564 6         57 $endhop = $hop;
565             }
566             }
567              
568             # No longer waiting for this packet
569 75         340 delete $packets{$id};
570             }
571             }
572             # Check if more data is waiting to be read, if so keep reading
573 75         883 $nfound = select((my $rout = $rbits),undef,undef,0);
574             }
575              
576             # Process timed out packets
577 134 100 100     2011 if (keys %packets and $nexttimeout < time)
578             {
579 14         30 undef $nexttimeout;
580            
581 14         111 foreach my $id (sort keys %packets)
582             {
583 28         73 my $hop = $packets{$id}{'hop'};
584            
585 28 100       152 if ($packets{$id}{'timeout'} < time)
    50          
586             {
587 15         33 my $query = $packets{$id}{'query'};
588              
589 15         110 $self->debug_print(1,"Timeout for $hop $query\n");
590 15         237 $self->_add_hop_query($hop, $query+1, TRACEROUTE_TIMEOUT,
591             "", 0 );
592              
593 15 50 66     256 if ($endhop and $hop == $endhop)
594             {
595             # Sometimes a route will change and last_hop won't be set
596             # causing the traceroute to hang. Therefore if hop = endhop
597             # we set $end to the number of query responses for the
598             # hop recieved so far.
599              
600 0         0 $end = $self->hop_queries($hop);
601             }
602            
603             # No longer waiting for this packet
604 15         72 delete $packets{$id};
605             }
606             elsif (not defined $nexttimeout)
607             {
608             # Reset next timeout to the next packet
609 13         21 $nexttimeout = $packets{$id}{'timeout'};
610 13         29 last;
611             }
612             }
613             }
614              
615             # Check if it is time to stop the looping
616 134 100 100     2073 if ($currenthop > $self->max_ttl and not keys %packets)
    100          
617             {
618 2         29 $self->debug_print(1,"No more packets, reached max_ttl\n");
619 2         25 $stop = 1;
620             }
621             elsif ($end >= $self->queries)
622             {
623             # Delete packets for hops after $endhop
624 32         1984 foreach my $id (sort keys %packets)
625             {
626 94         359 my $hop = $packets{$id}{'hop'};
627 94 50 33     834 if (not $hop or ( $endhop and $hop > $endhop) )
      33        
628             {
629             # No longer care about this packet
630 0         0 delete $packets{$id};
631             }
632             }
633 32 50       230 if (not keys %packets)
634             {
635 0         0 $self->debug_print(1,"Reached host on $endhop hop\n");
636 0         0 $end = 1;
637 0         0 $stop = 1;
638             }
639             }
640              
641             # Looping
642             }
643              
644 2         61 return $end;
645             }
646              
647             # _create_tracert_socket (private method)
648             # Reuses the ICMP socket already created for icmp traceroutes, or creates a
649             # new socket. It then binds the socket to the user defined device and/or
650             # source address if provided and returns the created socket.
651             sub _create_tracert_socket
652             {
653 2     2   5 my $self = shift;
654 2         7 my $socket;
655            
656 2 100       18 if ($self->protocol eq "icmp")
    50          
657             {
658 1         4 $socket = $self->{'_icmp_socket'};
659             }
660             elsif ($self->protocol eq "udp")
661             {
662 1         8 $socket = FileHandle->new();
663            
664 1 50       141 socket($socket, PF_INET, SOCK_DGRAM, getprotobyname('udp')) or
665             croak "UDP Socket creation error - $!";
666              
667 1         7 $self->debug_print(2,"Created UDP socket");
668             }
669              
670 2 50       31 if ($self->device)
671             {
672 0 0       0 setsockopt($socket, SOL_SOCKET, SO_BINDTODEVICE,
673             pack('Z*', $self->device)) or
674             croak "error binding to ". $self->device ." - $!";
675              
676 0         0 $self->debug_print(2,"Bound socket to ". $self->device ."\n");
677             }
678              
679 2 50 33     12 if ($self->source_address and $self->source_address ne '0.0.0.0')
680             {
681 0         0 $self->_bind($socket);
682             }
683              
684 2         53 return $socket;
685             }
686              
687             # _bind (private method)
688             # binds a sockets to a local address so all packets originate from that IP.
689             sub _bind
690             {
691 0     0   0 my $self = shift;
692 0         0 my $socket = shift;
693              
694 0         0 my $ip = inet_aton($self->source_address);
695              
696 0 0       0 croak "Nonexistant local address ". $self->source_address
697             unless (defined $ip);
698              
699 0 0       0 CORE::bind($socket, sockaddr_in(0,$ip)) or
700             croak "Error binding to ".$self->source_address.", $!";
701              
702 0         0 $self->debug_print(2,"Bound socket to " . $self->source_address . "\n");
703              
704 0         0 return;
705             }
706              
707             # _send_packet (private method)
708             # Sends the packet for $hop, $query to the destination. Actually calls
709             # submethods for the different protocols which create and send the packet.
710             sub _send_packet
711             {
712 90     90   115 my $self = shift;
713 90         242 my ($hop,$query) = @_;
714              
715 90 100       415 if ($self->protocol eq "icmp")
    50          
716             {
717             # Sequence ID for the ICMP echo request
718 45         133 my $seq = ($hop-1) * $self->queries + $query + 1;
719 45         418 $self->_send_icmp_packet($seq,$hop);
720 45         97 $self->{'_last_id'} = $seq;
721             }
722             elsif ($self->protocol eq "udp")
723             {
724             # Destination port for the UDP packet
725 45         6268 my $dport = $self->base_port + ($hop-1) * $self->queries + $query;
726 45         822 $self->_send_udp_packet($dport,$hop);
727 45         121 $self->{'_last_id'} = $dport;
728             }
729              
730 90         395 return time;
731             }
732              
733             # _send_icmp_packet (private method)
734             # Sends an ICMP packet with the given sequence number. The PID is used as
735             # the packet ID and $seq is the sequence number.
736             sub _send_icmp_packet
737             {
738 45     45   55 my $self = shift;
739 45         66 my ($seq,$hop) = @_;
740            
741             # Set TTL of socket to $hop.
742 45         104 my $saddr = $self->_connect(ICMP_PORT,$hop);
743 45         161 my $data = 'a' x ($self->packetlen - ICMP_DATA);
744              
745 45         332 my ($pkt, $chksum) = (0,0);
746              
747             # Create packet twice, once without checksum, once with it
748 45         93 foreach (1 .. 2)
749             {
750 90         385 $pkt = pack('CC n3 A*',
751             ICMP_TYPE_ECHO, # Type
752             ICMP_CODE_ECHO, # Code
753             $chksum, # Checksum
754             $$, # ID (pid)
755             $seq, # Sequence
756             $data, # Data
757             );
758            
759 90 100       634 $chksum = $self->_checksum($pkt) unless ($chksum);
760             }
761              
762 45         7186 send($self->{'_trace_socket'}, $pkt, 0, $saddr);
763              
764 45         152 return;
765             }
766              
767             # _send_udp_packet (private method)
768             # Sends a udp packet to the given destination port.
769             sub _send_udp_packet
770             {
771 45     45   70 my $self = shift;
772 45         74 my ($dport,$hop) = @_;
773            
774             # Connect socket to destination port and set TTL
775 45         121 my $saddr = $self->_connect($dport,$hop);
776 45         186 my $data = 'a' x ($self->packetlen - UDP_DATA);
777              
778 45         470 $self->_connect($dport,$hop);
779              
780 45         4580 send($self->{'_trace_socket'}, $data, 0);
781              
782 45         118 return;
783             }
784              
785             # _connect (private method)
786             # Connects the socket unless the protocol is ICMP and sets the TTL.
787             sub _connect
788             {
789 135     135   179 my $self = shift;
790 135         285 my ($port,$hop) = @_;
791              
792 135         499 my $socket_addr = sockaddr_in($port,$self->{_destination});
793            
794 135 100       1529 if ($self->protocol eq 'udp')
795             {
796 90         772 CORE::connect($self->{'_trace_socket'},$socket_addr);
797 90         579 $self->debug_print(2,"Connected to " . $self->host . "\n");
798             }
799              
800 135         20551 setsockopt($self->{'_trace_socket'}, IPPROTO_IP, IP_TTL, pack('C',$hop));
801 135         762 $self->debug_print(2,"Set TTL to $hop\n");
802              
803 135 100       1704 if ($self->protocol eq 'udp')
804             {
805 90         577 my $localaddr = getsockname($self->{'_trace_socket'});
806 90         408 my ($lport,undef) = sockaddr_in($localaddr);
807 90         896 $self->{'_local_port'} = $lport;
808             }
809              
810 135 100       898 return ($self->protocol eq 'icmp') ? $socket_addr : undef;
811             }
812              
813             # _checksum (private method)
814             # Lifted verbatum from Net::Ping 2.31
815             # Description: Do a checksum on the message. Basically sum all of
816             # the short words and fold the high order bits into the low order bits.
817             sub _checksum
818             {
819 45     45   65 my $self = shift;
820 45         51 my $msg = shift;
821              
822 45         47 my ( $len_msg, # Length of the message
823             $num_short, # The number of short words in the message
824             $short, # One short word
825             $chk # The checksum
826             );
827              
828 45         52 $len_msg = length($msg);
829 45         80 $num_short = int($len_msg / 2);
830 45         110 $chk = 0;
831 45         189 foreach $short (unpack("n$num_short", $msg))
832             {
833 450         549 $chk += $short;
834             } # Add the odd byte in
835 45 50       131 $chk += (unpack("C", substr($msg, $len_msg - 1, 1)) << 8) if $len_msg % 2;
836 45         63 $chk = ($chk >> 16) + ($chk & 0xffff); # Fold high into low
837 45         119 return(~(($chk >> 16) + $chk) & 0xffff); # Again and complement
838             }
839              
840             1;
841              
842             __END__