ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent/lib/AnyEvent/DNS.pm
Revision: 1.8
Committed: Fri May 23 05:34:32 2008 UTC (16 years, 1 month ago) by root
Branch: MAIN
Changes since 1.7: +2 -2 lines
Log Message:
disable edns0, partially

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