File: Recurse.pm

package info (click to toggle)
libnet-dns-perl 0.59-1etch1
  • links: PTS
  • area: main
  • in suites: etch
  • size: 828 kB
  • ctags: 400
  • sloc: perl: 6,650; sh: 220; ansic: 101; makefile: 60
file content (485 lines) | stat: -rw-r--r-- 16,593 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
package Net::DNS::Resolver::Recurse;
#
# $Id: Recurse.pm 591 2006-05-22 21:32:38Z olaf $
#
use strict;
use Net::DNS::Resolver;

use vars qw($VERSION @ISA);

$VERSION = (qw$LastChangedRevision: 591 $)[1];
@ISA = qw(Net::DNS::Resolver);

sub hints {
  my $self = shift;
  my @hints = @_;
  print ";; hints(@hints)\n" if $self->{'debug'};
  if (!@hints && $self->nameservers) {
    $self->hints($self->nameservers);
  } else {
    $self->nameservers(@hints);
  }

  print ";; verifying (root) zone...\n" if $self->{'debug'};
  # bind always asks one of the hint servers
  # for who it thinks is authoritative for
  # the (root) zone as a sanity check.
  # Nice idea.
  
  $self->recurse(1); 
  my $packet=$self->query(".", "NS", "IN");
  $self->recurse(0); 
  my %hints = ();
  if ($packet) {
    if (my @ans = $packet->answer) {
      foreach my $rr (@ans) {
        if ($rr->name =~ /^\.?$/ and
            $rr->type eq "NS") {
          # Found root authority
          my $server = lc $rr->rdatastr;
          $server =~ s/\.$//;
          print ";; FOUND HINT: $server\n" if $self->{'debug'};
          $hints{$server} = [];
        }
      }
      foreach my $rr ($packet->additional) {
	print ";; ADDITIONAL: ",$rr->string,"\n" if $self->{'debug'};
	if (my $server = lc $rr->name){
	  if ( $rr->type eq "A") {
	    #print ";; ADDITIONAL HELP: $server -> [".$rr->rdatastr."]\n" if $self->{'debug'};
	    if ($hints{$server}) {
	      print ";; STORING IP: $server IN A ",$rr->rdatastr,"\n" if $self->{'debug'};
	      push @{ $hints{$server} }, $rr->rdatastr;
	    }
	  }
	  if ( $rr->type eq "AAAA") {
	    #print ";; ADDITIONAL HELP: $server -> [".$rr->rdatastr."]\n" if $self->{'debug'};
	    if ($hints{$server}) {
	      print ";; STORING IP6: $server IN AAAA ",$rr->rdatastr,"\n" if $self->{'debug'};
	      push @{ $hints{$server} }, $rr->rdatastr;
	    }
	  }
	  
	} 
      }
    }
    foreach my $server (keys %hints) {
      if (!@{ $hints{$server} }) {
	# Wipe the servers without lookups
	delete $hints{$server};
      }
    }
    $self->{'hints'} = \%hints;
  } else {
    $self->{'hints'} = {};
  } 
  if (%{ $self->{'hints'} }) {
    if ($self->{'debug'}) {
      print ";; USING THE FOLLOWING HINT IPS:\n";
      foreach my $ips (values %{ $self->{'hints'} }) {
	foreach my $server (@{ $ips }) {
	  print ";;  $server\n";
	}
      }
    }
  } else {
    warn "Server [".($self->nameservers)[0]."] did not give answers";
  }
  
  # Disable recursion flag.

  
  return $self->nameservers( map { @{ $_ } } values %{ $self->{'hints'} } );
}


sub recursion_callback {
	my ($self, $sub) = @_;
	
	if ($sub && UNIVERSAL::isa($sub, 'CODE')) {
		$self->{'callback'} = $sub;
	}
	
	return $self->{'callback'};
}


