File Coverage

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


line stmt bran cond sub pod time code
1             package Net::DNS::Resolver::Recurse;
2              
3 2     2   963 use strict;
  2         3  
  2         56  
4 2     2   7 use warnings;
  2         3  
  2         120  
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   8 use base qw(Net::DNS::Resolver::Base);
  2         2  
  2         1843  
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 31 my ( undef, @argument ) = @_;
57 4 100       19 return @hints unless scalar @argument;
58 2         16 undef $root;
59 2         9 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 680 my ( $self, @q ) = @_;
80 16         55 my @conf = ( recurse => 0, udppacketsize => 1232 );
81 16         400 return bless( {persistent => {'.' => $root}, %$self, @conf}, ref($self) )->_send(@q);
82             }
83              
84              
85             sub query_dorecursion { ## historical
86 1     1 0 30 my ($self) = @_; # uncoverable pod
87 1         9 $self->_deprecate('prefer $resolver->send(...)');
88 1         5 return &send;
89             }
90              
91              
92             sub _send {
93 16     16   56 my ( $self, @q ) = @_;
94 16         82 my $query = $self->_make_query_packet(@q);
95              
96 16 100       63 unless ($root) {
97 4         24 $self->_diag('resolver priming query');
98 4 100       37 $self->nameservers( scalar(@hints) ? @hints : $self->_hints );
99 4         31 $self->_referral( $self->SUPER::send(qw(. NS)) );
100 4         204 $root = $self->{persistent}->{'.'};
101             }
102              
103 16         69 return $self->_recurse( $query, '.' );
104             }
105              
106              
107             sub _recurse {
108 42     42   121 my ( $self, $query, $apex ) = @_;
109 42         265 $self->_diag("using cached nameservers for $apex");
110 42         1938 my $cache = $self->{persistent}->{$apex};
111 42         247 my @nslist = keys %$cache;
112 42         116 my @glue = grep { $$cache{$_} } @nslist;
  360         721  
113 42         129 my @noglue = grep { !$$cache{$_} } @nslist;
  360         1064  
114 42         79 my $reply;
115 42         117 foreach my $ns ( @glue, @noglue ) {
116 40 100       160 if ( my $iplist = $$cache{$ns} ) {
117 35         216 $self->nameservers(@$iplist);
118             } else {
119 5         23 $self->_diag("recover missing glue for $ns");
120 5 100       24 next if substr( lc($ns), -length($apex) ) eq $apex;
121 4         21 my @ip = $self->nameservers($ns);
122 4         23 $$cache{$ns} = \@ip;
123             }
124 39         207 $query->header->id(undef);
125 39 100       216 last if $reply = $self->SUPER::send($query);
126 1         4 $$cache{$ns} = undef; # park non-responder
127             }
128 42         271 $self->_callback($reply);
129 42 100       159 return unless $reply;
130 38   100     165 my $zone = $self->_referral($reply) || return $reply;
131 26 100       532 die '_recurse exceeded depth limit' if $self->{recurse_depth}++ > 50;
132 25         168 my $qname = lc( ( $query->question )[0]->qname );
133 25         93 my $suffix = substr( $qname, -length($zone) );
134 25 100       277 return $zone eq $suffix ? $self->_recurse( $query, $zone ) : undef;
135             }
136              
137              
138             sub _referral {
139 44     44   130 my ( $self, $packet ) = @_;
140 44 100       196 return unless $packet;
141 42         239 my @ans = $packet->answer;
142 42         210 my @auth = grep { $_->type eq 'NS' } $packet->authority, @ans;
  262         815  
143 42 100       1150 return unless scalar(@auth);
144 37         335 my $owner = lc( $auth[0]->owner );
145 37         196 my $cache = $self->{persistent}->{$owner};
146 37 100       1195 return scalar(@ans) ? undef : $owner if $cache;
    100          
147              
148 21         142 $self->_diag("caching nameservers for $owner");
149 21         50 my %addr;
150 21         107 my @addr = grep { $_->can('address') } $packet->additional;
  236         794  
151 21         88 push @{$addr{lc $_->owner}}, $_->address foreach @addr;
  217         517  
152              
153 21         54 my %cache;
154 21         55 foreach my $ns ( map { lc( $_->nsdname ) } @auth ) {
  131         1790  
155 131         3130 $cache{$ns} = $addr{$ns};
156             }
157              
158 21         103 $self->{persistent}->{$owner} = \%cache;
159 21 100       187 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 41 my ( $self, @argument ) = @_;
186 4         11 for ( grep { ref($_) eq 'CODE' } @argument ) {
  4         22  
187 3         8 $self->{callback} = $_;
188             }
189 4         12 return;
190             }
191              
192             sub _callback {
193 42     42   137 my ( $self, @argument ) = @_;
194 42         121 my $callback = $self->{callback};
195 42 100       174 $callback->(@argument) if $callback;
196 42         140 return;
197             }
198              
199             sub recursion_callback { ## historical
200 1     1 0 59 my ($self) = @_; # uncoverable pod
201 1         6 $self->_deprecate('prefer $resolver->callback(...)');
202 1         5 &callback;
203 1         2 return;
204             }
205              
206              
207             1;
208              
209             __END__