File Coverage

blib/lib/AnyEvent/DNS/EtcHosts.pm
Criterion Covered Total %
statement 104 111 93.6
branch 23 38 60.5
condition 13 18 72.2
subroutine 20 21 95.2
pod 2 2 100.0
total 162 190 85.2


line stmt bran cond sub pod time code
1             package AnyEvent::DNS::EtcHosts;
2              
3             =head1 NAME
4              
5             AnyEvent::DNS::EtcHosts - Use /etc/hosts before DNS
6              
7             =head1 SYNOPSIS
8              
9             =for markdown ```perl
10              
11             use AnyEvent::DNS::EtcHosts;
12              
13             use AnyEvent::DNS;
14             my $cv = AE::cv;
15             AnyEvent::DNS::any 'example.com', sub {
16             say foreach map { $_->[4] } grep { $_->[1] =~ /^(a|aaaa)$/ } @_;
17             $cv->send;
18             };
19              
20             use AnyEvent::Socket;
21             my $cv = AE::cv;
22             AnyEvent::Socket::resolve_sockaddr $domain, $service, $proto, $family, undef, sub {
23             say foreach map { format_address((AnyEvent::Socket::unpack_sockaddr($_->[3]))[1]) } @_;
24             $cv->send;
25             };
26              
27             =for markdown ```
28              
29             =head1 DESCRIPTION
30              
31             AnyEvent::DNS::EtcHosts changes AnyEvent::DNS behavior. The F file
32             is searched before DNS, so it is possible to override DNS entries.
33              
34             The DNS lookups are emulated. This resolver returns the standard DNS reply
35             based on F file rather than real DNS.
36              
37             You can choose a different file by changing C environment
38             variable.
39              
40             This module also disables the original L's helper function
41             which reads F file after the DNS entry was not found. It prevents
42             reading this file twice.
43              
44             The L resolver searches IPv4 and IPv6 addresses separately.
45             If you don't want to check the addresses in DNS, both IPv4 and IPv6 addresses
46             should be placed in F or the protocol family should be set
47             explicitly for C function.
48              
49             =for readme stop
50              
51             =cut
52              
53 3     3   76259 use 5.008_001;
  3         11  
54              
55 3     3   19 use strict;
  3         7  
  3         59  
56 3     3   25 use warnings;
  3         7  
  3         187  
57              
58             our $VERSION = '0.0105';
59              
60 3     3   19 use base 'AnyEvent::DNS';
  3         7  
  3         1009  
61              
62 3     3   35566 use AnyEvent ();
  3         6  
  3         71  
63 3     3   15 use AnyEvent::Socket ();
  3         6  
  3         86  
64              
65 3     3   16 use constant DEBUG => $ENV{PERL_ANYEVENT_DNS_ETCHOSTS_DEBUG};
  3         6  
  3         345  
66 3     3   2077 use if DEBUG, 'Data::Dumper';
  3         42  
  3         15  
