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