# $res->query_dorecursion( args );
# Takes same args as Net::DNS::Resolver->query
# Purpose: Do that "hot pototo dance" on args.
sub query_dorecursion {
  my $self = shift;
  my @query = @_;

  # Make sure the hint servers are initialized.
  $self->hints unless $self->{'hints'};
  $self->recurse(0);
  # Make sure the authority cache is clean.
  # It is only used to store A and AAAA records of
  # the suposedly authoritative name servers.
  $self->{'authority_cache'} = {};

  # Obtain real question Net::DNS::Packet
  my $query_packet = $self->make_query_packet(@query);

  # Seed name servers with hints
  return $self->_dorecursion( $query_packet, ".", $self->{'hints'}, 0);
}

sub _dorecursion {
  my $self = shift;
  my $query_packet = shift;
  my $known_zone = shift;
  my $known_authorities = shift;
  my $depth = shift;
  my $cache = $self->{'authority_cache'};

  # die "Recursion too deep, aborting..." if $depth > 255;
  if ( $depth > 255 ) {
      print ";; _dorecursion() Recursion too deep, aborting...\n" if
	  $self->{'debug'};
      $self->errorstring="Recursion to deep, abborted";
      return undef;
  }
  
  $known_zone =~ s/\.*$/./;

  # Get IPs from authorities
  my @ns = ();
  foreach my $ns (keys %{ $known_authorities }) {
    if (scalar @{ $known_authorities->{$ns} }) {
      $cache->{$ns} = $known_authorities->{$ns};
      push (@ns, @{ $cache->{$ns} });
    } elsif ($cache->{$ns}) {
      $known_authorities->{$ns} = $cache->{$ns};
      push (@ns, @{ $cache->{$ns} });
    }
  }

  if (!@ns) {
    my $found_auth = 0;
    if ($self->{'debug'}) {
      require Data::Dumper;
      print ";; _dorecursion() Failed to extract nameserver IPs:\n";
      print Data::Dumper::Dumper([$known_authorities,$cache]);
    }
    foreach my $ns (keys %{ $known_authorities }) {
      if (!@{ $known_authorities->{$ns} }) {
        print ";; _dorecursion() Manual lookup for authority [$ns]\n" if $self->{'debug'};

        my $auth_packet;
	my @ans;

	# Don't query for V6 if its not there.
	if ($Net::DNS::Resolver::Base::has_inet6 && ! $self->{force_v4}){
	    $auth_packet =
		$self->_dorecursion
		($self->make_query_packet($ns,"AAAA"),  # packet
		 ".",               # known_zone
		 $self->{'hints'},  # known_authorities
		 $depth+1);         # depth
	    @ans = $auth_packet->answer if $auth_packet;
	}
	
	$auth_packet =
	    $self->_dorecursion
	    ($self->make_query_packet($ns,"A"),  # packet
	     ".",               # known_zone
	     $self->{'hints'},  # known_authorities
	     $depth+1);         # depth
	
	push (@ans,$auth_packet->answer ) if $auth_packet;

        if ( @ans ) {
          print ";; _dorecursion() Answers found for [$ns]\n" if $self->{'debug'};
          foreach my $rr (@ans) {
	    print ";; RR:".$rr->string."\n" if $self->{'debug'};
            if ($rr->type eq "CNAME") {
              # Follow CNAME
              if (my $server = lc $rr->name) {
                $server =~ s/\.*$/./;
                if ($server eq $ns) {
                  my $cname = lc $rr->rdatastr;
                  $cname =~ s/\.*$/./;
                  print ";; _dorecursion() Following CNAME ns [$ns] -> [$cname]\n" if $self->{'debug'};
                  $known_authorities->{$cname} ||= [];
                  delete $known_authorities->{$ns};
                  next;
                }
              }
            } elsif ($rr->type eq "A" ||$rr->type eq "AAAA" ) {
              if (my $server = lc $rr->name) {
                $server =~ s/\.*$/./;
                if ($known_authorities->{$server}) {
                  my $ip = $rr->rdatastr;
                  print ";; _dorecursion() Found ns: $server IN A $ip\n" if $self->{'debug'};
                  $cache->{$server} = $known_authorities->{$server};
                  push (@{ $cache->{$ns} }, $ip);
                  $found_auth++;
                  next;
                }
              }
            }
            print ";; _dorecursion() Ignoring useless answer: ",$rr->string,"\n" if $self->{'debug'};
          }
        } else {
          print ";; _dorecursion() Could not find A records for [$ns]\n" if $self->{'debug'};
        }
      }
    }
    if ($found_auth) {
      print ";; _dorecursion() Found $found_auth new NS authorities...\n" if $self->{'debug'};
      return $self->_dorecursion( $query_packet, $known_zone, $known_authorities, $depth+1);
    }
    print ";; _dorecursion() No authority information could be obtained.\n" if $self->{'debug'};
    return undef;
  }
  
  # Cut the deck of IPs in a random place.
  print ";; _dorecursion() cutting deck of (".scalar(@ns).") authorities...\n" if $self->{'debug'};
  splice(@ns, 0, 0, splice(@ns, int(rand @ns)));
  
  
 LEVEL:  foreach my $levelns (@ns){
   print ";; _dorecursion() Trying nameserver [$levelns]\n" if $self->{'debug'};
   $self->nameservers($levelns);
   
   if (my $packet = $self->send( $query_packet )) {
     
     if ($self->{'callback'}) {
       $self->{'callback'}->($packet);
     }
     
     my $of = undef;
     print ";; _dorecursion() Response received from [",$self->answerfrom,"]\n" if $self->{'debug'};
     if (my $status = $packet->header->rcode) {
       if ($status eq "NXDOMAIN") {
	 # I guess NXDOMAIN is the best we'll ever get
	 print ";; _dorecursion() returning NXDOMAIN\n" if $self->{'debug'};
	 return $packet;
       } elsif (my @ans = $packet->answer) {
	 print ";; _dorecursion() Answers were found.\n" if $self->{'debug'};
	 return $packet;
       } elsif (my @authority = $packet->authority) {
	 my %auth = ();
	 foreach my $rr (@authority) {
	   if ($rr->type =~ /^(NS|SOA)$/) {
	     my $server = lc ($1 eq "NS" ? $rr->nsdname : $rr->mname);
	     $server =~ s/\.*$/./;
	     $of = lc $rr->name;
	     $of =~ s/\.*$/./;
	     print ";; _dorecursion() Received authority [$of] [",$rr->type(),"] [$server]\n" if $self->{'debug'};
	     if (length $of <= length $known_zone) {
	       print ";; _dorecursion() Deadbeat name server did not provide new information.\n" if $self->{'debug'};
	       next LEVEL;
	     } elsif ($of =~ /$known_zone$/) {
	       print ";; _dorecursion() FOUND closer authority for [$of] at [$server].\n" if $self->{'debug'};
	       $auth{$server} ||= [];
	     } else {
	       print ";; _dorecursion() Confused name server [",$self->answerfrom,"] thinks [$of] is closer than [$known_zone]?\n" if $self->{'debug'};
	       last;
	     }
	   } else {
	     print ";; _dorecursion() Ignoring NON NS entry found in authority section: ",$rr->string,"\n" if $self->{'debug'};
	   }
	 }
	 foreach my $rr ($packet->additional) {
	   if ($rr->type eq "CNAME") {
	     # Store this CNAME into %auth too
	     if (my $server = lc $rr->name) {
	       $server =~ s/\.*$/./;
	       if ($auth{$server}) {
		 my $cname = lc $rr->rdatastr;
		 $cname =~ s/\.*$/./;
		 print ";; _dorecursion() FOUND CNAME authority: ",$rr->string,"\n" if $self->{'debug'};
		 $auth{$cname} ||= [];
		 $auth{$server} = $auth{$cname};
		 next;
	       }
	     }
	   } elsif ($rr->type eq "A" || $rr->type eq "AAAA") {
	     if (my $server = lc $rr->name) {
	       $server =~ s/\.*$/./;
	       if ($auth{$server}) {
		 print ";; _dorecursion() STORING: $server IN A    ",$rr->rdatastr,"\n" if $self->{'debug'} &&  $rr->type eq "A";
		 print ";; _dorecursion() STORING: $server IN AAAA ",$rr->rdatastr,"\n" if $self->{'debug'}&&  $rr->type eq "AAAA";
		 push @{ $auth{$server} }, $rr->rdatastr;
		 next;
	       }
	     }
	   }
	   print ";; _dorecursion() Ignoring useless: ",$rr->string,"\n" if $self->{'debug'};
	 }
	 if ($of =~ /$known_zone$/) {
	   return $self->_dorecursion( $query_packet, $of, \%auth, $depth+1 );
	 } else {
	   return $self->_dorecursion( $query_packet, $known_zone, $known_authorities, $depth+1 );
	 }
      }
     }
   }
 }
  
  return undef;
}

