File Coverage

blib/lib/AnyEvent/DNS/EtcHosts.pm
Criterion Covered Total %
statement 105 112 93.7
branch 25 38 65.7
condition 13 18 72.2
subroutine 20 21 95.2
pod 2 2 100.0
total 165 191 86.3


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