File Coverage

blib/lib/POE/Component/Client/Traceroute.pm
Criterion Covered Total %
statement 373 422 88.3
branch 104 200 52.0
condition 49 86 56.9
subroutine 45 47 95.7
pod 0 7 0.0
total 571 762 74.9


line stmt bran cond sub pod time code
1             package POE::Component::Client::Traceroute;
2              
3 1     1   283880 use warnings;
  1         4  
  1         52  
4 1     1   6 use strict;
  1         3  
  1         46  
5              
6 1     1   5 use vars qw($VERSION $debug $debug_socket);
  1         8  
  1         91  
7              
8             $VERSION = '0.21';
9              
10 1     1   5 use Carp qw(croak);
  1         2  
  1         82  
11 1     1   6 use Socket;
  1         11  
  1         1033  
12 1     1   33 use FileHandle;
  1         2  
  1         10  
13 1     1   1241 use Time::HiRes qw(time);
  1         4  
  1         10  
14              
15 1     1   453 use POE::Session;
  1         2  
  1         8  
16              
17             BEGIN
18             {
19 1 50 33 1   311 if ($^O eq "MSWin32" and $^V eq v5.8.6)
20             {
21 0         0 $ENV{PERL_ALLOW_NON_IFS_LSP} = 1;
22             }
23             }
24              
25             $debug = 0;
26             $debug_socket = 0;
27              
28 239     239 0 1421 sub DEBUG { return $debug } # Enable debug output.
29 123     123 0 1118 sub DEBUG_SOCKET { return $debug_socket } # Output socket information.
30              
31 1     1   6 use constant SO_BINDTODEVICE => 25; # from asm/socket.h
  1         2  
  1         83  
32 1     1   5 use constant IPPROTO_IP => 0; # from netinet/in.h
  1         1  
  1         167  
33              
34 74 50   74   1014 sub IP_TTL { return ($^O eq "MSWin32") ? 4 : 2 } # Winsock2 vs bits.h
35              
36 1     1   6 use constant IP_HEADERS => 20; # Length of IP headers
  1         2  
  1         338  
37 1     1   15 use constant ICMP_HEADERS => 8;
  1         3  
  1         53  
38 1     1   4 use constant UDP_HEADERS => 8;
  1         2  
  1         220  
39              
40 1     1   6 use constant IP_PROTOCOL => 9;
  1         2  
  1         86  
41              
42 1     1   7 use constant UDP_DATA => IP_HEADERS + UDP_HEADERS;
  1         2  
  1         65  
43 1     1   6 use constant ICMP_DATA => IP_HEADERS + ICMP_HEADERS;
  1         2  
  1         62  
44              
45 1     1   5 use constant UDP_SPORT => IP_HEADERS + 0;
  1         2  
  1         59  
46 1     1   6 use constant UDP_DPORT => IP_HEADERS + 2;
  1         2  
  1         53  
47              
48 1     1   5 use constant ICMP_TYPE => IP_HEADERS + 0;
  1         2  
  1         396  
49 1     1   8 use constant ICMP_CODE => IP_HEADERS + 2;
  1         1  
  1         71  
50 1     1   7 use constant ICMP_ID => IP_HEADERS + 4;
  1         2  
  1         63  
51 1     1   6 use constant ICMP_SEQ => IP_HEADERS + 6;
  1         2  
  1         53  
52              
53 1     1   5 use constant ICMP_PORT => 0;
  1         3  
  1         48  
54              
55 1     1   5 use constant ICMP_TIMEEXCEED => 11;
  1         1  
  1         53  
56 1     1   6 use constant ICMP_ECHO => 8;
  1         2  
  1         42  
57 1     1   5 use constant ICMP_UNREACHABLE => 3;
  1         2  
  1         62  
58 1     1   6 use constant ICMP_ECHOREPLY => 0;
  1         2  
  1         9581  