1;

__END__


=head1 NAME

Net::DNS::Resolver::Recurse - Perform recursive dns lookups

=head1 SYNOPSIS

  use Net::DNS::Resolver::Recurse;
  my $res = Net::DNS::Resolver::Recurse->new;

=head1 DESCRIPTION

This module is a sub class of Net::DNS::Resolver. So the methods for
Net::DNS::Resolver still work for this module as well.  There are just a
couple methods added:

=head2 hints

Initialize the hint servers.  Recursive queries need a starting name
server to work off of. This method takes a list of IP addresses to use
as the starting servers.  These name servers should be authoritative for
the root (.) zone.

  $res->hints(@ips);

If no hints are passed, the default nameserver is asked for the hints. 
Normally these IPs can be obtained from the following location:

  ftp://ftp.internic.net/domain/named.root
  
=head2 recursion_callback

This method is takes a code reference, which is then invoked each time a
packet is received during the recursive lookup.  For example to emulate
dig's C<+trace> function:

 $res->recursion_callback(sub {
     my $packet = shift;
		
     $_->print for $packet->additional;
		
     printf(";; Received %d bytes from %s\n\n", 
         $packet->answersize, 
         $packet->answerfrom
     );
 });

=head2 query_dorecursion

This method is much like the normal query() method except it disables
the recurse flag in the packet and explicitly performs the recursion.

  $packet = $res->query_dorecursion( "www.netscape.com.", "A");


