File Coverage

blib/lib/Net/DNS/Resolver/Recurse.pm
Criterion Covered Total %
statement 87 87 100.0
branch 30 30 100.0
path n/a
condition 2 2 100.0
subroutine 12 12 100.0
pod 3 5 100.0
total 134 136 100.0


line stmt bran path cond sub pod time code
1               package Net::DNS::Resolver::Recurse;
2                
3 2       2   1002 use strict;
  2           4  
  2           59  
4 2       2   7 use warnings;
  2           3  
  2           126  
5               our $VERSION = (qw$Id: Recurse.pm 2002 2025-01-07 09:57:46Z willem $)[2];
6                
7                
8               =head1 NAME
9                
10               Net::DNS::Resolver::Recurse - DNS recursive resolver
11                
12                
13               =head1 SYNOPSIS
14                
15               use Net::DNS::Resolver::Recurse;
16                
17               my $resolver = new Net::DNS::Resolver::Recurse();
18                
19               $resolver->hints('198.41.0.4'); # A.ROOT-SERVER.NET.
20                
21               my $packet = $resolver->send( 'www.rob.com.au.', 'A' );
22                
23                
24               =head1 DESCRIPTION
25                
26               This module resolves queries by following the delegation path from the DNS root.
27                
28               =cut
29                
30                
31 2       2   9 use base qw(Net::DNS::Resolver::Base);
  2           2  
  2           2062  
