ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent/lib/AnyEvent/DNS.pm
Revision: 1.5
Committed: Fri May 23 04:54:52 2008 UTC (16 years, 1 month ago) by root
Branch: MAIN
Changes since 1.4: +52 -14 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::DNS - fully asynchronous DNS resolution
4    
5     =head1 SYNOPSIS
6    
7     use AnyEvent::DNS;
8    
9     =head1 DESCRIPTION
10    
11     This module offers both a number of DNS convenience functions as well
12     as a fully asynchronous and high-performance pure-perl stub resolver.
13    
14     =head2 CONVENIENCE FUNCTIONS
15    
16     =over 4
17    
18     =cut
19    
20     package AnyEvent::DNS;
21    
22 root 1.3 no warnings;
23 root 1.1 use strict;
24    
25     use AnyEvent::Util ();
26    
27 root 1.4 =item AnyEvent::DNS::addr $node, $service, $family, $type, $cb->(@addrs)
28    
29     NOT YET IMPLEMENTED
30    
31     Tries to resolve the given nodename and service name into sockaddr
32     structures usable to connect to this node and service in a
33     protocol-independent way. It works similarly to the getaddrinfo posix
34     function.
35    
36     Example:
37    
38     AnyEvent::DNS::addr "google.com", "http", AF_UNSPEC, SOCK_STREAM, sub { ... };
39    
40     =item AnyEvent::DNS::a $domain, $cb->(@addrs)
41    
42     Tries to resolve the given domain to IPv4 address(es).
43    
44     =item AnyEvent::DNS::mx $domain, $cb->(@hostnames)
45    
46     Tries to resolve the given domain into a sorted (lower preference value
47     first) list of domain names.
48    
49     =item AnyEvent::DNS::ns $domain, $cb->(@hostnames)
50    
51     Tries to resolve the given domain name into a list of name servers.
52    
53     =item AnyEvent::DNS::txt $domain, $cb->(@hostnames)
54    
55     Tries to resolve the given domain name into a list of text records.
56    
57     =item AnyEvent::DNS::srv $service, $proto, $domain, $cb->(@srv_rr)
58    
59     Tries to resolve the given service, protocol and domain name into a list
60     of service records.
61    
62     Each srv_rr is an arrayref with the following contents:
63     C<[$priority, $weight, $transport, $target]>.
64    
65     They will be sorted with lowest priority, highest weight first (TODO:
66     should use the rfc algorithm to reorder same-priority records for weight).
67    
68     Example:
69    
70     AnyEvent::DNS::srv "sip", "udp", "schmorp.de", sub { ...
71     # @_ = ( [10, 10, 5060, "sip1.schmorp.de" ] )
72    
73     =item AnyEvent::DNS::ptr $ipv4_or_6, $cb->(@hostnames)
74    
75     Tries to reverse-resolve the given IPv4 or IPv6 address (in textual form)
76     into it's hostname(s).
77    
78     Requires the Socket6 module for IPv6 support.
79    
80     Example:
81    
82     AnyEvent::DNS::ptr "2001:500:2f::f", sub { print shift };
83     # => f.root-servers.net
84    
85 root 1.5 =item AnyEvent::DNS::any $domain, $cb->(@rrs)
86    
87     Tries to resolve the given domain and passes all resource records found to
88     the callback.
89    
90 root 1.4 =cut
91    
92     sub resolver;
93    
94     sub a($$) {
95     my ($domain, $cb) = @_;
96    
97     resolver->resolve ($domain => "a", sub {
98     $cb->(map $_->[3], @_);
99     });
100     }
101    
102     sub mx($$) {
103     my ($domain, $cb) = @_;
104    
105     resolver->resolve ($domain => "mx", sub {
106     $cb->(map $_->[4], sort { $a->[3] <=> $b->[3] } @_);
107     });
108     }
109    
110     sub ns($$) {
111     my ($domain, $cb) = @_;
112    
113     resolver->resolve ($domain => "ns", sub {
114     $cb->(map $_->[3], @_);
115     });
116     }
117    
118     sub txt($$) {
119     my ($domain, $cb) = @_;
120    
121     resolver->resolve ($domain => "txt", sub {
122     $cb->(map $_->[3], @_);
123     });
124     }
125    
126     sub srv($$$$) {
127     my ($service, $proto, $domain, $cb) = @_;
128    
129     # todo, ask for any and check glue records
130     resolver->resolve ("_$service._$proto.$domain" => "srv", sub {
131     $cb->(map [@$_[3,4,5,6]], sort { $a->[3] <=> $b->[3] || $b->[4] <=> $a->[4] } @_);
132     });
133     }
134    
135     sub ptr($$) {
136     my ($ip, $cb) = @_;
137    
138     my $name;
139    
140     if (AnyEvent::Util::dotted_quad $ip) {
141     $name = join ".", (reverse split /\./, $ip), "in-addr.arpa.";
142     } else {
143     require Socket6;
144     $name = join ".",
145     (reverse split //,
146     unpack "H*", Socket6::inet_pton (Socket::AF_INET6, $ip)),
147     "ip6.arpa.";
148     }
149    
150     resolver->resolve ($name => "ptr", sub {
151     $cb->(map $_->[3], @_);
152     });
153     }
154 root 1.1
155 root 1.5 sub any($$) {
156     my ($domain, $cb) = @_;
157    
158     resolver->resolve ($domain => "*", $cb);
159     }
160    
161 root 1.1 =head2 DNS EN-/DECODING FUNCTIONS
162    
163     =over 4
164    
165     =cut
166    
167     our %opcode_id = (
168     query => 0,
169     iquery => 1,
170     status => 2,
171 root 1.5 notify => 4,
172     update => 5,
173     map +($_ => $_), 3, 6..15
174 root 1.1 );
175    
176     our %opcode_str = reverse %opcode_id;
177    
178     our %rcode_id = (
179 root 1.5 noerror => 0,
180     formerr => 1,
181     servfail => 2,
182     nxdomain => 3,
183     notimp => 4,
184     refused => 5,
185     yxdomain => 6, # Name Exists when it should not [RFC 2136]
186     yxrrset => 7, # RR Set Exists when it should not [RFC 2136]
187     nxrrset => 8, # RR Set that should exist does not [RFC 2136]
188     notauth => 9, # Server Not Authoritative for zone [RFC 2136]
189     notzone => 10, # Name not contained in zone [RFC 2136]
190     # EDNS0 16 BADVERS Bad OPT Version [RFC 2671]
191     # EDNS0 16 BADSIG TSIG Signature Failure [RFC 2845]
192     # EDNS0 17 BADKEY Key not recognized [RFC 2845]
193     # EDNS0 18 BADTIME Signature out of time window [RFC 2845]
194     # EDNS0 19 BADMODE Bad TKEY Mode [RFC 2930]
195     # EDNS0 20 BADNAME Duplicate key name [RFC 2930]
196     # EDNS0 21 BADALG Algorithm not supported [RFC 2930]
197     map +($_ => $_), 11..15
198 root 1.1 );
199    
200     our %rcode_str = reverse %rcode_id;
201    
202     our %type_id = (
203     a => 1,
204     ns => 2,
205     md => 3,
206     mf => 4,
207     cname => 5,
208     soa => 6,
209     mb => 7,
210     mg => 8,
211     mr => 9,
212     null => 10,
213     wks => 11,
214     ptr => 12,
215     hinfo => 13,
216     minfo => 14,
217     mx => 15,
218     txt => 16,
219     aaaa => 28,
220     srv => 33,
221 root 1.5 opt => 41,
222     spf => 99,
223     tkey => 249,
224     tsig => 250,
225     ixfr => 251,
226 root 1.1 axfr => 252,
227     mailb => 253,
228     "*" => 255,
229     );
230    
231     our %type_str = reverse %type_id;
232    
233     our %class_id = (
234 root 1.5 in => 1,
235     ch => 3,
236     hs => 4,
237     none => 254,
238     "*" => 255,
239 root 1.1 );
240    
241     our %class_str = reverse %class_id;
242    
243     # names MUST have a trailing dot
244     sub _enc_qname($) {
245     pack "(C/a)*", (split /\./, shift), ""
246     }
247    
248     sub _enc_qd() {
249     (_enc_qname $_->[0]) . pack "nn",
250     ($_->[1] > 0 ? $_->[1] : $type_id {$_->[1]}),
251     ($_->[2] > 0 ? $_->[2] : $class_id{$_->[2] || "in"})
252     }
253    
254     sub _enc_rr() {
255     die "encoding of resource records is not supported";
256     }
257    
258     =item $pkt = AnyEvent::DNS::dns_pack $dns
259    
260     Packs a perl data structure into a DNS packet. Reading RFC1034 is strongly
261     recommended, then everything will be totally clear. Or maybe not.
262    
263     Resource records are not yet encodable.
264    
265     Examples:
266    
267     # very simple request, using lots of default values:
268     { rd => 1, qd => [ [ "host.domain", "a"] ] }
269    
270     # more complex example, showing how flags etc. are named:
271    
272     {
273     id => 10000,
274     op => "query",
275     rc => "nxdomain",
276    
277     # flags
278     qr => 1,
279     aa => 0,
280     tc => 0,
281     rd => 0,
282     ra => 0,
283 root 1.5 ad => 0,
284     cd => 0,
285 root 1.1
286     qd => [@rr], # query section
287     an => [@rr], # answer section
288     ns => [@rr], # authority section
289     ar => [@rr], # additional records section
290     }
291    
292     =cut
293    
294     sub dns_pack($) {
295     my ($req) = @_;
296    
297     pack "nn nnnn a* a* a* a*",
298     $req->{id},
299    
300     ! !$req->{qr} * 0x8000
301     + $opcode_id{$req->{op}} * 0x0800
302     + ! !$req->{aa} * 0x0400
303     + ! !$req->{tc} * 0x0200
304     + ! !$req->{rd} * 0x0100
305     + ! !$req->{ra} * 0x0080
306 root 1.5 + ! !$req->{ad} * 0x0020
307     + ! !$req->{cd} * 0x0010
308 root 1.1 + $rcode_id{$req->{rc}} * 0x0001,
309    
310     scalar @{ $req->{qd} || [] },
311     scalar @{ $req->{an} || [] },
312     scalar @{ $req->{ns} || [] },
313     scalar @{ $req->{ar} || [] },
314    
315     (join "", map _enc_qd, @{ $req->{qd} || [] }),
316     (join "", map _enc_rr, @{ $req->{an} || [] }),
317     (join "", map _enc_rr, @{ $req->{ns} || [] }),
318     (join "", map _enc_rr, @{ $req->{ar} || [] });
319     }
320    
321     our $ofs;
322     our $pkt;
323    
324     # bitches
325     sub _dec_qname {
326     my @res;
327     my $redir;
328     my $ptr = $ofs;
329     my $cnt;
330    
331     while () {
332     return undef if ++$cnt >= 256; # to avoid DoS attacks
333    
334     my $len = ord substr $pkt, $ptr++, 1;
335    
336     if ($len & 0xc0) {
337     $ptr++;
338     $ofs = $ptr if $ptr > $ofs;
339     $ptr = (unpack "n", substr $pkt, $ptr - 2, 2) & 0x3fff;
340     } elsif ($len) {
341     push @res, substr $pkt, $ptr, $len;
342     $ptr += $len;
343     } else {
344     $ofs = $ptr if $ptr > $ofs;
345     return join ".", @res;
346     }
347     }
348     }
349    
350     sub _dec_qd {
351     my $qname = _dec_qname;
352     my ($qt, $qc) = unpack "nn", substr $pkt, $ofs; $ofs += 4;
353     [$qname, $type_str{$qt} || $qt, $class_str{$qc} || $qc]
354     }
355    
356     our %dec_rr = (
357     1 => sub { Socket::inet_ntoa $_ }, # a
358     2 => sub { local $ofs = $ofs - length; _dec_qname }, # ns
359     5 => sub { local $ofs = $ofs - length; _dec_qname }, # cname
360     6 => sub {
361     local $ofs = $ofs - length;
362     my $mname = _dec_qname;
363     my $rname = _dec_qname;
364     ($mname, $rname, unpack "NNNNN", substr $pkt, $ofs)
365     }, # soa
366     11 => sub { ((Socket::inet_aton substr $_, 0, 4), unpack "C a*", substr $_, 4) }, # wks
367     12 => sub { local $ofs = $ofs - length; _dec_qname }, # ptr
368 root 1.5 13 => sub { unpack "C/a C/a", $_ }, # hinfo
369 root 1.1 15 => sub { local $ofs = $ofs + 2 - length; ((unpack "n", $_), _dec_qname) }, # mx
370 root 1.5 16 => sub { unpack "(C/a)*", $_ }, # txt
371 root 1.1 28 => sub { sprintf "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", unpack "n8" }, # aaaa
372     33 => sub { local $ofs = $ofs + 6 - length; ((unpack "nnn", $_), _dec_qname) }, # srv
373 root 1.5 99 => sub { unpack "(C/a)*", $_ }, # spf
374 root 1.1 );
375    
376     sub _dec_rr {
377     my $qname = _dec_qname;
378    
379     my ($rt, $rc, $ttl, $rdlen) = unpack "nn N n", substr $pkt, $ofs; $ofs += 10;
380     local $_ = substr $pkt, $ofs, $rdlen; $ofs += $rdlen;
381    
382     [
383     $qname,
384     $type_str{$rt} || $rt,
385     $class_str{$rc} || $rc,
386     ($dec_rr{$rt} || sub { $_ })->(),
387     ]
388     }
389    
390     =item $dns = AnyEvent::DNS::dns_unpack $pkt
391    
392     Unpacks a DNS packet into a perl data structure.
393    
394     Examples:
395    
396     # a non-successful reply
397     {
398     'qd' => [
399     [ 'ruth.plan9.de.mach.uni-karlsruhe.de', '*', 'in' ]
400     ],
401     'rc' => 'nxdomain',
402     'ar' => [],
403     'ns' => [
404     [
405     'uni-karlsruhe.de',
406     'soa',
407     'in',
408     'netserv.rz.uni-karlsruhe.de',
409     'hostmaster.rz.uni-karlsruhe.de',
410     2008052201,
411     10800,
412     1800,
413     2592000,
414     86400
415     ]
416     ],
417     'tc' => '',
418     'ra' => 1,
419     'qr' => 1,
420     'id' => 45915,
421     'aa' => '',
422     'an' => [],
423     'rd' => 1,
424     'op' => 'query'
425     }
426    
427     # a successful reply
428    
429     {
430     'qd' => [ [ 'www.google.de', 'a', 'in' ] ],
431     'rc' => 0,
432     'ar' => [
433     [ 'a.l.google.com', 'a', 'in', '209.85.139.9' ],
434     [ 'b.l.google.com', 'a', 'in', '64.233.179.9' ],
435     [ 'c.l.google.com', 'a', 'in', '64.233.161.9' ],
436     ],
437     'ns' => [
438     [ 'l.google.com', 'ns', 'in', 'a.l.google.com' ],
439     [ 'l.google.com', 'ns', 'in', 'b.l.google.com' ],
440     ],
441     'tc' => '',
442     'ra' => 1,
443     'qr' => 1,
444     'id' => 64265,
445     'aa' => '',
446     'an' => [
447     [ 'www.google.de', 'cname', 'in', 'www.google.com' ],
448     [ 'www.google.com', 'cname', 'in', 'www.l.google.com' ],
449     [ 'www.l.google.com', 'a', 'in', '66.249.93.104' ],
450     [ 'www.l.google.com', 'a', 'in', '66.249.93.147' ],
451     ],
452     'rd' => 1,
453     'op' => 0
454     }
455    
456     =cut
457    
458     sub dns_unpack($) {
459     local $pkt = shift;
460     my ($id, $flags, $qd, $an, $ns, $ar)
461     = unpack "nn nnnn A*", $pkt;
462    
463     local $ofs = 6 * 2;
464    
465     {
466     id => $id,
467     qr => ! ! ($flags & 0x8000),
468     aa => ! ! ($flags & 0x0400),
469     tc => ! ! ($flags & 0x0200),
470     rd => ! ! ($flags & 0x0100),
471     ra => ! ! ($flags & 0x0080),
472 root 1.5 ad => ! ! ($flags & 0x0020),
473     cd => ! ! ($flags & 0x0010),
474 root 1.1 op => $opcode_str{($flags & 0x001e) >> 11},
475     rc => $rcode_str{($flags & 0x000f)},
476    
477     qd => [map _dec_qd, 1 .. $qd],
478     an => [map _dec_rr, 1 .. $an],
479     ns => [map _dec_rr, 1 .. $ns],
480     ar => [map _dec_rr, 1 .. $ar],
481     }
482     }
483    
484     #############################################################################
485    
486     =back
487    
488     =head2 THE AnyEvent::DNS RESOLVER CLASS
489    
490     This is the class which deos the actual protocol work.
491    
492     =over 4
493    
494     =cut
495    
496     use Carp ();
497     use Scalar::Util ();
498     use Socket ();
499    
500     our $NOW;
501    
502 root 1.2 =item AnyEvent::DNS::resolver
503    
504     This function creates and returns a resolver that is ready to use and
505     should mimic the default resolver for your system as good as possible.
506    
507     It only ever creates one resolver and returns this one on subsequent
508     calls.
509    
510     Unless you have special needs, prefer this function over creating your own
511     resolver object.
512    
513     =cut
514    
515     our $RESOLVER;
516    
517     sub resolver() {
518     $RESOLVER || do {
519     $RESOLVER = new AnyEvent::DNS;
520     $RESOLVER->load_resolv_conf;
521     $RESOLVER
522     }
523     }
524    
525 root 1.1 =item $resolver = new AnyEvent::DNS key => value...
526    
527 root 1.2 Creates and returns a new resolver. It only supports UDP, so make sure
528     your answer sections fit into a DNS packet.
529    
530     The following options are supported:
531 root 1.1
532     =over 4
533    
534     =item server => [...]
535    
536     A list of server addressses (default C<v127.0.0.1>) in network format (4
537     octets for IPv4, 16 octets for IPv6 - not yet supported).
538    
539     =item timeout => [...]
540    
541     A list of timeouts to use (also determines the number of retries). To make
542     three retries with individual time-outs of 2, 5 and 5 seconds, use C<[2,
543     5, 5]>, which is also the default.
544    
545     =item search => [...]
546    
547     The default search list of suffixes to append to a domain name (default: none).
548    
549 root 1.2 =item ndots => $integer
550 root 1.1
551     The number of dots (default: C<1>) that a name must have so that the resolver
552     tries to resolve the name without any suffixes first.
553    
554 root 1.2 =item max_outstanding => $integer
555 root 1.1
556     Most name servers do not handle many parallel requests very well. This option
557     limits the numbe rof outstanding requests to C<$n> (default: C<10>), that means
558     if you request more than this many requests, then the additional requests will be queued
559     until some other requests have been resolved.
560    
561     =back
562    
563     =cut
564    
565     sub new {
566     my ($class, %arg) = @_;
567    
568     socket my $fh, &Socket::AF_INET, &Socket::SOCK_DGRAM, 0
569     or Carp::croak "socket: $!";
570    
571     AnyEvent::Util::fh_nonblocking $fh, 1;
572    
573     my $self = bless {
574     server => [v127.0.0.1],
575     timeout => [2, 5, 5],
576     search => [],
577     ndots => 1,
578     max_outstanding => 10,
579     reuse => 300, # reuse id's after 5 minutes only, if possible
580     %arg,
581     fh => $fh,
582     reuse_q => [],
583     }, $class;
584    
585     # search should default to gethostname's domain
586     # but perl lacks a good posix module
587    
588     Scalar::Util::weaken (my $wself = $self);
589     $self->{rw} = AnyEvent->io (fh => $fh, poll => "r", cb => sub { $wself->_recv });
590    
591     $self->_compile;
592    
593     $self
594     }
595    
596     =item $resolver->parse_resolv_conv ($string)
597    
598     Parses the given string a sif it were a F<resolv.conf> file. The following
599     directives are supported:
600    
601     C<#>-style comments, C<nameserver>, C<domain>, C<search>, C<sortlist>,
602     C<options> (C<timeout>, C<attempts>, C<ndots>).
603    
604     Everything else is silently ignored.
605    
606     =cut
607    
608     sub parse_resolv_conf {
609     my ($self, $resolvconf) = @_;
610    
611     $self->{server} = [];
612     $self->{search} = [];
613    
614     my $attempts;
615    
616     for (split /\n/, $resolvconf) {
617     if (/^\s*#/) {
618     # comment
619     } elsif (/^\s*nameserver\s+(\S+)\s*$/i) {
620     my $ip = $1;
621     if (AnyEvent::Util::dotted_quad $ip) {
622     push @{ $self->{server} }, AnyEvent::Util::socket_inet_aton $ip;
623     } else {
624     warn "nameserver $ip invalid and ignored\n";
625     }
626     } elsif (/^\s*domain\s+(\S*)\s+$/i) {
627     $self->{search} = [$1];
628     } elsif (/^\s*search\s+(.*?)\s*$/i) {
629     $self->{search} = [split /\s+/, $1];
630     } elsif (/^\s*sortlist\s+(.*?)\s*$/i) {
631     # ignored, NYI
632     } elsif (/^\s*options\s+(.*?)\s*$/i) {
633     for (split /\s+/, $1) {
634     if (/^timeout:(\d+)$/) {
635     $self->{timeout} = [$1];
636     } elsif (/^attempts:(\d+)$/) {
637     $attempts = $1;
638     } elsif (/^ndots:(\d+)$/) {
639     $self->{ndots} = $1;
640     } else {
641     # debug, rotate, no-check-names, inet6
642     }
643     }
644     }
645     }
646    
647     $self->{timeout} = [($self->{timeout}[0]) x $attempts]
648     if $attempts;
649    
650     $self->_compile;
651     }
652    
653     =item $resolver->load_resolv_conf
654    
655     Tries to load and parse F</etc/resolv.conf>. If there will ever be windows
656     support, then this function will do the right thing under windows, too.
657    
658     =cut
659    
660     sub load_resolv_conf {
661     my ($self) = @_;
662    
663     open my $fh, "</etc/resolv.conf"
664     or return;
665    
666     local $/;
667     $self->parse_resolv_conf (<$fh>);
668     }
669    
670     sub _compile {
671     my $self = shift;
672    
673     my @retry;
674    
675     for my $timeout (@{ $self->{timeout} }) {
676     for my $server (@{ $self->{server} }) {
677     push @retry, [$server, $timeout];
678     }
679     }
680    
681     $self->{retry} = \@retry;
682     }
683    
684     sub _recv {
685     my ($self) = @_;
686    
687     while (my $peer = recv $self->{fh}, my $res, 1024, 0) {
688     my ($port, $host) = Socket::unpack_sockaddr_in $peer;
689    
690     return unless $port == 53 && grep $_ eq $host, @{ $self->{server} };
691    
692 root 1.3 $res = dns_unpack $res
693 root 1.1 or return;
694    
695     my $id = $self->{id}{$res->{id}};
696    
697     return unless ref $id;
698    
699     $NOW = time;
700     $id->[1]->($res);
701     }
702     }
703    
704     sub _exec {
705     my ($self, $req, $retry) = @_;
706    
707     if (my $retry_cfg = $self->{retry}[$retry]) {
708     my ($server, $timeout) = @$retry_cfg;
709    
710     $self->{id}{$req->[2]} = [AnyEvent->timer (after => $timeout, cb => sub {
711     $NOW = time;
712    
713     # timeout, try next
714     $self->_exec ($req, $retry + 1);
715     }), sub {
716     my ($res) = @_;
717    
718     # success
719     $self->{id}{$req->[2]} = 1;
720     push @{ $self->{reuse_q} }, [$NOW + $self->{reuse}, $req->[2]];
721     --$self->{outstanding};
722     $self->_scheduler;
723    
724     $req->[1]->($res);
725     }];
726    
727     send $self->{fh}, $req->[0], 0, Socket::pack_sockaddr_in 53, $server;
728     } else {
729     # failure
730     $self->{id}{$req->[2]} = 1;
731     push @{ $self->{reuse_q} }, [$NOW + $self->{reuse}, $req->[2]];
732     --$self->{outstanding};
733     $self->_scheduler;
734    
735     $req->[1]->();
736     }
737     }
738    
739     sub _scheduler {
740     my ($self) = @_;
741    
742     $NOW = time;
743    
744     # first clear id reuse queue
745     delete $self->{id}{ (shift @{ $self->{reuse_q} })->[1] }
746     while @{ $self->{reuse_q} } && $self->{reuse_q}[0] <= $NOW;
747    
748     while ($self->{outstanding} < $self->{max_outstanding}) {
749     my $req = shift @{ $self->{queue} }
750     or last;
751    
752     while () {
753     $req->[2] = int rand 65536;
754     last unless exists $self->{id}{$req->[2]};
755     }
756    
757     $self->{id}{$req->[2]} = 1;
758     substr $req->[0], 0, 2, pack "n", $req->[2];
759    
760     ++$self->{outstanding};
761     $self->_exec ($req, 0);
762     }
763     }
764    
765     =item $resolver->request ($req, $cb->($res))
766    
767     Sends a single request (a hash-ref formated as specified for
768 root 1.3 C<dns_pack>) to the configured nameservers including
769 root 1.1 retries. Calls the callback with the decoded response packet if a reply
770     was received, or no arguments on timeout.
771    
772     =cut
773    
774     sub request($$) {
775     my ($self, $req, $cb) = @_;
776    
777 root 1.3 push @{ $self->{queue} }, [dns_pack $req, $cb];
778 root 1.1 $self->_scheduler;
779     }
780    
781     =item $resolver->resolve ($qname, $qtype, %options, $cb->($rcode, @rr))
782    
783     Queries the DNS for the given domain name C<$qname> of type C<$qtype> (a
784     qtype of "*" is supported and means "any").
785    
786     The callback will be invoked with a list of matching result records or
787     none on any error or if the name could not be found.
788    
789     CNAME chains (although illegal) are followed up to a length of 8.
790    
791 root 1.3 Note that this resolver is just a stub resolver: it requires a nameserver
792     supporting recursive queries, will not do any recursive queries itself and
793     is not secure when used against an untrusted name server.
794    
795 root 1.1 The following options are supported:
796    
797     =over 4
798    
799     =item search => [$suffix...]
800    
801     Use the given search list (which might be empty), by appending each one
802     in turn to the C<$qname>. If this option is missing then the configured
803     C<ndots> and C<search> define its value. If the C<$qname> ends in a dot,
804 root 1.2 then the searchlist will be ignored.
805 root 1.1
806     =item accept => [$type...]
807    
808     Lists the acceptable result types: only result types in this set will be
809     accepted and returned. The default includes the C<$qtype> and nothing
810     else.
811    
812     =item class => "class"
813    
814     Specify the query class ("in" for internet, "ch" for chaosnet and "hs" for
815 root 1.2 hesiod are the only ones making sense). The default is "in", of course.
816 root 1.1
817     =back
818    
819     Examples:
820    
821     $res->resolve ("ruth.plan9.de", "a", sub {
822     warn Dumper [@_];
823     });
824    
825     [
826     [
827     'ruth.schmorp.de',
828     'a',
829     'in',
830     '129.13.162.95'
831     ]
832     ]
833    
834     $res->resolve ("test1.laendle", "*",
835     accept => ["a", "aaaa"],
836     sub {
837     warn Dumper [@_];
838     }
839     );
840    
841     [
842     [
843     'test1.laendle',
844     'a',
845     'in',
846     '10.0.0.255'
847     ],
848     [
849     'test1.laendle',
850     'aaaa',
851     'in',
852     '3ffe:1900:4545:0002:0240:0000:0000:f7e1'
853     ]
854     ]
855    
856     =cut
857    
858     sub resolve($%) {
859     my $cb = pop;
860     my ($self, $qname, $qtype, %opt) = @_;
861    
862     my @search = $qname =~ s/\.$//
863     ? ""
864     : $opt{search}
865     ? @{ $opt{search} }
866     : ($qname =~ y/.//) >= $self->{ndots}
867     ? ("", @{ $self->{search} })
868     : (@{ $self->{search} }, "");
869    
870     my $class = $opt{class} || "in";
871    
872     my %atype = $opt{accept}
873     ? map +($_ => 1), @{ $opt{accept} }
874     : ($qtype => 1);
875    
876     # advance in searchlist
877     my $do_search; $do_search = sub {
878     @search
879     or return $cb->();
880    
881 root 1.4 (my $name = lc "$qname." . shift @search) =~ s/\.$//;
882 root 1.1 my $depth = 2;
883    
884     # advance in cname-chain
885     my $do_req; $do_req = sub {
886     $self->request ({
887     rd => 1,
888     qd => [[$name, $qtype, $class]],
889     }, sub {
890     my ($res) = @_
891     or return $do_search->();
892    
893     my $cname;
894    
895     while () {
896 root 1.2 # results found?
897 root 1.4 my @rr = grep $name eq lc $_->[0] && ($atype{"*"} || $atype{$_->[1]}), @{ $res->{an} };
898 root 1.1
899     return $cb->(@rr)
900     if @rr;
901    
902     # see if there is a cname we can follow
903 root 1.4 my @rr = grep $name eq lc $_->[0] && $_->[1] eq "cname", @{ $res->{an} };
904 root 1.1
905     if (@rr) {
906     $depth--
907     or return $do_search->(); # cname chain too long
908    
909     $cname = 1;
910     $name = $rr[0][3];
911    
912     } elsif ($cname) {
913     # follow the cname
914     return $do_req->();
915    
916     } else {
917 root 1.2 # no, not found anything
918 root 1.1 return $do_search->();
919     }
920     }
921     });
922     };
923    
924     $do_req->();
925     };
926    
927     $do_search->();
928     }
929    
930     1;
931    
932     =back
933    
934     =head1 AUTHOR
935    
936     Marc Lehmann <schmorp@schmorp.de>
937     http://home.schmorp.de/
938    
939     =cut
940