=head1 IPv6 transport

If the appropriate IPv6 libraries are installed the recursive resolver
will randomly choose between IPv6 and IPv4 addresses of the
nameservers it encounters during recursion.

If you want to force IPv4 transport use the force_v4() method. Also see
the IPv6 transport notes in the Net::DNS::Resolver documentation.

=head1 AUTHOR

Rob Brown, bbb@cpan.org

=head1 SEE ALSO

L<Net::DNS::Resolver>,

=head1 COPYRIGHT

Copyright (c) 2002, Rob Brown.  All rights reserved.
Portions Copyright (c) 2005, Olaf M Kolkman.

This module is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

$Id: Recurse.pm 591 2006-05-22 21:32:38Z olaf $

=cut

Example lookup process:

[root@box root]# dig +trace www.rob.com.au.

; <<>> DiG 9.2.0 <<>> +trace www.rob.com.au.
;; global options:  printcmd
.                       507343  IN      NS      C.ROOT-SERVERS.NET.
.                       507343  IN      NS      D.ROOT-SERVERS.NET.
.                       507343  IN      NS      E.ROOT-SERVERS.NET.
.                       507343  IN      NS      F.ROOT-SERVERS.NET.
.                       507343  IN      NS      G.ROOT-SERVERS.NET.
.                       507343  IN      NS      H.ROOT-SERVERS.NET.
.                       507343  IN      NS      I.ROOT-SERVERS.NET.
.                       507343  IN      NS      J.ROOT-SERVERS.NET.
.                       507343  IN      NS      K.ROOT-SERVERS.NET.
.                       507343  IN      NS      L.ROOT-SERVERS.NET.
.                       507343  IN      NS      M.ROOT-SERVERS.NET.
.                       507343  IN      NS      A.ROOT-SERVERS.NET.
.                       507343  IN      NS      B.ROOT-SERVERS.NET.
;; Received 436 bytes from 127.0.0.1#53(127.0.0.1) in 9 ms
  ;;; But these should be hard coded as the hints

  ;;; Ask H.ROOT-SERVERS.NET gave:
