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