67              
68             our $GUARD;
69              
70             =head1 IMPORTS
71              
72             =head2 use AnyEvent::DNS::EtcHosts %args
73              
74             =for markdown ```perl
75              
76             use AnyEvent::DNS::EtcHosts server => '8.8.8.8';
77              
78             $ perl -MAnyEvent::DNS::EtcHosts script.pl
79              
80             =for markdown ```
81              
82             Enables this module globally. Additional arguments will be passed to
83             L constructor.
84              
85             =cut
86              
87             sub import {
88 1     1   12 my ($class, %args) = @_;
89 1         4 $GUARD = $class->register(%args);
90             }
91              
92             =head2 no AnyEvent::DNS::EtcHosts
93              
94             Disables this module globally.
95              
96             =cut
97              
98             sub unimport {
99 0     0   0 my ($class) = @_;
100 0         0 undef $GUARD;
101             }
102              
103             =head1 METHODS
104              
105             =head2 register
106              
107             =for markdown ```perl
108              
109             require AnyEvent::DNS::EtcHosts;
110              
111             $guard = AnyEvent::DNS::EtcHosts->register(%args);
112              
113             undef $guard;
114              
115             =for markdown ```
116              
117             Enables this module in lexical scope. The module will be disabled out of
118             scope. Additional arguments will be passed to L constructor.
119              
120             If you want to use AnyEvent::DNS::EtcHosts in lexical scope only, you should
121             use C rather than C keyword, because C method enables
122             AnyEvent::DNS::EtcHosts globally.
123              
124             =cut
125              
126             sub register {
127 3     3 1 44 my ($class, %args) = @_;
128              
129 3         15 my $old_resolver = $AnyEvent::DNS::RESOLVER;
130              
131 3         5 $AnyEvent::DNS::RESOLVER = do {
132             my $resolver = AnyEvent::DNS::EtcHosts->new(
133             untaint => 1,
134 3   50     52 max_outstanding => $ENV{PERL_ANYEVENT_MAX_OUTSTANDING_DNS} || 1,
135             %args
136             );
137 3 50       6036 if (not $args{server}) {
138             $ENV{PERL_ANYEVENT_RESOLV_CONF}
139             ? $resolver->_load_resolv_conf_file($ENV{PERL_ANYEVENT_RESOLV_CONF})
140 3 50       30 : $resolver->os_config;
141             }
142 3         9876 $resolver;
143             };
144              
145             # Overwrite original helper function only if exists
146 3         6 my $old_helper = do {
147 3 50 50     32 \&AnyEvent::Socket::_load_hosts_unless
148             if ((prototype 'AnyEvent::Socket::_load_hosts_unless') || '') eq '&$@';
149             };
150              
151 3 50       10 if ($old_helper) {
152 3     3   654 no warnings 'redefine';
  3         6  
  3         439  
153             *AnyEvent::Socket::_load_hosts_unless = sub (&$@) {
154 6     6   573 my ($cont, $cv, @dns) = @_;
155 6         23 $cv->end;
156 3         21 };
157             }
158              
159             return AnyEvent::Util::guard {
160 2     2   705 $AnyEvent::DNS::RESOLVER = $old_resolver;
161 3     3   41 no warnings 'redefine';
  3         6  
  3         4373  
162 2 50       597 *AnyEvent::Socket::_load_hosts_unless = $old_helper if $old_helper;
163 3         37 };
164             }
165              
166             # Helper functions taken from AnyEvent::Socket 7.05
167              
168             our %HOSTS; # $HOSTS{$nodename}[$ipv6] = [@aliases...]
169             our @HOSTS_CHECKING; # callbacks to call when hosts have been loaded
170             our $HOSTS_MTIME;
171              
172             sub _parse_hosts($) {
173 2     2   6 %HOSTS = ();
174              
175 2         9 for (split /\n/, $_[0]) {
176 6         24 s/#.*$//;
177 6         15 s/^[ \t]+//;
178 6         13 y/A-Z/a-z/;
179              
180 6         25 my ($addr, @aliases) = split /[ \t]+/;
181 6 50       18 next unless @aliases;
182              
183 6 100       21 if (my $ipv4 = AnyEvent::Socket::parse_ipv4 $addr) {
    50          
184 4         95 ($ipv4) = $ipv4 =~ /^(.*)$/s if AnyEvent::TAINT;
185 4         7 push @{ $HOSTS{$_}[0] }, $ipv4 for @aliases;
  4         18  
186             } elsif (my $ipv6 = AnyEvent::Socket::parse_ipv6 $addr) {
187 2         122 ($ipv6) = $ipv6 =~ /^(.*)$/s if AnyEvent::TAINT;
188 2         5 push @{ $HOSTS{$_}[1] }, $ipv6 for @aliases;
  2         9  
189             }
190             }
191             }
192              
193             # helper function - unless dns delivered results, check and parse hosts, then call continuation code
194             sub _load_hosts_unless(&$@) {
195 13     13   31 my ($cont, $cv, @dns) = @_;
196              
197 13 50       32 if (@dns) {
198 0         0 $cv->end;
199             } else {
200             my $etc_hosts
201             = length $ENV{PERL_ANYEVENT_HOSTS} ? $ENV{PERL_ANYEVENT_HOSTS}
202 13 50       40 : AnyEvent::WIN32 ? "$ENV{SystemRoot}/system32/drivers/etc/hosts"
203             : "/etc/hosts";
204              
205             push @HOSTS_CHECKING, sub {
206 13     13   33 $cont->();
207 13         26640 $cv->end;
208 13         40 };
209              
210 13 50       39 unless ($#HOSTS_CHECKING) {
211              
212             # we are not the first, so we actually have to do the work
213 13         66 require AnyEvent::IO;
214              
215             AnyEvent::IO::aio_stat(
216             $etc_hosts,
217             sub {
218 13 100 100 13   322 if ((stat _)[9] ne ($HOSTS_MTIME || 0)) {
219 2         12 AE::log 8 => "(re)loading $etc_hosts.";
220 2         12 $HOSTS_MTIME = (stat _)[9];
221              
222             # we might load a newer version of hosts,but that's a harmless race,
223             # as the next call will just load it again.
224             AnyEvent::IO::aio_load(
225             $etc_hosts,
226             sub {
227 2         142 _parse_hosts $_[0];
228 2         17 (shift @HOSTS_CHECKING)->() while @HOSTS_CHECKING;
229             }
230 2         10 );
231             } else {
232 11         42 (shift @HOSTS_CHECKING)->() while @HOSTS_CHECKING;
233             }
234             }
235 13         58 );
236             }
237             }
238             }
239              
240             =head2 request
241              
242             =for markdown ```perl
243              
244             $resolver->request($req, $cb->($res))
245              
246             =for markdown ```
247              
248             This is a wrapper for L->request method.
249              
250             =cut
251              
252             sub request {
253 13     13 1 9944 my ($self, $req, $cb) = @_;
254 13         21 warn "req = " . Dumper $req if DEBUG;
255              
256 13         29 my $node = my $domain = $req->{qd}[0][0];
257 13 100       51 $node =~ s/^_[a-z0-9-]*\._[a-z0-9-]*\.// if ($req->{qd}[0][1] eq 'srv');
258              
259 13         27 my $type = $req->{qd}[0][1];
260              
261 13         23 my (@ipv4, @ipv6, @srv);
262              
263 13         309 my $cv = AE::cv;
264              
265 13         99 $cv->begin;
266             _load_hosts_unless {
267 13 50   13   36 if (exists $HOSTS{$node}) {
268 13 100       64 if ($type =~ /^([*]|srv)$/) {
269 5         10 push @srv, $node;
270             }
271 13 50       41 if (ref $HOSTS{$node} eq 'ARRAY') {
272 13 50 66     57 if ($type =~ /^([*]|a)$/ and exists $HOSTS{$node}[0]) {
273 5         10 push @ipv4, @{ $HOSTS{$node}[0] };
  5         13  
274             }
275 13 50 66     54 if ($type =~ /^([*]|aaaa)$/ and exists $HOSTS{$node}[1]) {
276 5         11 push @ipv6, @{ $HOSTS{$node}[1] };
  5         14  
277             }
278             }
279             }
280              
281 13 50 100     68 if (@ipv4 or @ipv6 or @srv) {
      66        
282             my $res = {
283             id => int rand(0xffff),
284             op => 'query',
285             rc => 'noerror',
286             qr => 1,
287             aa => '',
288             tc => '',
289             rd => $req->{rd},
290             ra => 1,
291             ad => '',
292             cd => '',
293             qd => $req->{qd},
294             an => [
295 5         39 (map { [$domain, 'srv', 'in', 0, 0, 0, 0, $_] } @srv),
296 10         82 (map { [$node, 'a', 'in', 0, AnyEvent::Socket::format_ipv4 $_ ] } @ipv4),
297 13         59 (map { [$node, 'aaaa', 'in', 0, AnyEvent::Socket::format_ipv6 $_ ] } @ipv6),
  5         21  
298             ],
299             ns => [],
300             ar => [],
301             };
302              
303 13         237 warn "res = " . Dumper $res if DEBUG;
304              
305 13         39 return $cb->($res);
306             }
307              
308             return $self->SUPER::request(
309             $req,
310             sub {
311 0         0 my ($res) = @_;
312 0         0 warn "SUPER::request res = " . Dumper $res if DEBUG;
313 0         0 $cb->($res);
314             }
315 0         0 );
316              
317             }
318 13         130 $cv;
319              
320 13         677 return;
321             }
322              
323             1;
324              
325             __END__