32                
33                
34               =head1 METHODS
35                
36               This module inherits almost all the methods from Net::DNS::Resolver.
37               Additional module-specific methods are described below.
38                
39                
40               =head2 hints
41                
42               This method specifies a list of the IP addresses of nameservers to
43               be used to discover the addresses of the root nameservers.
44                
45               $resolver->hints(@ip);
46                
47               If no hints are passed, the priming query is directed to nameservers
48               drawn from a built-in list of IP addresses.
49                
50               =cut
51                
52               my @hints;
53               my $root;
54                
55               sub hints {
56 4       4 1 46 my ( undef, @argument ) = @_;
57 4 100         37 return @hints unless scalar @argument;
58 2           17 undef $root;
59 2           10 return @hints = @argument;
60               }
61                
62                
63               =head2 query, search, send
64                
65               The query(), search() and send() methods produce the same result
66               as their counterparts in Net::DNS::Resolver.
67                
68               $packet = $resolver->send( 'www.example.com.', 'A' );
69                
70               Server-side recursion is suppressed by clearing the recurse flag in
71               query packets and recursive name resolution is performed explicitly.
72                
73               The query() and search() methods are inherited from Net::DNS::Resolver
74               and invoke send() indirectly.
75                
76               =cut
77                
78               sub send {
79 16       16 1 473 my ( $self, @q ) = @_;
80 16           59 my @conf = ( recurse => 0, udppacketsize => 1232 );
81 16           326 return bless( {persistent => {'.' => $root}, %$self, @conf}, ref($self) )->_send(@q);
82               }
83                
84                
85               sub query_dorecursion { ## historical
86 1       1 0 18 my ($self) = @_; # uncoverable pod
87 1           7 $self->_deprecate('prefer $resolver->send(...)');
88 1           2 return &send;
89               }
90                
91                
92               sub _send {
93 16       16   47 my ( $self, @q ) = @_;
94 16           70 my $query = $self->_make_query_packet(@q);
95                
96 16 100         48 unless ($root) {
97 4           16 $self->_diag('resolver priming query');
98 4 100         33 $self->nameservers( scalar(@hints) ? @hints : $self->_hints );
99 4           20 $self->_referral( $self->SUPER::send(qw(. NS)) );
100 4           78 $root = $self->{persistent}->{'.'};
101               }
102                
103 16           96 return $self->_recurse( $query, '.' );
104               }
105                
106                
107               sub _recurse {
108 43       43   102 my ( $self, $query, $apex ) = @_;
109 43           203 $self->_diag("using cached nameservers for $apex");
110 43           114 my $cache = $self->{persistent}->{$apex};
111 43           217 my @nslist = keys %$cache;
112 43           80 my @glue = grep { $$cache{$_} } @nslist;
  340           536  
113 43           81 my @noglue = grep { !$$cache{$_} } @nslist;
  340           498  
114 43           64 my $reply;
115 43           91 foreach my $ns ( @glue, @noglue ) {
116 40 100         123 if ( my $iplist = $$cache{$ns} ) {
117 35           271 $self->nameservers(@$iplist);
118               } else {
119 5           19 $self->_diag("recover missing glue for $ns");
120 5 100         20 next if substr( lc($ns), -length($apex) ) eq $apex;
121 4           20 my @ip = $self->nameservers($ns);
122 4           15 $$cache{$ns} = \@ip;
123               }
124 39           185 $query->header->id(undef);
125 39 100         164 last if $reply = $self->SUPER::send($query);
126 1           3 $$cache{$ns} = undef; # park non-responder
127               }
128 43           221 $self->_callback($reply);
129 43 100         133 return unless $reply;
130 38     100     217 my $zone = $self->_referral($reply) || return $reply;
131 26 100         160 die '_recurse exceeded depth limit' if $self->{recurse_depth}++ > 50;
132 25           137 my $qname = lc( ( $query->question )[0]->qname );
133 25           74 my $suffix = substr( $qname, -length($zone) );
134 25 100         178 return $zone eq $suffix ? $self->_recurse( $query, $zone ) : undef;
135               }
136                
137                
138               sub _referral {
139 45       45   118 my ( $self, $packet ) = @_;
140 45 100         110 return unless $packet;
141 43           163 my @ans = $packet->answer;
142 43           144 my @auth = grep { $_->type eq 'NS' } $packet->authority, @ans;
  242           507  
143 43 100         964 return unless scalar(@auth);
144 37           137 my $owner = lc( $auth[0]->owner );
145 37           140 my $cache = $self->{persistent}->{$owner};
146 37 100         939 return scalar(@ans) ? undef : $owner if $cache;
    100            
147                
148 21           93 $self->_diag("caching nameservers for $owner");
149 21           32 my %addr;
150 21           65 my @addr = grep { $_->can('address') } $packet->additional;
  218           483  
151 21           51 push @{$addr{lc $_->owner}}, $_->address foreach @addr;
  199           343  
152                
153 21           30 my %cache;
154 21           36 foreach my $ns ( map { lc( $_->nsdname ) } @auth ) {
  121           361  
155 121           236 $cache{$ns} = $addr{$ns};
156               }
157                
158 21           75 $self->{persistent}->{$owner} = \%cache;
159 21 100         132 return scalar(@ans) ? undef : $owner;
160               }
161                
162                
163               =head2 callback
164                
165               This method specifies a code reference to a subroutine,
166               which is then invoked at each stage of the recursive lookup.
167                
168               For example to emulate dig's C<+trace> function:
169                
170               my $coderef = sub {
171               my $packet = shift;
172                
173               printf ";; Received %d bytes from %s\n\n",
174               $packet->answersize, $packet->answerfrom;
175               };
176                
177               $resolver->callback($coderef);
178                
179               The callback subroutine is not called
180               for queries for missing glue records.
181                
182               =cut
183                
184               sub callback {
185 4       4 1 42 my ( $self, @argument ) = @_;
186 4           9 for ( grep { ref($_) eq 'CODE' } @argument ) {
  4           32  
187 3           8 $self->{callback} = $_;
188               }
189 4           10 return;
190               }
191                
192               sub _callback {
193 43       43   126 my ( $self, @argument ) = @_;
194 43           115 my $callback = $self->{callback};
195 43 100         181 $callback->(@argument) if $callback;
196 43           132 return;
197               }
198                
199               sub recursion_callback { ## historical
200 1       1 0 16 my ($self) = @_; # uncoverable pod
201 1           4 $self->_deprecate('prefer $resolver->callback(...)');
202 1           2 &callback;
203 1           2 return;
204               }
205                
206                
207               1;
208                
209               __END__