ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent/lib/AnyEvent/DNS.pm
Revision: 1.30
Committed: Sun May 25 04:31:49 2008 UTC (16 years, 1 month ago) by root
Branch: MAIN
Changes since 1.29: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::DNS - fully asynchronous DNS resolution
4
5 =head1 SYNOPSIS
6
7 use AnyEvent::DNS;
8
9 my $cv = AnyEvent->condvar;
10 AnyEvent::DNS::a "www.google.de", $cv;
11 # ... later
12 my @addrs = $cv->recv;
13
14 =head1 DESCRIPTION
15
16 This module offers both a number of DNS convenience functions as well
17 as a fully asynchronous and high-performance pure-perl stub resolver.
18
19 The stub resolver supports DNS over UDP, optional EDNS0 support for up to
20 4kiB datagrams and automatically falls back to virtual circuit mode for
21 large responses.
22
23 =head2 CONVENIENCE FUNCTIONS
24
25 =over 4
26
27 =cut
28
29 package AnyEvent::DNS;
30
31 no warnings;
32 use strict;
33
34 use Socket qw(AF_INET SOCK_DGRAM SOCK_STREAM);
35
36 use AnyEvent::Handle ();
37
38 =item AnyEvent::DNS::addr $node, $service, $proto, $family, $type, $cb->([$family, $type, $proto, $sockaddr], ...)
39
40 Tries to resolve the given nodename and service name into protocol families
41 and sockaddr structures usable to connect to this node and service in a
42 protocol-independent way. It works remotely similar to the getaddrinfo
43 posix function.
44
45 C<$node> is either an IPv4 or IPv6 address or a hostname, C<$service> is
46 either a service name (port name from F</etc/services>) or a numerical
47 port number. If both C<$node> and C<$service> are names, then SRV records
48 will be consulted to find the real service, otherwise they will be
49 used as-is. If you know that the service name is not in your services
50 database, then you can specify the service in the format C<name=port>
51 (e.g. C<http=80>).
52
53 C<$proto> must be a protocol name, currently C<tcp>, C<udp> or
54 C<sctp>. The default is C<tcp>.
55
56 C<$family> must be either C<0> (meaning any protocol is OK), C<4> (use
57 only IPv4) or C<6> (use only IPv6). This setting might be influenced by
58 C<$ENV{PERL_ANYEVENT_PROTOCOLS}>.
59
60 C<$type> must be C<SOCK_STREAM>, C<SOCK_DGRAM> or C<SOCK_SEQPACKET> (or
61 C<undef> in which case it gets automatically chosen).
62
63 The callback will receive zero or more array references that contain
64 C<$family, $type, $proto> for use in C<socket> and a binary
65 C<$sockaddr> for use in C<connect> (or C<bind>).
66
67 The application should try these in the order given.
68
69 Example:
70
71 AnyEvent::DNS::addr "google.com", "http", 0, undef, undef, sub { ... };
72
73 =item AnyEvent::DNS::a $domain, $cb->(@addrs)
74
75 Tries to resolve the given domain to IPv4 address(es).
76
77 =item AnyEvent::DNS::aaaa $domain, $cb->(@addrs)
78
79 Tries to resolve the given domain to IPv6 address(es).
80
81 =item AnyEvent::DNS::mx $domain, $cb->(@hostnames)
82
83 Tries to resolve the given domain into a sorted (lower preference value
84 first) list of domain names.
85
86 =item AnyEvent::DNS::ns $domain, $cb->(@hostnames)
87
88 Tries to resolve the given domain name into a list of name servers.
89
90 =item AnyEvent::DNS::txt $domain, $cb->(@hostnames)
91
92 Tries to resolve the given domain name into a list of text records.
93
94 =item AnyEvent::DNS::srv $service, $proto, $domain, $cb->(@srv_rr)
95
96 Tries to resolve the given service, protocol and domain name into a list
97 of service records.
98
99 Each srv_rr is an array reference with the following contents:
100 C<[$priority, $weight, $transport, $target]>.
101
102 They will be sorted with lowest priority, highest weight first (TODO:
103 should use the RFC algorithm to reorder same-priority records for weight).
104
105 Example:
106
107 AnyEvent::DNS::srv "sip", "udp", "schmorp.de", sub { ...
108 # @_ = ( [10, 10, 5060, "sip1.schmorp.de" ] )
109
110 =item AnyEvent::DNS::ptr $ipv4_or_6, $cb->(@hostnames)
111
112 Tries to reverse-resolve the given IPv4 or IPv6 address (in textual form)
113 into it's hostname(s).
114
115 Example:
116
117 AnyEvent::DNS::ptr "2001:500:2f::f", sub { print shift };
118 # => f.root-servers.net
119
120 =item AnyEvent::DNS::any $domain, $cb->(@rrs)
121
122 Tries to resolve the given domain and passes all resource records found to
123 the callback.
124
125 =cut
126
127 sub resolver;
128
129 sub a($$) {
130 my ($domain, $cb) = @_;
131
132 resolver->resolve ($domain => "a", sub {
133 $cb->(map $_->[3], @_);
134 });
135 }
136
137 sub aaaa($$) {
138 my ($domain, $cb) = @_;
139
140 resolver->resolve ($domain => "aaaa", sub {
141 $cb->(map $_->[3], @_);
142 });
143 }
144
145 sub mx($$) {
146 my ($domain, $cb) = @_;
147
148 resolver->resolve ($domain => "mx", sub {
149 $cb->(map $_->[4], sort { $a->[3] <=> $b->[3] } @_);
150 });
151 }
152
153 sub ns($$) {
154 my ($domain, $cb) = @_;
155
156 resolver->resolve ($domain => "ns", sub {
157 $cb->(map $_->[3], @_);
158 });
159 }
160
161 sub txt($$) {
162 my ($domain, $cb) = @_;
163
164 resolver->resolve ($domain => "txt", sub {
165 $cb->(map $_->[3], @_);
166 });
167 }
168
169 sub srv($$$$) {
170 my ($service, $proto, $domain, $cb) = @_;
171
172 # todo, ask for any and check glue records
173 resolver->resolve ("_$service._$proto.$domain" => "srv", sub {
174 $cb->(map [@$_[3,4,5,6]], sort { $a->[3] <=> $b->[3] || $b->[4] <=> $a->[4] } @_);
175 });
176 }
177
178 sub ptr($$) {
179 my ($ip, $cb) = @_;
180
181 $ip = AnyEvent::Socket::parse_ip ($ip)
182 or return $cb->();
183
184 if (4 == length $ip) {
185 $ip = join ".", (reverse split /\./, $ip), "in-addr.arpa.";
186 } else {
187 $ip = join ".", (reverse split //, unpack "H*", $ip), "ip6.arpa.";
188 }
189
190 resolver->resolve ($ip => "ptr", sub {
191 $cb->(map $_->[3], @_);
192 });
193 }
194
195 sub any($$) {
196 my ($domain, $cb) = @_;
197
198 resolver->resolve ($domain => "*", $cb);
199 }
200
201 #############################################################################
202
203 sub addr($$$$$$) {
204 my ($node, $service, $proto, $family, $type, $cb) = @_;
205
206 unless (&AnyEvent::Util::AF_INET6) {
207 $family != 6
208 or return $cb->();
209
210 $family ||= 4;
211 }
212
213 $cb->() if $family == 4 && !$AnyEvent::PROTOCOL{ipv4};
214 $cb->() if $family == 6 && !$AnyEvent::PROTOCOL{ipv6};
215
216 $family ||=4 unless $AnyEvent::PROTOCOL{ipv6};
217 $family ||=6 unless $AnyEvent::PROTOCOL{ipv4};
218
219 $proto ||= "tcp";
220 $type ||= $proto eq "udp" ? SOCK_DGRAM : SOCK_STREAM;
221
222 my $proton = (getprotobyname $proto)[2]
223 or Carp::croak "$proto: protocol unknown";
224
225 my $port;
226
227 if ($service =~ /^(\S+)=(\d+)$/) {
228 ($service, $port) = ($1, $2);
229 } elsif ($service =~ /^\d+$/) {
230 ($service, $port) = (undef, $service);
231 } else {
232 $port = (getservbyname $service, $proto)[2]
233 or Carp::croak "$service/$proto: service unknown";
234 }
235
236 my @target = [$node, $port];
237
238 # resolve a records / provide sockaddr structures
239 my $resolve = sub {
240 my @res;
241 my $cv = AnyEvent->condvar (cb => sub {
242 $cb->(
243 map $_->[2],
244 sort {
245 $AnyEvent::PROTOCOL{$a->[1]} <=> $AnyEvent::PROTOCOL{$b->[1]}
246 or $a->[0] <=> $b->[0]
247 }
248 @res
249 )
250 });
251
252 $cv->begin;
253 for my $idx (0 .. $#target) {
254 my ($node, $port) = @{ $target[$idx] };
255
256 if (my $noden = AnyEvent::Socket::parse_ip ($node)) {
257 if (4 == length $noden && $family != 6) {
258 push @res, [$idx, "ipv4", [AF_INET, $type, $proton,
259 AnyEvent::Socket::pack_sockaddr ($port, $noden)]]
260 }
261
262 if (16 == length $noden && $family != 4) {
263 push @res, [$idx, "ipv6", [&AnyEvent::Util::AF_INET6, $type, $proton,
264 AnyEvent::Socket::pack_sockaddr ( $port, $noden)]]
265 }
266 } else {
267 # ipv4
268 if ($family != 6) {
269 $cv->begin;
270 a $node, sub {
271 push @res, [$idx, "ipv4", [AF_INET, $type, $proton,
272 AnyEvent::Socket::pack_sockaddr ($port, AnyEvent::Socket::parse_ipv4 ($_))]]
273 for @_;
274 $cv->end;
275 };
276 }
277
278 # ipv6
279 if ($family != 4) {
280 $cv->begin;
281 aaaa $node, sub {
282 push @res, [$idx, "ipv6", [&AnyEvent::Socket::AF_INET6, $type, $proton,
283 AnyEvent::Socket::pack_sockaddr ($port, AnyEvent::Socket::parse_ipv6 ($_))]]
284 for @_;
285 $cv->end;
286 };
287 }
288 }
289 }
290 $cv->end;
291 };
292
293 # try srv records, if applicable
294 if ($node eq "localhost") {
295 @target = (["127.0.0.1", $port], ["::1", $port]);
296 &$resolve;
297 } elsif (defined $service && !AnyEvent::Socket::parse_ip ($node)) {
298 srv $service, $proto, $node, sub {
299 my (@srv) = @_;
300
301 # no srv records, continue traditionally
302 @srv
303 or return &$resolve;
304
305 # only srv record has "." => abort
306 $srv[0][2] ne "." || $#srv
307 or return $cb->();
308
309 # use srv records then
310 @target = map ["$_->[3].", $_->[2]],
311 grep $_->[3] ne ".",
312 @srv;
313
314 &$resolve;
315 };
316 } else {
317 &$resolve;
318 }
319 }
320
321 #############################################################################
322
323 =back
324
325 =head2 LOW-LEVEL DNS EN-/DECODING FUNCTIONS
326
327 =over 4
328
329 =item $AnyEvent::DNS::EDNS0
330
331 This variable decides whether dns_pack automatically enables EDNS0
332 support. By default, this is disabled (C<0>), unless overridden by
333 C<$ENV{PERL_ANYEVENT_EDNS0>), but when set to C<1>, AnyEvent::DNS will use
334 EDNS0 in all requests.
335
336 =cut
337
338 our $EDNS0 = $ENV{PERL_ANYEVENT_EDNS0} * 1; # set to 1 to enable (partial) edns0
339
340 our %opcode_id = (
341 query => 0,
342 iquery => 1,
343 status => 2,
344 notify => 4,
345 update => 5,
346 map +($_ => $_), 3, 6..15
347 );
348
349 our %opcode_str = reverse %opcode_id;
350
351 our %rcode_id = (
352 noerror => 0,
353 formerr => 1,
354 servfail => 2,
355 nxdomain => 3,
356 notimp => 4,
357 refused => 5,
358 yxdomain => 6, # Name Exists when it should not [RFC 2136]
359 yxrrset => 7, # RR Set Exists when it should not [RFC 2136]
360 nxrrset => 8, # RR Set that should exist does not [RFC 2136]
361 notauth => 9, # Server Not Authoritative for zone [RFC 2136]
362 notzone => 10, # Name not contained in zone [RFC 2136]
363 # EDNS0 16 BADVERS Bad OPT Version [RFC 2671]
364 # EDNS0 16 BADSIG TSIG Signature Failure [RFC 2845]
365 # EDNS0 17 BADKEY Key not recognized [RFC 2845]
366 # EDNS0 18 BADTIME Signature out of time window [RFC 2845]
367 # EDNS0 19 BADMODE Bad TKEY Mode [RFC 2930]
368 # EDNS0 20 BADNAME Duplicate key name [RFC 2930]
369 # EDNS0 21 BADALG Algorithm not supported [RFC 2930]
370 map +($_ => $_), 11..15
371 );
372
373 our %rcode_str = reverse %rcode_id;
374
375 our %type_id = (
376 a => 1,
377 ns => 2,
378 md => 3,
379 mf => 4,
380 cname => 5,
381 soa => 6,
382 mb => 7,
383 mg => 8,
384 mr => 9,
385 null => 10,
386 wks => 11,
387 ptr => 12,
388 hinfo => 13,
389 minfo => 14,
390 mx => 15,
391 txt => 16,
392 aaaa => 28,
393 srv => 33,
394 opt => 41,
395 spf => 99,
396 tkey => 249,
397 tsig => 250,
398 ixfr => 251,
399 axfr => 252,
400 mailb => 253,
401 "*" => 255,
402 );
403
404 our %type_str = reverse %type_id;
405
406 our %class_id = (
407 in => 1,
408 ch => 3,
409 hs => 4,
410 none => 254,
411 "*" => 255,
412 );
413
414 our %class_str = reverse %class_id;
415
416 # names MUST have a trailing dot
417 sub _enc_qname($) {
418 pack "(C/a*)*", (split /\./, shift), ""
419 }
420
421 sub _enc_qd() {
422 (_enc_qname $_->[0]) . pack "nn",
423 ($_->[1] > 0 ? $_->[1] : $type_id {$_->[1]}),
424 ($_->[2] > 0 ? $_->[2] : $class_id{$_->[2] || "in"})
425 }
426
427 sub _enc_rr() {
428 die "encoding of resource records is not supported";
429 }
430
431 =item $pkt = AnyEvent::DNS::dns_pack $dns
432
433 Packs a perl data structure into a DNS packet. Reading RFC1034 is strongly
434 recommended, then everything will be totally clear. Or maybe not.
435
436 Resource records are not yet encodable.
437
438 Examples:
439
440 # very simple request, using lots of default values:
441 { rd => 1, qd => [ [ "host.domain", "a"] ] }
442
443 # more complex example, showing how flags etc. are named:
444
445 {
446 id => 10000,
447 op => "query",
448 rc => "nxdomain",
449
450 # flags
451 qr => 1,
452 aa => 0,
453 tc => 0,
454 rd => 0,
455 ra => 0,
456 ad => 0,
457 cd => 0,
458
459 qd => [@rr], # query section
460 an => [@rr], # answer section
461 ns => [@rr], # authority section
462 ar => [@rr], # additional records section
463 }
464
465 =cut
466
467 sub dns_pack($) {
468 my ($req) = @_;
469
470 pack "nn nnnn a* a* a* a* a*",
471 $req->{id},
472
473 ! !$req->{qr} * 0x8000
474 + $opcode_id{$req->{op}} * 0x0800
475 + ! !$req->{aa} * 0x0400
476 + ! !$req->{tc} * 0x0200
477 + ! !$req->{rd} * 0x0100
478 + ! !$req->{ra} * 0x0080
479 + ! !$req->{ad} * 0x0020
480 + ! !$req->{cd} * 0x0010
481 + $rcode_id{$req->{rc}} * 0x0001,
482
483 scalar @{ $req->{qd} || [] },
484 scalar @{ $req->{an} || [] },
485 scalar @{ $req->{ns} || [] },
486 $EDNS0 + scalar @{ $req->{ar} || [] }, # include EDNS0 option here
487
488 (join "", map _enc_qd, @{ $req->{qd} || [] }),
489 (join "", map _enc_rr, @{ $req->{an} || [] }),
490 (join "", map _enc_rr, @{ $req->{ns} || [] }),
491 (join "", map _enc_rr, @{ $req->{ar} || [] }),
492
493 ($EDNS0 ? pack "C nnNn", 0, 41, 4096, 0, 0 : "") # EDNS0, 4kiB udp payload size
494 }
495
496 our $ofs;
497 our $pkt;
498
499 # bitches
500 sub _dec_qname {
501 my @res;
502 my $redir;
503 my $ptr = $ofs;
504 my $cnt;
505
506 while () {
507 return undef if ++$cnt >= 256; # to avoid DoS attacks
508
509 my $len = ord substr $pkt, $ptr++, 1;
510
511 if ($len & 0xc0) {
512 $ptr++;
513 $ofs = $ptr if $ptr > $ofs;
514 $ptr = (unpack "n", substr $pkt, $ptr - 2, 2) & 0x3fff;
515 } elsif ($len) {
516 push @res, substr $pkt, $ptr, $len;
517 $ptr += $len;
518 } else {
519 $ofs = $ptr if $ptr > $ofs;
520 return join ".", @res;
521 }
522 }
523 }
524
525 sub _dec_qd {
526 my $qname = _dec_qname;
527 my ($qt, $qc) = unpack "nn", substr $pkt, $ofs; $ofs += 4;
528 [$qname, $type_str{$qt} || $qt, $class_str{$qc} || $qc]
529 }
530
531 our %dec_rr = (
532 1 => sub { join ".", unpack "C4", $_ }, # a
533 2 => sub { local $ofs = $ofs - length; _dec_qname }, # ns
534 5 => sub { local $ofs = $ofs - length; _dec_qname }, # cname
535 6 => sub {
536 local $ofs = $ofs - length;
537 my $mname = _dec_qname;
538 my $rname = _dec_qname;
539 ($mname, $rname, unpack "NNNNN", substr $pkt, $ofs)
540 }, # soa
541 11 => sub { ((join ".", unpack "C4", $_), unpack "C a*", substr $_, 4) }, # wks
542 12 => sub { local $ofs = $ofs - length; _dec_qname }, # ptr
543 13 => sub { unpack "C/a* C/a*", $_ }, # hinfo
544 15 => sub { local $ofs = $ofs + 2 - length; ((unpack "n", $_), _dec_qname) }, # mx
545 16 => sub { unpack "(C/a*)*", $_ }, # txt
546 28 => sub { AnyEvent::Socket::format_ip ($_) }, # aaaa
547 33 => sub { local $ofs = $ofs + 6 - length; ((unpack "nnn", $_), _dec_qname) }, # srv
548 99 => sub { unpack "(C/a*)*", $_ }, # spf
549 );
550
551 sub _dec_rr {
552 my $qname = _dec_qname;
553
554 my ($rt, $rc, $ttl, $rdlen) = unpack "nn N n", substr $pkt, $ofs; $ofs += 10;
555 local $_ = substr $pkt, $ofs, $rdlen; $ofs += $rdlen;
556
557 [
558 $qname,
559 $type_str{$rt} || $rt,
560 $class_str{$rc} || $rc,
561 ($dec_rr{$rt} || sub { $_ })->(),
562 ]
563 }
564
565 =item $dns = AnyEvent::DNS::dns_unpack $pkt
566
567 Unpacks a DNS packet into a perl data structure.
568
569 Examples:
570
571 # an unsuccessful reply
572 {
573 'qd' => [
574 [ 'ruth.plan9.de.mach.uni-karlsruhe.de', '*', 'in' ]
575 ],
576 'rc' => 'nxdomain',
577 'ar' => [],
578 'ns' => [
579 [
580 'uni-karlsruhe.de',
581 'soa',
582 'in',
583 'netserv.rz.uni-karlsruhe.de',
584 'hostmaster.rz.uni-karlsruhe.de',
585 2008052201, 10800, 1800, 2592000, 86400
586 ]
587 ],
588 'tc' => '',
589 'ra' => 1,
590 'qr' => 1,
591 'id' => 45915,
592 'aa' => '',
593 'an' => [],
594 'rd' => 1,
595 'op' => 'query'
596 }
597
598 # a successful reply
599
600 {
601 'qd' => [ [ 'www.google.de', 'a', 'in' ] ],
602 'rc' => 0,
603 'ar' => [
604 [ 'a.l.google.com', 'a', 'in', '209.85.139.9' ],
605 [ 'b.l.google.com', 'a', 'in', '64.233.179.9' ],
606 [ 'c.l.google.com', 'a', 'in', '64.233.161.9' ],
607 ],
608 'ns' => [
609 [ 'l.google.com', 'ns', 'in', 'a.l.google.com' ],
610 [ 'l.google.com', 'ns', 'in', 'b.l.google.com' ],
611 ],
612 'tc' => '',
613 'ra' => 1,
614 'qr' => 1,
615 'id' => 64265,
616 'aa' => '',
617 'an' => [
618 [ 'www.google.de', 'cname', 'in', 'www.google.com' ],
619 [ 'www.google.com', 'cname', 'in', 'www.l.google.com' ],
620 [ 'www.l.google.com', 'a', 'in', '66.249.93.104' ],
621 [ 'www.l.google.com', 'a', 'in', '66.249.93.147' ],
622 ],
623 'rd' => 1,
624 'op' => 0
625 }
626
627 =cut
628
629 sub dns_unpack($) {
630 local $pkt = shift;
631 my ($id, $flags, $qd, $an, $ns, $ar)
632 = unpack "nn nnnn A*", $pkt;
633
634 local $ofs = 6 * 2;
635
636 {
637 id => $id,
638 qr => ! ! ($flags & 0x8000),
639 aa => ! ! ($flags & 0x0400),
640 tc => ! ! ($flags & 0x0200),
641 rd => ! ! ($flags & 0x0100),
642 ra => ! ! ($flags & 0x0080),
643 ad => ! ! ($flags & 0x0020),
644 cd => ! ! ($flags & 0x0010),
645 op => $opcode_str{($flags & 0x001e) >> 11},
646 rc => $rcode_str{($flags & 0x000f)},
647
648 qd => [map _dec_qd, 1 .. $qd],
649 an => [map _dec_rr, 1 .. $an],
650 ns => [map _dec_rr, 1 .. $ns],
651 ar => [map _dec_rr, 1 .. $ar],
652 }
653 }
654
655 #############################################################################
656
657 =back
658
659 =head2 THE AnyEvent::DNS RESOLVER CLASS
660
661 This is the class which does the actual protocol work.
662
663 =over 4
664
665 =cut
666
667 use Carp ();
668 use Scalar::Util ();
669 use Socket ();
670
671 our $NOW;
672
673 =item AnyEvent::DNS::resolver
674
675 This function creates and returns a resolver that is ready to use and
676 should mimic the default resolver for your system as good as possible.
677
678 It only ever creates one resolver and returns this one on subsequent
679 calls.
680
681 Unless you have special needs, prefer this function over creating your own
682 resolver object.
683
684 =cut
685
686 our $RESOLVER;
687
688 sub resolver() {
689 $RESOLVER || do {
690 $RESOLVER = new AnyEvent::DNS;
691 $RESOLVER->os_config;
692 $RESOLVER
693 }
694 }
695
696 =item $resolver = new AnyEvent::DNS key => value...
697
698 Creates and returns a new resolver.
699
700 The following options are supported:
701
702 =over 4
703
704 =item server => [...]
705
706 A list of server addresses (default: C<v127.0.0.1>) in network format (4
707 octets for IPv4, 16 octets for IPv6 - not yet supported).
708
709 =item timeout => [...]
710
711 A list of timeouts to use (also determines the number of retries). To make
712 three retries with individual time-outs of 2, 5 and 5 seconds, use C<[2,
713 5, 5]>, which is also the default.
714
715 =item search => [...]
716
717 The default search list of suffixes to append to a domain name (default: none).
718
719 =item ndots => $integer
720
721 The number of dots (default: C<1>) that a name must have so that the resolver
722 tries to resolve the name without any suffixes first.
723
724 =item max_outstanding => $integer
725
726 Most name servers do not handle many parallel requests very well. This option
727 limits the number of outstanding requests to C<$n> (default: C<10>), that means
728 if you request more than this many requests, then the additional requests will be queued
729 until some other requests have been resolved.
730
731 =item reuse => $seconds
732
733 The number of seconds (default: C<300>) that a query id cannot be re-used
734 after a timeout. If there as no time-out then query id's can be reused
735 immediately.
736
737 =back
738
739 =cut
740
741 sub new {
742 my ($class, %arg) = @_;
743
744 socket my $fh, AF_INET, &Socket::SOCK_DGRAM, 0
745 or Carp::croak "socket: $!";
746
747 AnyEvent::Util::fh_nonblocking $fh, 1;
748
749 my $self = bless {
750 server => [v127.0.0.1],
751 timeout => [2, 5, 5],
752 search => [],
753 ndots => 1,
754 max_outstanding => 10,
755 reuse => 300, # reuse id's after 5 minutes only, if possible
756 %arg,
757 fh => $fh,
758 reuse_q => [],
759 }, $class;
760
761 # search should default to gethostname's domain
762 # but perl lacks a good posix module
763
764 Scalar::Util::weaken (my $wself = $self);
765 $self->{rw} = AnyEvent->io (fh => $fh, poll => "r", cb => sub { $wself->_recv });
766
767 $self->_compile;
768
769 $self
770 }
771
772 =item $resolver->parse_resolv_conv ($string)
773
774 Parses the given string as if it were a F<resolv.conf> file. The following
775 directives are supported (but not necessarily implemented).
776
777 C<#>-style comments, C<nameserver>, C<domain>, C<search>, C<sortlist>,
778 C<options> (C<timeout>, C<attempts>, C<ndots>).
779
780 Everything else is silently ignored.
781
782 =cut
783
784 sub parse_resolv_conf {
785 my ($self, $resolvconf) = @_;
786
787 $self->{server} = [];
788 $self->{search} = [];
789
790 my $attempts;
791
792 for (split /\n/, $resolvconf) {
793 if (/^\s*#/) {
794 # comment
795 } elsif (/^\s*nameserver\s+(\S+)\s*$/i) {
796 my $ip = $1;
797 if (my $ipn = AnyEvent::Socket::parse_ip ($ip)) {
798 push @{ $self->{server} }, $ipn;
799 } else {
800 warn "nameserver $ip invalid and ignored\n";
801 }
802 } elsif (/^\s*domain\s+(\S*)\s+$/i) {
803 $self->{search} = [$1];
804 } elsif (/^\s*search\s+(.*?)\s*$/i) {
805 $self->{search} = [split /\s+/, $1];
806 } elsif (/^\s*sortlist\s+(.*?)\s*$/i) {
807 # ignored, NYI
808 } elsif (/^\s*options\s+(.*?)\s*$/i) {
809 for (split /\s+/, $1) {
810 if (/^timeout:(\d+)$/) {
811 $self->{timeout} = [$1];
812 } elsif (/^attempts:(\d+)$/) {
813 $attempts = $1;
814 } elsif (/^ndots:(\d+)$/) {
815 $self->{ndots} = $1;
816 } else {
817 # debug, rotate, no-check-names, inet6
818 }
819 }
820 }
821 }
822
823 $self->{timeout} = [($self->{timeout}[0]) x $attempts]
824 if $attempts;
825
826 $self->_compile;
827 }
828
829 =item $resolver->os_config
830
831 Tries so load and parse F</etc/resolv.conf> on portable operating systems. Tries various
832 egregious hacks on windows to force the DNS servers and searchlist out of the system.
833
834 =cut
835
836 sub os_config {
837 my ($self) = @_;
838
839 if ($^O =~ /mswin32|cygwin/i) {
840 # yeah, it suxx... lets hope DNS is DNS in all locales
841
842 if (open my $fh, "ipconfig /all |") {
843 delete $self->{server};
844 delete $self->{search};
845
846 while (<$fh>) {
847 # first DNS.* is suffix list
848 if (/^\s*DNS/) {
849 while (/\s+([[:alnum:].\-]+)\s*$/) {
850 push @{ $self->{search} }, $1;
851 $_ = <$fh>;
852 }
853 last;
854 }
855 }
856
857 while (<$fh>) {
858 # second DNS.* is server address list
859 if (/^\s*DNS/) {
860 while (/\s+(\d+\.\d+\.\d+\.\d+)\s*$/) {
861 my $ipn = AnyEvent::Socket::parse_ip ("$1"); # "" is necessary here, apparently
862 push @{ $self->{server} }, $ipn
863 if $ipn;
864 $_ = <$fh>;
865 }
866 last;
867 }
868 }
869
870 $self->_compile;
871 }
872 } else {
873 # try resolv.conf everywhere
874
875 if (open my $fh, "</etc/resolv.conf") {
876 local $/;
877 $self->parse_resolv_conf (<$fh>);
878 }
879 }
880 }
881
882 sub _compile {
883 my $self = shift;
884
885 my @retry;
886
887 for my $timeout (@{ $self->{timeout} }) {
888 for my $server (@{ $self->{server} }) {
889 push @retry, [$server, $timeout];
890 }
891 }
892
893 $self->{retry} = \@retry;
894 }
895
896 sub _feed {
897 my ($self, $res) = @_;
898
899 $res = dns_unpack $res
900 or return;
901
902 my $id = $self->{id}{$res->{id}};
903
904 return unless ref $id;
905
906 $NOW = time;
907 $id->[1]->($res);
908 }
909
910 sub _recv {
911 my ($self) = @_;
912
913 while (my $peer = recv $self->{fh}, my $res, 4096, 0) {
914 my ($port, $host) = AnyEvent::Socket::unpack_sockaddr ($peer);
915
916 return unless $port == 53 && grep $_ eq $host, @{ $self->{server} };
917
918 $self->_feed ($res);
919 }
920 }
921
922 sub _free_id {
923 my ($self, $id, $timeout) = @_;
924
925 if ($timeout) {
926 # we need to block the id for a while
927 $self->{id}{$id} = 1;
928 push @{ $self->{reuse_q} }, [$NOW + $self->{reuse}, $id];
929 } else {
930 # we can quickly recycle the id
931 delete $self->{id}{$id};
932 }
933
934 --$self->{outstanding};
935 $self->_scheduler;
936 }
937
938 # execute a single request, involves sending it with timeouts to multiple servers
939 sub _exec {
940 my ($self, $req) = @_;
941
942 my $retry; # of retries
943 my $do_retry;
944
945 $do_retry = sub {
946 my $retry_cfg = $self->{retry}[$retry++]
947 or do {
948 # failure
949 $self->_free_id ($req->[2], $retry > 1);
950 undef $do_retry; return $req->[1]->();
951 };
952
953 my ($server, $timeout) = @$retry_cfg;
954
955 $self->{id}{$req->[2]} = [AnyEvent->timer (after => $timeout, cb => sub {
956 $NOW = time;
957
958 # timeout, try next
959 &$do_retry;
960 }), sub {
961 my ($res) = @_;
962
963 if ($res->{tc}) {
964 # success, but truncated, so use tcp
965 AnyEvent::Socket::tcp_connect ((Socket::inet_ntoa $server), 53, sub {
966 my ($fh) = @_
967 or return &$do_retry;
968
969 my $handle = new AnyEvent::Handle
970 fh => $fh,
971 on_error => sub {
972 # failure, try next
973 &$do_retry;
974 };
975
976 $handle->push_write (pack "n/a", $req->[0]);
977 $handle->push_read (chunk => 2, sub {
978 $handle->unshift_read (chunk => (unpack "n", $_[1]), sub {
979 $self->_feed ($_[1]);
980 });
981 });
982 shutdown $fh, 1;
983
984 }, sub { $timeout });
985
986 } else {
987 # success
988 $self->_free_id ($req->[2], $retry > 1);
989 undef $do_retry; return $req->[1]->($res);
990 }
991 }];
992
993 send $self->{fh}, $req->[0], 0, AnyEvent::Socket::pack_sockaddr (53, $server);
994 };
995
996 &$do_retry;
997 }
998
999 sub _scheduler {
1000 my ($self) = @_;
1001
1002 $NOW = time;
1003
1004 # first clear id reuse queue
1005 delete $self->{id}{ (shift @{ $self->{reuse_q} })->[1] }
1006 while @{ $self->{reuse_q} } && $self->{reuse_q}[0][0] <= $NOW;
1007
1008 while ($self->{outstanding} < $self->{max_outstanding}) {
1009
1010 if (@{ $self->{reuse_q} } >= 30000) {
1011 # we ran out of ID's, wait a bit
1012 $self->{reuse_to} ||= AnyEvent->timer (after => $self->{reuse_q}[0][0] - $NOW, cb => sub {
1013 delete $self->{reuse_to};
1014 $self->_scheduler;
1015 });
1016 last;
1017 }
1018
1019 my $req = shift @{ $self->{queue} }
1020 or last;
1021
1022 while () {
1023 $req->[2] = int rand 65536;
1024 last unless exists $self->{id}{$req->[2]};
1025 }
1026
1027 ++$self->{outstanding};
1028 $self->{id}{$req->[2]} = 1;
1029 substr $req->[0], 0, 2, pack "n", $req->[2];
1030
1031 $self->_exec ($req);
1032 }
1033 }
1034
1035 =item $resolver->request ($req, $cb->($res))
1036
1037 Sends a single request (a hash-ref formated as specified for
1038 C<dns_pack>) to the configured nameservers including
1039 retries. Calls the callback with the decoded response packet if a reply
1040 was received, or no arguments on timeout.
1041
1042 =cut
1043
1044 sub request($$) {
1045 my ($self, $req, $cb) = @_;
1046
1047 push @{ $self->{queue} }, [dns_pack $req, $cb];
1048 $self->_scheduler;
1049 }
1050
1051 =item $resolver->resolve ($qname, $qtype, %options, $cb->($rcode, @rr))
1052
1053 Queries the DNS for the given domain name C<$qname> of type C<$qtype> (a
1054 qtype of "*" is supported and means "any").
1055
1056 The callback will be invoked with a list of matching result records or
1057 none on any error or if the name could not be found.
1058
1059 CNAME chains (although illegal) are followed up to a length of 8.
1060
1061 Note that this resolver is just a stub resolver: it requires a name server
1062 supporting recursive queries, will not do any recursive queries itself and
1063 is not secure when used against an untrusted name server.
1064
1065 The following options are supported:
1066
1067 =over 4
1068
1069 =item search => [$suffix...]
1070
1071 Use the given search list (which might be empty), by appending each one
1072 in turn to the C<$qname>. If this option is missing then the configured
1073 C<ndots> and C<search> define its value. If the C<$qname> ends in a dot,
1074 then the searchlist will be ignored.
1075
1076 =item accept => [$type...]
1077
1078 Lists the acceptable result types: only result types in this set will be
1079 accepted and returned. The default includes the C<$qtype> and nothing
1080 else.
1081
1082 =item class => "class"
1083
1084 Specify the query class ("in" for internet, "ch" for chaosnet and "hs" for
1085 hesiod are the only ones making sense). The default is "in", of course.
1086
1087 =back
1088
1089 Examples:
1090
1091 $res->resolve ("ruth.plan9.de", "a", sub {
1092 warn Dumper [@_];
1093 });
1094
1095 [
1096 [
1097 'ruth.schmorp.de',
1098 'a',
1099 'in',
1100 '129.13.162.95'
1101 ]
1102 ]
1103
1104 $res->resolve ("test1.laendle", "*",
1105 accept => ["a", "aaaa"],
1106 sub {
1107 warn Dumper [@_];
1108 }
1109 );
1110
1111 [
1112 [
1113 'test1.laendle',
1114 'a',
1115 'in',
1116 '10.0.0.255'
1117 ],
1118 [
1119 'test1.laendle',
1120 'aaaa',
1121 'in',
1122 '3ffe:1900:4545:0002:0240:0000:0000:f7e1'
1123 ]
1124 ]
1125
1126 =cut
1127
1128 sub resolve($%) {
1129 my $cb = pop;
1130 my ($self, $qname, $qtype, %opt) = @_;
1131
1132 my @search = $qname =~ s/\.$//
1133 ? ""
1134 : $opt{search}
1135 ? @{ $opt{search} }
1136 : ($qname =~ y/.//) >= $self->{ndots}
1137 ? ("", @{ $self->{search} })
1138 : (@{ $self->{search} }, "");
1139
1140 my $class = $opt{class} || "in";
1141
1142 my %atype = $opt{accept}
1143 ? map +($_ => 1), @{ $opt{accept} }
1144 : ($qtype => 1);
1145
1146 # advance in searchlist
1147 my ($do_search, $do_req);
1148
1149 $do_search = sub {
1150 @search
1151 or (undef $do_search), (undef $do_req), return $cb->();
1152
1153 (my $name = lc "$qname." . shift @search) =~ s/\.$//;
1154 my $depth = 2;
1155
1156 # advance in cname-chain
1157 $do_req = sub {
1158 $self->request ({
1159 rd => 1,
1160 qd => [[$name, $qtype, $class]],
1161 }, sub {
1162 my ($res) = @_
1163 or return $do_search->();
1164
1165 my $cname;
1166
1167 while () {
1168 # results found?
1169 my @rr = grep $name eq lc $_->[0] && ($atype{"*"} || $atype{$_->[1]}), @{ $res->{an} };
1170
1171 (undef $do_search), (undef $do_req), return $cb->(@rr)
1172 if @rr;
1173
1174 # see if there is a cname we can follow
1175 my @rr = grep $name eq lc $_->[0] && $_->[1] eq "cname", @{ $res->{an} };
1176
1177 if (@rr) {
1178 $depth--
1179 or return $do_search->(); # cname chain too long
1180
1181 $cname = 1;
1182 $name = $rr[0][3];
1183
1184 } elsif ($cname) {
1185 # follow the cname
1186 return $do_req->();
1187
1188 } else {
1189 # no, not found anything
1190 return $do_search->();
1191 }
1192 }
1193 });
1194 };
1195
1196 $do_req->();
1197 };
1198
1199 $do_search->();
1200 }
1201
1202 use AnyEvent::Socket (); # circular dependency, so do not import anything and do it at the end
1203
1204 1;
1205
1206 =back
1207
1208 =head1 AUTHOR
1209
1210 Marc Lehmann <schmorp@schmorp.de>
1211 http://home.schmorp.de/
1212
1213 =cut
1214