au.                     172800  IN      NS      NS2.BERKELEY.EDU.
au.                     172800  IN      NS      NS1.BERKELEY.EDU.
au.                     172800  IN      NS      NS.UU.NET.
au.                     172800  IN      NS      BOX2.AUNIC.NET.
au.                     172800  IN      NS      SEC1.APNIC.NET.
au.                     172800  IN      NS      SEC3.APNIC.NET.
;; Received 300 bytes from 128.63.2.53#53(H.ROOT-SERVERS.NET) in 322 ms
  ;;; A little closer than before

  ;;; Ask NS2.BERKELEY.EDU gave:
com.au.                 259200  IN      NS      ns4.ausregistry.net.
com.au.                 259200  IN      NS      dns1.telstra.net.
com.au.                 259200  IN      NS      au2ld.CSIRO.au.
com.au.                 259200  IN      NS      audns01.syd.optus.net.
com.au.                 259200  IN      NS      ns.ripe.net.
com.au.                 259200  IN      NS      ns1.ausregistry.net.
com.au.                 259200  IN      NS      ns2.ausregistry.net.
com.au.                 259200  IN      NS      ns3.ausregistry.net.
com.au.                 259200  IN      NS      ns3.melbourneit.com.
;; Received 387 bytes from 128.32.206.12#53(NS2.BERKELEY.EDU) in 10312 ms
  ;;; A little closer than before

  ;;; Ask ns4.ausregistry.net gave:
com.au.                 259200  IN      NS      ns1.ausregistry.net.
com.au.                 259200  IN      NS      ns2.ausregistry.net.
com.au.                 259200  IN      NS      ns3.ausregistry.net.
com.au.                 259200  IN      NS      ns4.ausregistry.net.
com.au.                 259200  IN      NS      ns3.melbourneit.com.
com.au.                 259200  IN      NS      dns1.telstra.net.
com.au.                 259200  IN      NS      au2ld.CSIRO.au.
com.au.                 259200  IN      NS      ns.ripe.net.
com.au.                 259200  IN      NS      audns01.syd.optus.net.
;; Received 259 bytes from 137.39.1.3#53(ns4.ausregistry.net) in 606 ms
  ;;; Uh... yeah... I already knew this
  ;;; from what NS2.BERKELEY.EDU told me.
  ;;; ns4.ausregistry.net must have brain damage

  ;;; Ask ns1.ausregistry.net gave:
rob.com.au.             86400   IN      NS      sy-dns02.tmns.net.au.
rob.com.au.             86400   IN      NS      sy-dns01.tmns.net.au.
;; Received 87 bytes from 203.18.56.41#53(ns1.ausregistry.net) in 372 ms
  ;;; Ah, much better.  Something more useful.

  ;;; Ask sy-dns02.tmns.net.au gave:
www.rob.com.au.         7200    IN      A       139.134.5.123
rob.com.au.             7200    IN      NS      sy-dns01.tmns.net.au.
rob.com.au.             7200    IN      NS      sy-dns02.tmns.net.au.
;; Received 135 bytes from 139.134.2.18#53(sy-dns02.tmns.net.au) in 525 ms
  ;;; FINALLY, THE ANSWER!