ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent/lib/AnyEvent/DNS.pm
Revision: 1.1
Committed: Fri May 23 02:47:50 2008 UTC (16 years, 1 month ago) by root
Branch: MAIN
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 # none yet
17
18 =over 4
19
20 =cut
21
22 package AnyEvent::DNS;
23
24 use strict;
25
26 use AnyEvent::Util ();
27
28 =back
29
30 =head2 DNS EN-/DECODING FUNCTIONS
31
32 =over 4
33
34 =cut
35
36 our %opcode_id = (
37 query => 0,
38 iquery => 1,
39 status => 2,
40 map +($_ => $_), 3..15
41 );
42
43 our %opcode_str = reverse %opcode_id;
44
45 our %rcode_id = (
46 ok => 0,
47 formerr => 1,
48 servfail => 2,
49 nxdomain => 3,
50 notimp => 4,
51 refused => 5,
52 map +($_ => $_), 6..15
53 );
54
55 our %rcode_str = reverse %rcode_id;
56
57 our %type_id = (
58 a => 1,
59 ns => 2,
60 md => 3,
61 mf => 4,
62 cname => 5,
63 soa => 6,
64 mb => 7,
65 mg => 8,
66 mr => 9,
67 null => 10,
68 wks => 11,
69 ptr => 12,
70 hinfo => 13,
71 minfo => 14,
72 mx => 15,
73 txt => 16,
74 aaaa => 28,
75 srv => 33,
76 axfr => 252,
77 mailb => 253,
78 "*" => 255,
79 );
80
81 our %type_str = reverse %type_id;
82
83 our %class_id = (
84 in => 1,
85 ch => 3,
86 hs => 4,
87 "*" => 255,
88 );
89
90 our %class_str = reverse %class_id;
91
92 # names MUST have a trailing dot
93 sub _enc_qname($) {
94 pack "(C/a)*", (split /\./, shift), ""
95 }
96
97 sub _enc_qd() {
98 (_enc_qname $_->[0]) . pack "nn",
99 ($_->[1] > 0 ? $_->[1] : $type_id {$_->[1]}),
100 ($_->[2] > 0 ? $_->[2] : $class_id{$_->[2] || "in"})
101 }
102
103 sub _enc_rr() {
104 die "encoding of resource records is not supported";
105 }
106
107 =item $pkt = AnyEvent::DNS::dns_pack $dns
108
109 Packs a perl data structure into a DNS packet. Reading RFC1034 is strongly
110 recommended, then everything will be totally clear. Or maybe not.
111
112 Resource records are not yet encodable.
113
114 Examples:
115
116 # very simple request, using lots of default values:
117 { rd => 1, qd => [ [ "host.domain", "a"] ] }
118
119 # more complex example, showing how flags etc. are named:
120
121 {
122 id => 10000,
123 op => "query",
124 rc => "nxdomain",
125
126 # flags
127 qr => 1,
128 aa => 0,
129 tc => 0,
130 rd => 0,
131 ra => 0,
132
133 qd => [@rr], # query section
134 an => [@rr], # answer section
135 ns => [@rr], # authority section
136 ar => [@rr], # additional records section
137 }
138
139 =cut
140
141 sub dns_pack($) {
142 my ($req) = @_;
143
144 pack "nn nnnn a* a* a* a*",
145 $req->{id},
146
147 ! !$req->{qr} * 0x8000
148 + $opcode_id{$req->{op}} * 0x0800
149 + ! !$req->{aa} * 0x0400
150 + ! !$req->{tc} * 0x0200
151 + ! !$req->{rd} * 0x0100
152 + ! !$req->{ra} * 0x0080
153 + $rcode_id{$req->{rc}} * 0x0001,
154
155 scalar @{ $req->{qd} || [] },
156 scalar @{ $req->{an} || [] },
157 scalar @{ $req->{ns} || [] },
158 scalar @{ $req->{ar} || [] },
159
160 (join "", map _enc_qd, @{ $req->{qd} || [] }),
161 (join "", map _enc_rr, @{ $req->{an} || [] }),
162 (join "", map _enc_rr, @{ $req->{ns} || [] }),
163 (join "", map _enc_rr, @{ $req->{ar} || [] });
164 }
165
166 our $ofs;
167 our $pkt;
168
169 # bitches
170 sub _dec_qname {
171 my @res;
172 my $redir;
173 my $ptr = $ofs;
174 my $cnt;
175
176 while () {
177 return undef if ++$cnt >= 256; # to avoid DoS attacks
178
179 my $len = ord substr $pkt, $ptr++, 1;
180
181 if ($len & 0xc0) {
182 $ptr++;
183 $ofs = $ptr if $ptr > $ofs;
184 $ptr = (unpack "n", substr $pkt, $ptr - 2, 2) & 0x3fff;
185 } elsif ($len) {
186 push @res, substr $pkt, $ptr, $len;
187 $ptr += $len;
188 } else {
189 $ofs = $ptr if $ptr > $ofs;
190 return join ".", @res;
191 }
192 }
193 }
194
195 sub _dec_qd {
196 my $qname = _dec_qname;
197 my ($qt, $qc) = unpack "nn", substr $pkt, $ofs; $ofs += 4;
198 [$qname, $type_str{$qt} || $qt, $class_str{$qc} || $qc]
199 }
200
201 our %dec_rr = (
202 1 => sub { Socket::inet_ntoa $_ }, # a
203 2 => sub { local $ofs = $ofs - length; _dec_qname }, # ns
204 5 => sub { local $ofs = $ofs - length; _dec_qname }, # cname
205 6 => sub {
206 local $ofs = $ofs - length;
207 my $mname = _dec_qname;
208 my $rname = _dec_qname;
209 ($mname, $rname, unpack "NNNNN", substr $pkt, $ofs)
210 }, # soa
211 11 => sub { ((Socket::inet_aton substr $_, 0, 4), unpack "C a*", substr $_, 4) }, # wks
212 12 => sub { local $ofs = $ofs - length; _dec_qname }, # ptr
213 13 => sub { unpack "C/a C/a", $_ },
214 15 => sub { local $ofs = $ofs + 2 - length; ((unpack "n", $_), _dec_qname) }, # mx
215 16 => sub { unpack "C/a", $_ }, # txt
216 28 => sub { sprintf "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x", unpack "n8" }, # aaaa
217 33 => sub { local $ofs = $ofs + 6 - length; ((unpack "nnn", $_), _dec_qname) }, # srv
218 );
219
220 sub _dec_rr {
221 my $qname = _dec_qname;
222
223 my ($rt, $rc, $ttl, $rdlen) = unpack "nn N n", substr $pkt, $ofs; $ofs += 10;
224 local $_ = substr $pkt, $ofs, $rdlen; $ofs += $rdlen;
225
226 [
227 $qname,
228 $type_str{$rt} || $rt,
229 $class_str{$rc} || $rc,
230 ($dec_rr{$rt} || sub { $_ })->(),
231 ]
232 }
233
234 =item $dns = AnyEvent::DNS::dns_unpack $pkt
235
236 Unpacks a DNS packet into a perl data structure.
237
238 Examples:
239
240 # a non-successful reply
241 {
242 'qd' => [
243 [ 'ruth.plan9.de.mach.uni-karlsruhe.de', '*', 'in' ]
244 ],
245 'rc' => 'nxdomain',
246 'ar' => [],
247 'ns' => [
248 [
249 'uni-karlsruhe.de',
250 'soa',
251 'in',
252 'netserv.rz.uni-karlsruhe.de',
253 'hostmaster.rz.uni-karlsruhe.de',
254 2008052201,
255 10800,
256 1800,
257 2592000,
258 86400
259 ]
260 ],
261 'tc' => '',
262 'ra' => 1,
263 'qr' => 1,
264 'id' => 45915,
265 'aa' => '',
266 'an' => [],
267 'rd' => 1,
268 'op' => 'query'
269 }
270
271 # a successful reply
272
273 {
274 'qd' => [ [ 'www.google.de', 'a', 'in' ] ],
275 'rc' => 0,
276 'ar' => [
277 [ 'a.l.google.com', 'a', 'in', '209.85.139.9' ],
278 [ 'b.l.google.com', 'a', 'in', '64.233.179.9' ],
279 [ 'c.l.google.com', 'a', 'in', '64.233.161.9' ],
280 ],
281 'ns' => [
282 [ 'l.google.com', 'ns', 'in', 'a.l.google.com' ],
283 [ 'l.google.com', 'ns', 'in', 'b.l.google.com' ],
284 ],
285 'tc' => '',
286 'ra' => 1,
287 'qr' => 1,
288 'id' => 64265,
289 'aa' => '',
290 'an' => [
291 [ 'www.google.de', 'cname', 'in', 'www.google.com' ],
292 [ 'www.google.com', 'cname', 'in', 'www.l.google.com' ],
293 [ 'www.l.google.com', 'a', 'in', '66.249.93.104' ],
294 [ 'www.l.google.com', 'a', 'in', '66.249.93.147' ],
295 ],
296 'rd' => 1,
297 'op' => 0
298 }
299
300 =cut
301
302 sub dns_unpack($) {
303 local $pkt = shift;
304 my ($id, $flags, $qd, $an, $ns, $ar)
305 = unpack "nn nnnn A*", $pkt;
306
307 local $ofs = 6 * 2;
308
309 {
310 id => $id,
311 qr => ! ! ($flags & 0x8000),
312 aa => ! ! ($flags & 0x0400),
313 tc => ! ! ($flags & 0x0200),
314 rd => ! ! ($flags & 0x0100),
315 ra => ! ! ($flags & 0x0080),
316 op => $opcode_str{($flags & 0x001e) >> 11},
317 rc => $rcode_str{($flags & 0x000f)},
318
319 qd => [map _dec_qd, 1 .. $qd],
320 an => [map _dec_rr, 1 .. $an],
321 ns => [map _dec_rr, 1 .. $ns],
322 ar => [map _dec_rr, 1 .. $ar],
323 }
324 }
325
326 #############################################################################
327
328 =back
329
330 =head2 THE AnyEvent::DNS RESOLVER CLASS
331
332 This is the class which deos the actual protocol work.
333
334 =over 4
335
336 =cut
337
338 use Carp ();
339 use Scalar::Util ();
340 use Socket ();
341
342 our $NOW;
343
344 =item $resolver = new AnyEvent::DNS key => value...
345
346 Creates and returns a new resolver. The following options are supported:
347
348 =over 4
349
350 =item server => [...]
351
352 A list of server addressses (default C<v127.0.0.1>) in network format (4
353 octets for IPv4, 16 octets for IPv6 - not yet supported).
354
355 =item timeout => [...]
356
357 A list of timeouts to use (also determines the number of retries). To make
358 three retries with individual time-outs of 2, 5 and 5 seconds, use C<[2,
359 5, 5]>, which is also the default.
360
361 =item search => [...]
362
363 The default search list of suffixes to append to a domain name (default: none).
364
365 =item ndots => $n
366
367 The number of dots (default: C<1>) that a name must have so that the resolver
368 tries to resolve the name without any suffixes first.
369
370 =item max_outstanding => $n
371
372 Most name servers do not handle many parallel requests very well. This option
373 limits the numbe rof outstanding requests to C<$n> (default: C<10>), that means
374 if you request more than this many requests, then the additional requests will be queued
375 until some other requests have been resolved.
376
377 =back
378
379 =cut
380
381 sub new {
382 my ($class, %arg) = @_;
383
384 socket my $fh, &Socket::AF_INET, &Socket::SOCK_DGRAM, 0
385 or Carp::croak "socket: $!";
386
387 AnyEvent::Util::fh_nonblocking $fh, 1;
388
389 my $self = bless {
390 server => [v127.0.0.1],
391 timeout => [2, 5, 5],
392 search => [],
393 ndots => 1,
394 max_outstanding => 10,
395 reuse => 300, # reuse id's after 5 minutes only, if possible
396 %arg,
397 fh => $fh,
398 reuse_q => [],
399 }, $class;
400
401 # search should default to gethostname's domain
402 # but perl lacks a good posix module
403
404 Scalar::Util::weaken (my $wself = $self);
405 $self->{rw} = AnyEvent->io (fh => $fh, poll => "r", cb => sub { $wself->_recv });
406
407 $self->_compile;
408
409 $self
410 }
411
412 =item $resolver->parse_resolv_conv ($string)
413
414 Parses the given string a sif it were a F<resolv.conf> file. The following
415 directives are supported:
416
417 C<#>-style comments, C<nameserver>, C<domain>, C<search>, C<sortlist>,
418 C<options> (C<timeout>, C<attempts>, C<ndots>).
419
420 Everything else is silently ignored.
421
422 =cut
423
424 sub parse_resolv_conf {
425 my ($self, $resolvconf) = @_;
426
427 $self->{server} = [];
428 $self->{search} = [];
429
430 my $attempts;
431
432 for (split /\n/, $resolvconf) {
433 if (/^\s*#/) {
434 # comment
435 } elsif (/^\s*nameserver\s+(\S+)\s*$/i) {
436 my $ip = $1;
437 if (AnyEvent::Util::dotted_quad $ip) {
438 push @{ $self->{server} }, AnyEvent::Util::socket_inet_aton $ip;
439 } else {
440 warn "nameserver $ip invalid and ignored\n";
441 }
442 } elsif (/^\s*domain\s+(\S*)\s+$/i) {
443 $self->{search} = [$1];
444 } elsif (/^\s*search\s+(.*?)\s*$/i) {
445 $self->{search} = [split /\s+/, $1];
446 } elsif (/^\s*sortlist\s+(.*?)\s*$/i) {
447 # ignored, NYI
448 } elsif (/^\s*options\s+(.*?)\s*$/i) {
449 for (split /\s+/, $1) {
450 if (/^timeout:(\d+)$/) {
451 $self->{timeout} = [$1];
452 } elsif (/^attempts:(\d+)$/) {
453 $attempts = $1;
454 } elsif (/^ndots:(\d+)$/) {
455 $self->{ndots} = $1;
456 } else {
457 # debug, rotate, no-check-names, inet6
458 }
459 }
460 }
461 }
462
463 $self->{timeout} = [($self->{timeout}[0]) x $attempts]
464 if $attempts;
465
466 $self->_compile;
467 }
468
469 =item $resolver->load_resolv_conf
470
471 Tries to load and parse F</etc/resolv.conf>. If there will ever be windows
472 support, then this function will do the right thing under windows, too.
473
474 =cut
475
476 sub load_resolv_conf {
477 my ($self) = @_;
478
479 open my $fh, "</etc/resolv.conf"
480 or return;
481
482 local $/;
483 $self->parse_resolv_conf (<$fh>);
484 }
485
486 sub _compile {
487 my $self = shift;
488
489 my @retry;
490
491 for my $timeout (@{ $self->{timeout} }) {
492 for my $server (@{ $self->{server} }) {
493 push @retry, [$server, $timeout];
494 }
495 }
496
497 $self->{retry} = \@retry;
498 }
499
500 sub _recv {
501 my ($self) = @_;
502
503 while (my $peer = recv $self->{fh}, my $res, 1024, 0) {
504 my ($port, $host) = Socket::unpack_sockaddr_in $peer;
505
506 return unless $port == 53 && grep $_ eq $host, @{ $self->{server} };
507
508 $res = AnyEvent::DNS::dns_unpack $res
509 or return;
510
511 my $id = $self->{id}{$res->{id}};
512
513 return unless ref $id;
514
515 $NOW = time;
516 $id->[1]->($res);
517 }
518 }
519
520 sub _exec {
521 my ($self, $req, $retry) = @_;
522
523 if (my $retry_cfg = $self->{retry}[$retry]) {
524 my ($server, $timeout) = @$retry_cfg;
525
526 $self->{id}{$req->[2]} = [AnyEvent->timer (after => $timeout, cb => sub {
527 $NOW = time;
528
529 # timeout, try next
530 $self->_exec ($req, $retry + 1);
531 }), sub {
532 my ($res) = @_;
533
534 # success
535 $self->{id}{$req->[2]} = 1;
536 push @{ $self->{reuse_q} }, [$NOW + $self->{reuse}, $req->[2]];
537 --$self->{outstanding};
538 $self->_scheduler;
539
540 $req->[1]->($res);
541 }];
542
543 send $self->{fh}, $req->[0], 0, Socket::pack_sockaddr_in 53, $server;
544 } else {
545 # failure
546 $self->{id}{$req->[2]} = 1;
547 push @{ $self->{reuse_q} }, [$NOW + $self->{reuse}, $req->[2]];
548 --$self->{outstanding};
549 $self->_scheduler;
550
551 $req->[1]->();
552 }
553 }
554
555 sub _scheduler {
556 my ($self) = @_;
557
558 $NOW = time;
559
560 # first clear id reuse queue
561 delete $self->{id}{ (shift @{ $self->{reuse_q} })->[1] }
562 while @{ $self->{reuse_q} } && $self->{reuse_q}[0] <= $NOW;
563
564 while ($self->{outstanding} < $self->{max_outstanding}) {
565 my $req = shift @{ $self->{queue} }
566 or last;
567
568 while () {
569 $req->[2] = int rand 65536;
570 last unless exists $self->{id}{$req->[2]};
571 }
572
573 $self->{id}{$req->[2]} = 1;
574 substr $req->[0], 0, 2, pack "n", $req->[2];
575
576 ++$self->{outstanding};
577 $self->_exec ($req, 0);
578 }
579 }
580
581 =item $resolver->request ($req, $cb->($res))
582
583 Sends a single request (a hash-ref formated as specified for
584 C<AnyEvent::DNS::dns_pack>) to the configured nameservers including
585 retries. Calls the callback with the decoded response packet if a reply
586 was received, or no arguments on timeout.
587
588 =cut
589
590 sub request($$) {
591 my ($self, $req, $cb) = @_;
592
593 push @{ $self->{queue} }, [(AnyEvent::DNS::dns_pack $req), $cb];
594 $self->_scheduler;
595 }
596
597 =item $resolver->resolve ($qname, $qtype, %options, $cb->($rcode, @rr))
598
599 Queries the DNS for the given domain name C<$qname> of type C<$qtype> (a
600 qtype of "*" is supported and means "any").
601
602 The callback will be invoked with a list of matching result records or
603 none on any error or if the name could not be found.
604
605 CNAME chains (although illegal) are followed up to a length of 8.
606
607 The following options are supported:
608
609 =over 4
610
611 =item search => [$suffix...]
612
613 Use the given search list (which might be empty), by appending each one
614 in turn to the C<$qname>. If this option is missing then the configured
615 C<ndots> and C<search> define its value. If the C<$qname> ends in a dot,
616 then this option is ignored completely.
617
618 =item accept => [$type...]
619
620 Lists the acceptable result types: only result types in this set will be
621 accepted and returned. The default includes the C<$qtype> and nothing
622 else.
623
624 =item class => "class"
625
626 Specify the query class ("in" for internet, "ch" for chaosnet and "hs" for
627 hesiod are the only ones making sense).
628
629 =back
630
631 Examples:
632
633 $res->resolve ("ruth.plan9.de", "a", sub {
634 warn Dumper [@_];
635 });
636
637 [
638 [
639 'ruth.schmorp.de',
640 'a',
641 'in',
642 '129.13.162.95'
643 ]
644 ]
645
646 $res->resolve ("test1.laendle", "*",
647 accept => ["a", "aaaa"],
648 sub {
649 warn Dumper [@_];
650 }
651 );
652
653 [
654 [
655 'test1.laendle',
656 'a',
657 'in',
658 '10.0.0.255'
659 ],
660 [
661 'test1.laendle',
662 'aaaa',
663 'in',
664 '3ffe:1900:4545:0002:0240:0000:0000:f7e1'
665 ]
666 ]
667
668 =cut
669
670 sub resolve($%) {
671 my $cb = pop;
672 my ($self, $qname, $qtype, %opt) = @_;
673
674 my @search = $qname =~ s/\.$//
675 ? ""
676 : $opt{search}
677 ? @{ $opt{search} }
678 : ($qname =~ y/.//) >= $self->{ndots}
679 ? ("", @{ $self->{search} })
680 : (@{ $self->{search} }, "");
681
682 my $class = $opt{class} || "in";
683
684 my %atype = $opt{accept}
685 ? map +($_ => 1), @{ $opt{accept} }
686 : ($qtype => 1);
687
688 # advance in searchlist
689 my $do_search; $do_search = sub {
690 @search
691 or return $cb->();
692
693 (my $name = "$qname." . shift @search) =~ s/\.$//;
694 my $depth = 2;
695
696 # advance in cname-chain
697 my $do_req; $do_req = sub {
698 $self->request ({
699 rd => 1,
700 qd => [[$name, $qtype, $class]],
701 }, sub {
702 my ($res) = @_
703 or return $do_search->();
704
705 my $cname;
706
707 while () {
708 my @rr = grep $_->[0] eq $name && ($atype{"*"} || $atype{$_->[1]}), @{ $res->{an} };
709
710 return $cb->(@rr)
711 if @rr;
712
713 # see if there is a cname we can follow
714 my @rr = grep $_->[0] eq $name && $_->[1] eq "cname", @{ $res->{an} };
715
716 if (@rr) {
717 $depth--
718 or return $do_search->(); # cname chain too long
719
720 $cname = 1;
721 $name = $rr[0][3];
722
723 } elsif ($cname) {
724 # follow the cname
725 return $do_req->();
726
727 } else {
728 return $do_search->();
729 }
730 }
731 });
732 };
733
734 $do_req->();
735 };
736
737 $do_search->();
738 }
739
740 1;
741
742 =back
743
744 =head1 AUTHOR
745
746 Marc Lehmann <schmorp@schmorp.de>
747 http://home.schmorp.de/
748
749 =cut
750