59              
60             # Spawn a new PoCo::Client::Traceroute session. This is the basic
61             # constructor, but it does not return an object. Instead it launches
62             # a new POE session.
63              
64             sub spawn
65             {
66 1     1 0 26 my $type = shift;
67              
68 1 50       6 croak "$type->spawn() requires an even number of parameters\n" if (@_ % 2);
69 1         3 my %params;
70              
71             # Force parameters to lower case to be nice to users
72 1         5 for (my $i=0; $i<@_; $i+=2)
73             {
74 14         58 $params{lc($_[$i])} = $_[$i+1];
75             }
76              
77 1   50     8 my $alias = delete $params{alias} || 'tracer';
78 1   50     6 my $firsthop = delete $params{firsthop} || 1;
79 1   50     6 my $maxttl = delete $params{maxttl} || 32;
80 1   50     6 my $timeout = delete $params{timeout} || undef;
81 1   50     5 my $qtimeout = delete $params{querytimeout} || 3;
82 1   50     5 my $queries = delete $params{queries} || 3;
83 1   50     6 my $baseport = delete $params{baseport} || 33434;
84 1   50     4 my $packetlen = delete $params{packetlen} || 68;
85 1   50     7 my $srcaddr = delete $params{sourceaddress} || undef;
86 1   50     9 my $device = delete $params{device} || undef;
87 1   50     7 my $perhop = delete $params{perhoppostback} || 0;
88 1   50     7 my $useicmp = delete $params{useicmp} || 0;
89              
90 1   33     6 $debug = delete $params{debug} || $debug;
91 1   33     6 $debug_socket = delete $params{debugsocket} || $debug_socket;
92              
93 1 50 33     27 if ($^O eq "MSWin32" and not $useicmp)
94             {
95 0 0       0 DEBUG and warn "TR: Windows version doesn't support UDP traceroute. " .
96             "Switching to ICMP\n";
97 0         0 $useicmp = 1;
98             }
99            
100             croak(
101 1 50       3 "$type doesn't know these parameters: ", join(', ', sort keys %params)
102             ) if %params;
103              
104 1 50       4 croak(
105             'FirstHop must be less than 255'
106             ) if ($firsthop > 255);
107            
108 1 50       5 croak(
109             'MaxTTL must be less than 255'
110             ) if ($maxttl > 255);
111              
112 1 50 33     11 croak(
113             'PacketLen can not be greater than 1492 or less than 68'
114             ) if ($packetlen > 1492 or $packetlen < 68);
115              
116             POE::Session->create(
117             inline_states => {
118             _start => \&tracer_start,
119 1     1   4972 _stop => sub { },
120 1         35 traceroute => \&tracer_traceroute,
121             shutdown => \&tracer_shutdown,
122             _start_traceroute => \&_start_traceroute,
123             _send_packet => \&_send_packet,
124             _recv_packet => \&_recv_packet,
125             _timeout => \&_timeout,
126             _default => \&tracer_default,
127             },
128             args => [
129             $alias, $firsthop, $maxttl, $timeout, $qtimeout, $queries,
130             $baseport, $packetlen, $srcaddr, $perhop, $useicmp, $device,
131             ],
132             );
133              
134 1         204 return;
135             }
136              
137             # Startup initialization method. Sets defaults from the spawn method
138             # and ties the alias to the session.
139             sub tracer_start
140             {
141             my (
142 1     1 0 431 $kernel, $heap,
143             $alias, $firsthop, $maxttl, $timeout, $qtimeout, $queries,
144             $baseport, $packetlen, $srcaddr, $perhop, $useicmp, $device,
145             ) = @_[ KERNEL, HEAP, ARG0..$#_ ];
146              
147 1 50       5 DEBUG and warn "PoCo::Client::Traceroute session $alias started\n";
148              
149 1         11 $heap->{defaults} = {
150             firsthop => $firsthop,
151             maxttl => $maxttl,
152             timeout => $timeout,
153             queries => $queries,
154             querytimeout => $qtimeout,
155             baseport => $baseport,
156             packetlen => $packetlen,
157             perhoppostback => $perhop,
158             useicmp => $useicmp,
159             sourceaddress => $srcaddr,
160             device => $device,
161             };
162              
163 1         1071 my $proto = getprotobyname('icmp');
164 1         15 my $socket = FileHandle->new();
165            
166 1 50       122 socket($socket, PF_INET, SOCK_RAW, $proto) ||
167             croak("ICMP Socket error - $!");
168            
169 1 50       4 DEBUG_SOCKET and warn "TRS: Created ICMP socket to receive errors\n";
170 1         3 $heap->{icmpsocket} = $socket;
171              
172 1         8 $kernel->select_read($socket, '_recv_packet');
173              
174 1         138 $heap->{alias} = $alias;
175 1         6 $kernel->alias_set($alias);
176              
177 1         45 return;
178             }
179              
180             # The traceroute state takes 2 required and one optional argument. The first
181             # is the event to post back to the sender, the second is the host to traceroute
182             # to, the last is an array ref with options to override the defaults.
183              
184             # The function verifies the options and returns an error if any are incorrect.
185             # It also starts the timer for the per traceroute timeout which is a safety
186             # net to keep the process from hanging if something goes wrong.
187             sub tracer_traceroute
188             {
189 2     2 0 905 my ($kernel, $heap, $sender, $event, $host, $useroptions) =
190             @_[ KERNEL, HEAP, SENDER, ARG0..ARG2 ];
191              
192 2 50       7 unless ($event)
193             {
194 0 0       0 if (DEBUG) { die "Postback state name required for traceroute\n" }
  0         0  
195 0         0 return;
196             }
197 2         3 my $error;
198              
199 2 50 50     8 DEBUG and warn "TR: Starting traceroute to $host\n" if ($host);
200 2 50       5 $error = "Host required for traceroute\n" unless ($host);
201              
202 2         3 my %options = %{$heap->{defaults}};
  2         26  
203 2         6 my $callback;
204              
205             # Allow user to override options for each traceroute request
206 2 50       8 if (ref $useroptions eq 'ARRAY')
    0          
207             {
208 2         6 my @useropts = @$useroptions;
209 2 50       7 $error = "traceroute useroptions requires an even number of parameters\n"
210             if (@useropts % 2);
211 2         4 my %uparams;
212              
213 2         8 for (my $i=0; $i<@useropts; $i+=2)
214             {
215 8         26 $uparams{lc($useropts[$i])} = $useropts[$i+1];
216             }
217              
218 2         3 $callback = delete $uparams{callback};
219              
220 2         9 foreach my $option (keys %options)
221             {
222 22 100       56 $options{$option} = delete $uparams{$option}
223             if (exists $uparams{$option});
224             }
225            
226 2 50       10 $error .= "traceroute doesn't know these parameters: " .
227             join(', ', sort keys %uparams) . "\n" if %uparams;
228             }
229             elsif (defined $useroptions)
230             {
231 0         0 $error .= "traceroute's third argument must be an array ref\n";
232             }
233              
234 2 50       6 $error .= "Baseport is too high, must be less than 65280\n"
235             if ($options{baseport} > 65279);
236              
237 2 50       6 $error .= "MaxTTL can not be higher than 255\n"
238             if ($options{maxttl} > 255);
239              
240 2 50       5 $error .= "FirstHop can not be higher than 255\n"
241             if ($options{firsthop} > 255);
242              
243 2 50       6 $error .= "FirstHop must be less than or equal to MaxTTL\n"
244             if ($options{firsthop} > $options{maxttl});
245              
246 2 50 33     15 $error .= "PacketLen can't be greater than 1492 or less than 68\n"
247             if ($options{packetlen} > 1492 or $options{packetlen} < 68);
248              
249 2         10 my $postback = $sender->postback( $event, $host, \%options, $callback );
250              
251 2 50       156 if ($error)
252             {
253 0 0       0 DEBUG and warn "Errors starting traceroute\n";
254 0         0 $postback->( _build_postback_options(undef, $error) );
255             }
256             else
257             {
258 2         5 my $trsessionid = ++$heap->{trsessionid};
259              
260 2         6 $heap->{sessions}{$trsessionid}{postback} = $postback;
261 2         5 $heap->{sessions}{$trsessionid}{options} = \%options;
262 2         7 $heap->{sessions}{$trsessionid}{host} = $host;
263              
264 2 100       8 if ($options{perhoppostback})
265             {
266 1         2 $heap->{sessions}{$trsessionid}{callback} = $callback;
267 1         178 $heap->{sessions}{$trsessionid}{sender} = $sender;
268             }
269              
270 2 50       6 if ($options{timeout})
271             {
272 2         10 my $alarm =
273             $kernel->delay_set('_timeout',$options{timeout},$trsessionid,1);
274              
275 2         144 $heap->{sessions}{$trsessionid}{timeout} = $alarm;
276             }
277              
278 2         8 $kernel->yield('_start_traceroute' => $trsessionid);
279             }
280              
281 2         160 return;
282             }
283              
284             # The shutdown state takes no parameters. It closes all open sockets,
285             # posts back data to waiting sessions and removes any alarms.
286             sub tracer_shutdown
287             {
288 1     1 0 1422 my ($kernel, $heap) = @_[ KERNEL, HEAP ];
289 1 50       2 DEBUG and warn "PoCo::Client::Traceroute session $heap->{alias} " .
290             "shutting down\n";
291              
292 1         7 $kernel->select_read($heap->{icmpsocket});
293 1         209 $kernel->alarm_remove_all();
294              
295 1         51 foreach my $trsessionid (keys %{$heap->{sessions}})
  1         5  
296             {
297 0         0 my $session = $heap->{sessions}{$trsessionid};
298              
299 0         0 my $error = "Traceroute session shutdown\n";
300              
301 0         0 $session->{postback}->(_build_postback_options($session,$error));
302 0         0 $kernel->alarm_remove($session->{timeout});
303 0         0 delete $heap->{sessions}{$trsessionid};
304             }
305              
306 1         9 $kernel->alias_remove($heap->{alias});
307              
308 1         50 return 1;
309             }
310              
311             # The following state functions are private, for internal use only.
312              
313             # This function opens up the socket and verifies it. It tells the POE Kernel
314             # to wait for read readiness on the socket and it starts the
315             # _send_packet / _recv_packet loop.
316              
317             sub _start_traceroute
318             {
319 2     2   347 my ($kernel,$heap,$trsessionid) = @_[ KERNEL, HEAP, ARG0 ];
320 2         6 my $session = $heap->{sessions}{$trsessionid};
321              
322 2 50       10 return unless ($_[SESSION] eq $_[SENDER]);
323              
324 2 50       80 DEBUG and warn "TR: Starting traceroute session $trsessionid\n";
325              
326 2         15 my $socket = FileHandle->new();
327 2 100 33     86 if (not $session->{options}{useicmp})
    50          
328             {
329 1         95 my $proto = getprotobyname('udp');
330            
331 1 50       31 socket($socket, PF_INET, SOCK_DGRAM, $proto) or
332             croak("UDP Socket error - $!");
333             }
334             elsif ($session->{options}{device} or
335             $session->{options}{sourceaddress} ne '0.0.0.0')
336             {
337 0         0 my $proto = getprotobyname('icmp');
338              
339 0 0       0 socket($socket, PF_INET, SOCK_RAW, $proto) or
340             croak("ICMP Socket Error - $!");
341             }
342             else
343             {
344 1         3 undef $socket;
345             }
346              
347 2 100       14 if ($socket)
    50          
348             {
349 1 50       4 DEBUG_SOCKET and warn "TRS: Created socket $socket\n";
350              
351 1 50       5 if ($session->{options}{device})
352             {
353 0         0 my $device = $session->{options}{device};
354 0 0       0 setsockopt($socket, SOL_SOCKET, SO_BINDTODEVICE(),
355             pack('Z*', $device)) or croak "error binding to $device - $!";
356              
357 0 0       0 DEBUG_SOCKET and warn "TRS: Bound socket to $device\n";
358             }
359              
360 1 50 33     13 if ( $session->{options}{sourceaddress} and
361             $session->{options}{sourceaddress} ne '0.0.0.0' )
362             {
363 0         0 _bind($socket, $session->{options}{sourceaddress});
364             }
365              
366             }
367             elsif ($session->{options}{useicmp})
368             {
369 1         2 $socket = $heap->{icmpsocket};
370 1         3 $session->{icmpsocket} = 1;
371             }
372             else
373             {
374 0         0 $session->{postback}->(
375             _build_postback_options(undef,"Could not create socket\n")
376             );
377             }
378              
379 2         158436 my $destination = inet_aton($session->{host});
380 2 50       17 if (not defined $destination)
381             {
382 0         0 $session->{postback}->(
383             _build_postback_options(undef,"Could not resolve $destination\n")
384             );
385             }
386             else
387             {
388 2         12 $session->{destination} = $destination;
389 2         7 $session->{socket_handle} = $socket;
390            
391 2         24 $kernel->yield( '_send_packet' => $trsessionid );
392             }
393              
394 2         291 return;
395             }
396              
397             # This function connects to the remote destination on the current port
398             # and sets the TTL on the socket before sending a UDP packet. It also
399             # starts the query timeout alarm which is cleared when a packet is
400             # received.
401              
402             sub _send_packet
403             {
404 72     72   28762 my ($kernel, $heap, $trsessionid) = @_[ KERNEL, HEAP, ARG0 ];
405 72         210 my $session = $heap->{sessions}{$trsessionid};
406              
407 72 50       367 return unless ($_[SESSION] eq $_[SENDER]);
408            
409 72 100       455 if (not exists $session->{hop})
410             {
411 2         9 $session->{hop} = $session->{options}{firsthop};
412             }
413              
414 72         149 my $hop = $session->{hop};
415 72         122 my $currentquery = scalar keys %{$session->{hops}{$hop}};
  72         269  
416              
417 72         118 my $message;
418             my $saddr;
419              
420 72 100       337 if ($session->{options}{useicmp})
421             {
422 27         52 my $port = ICMP_PORT;
423 27         94 $saddr = _connect($session,$port);
424              
425 27         68 my $seq = ++$heap->{lastseq} & 0xFFFF;
426            
427 27         171 my $data = sprintf("%05i/%03i/%02i/",$trsessionid,$hop,$currentquery);
428 27         132 $data .= 'a' x ($session->{options}{packetlen} - ICMP_DATA - 13);
429              
430 27         208 my $pkt = pack('CC n3 A' . length($data),
431             ICMP_ECHO, # Type
432             0, # Code
433             0, # Checksum (will be computed next)
434             $$, # ID (PID)
435             $seq, # Sequence
436             $data, # Data
437             );
438              
439 27         135 my $chksum = _checksum($pkt);
440              
441 27         156 $message = pack('CC n3 A' . length($data),
442             ICMP_ECHO, # Type
443             0, # Code
444             $chksum, # Checksum
445             $$, # ID (PID)
446             $seq, # Sequence
447             $data, # Data
448             );
449              
450 27         106 $heap->{sequences}{$seq} = $trsessionid;
451 27         72 $session->{lastseq} = $seq;
452             }
453             else
454             {
455 45 100       176 my $port = ($session->{lastport}) ?
456             $session->{lastport} + 1 : $session->{options}{baseport} + $hop - 1;
457              
458 45         270 $message = 'a' x ($session->{options}{packetlen} - UDP_DATA);
459            
460 45 50 66     472 if (not exists $session->{lastport} or $session->{lastport} != $port)
461             {
462 45         157 _connect($session,$port);
463              
464 45         87 $session->{lastport} = $port;
465 45         125 $heap->{ports}{$session->{localport}} = $trsessionid;
466             }
467             }
468              
469 72         419 $session->{lasttime} = time;
470 72         1038 my $alarm = $kernel->delay_set('_timeout',
471             $session->{options}{querytimeout}, $trsessionid, 0);
472              
473 72         9017 $session->{alarm} = $alarm;
474              
475 72 50       301 DEBUG and warn "TR: Sent packet for $trsessionid\n";
476 72 100       236 if ($session->{options}{useicmp})
477             {
478 27         3911 send($session->{socket_handle}, $message, 0, $saddr);
479             }
480             else
481             {
482 45         10562 send($session->{socket_handle}, $message, 0);
483             }
484            
485 72         408 return;
486             }
487              
488             # This function reads in the packet. Decodes the packet and then verifies
489             # the packet belongs to an active traceroute. If not the packet is discarded.
490             # If it does then the information from the packet and it's RTT are stored
491             # in the session heap and the alarm for the query timeout is cleared.
492             # Finally it checks if it needs to send more packets by calling
493             # _process_results.
494              
495             sub _recv_packet
496             {
497 70     70   673254 my ($kernel, $heap, $socket) = @_[ KERNEL, HEAP, ARG0 ];
498 70         220 my ($recv_msg, $from_saddr, $from_port, $from_ip);
499 0         0 my ($trsessionid, $replytime, $lasthop);
500              
501 70 50       520 return unless ($_[SESSION] eq $_[SENDER]);
502              
503 70         246 $replytime = time;
504 70         739 $lasthop = 0;
505              
506 70         1758 $from_saddr = recv($socket, $recv_msg, 1500, 0);
507 70 50       297 if (defined $from_saddr)
508             {
509 70         440 ($from_port,$from_ip) = sockaddr_in($from_saddr);
510 70         2129 $from_ip = inet_ntoa($from_ip);
511 70 50       327 DEBUG and warn "TR: Received packet from $from_ip\n";
512             }
513             else
514             {
515 0 0       0 DEBUG and warn "TR: No packet?\n";
516 0         0 return;
517             }
518              
519 70         642 my $proto = unpack('C',substr($recv_msg,IP_PROTOCOL,1));
520            
521 70 50       366 if ($proto != 1)
522             {
523 0         0 my $protoname = getprotobynumber($proto);
524 0 0       0 DEBUG and warn "TR: Packet protocol not ICMP $proto($protoname)\n";
525 0         0 return;
526             }
527              
528 70         9390 my ($type,$code) = unpack('CC',substr($recv_msg,ICMP_TYPE,2));
529 70         311 my $icmp_data = substr($recv_msg,ICMP_DATA);
530              
531 70 50       211 if (not $icmp_data)
532             {
533 0 0       0 DEBUG and warn "TR: No data in packet.\n";
534 0         0 return;
535             }
536            
537 70 50 100     666 if ( $type == ICMP_TIMEEXCEED or
      66        
538             $type == ICMP_UNREACHABLE or
539             $type == ICMP_ECHOREPLY )
540             {
541              
542             # This is kind of a hack. It checks if the first two bytes in little-endian
543             # order equal 8, which is 0800 (type 8 code 0). Otherwise the packet must be
544             # a udp traceroute reply. Only if the UDP source port was 8 would this fail.
545             # We always choose a high port for UDP, so it should never fail.
546 70         217 my $rawcode = unpack('v',substr($icmp_data,ICMP_TYPE,2));
547            
548 70 100 100     591 if ($type != ICMP_ECHOREPLY and $rawcode != ICMP_ECHO)
549             {
550 43         129 my $sport = unpack('n',substr($icmp_data,UDP_SPORT,2));
551 43         127 my $dport = unpack('n',substr($icmp_data,UDP_DPORT,2));
552              
553 43         90 $from_port = $dport; # Set $from_port from the UDP packet
554 43         217 $trsessionid = $heap->{ports}{$sport};
555 43 100       158 $lasthop = ($type == ICMP_UNREACHABLE) ? 1 : 0;
556             }
557             else # Must not be a UDP packet, try icmp
558             {
559 27 100       88 if ($type == ICMP_ECHOREPLY)
560             {
561 3         12 my $icmp_id = unpack('n',substr($recv_msg,ICMP_ID,2));
562 3 50       19 return unless ($icmp_id == $$);
563              
564 3         11 my $seq = unpack('n',substr($recv_msg,ICMP_SEQ,2));
565              
566 3         7 my ($hop, $currentquery);
567              
568 9         24 ($trsessionid,$hop,$currentquery) =
569 3         21 map{int $_} grep{/^\d+$/} split('/',$icmp_data);
  12         71  
570              
571 3 50       24 if ($hop != $heap->{sessions}{$trsessionid}{hop})
572             {
573 0 0       0 DEBUG and warn
574             "TR: Packet out of order or after timeout, dropping\n";
575 0         0 return;
576             }
577            
578 3         6 $from_port = $seq; # Reusing the variable
579 3         7 $lasthop = 1;
580              
581 3 50       8 DEBUG and warn "Got echo reply for $trsessionid\n";
582             }
583             else
584             {
585 24         127 my $icmp_id = unpack('n',substr($icmp_data,ICMP_ID,2));
586 24 50       136 return unless ($icmp_id == $$);
587              
588 24         72 my $ptype = unpack('C',substr($icmp_data,ICMP_TYPE,1));
589 24         59 my $pseq = unpack('n',substr($icmp_data,ICMP_SEQ,2));
590 24 50       74 if ($ptype eq ICMP_ECHO)
591             {
592 24         335 $trsessionid = $heap->{sequences}{$pseq};
593 24         85 $from_port = $pseq; # Reusing the variable
594             }
595             }
596             }
597             }
598              
599 70 50 33     370 if ($trsessionid and $from_ip)
600             {
601 70         233 my $session = $heap->{sessions}{$trsessionid};
602 70 50       180 DEBUG and warn "TR: Received packet for $trsessionid\n";
603              
604 70 50 66     1254 if (($session->{options}{useicmp} and $from_port != $session->{lastseq})
      66        
      33        
605             or ( not $session->{options}{useicmp} and
606             $from_port != $session->{lastport} ) )
607             {
608 0 0       0 DEBUG and warn "TR: Packet out of order or after timeout, dropping\n";
609 0         0 return;
610             }
611              
612 70         452 $kernel->alarm_remove($session->{alarm});
613              
614 70         15380 my $hop = $session->{hop};
615 70         142 my $currentquery = scalar keys %{$session->{hops}{$hop}};
  70         287  
616              
617 70         867 $session->{hops}{$hop}{$currentquery} = {
618             remoteip => $from_ip,
619             replytime => $replytime - $session->{lasttime},
620             };
621              
622 70         165 $session->{stop} = $lasthop;
623              
624 70         244 my $continue = _process_results($session,$currentquery);
625              
626 70 100       215 if ($continue)
627             {
628 68         313 $kernel->yield('_send_packet',$trsessionid);
629             }
630             else
631             {
632 2         10 $kernel->alarm_remove($session->{timeout});
633 2         200 delete $heap->{sessions}{$trsessionid};
634             }
635             }
636              
637 70         11347 return;
638             }
639              
640             # This function is called whenever a query times out or the whole traceroute
641             # times out. The $stop argument determines the state. When a query times out
642             # The port number is incremented so that late replies don't mess up the system.
643             # If a query timed out, then an * is stored for the RTT and the next packet
644             # is sent.
645              
646             sub _timeout
647             {
648 2     2   5798432 my ($kernel,$heap,$trsessionid,$stop) = @_[ KERNEL,HEAP,ARG0,ARG1 ];
649 2         12 my $session = $heap->{sessions}{$trsessionid};
650              
651 2 50       18 return unless $session;
652 2 50       16 return unless ($_[SESSION] eq $_[SENDER]);
653              
654 2 50       14 if ($stop)
655             {
656 0         0 my $error = "Traceroute session timeout\n";
657              
658 0         0 $session->{postback}->(_build_postback_options($session,$error));
659 0         0 $kernel->alarm_remove($session->{timeout});
660              
661 0         0 delete $heap->{sessions}{$trsessionid};
662 0         0 return;
663             }
664              
665 2         8 my $hop = $session->{hop};
666 2         16 my $currentquery = scalar keys %{$session->{hops}{$hop}};
  2         15  
667              
668 2         23 $session->{hops}{$hop}{$currentquery} = {
669             remoteip => '',
670             replytime => '*',
671             };
672              
673 2 50       19 DEBUG and warn "TR: Timeout on $hop ($currentquery) for $trsessionid\n";
674              
675 2         14 my $continue = _process_results($session,$currentquery);
676 2 50       12 if ($continue)
677             {
678             # Reconnect on timeout so we get a new port.
679 2 50       14 if (not $session->{options}{useicmp})
680             {
681 2         7 $session->{lastport}++;
682              
683 2         337 _connect($session,$session->{lastport});
684 2         10 $heap->{ports}{$session->{localport}} = $trsessionid;
685             }
686 2         16 $kernel->yield('_send_packet',$trsessionid);
687             }
688             else
689             {
690 0         0 $kernel->alarm_remove($session->{timeout});
691 0         0 delete $heap->{sessions}{$trsessionid};
692             }
693              
694 2         370 return;
695             }
696              
697             # Just in case we were sent a bad event name.
698              
699             sub tracer_default
700             {
701 0 0   0 0 0 DEBUG and warn "Unknown state: " . $_[ARG0] . "\n";
702 0         0 return;
703             }
704              
705             # Internal private functions
706              
707              
708             # This function binds the socket to a local IP address. It croaks on error.
709              
710             sub _bind
711             {
712 0     0   0 my ($socket, $sourceaddress) = @_;
713              
714 0         0 my $ip = inet_aton($sourceaddress);
715 0 0       0 croak("TR: nonexistant local address $sourceaddress") unless (defined $ip);
716              
717 0 0       0 CORE::bind($socket, sockaddr_in(0,$ip)) ||
718             croak("TR: bind error - $!\n");
719              
720 0 0       0 DEBUG_SOCKET and warn "TRS: Bound socket to $sourceaddress\n";
721              
722 0         0 return 1;
723             }
724              
725             # This function connects a socket to a remote system and sets the socket TTL
726              
727             sub _connect
728             {
729 74     74   396 my ($session,$port) = @_;
730            
731 74         153 my $hop = $session->{hop};
732 74         538 my $socket_addr = sockaddr_in($port,$session->{destination});
733              
734 74 100       1074 if (not $session->{options}{useicmp})
735             {
736 47         847 connect($session->{socket_handle},$socket_addr);
737 47 50       121 DEBUG_SOCKET and warn "TRS: Connected to $session->{host}\n";
738             }
739              
740 74         464 setsockopt($session->{socket_handle}, IPPROTO_IP, IP_TTL, pack('C',$hop));
741 74 50       230 DEBUG_SOCKET and warn "TRS: Set TTL to $hop\n";
742              
743 74 100       531 if (not $session->{options}{useicmp})
744             {
745 47         358 my $localaddr = getsockname($session->{socket_handle});
746 47         202 my ($lport,$addr) = sockaddr_in($localaddr);
747 47         539 $session->{localport} = $lport;
748             }
749              
750 74         165 return $socket_addr;
751             }
752              
753             # This function is called after every packet is received or timed out.
754             # It increments the hop count, sends PerHopPostbacks and regular Postbacks
755             # and then returns if there are more queries to be sent or the traceroute
756             # is complete.
757              
758             sub _process_results
759             {
760 72     72   154 my ($session,$currentquery) = @_;
761              
762 72 100       348 if ($currentquery + 1 == $session->{options}{queries})
763             {
764 24 100       109 if ($session->{options}{perhoppostback})
765             {
766 15         144 my $postback = $session->{sender}->postback(
767             $session->{options}{perhoppostback},
768             $session->{host},
769             $session->{options},
770             $session->{callback}
771             );
772              
773 15         1531 my $hop = $session->{hop};
774 15         73 my @rows = _build_hopdata($session->{hops}{$hop}, $hop);
775              
776 15         56 $postback->( $hop, \@rows, undef ); # No error
777             }
778              
779 24         3748 $session->{hop}++;
780 24 100 100     204 if ($session->{hop} > $session->{options}{maxttl} or $session->{stop})
781             {
782 2 50       9 my $error = ($session->{stop}) ?
783             undef : 'MaxTTL exceeded without reaching target';
784              
785 2         9 $session->{postback}->(_build_postback_options($session,$error));
786 2         282 return 0;
787             }
788             }
789              
790 70         149 return 1;
791             }
792              
793             # This function takes the session heap and turns it into the response
794             # which is sent back to the postback function.
795              
796             sub _build_postback_options
797             {
798 2     2   5 my ($session,$error) = @_;
799              
800 2         4 my $hops = 0;
801 2         5 my @hopdata = ();
802            
803 2 50       9 if (defined $session)
804             {
805 2         6 foreach my $hop (sort {$a <=> $b} keys %{$session->{hops}})
  63         67  
  2         23  
806             {
807 24         60 my @rows = _build_hopdata($session->{hops}{$hop},$hop);
808 24 50       65 $hops = $hop if $rows[0]->{routerip};
809              
810 24         57 push (@hopdata,@rows);
811             }
812             }
813              
814 2         8 my @response = ( $hops, \@hopdata, $error );
815 2         14 return @response;
816             }
817              
818             # This function builds the actual data structure for each hop.
819              
820             sub _build_hopdata
821             {
822 39     39   69 my ($hopref,$hop) = @_;
823              
824 39         67 my @hopdata = ();
825 39         80 my %row = ();
826 39         87 $row{hop} = $hop;
827            
828 39         64 my @results = ();
829 39         62 foreach my $query (sort {$a <=> $b} keys %{$hopref})
  103         247  
  39         219  
830             {
831 117         420 my $routerip = $hopref->{$query}{remoteip};
832 117         186 my $replytime = $hopref->{$query}{replytime};
833              
834 117         168 push (@results, $replytime);
835 117 100 100     808 if ( exists $row{routerip} and
      100        
      100        
836             $routerip and $row{routerip} and
837             $row{routerip} ne $routerip )
838             {
839 16 50       30 DEBUG and warn "TR: Router IP changed during hop $hop from " .
840             $row{routerip} . " to $routerip\n";
841              
842 16         82 my %newrow = %row;
843 16         32 my @newresults = @results;
844 16         32 $newrow{results} = \@newresults;
845 16         26 push (@hopdata,\%newrow);
846 16         36 undef %row;
847 16         25 undef @results;
848 16         39 $row{hop} = $hop;
849             }
850              
851 117 100       382 $row{routerip} = $routerip unless $row{routerip};
852             }
853              
854 39 100       113 if (@results)
855             {
856 33         65 $row{results} = \@results;
857 33         62 push (@hopdata,\%row);
858             }
859              
860 39         116 return @hopdata;
861             }
862              
863             # Lifted verbatum from Net::Ping 2.31
864             # Description: Do a checksum on the message. Basically sum all of
865             # the short words and fold the high order bits into the low order bits.
866              
867             sub _checksum
868             {
869 27     27   47 my $msg = shift;
870              
871 27         40 my ($len_msg, # Length of the message
872             $num_short, # The number of short words in the message
873             $short, # One short word
874             $chk # The checksum
875             );
876              
877 27         37 $len_msg = length($msg);
878 27         255 $num_short = int($len_msg / 2);
879 27         33 $chk = 0;
880 27         232 foreach $short (unpack("n$num_short", $msg))
881             {
882 1458         3316 $chk += $short;
883             } # Add the odd byte in
884 27 50       155 $chk += (unpack("C", substr($msg, $len_msg - 1, 1)) << 8) if $len_msg % 2;
885 27         59 $chk = ($chk >> 16) + ($chk & 0xffff); # Fold high into low
886 27         83 return(~(($chk >> 16) + $chk) & 0xffff); # Again and complement
887             }
888              
889             1;
890              
891             __END__