1package Net::DNS::Lite;
2
3use 5.008_001;
4
5use strict;
6use warnings;
7
8use Carp ();
9use Exporter qw(import);
10use List::MoreUtils qw(uniq);
11use List::Util qw(min);
12use Socket qw(AF_INET SOCK_DGRAM inet_ntoa sockaddr_in unpack_sockaddr_in);
13use Time::HiRes qw(time);
14
15our $VERSION = '0.12';
16
17our @EXPORT = qw();
18our @EXPORT_OK = qw(inet_aton);
19our %EXPORT_TAGS = (
20    'all' => [ @EXPORT_OK ],
21);
22
23sub DOMAIN_PORT () { 53 }
24
25our %opcode_id = (
26   query  => 0,
27   iquery => 1,
28   status => 2,
29   notify => 4,
30   update => 5,
31   map +($_ => $_), 3, 6..15
32);
33
34our %opcode_str = reverse %opcode_id;
35
36our %rcode_id = (
37   noerror  =>  0,
38   formerr  =>  1,
39   servfail =>  2,
40   nxdomain =>  3,
41   notimp   =>  4,
42   refused  =>  5,
43   yxdomain =>  6, # Name Exists when it should not     [RFC 2136]
44   yxrrset  =>  7, # RR Set Exists when it should not   [RFC 2136]
45   nxrrset  =>  8, # RR Set that should exist does not  [RFC 2136]
46   notauth  =>  9, # Server Not Authoritative for zone  [RFC 2136]
47   notzone  => 10, # Name not contained in zone         [RFC 2136]
48# EDNS0  16    BADVERS   Bad OPT Version                    [RFC 2671]
49# EDNS0  16    BADSIG    TSIG Signature Failure             [RFC 2845]
50# EDNS0  17    BADKEY    Key not recognized                 [RFC 2845]
51# EDNS0  18    BADTIME   Signature out of time window       [RFC 2845]
52# EDNS0  19    BADMODE   Bad TKEY Mode                      [RFC 2930]
53# EDNS0  20    BADNAME   Duplicate key name                 [RFC 2930]
54# EDNS0  21    BADALG    Algorithm not supported            [RFC 2930]
55   map +($_ => $_), 11..15
56);
57
58our %rcode_str = reverse %rcode_id;
59
60our %type_id = (
61   a     =>   1,
62   ns    =>   2,
63   md    =>   3,
64   mf    =>   4,
65   cname =>   5,
66   soa   =>   6,
67   mb    =>   7,
68   mg    =>   8,
69   mr    =>   9,
70   null  =>  10,
71   wks   =>  11,
72   ptr   =>  12,
73   hinfo =>  13,
74   minfo =>  14,
75   mx    =>  15,
76   txt   =>  16,
77   aaaa  =>  28,
78   srv   =>  33,
79   naptr =>  35, # rfc2915
80   dname =>  39, # rfc2672
81   opt   =>  41,
82   spf   =>  99,
83   tkey  => 249,
84   tsig  => 250,
85   ixfr  => 251,
86   axfr  => 252,
87   mailb => 253,
88   "*"   => 255,
89);
90
91our %type_str = reverse %type_id;
92
93our %class_id = (
94   in   =>   1,
95   ch   =>   3,
96   hs   =>   4,
97   none => 254,
98   "*"  => 255,
99);
100
101our %class_str = reverse %class_id;
102
103our $TIMEOUT = 10;
104our $CACHE;
105our $CACHE_TTL = 600;
106our $PID;
107
108sub new {
109    my ($class, %arg) = @_;
110
111    my $self = bless {
112        server          => [],
113        timeout         => [2, 5, 5],
114        search          => [],
115        ndots           => 1,
116        reuse           => 300,
117        %arg,
118    }, $class;
119
120    if (@{$self->{server}} == 0) {
121        if (-e '/etc/resolv.conf') {
122            $self->_parse_resolv_conf_file('/etc/resolv.conf');
123        } else {
124            Carp::croak "server was not specified and there is no /etc/resolv.conf";
125        }
126    }
127
128    $self->_compile;
129
130    $self
131}
132
133sub _compile {
134    my $self = shift;
135
136    $self->{search} = [ grep { length($_) } uniq @{$self->{search}} ];
137
138    $self->{server} = [
139        map {
140            Socket::inet_aton($_) or Carp::croak "invalid server address: $_"
141        } grep {
142          ! /:/ # ignore ipv6 address (for now)
143        } grep { length($_) } uniq @{$self->{server}},
144    ];
145
146    my @retry;
147
148    for my $timeout (@{$self->{timeout}}) {
149        for my $server (@{$self->{server}}) {
150            push @retry, [ $server, $timeout ];
151        }
152    }
153
154    $self->{retry} = \@retry;
155}
156
157sub resolve {
158    my ($self, $qname, $qtype, %opt) = @_;
159
160    my @search = $qname =~ s/\.$//
161       ? ""
162       : $opt{search}
163           ? @{ $opt{search} }
164           : ($qname =~ y/.//) >= $self->{ndots}
165               ? ("", @{ $self->{search} })
166               : (@{ $self->{search} }, "");
167
168    my $class = $opt{class} || "in";
169
170    my %atype = $opt{accept}
171        ? map { +($_ => 1) } @{$opt{accept}}
172        : ($qtype => 1);
173
174    # use some big value as default so that all servers and retries will be
175    # performed before total_timeout
176    my $timeout_at = time + (defined $opt{timeout} ? $opt{timeout} : $TIMEOUT);
177
178    # advance in searchlist
179    my ($do_search, $do_req);
180
181    $do_search = sub {
182        @search
183            or (undef $do_search), (undef $do_req), return ();
184
185        (my $name = lc "$qname." . shift @search) =~ s/\.$//;
186        my $depth = 10;
187
188        # advance in cname-chain
189        $do_req = sub {
190            my $res = $self->request($name, $qtype, $class, $timeout_at)
191                or return $do_search->();
192
193            my $cname;
194
195            while (1) {
196                # results found?
197                my @rr = grep {
198                    $name eq lc $_->[0] && ($atype{"*"} || $atype{$_->[1]})
199                } @{$res->{an}};
200
201                if (@rr) {
202                    (undef $do_search), (undef $do_req), return @rr;
203                }
204
205                # see if there is a cname we can follow
206                @rr = grep {
207                    $name eq lc $_->[0] && $_->[1] eq "cname"
208                } @{$res->{an}};
209
210                if (@rr) {
211                    $depth--
212                        or return $do_search->(); # cname chain too long
213
214                    $cname = 1;
215                    $name = lc $rr[0][4];
216
217                } elsif ($cname) {
218                    # follow the cname
219                    return $do_req->();
220
221                } else {
222                    # no, not found anything
223                    return $do_search->();
224                }
225            }
226        };
227
228        $do_req->();
229    };
230
231    $do_search->();
232}
233
234sub request {
235    my ($self, $name, $qtype, $class, $total_timeout_at) = @_;
236
237    my $cache = $self->{cache};
238    if (! defined $self->{cache}) {
239        $cache = $CACHE;
240    }
241    my $cache_key = "$class $qtype $name";
242
243    if ($cache) {
244        if (my $value = $cache->get($cache_key)) {
245            my ($res, $expires_at) = @$value;
246            return $res if time < $expires_at;
247            $cache->remove($cache_key);
248        }
249    }
250
251    $self->_open_socket()
252        if ! $self->{sock_v4} || $self->{pid} != $$;
253
254    my $req = {
255        id => $self->_new_id(),
256        rd => 1,
257        qd => [[$name, $qtype, $class]],
258    };
259
260    my $req_pkt = dns_pack($req);
261    my $pkt_sent;
262
263    for (my $retry = 0; $retry < @{$self->{retry}}; $retry++) {
264        my ($server, $server_timeout) = @{$self->{retry}->[$retry]};
265
266        my $now = time;
267        my $server_timeout_at = $now + $server_timeout;
268        $server_timeout_at = $total_timeout_at
269            if $total_timeout_at < $server_timeout_at;
270        if ($server_timeout_at <= $now) {
271            goto FAIL;
272        }
273
274        # send request
275        $pkt_sent = 1;
276        send(
277            $self->{sock_v4}, $req_pkt, 0,
278            scalar sockaddr_in(DOMAIN_PORT, $server),
279        ) or do {
280            warn "failed to send packet to @{[inet_ntoa($server)]}:$!";
281            next;
282        };
283
284        # wait for the response (or the timeout)
285        my $res;
286        for (; ; undef($res), $now = time) {
287            my $select_timeout = $server_timeout_at - $now;
288            if ($select_timeout <= 0) {
289                goto FAIL if $total_timeout_at <= $now;
290                last;
291            }
292            last if $select_timeout <= 0;
293            my $rfd = '';
294            vec($rfd, fileno($self->{sock_v4}), 1) = 1;
295            my $nfound = select(
296                $rfd, my $wfd = '', my $efd = '', $select_timeout);
297            next unless $nfound > 0;
298            my $from = recv($self->{sock_v4}, my $res_pkt, 1024, 0)
299                or next;
300            my ($from_port, $from_addr) = unpack_sockaddr_in($from);
301            if (! ($from_port == DOMAIN_PORT
302                       && grep { $from_addr eq $_ } @{$self->{server}})) {
303                next;
304            }
305            $res = dns_unpack($res_pkt)
306                or next;
307            if ($res->{id} == $req->{id}) {
308                $self->_register_unusable_id($req->{id})
309                    if $retry != 0;
310                if ($cache) {
311                    my $ttl = min(
312                        $self->{cache_ttl} || $CACHE_TTL,
313                        map {
314                            $_->[3]
315                        } (@{$res->{an}} ? @{$res->{an}} : @{$res->{ns}}),
316                    );
317                    $cache->set(
318                        $cache_key => [ $res, time + $ttl + 0.5 ],
319                    );
320                }
321                return $res;
322            }
323        }
324    }
325
326 FAIL:
327    $self->_register_unusable_id($req->{id})
328        if $pkt_sent;
329    return;
330}
331
332sub _open_socket {
333    my $self = shift;
334
335    my $got_socket = 0;
336    socket($self->{sock_v4}, AF_INET, SOCK_DGRAM, 0)
337        and $got_socket++;
338    # if (AF_INET6) {
339    #     socket($self->{sock_v6}, AF_INET6, SOCK_DGRAM, 0)
340    #         and $got_socket++;
341    # }
342
343    $got_socket
344        or Carp::croak "unable to create either an IPv4 or an IPv6 socket";
345
346    $self->{reuse_q} = [];
347    $self->{reuse_h} = +{};
348    $self->{pid} = $$;
349}
350
351sub _new_id {
352    my $self = shift;
353    my $id;
354
355    my $now = time;
356
357    if (@{$self->{reuse_q}} >= 30000) {
358        $self->_open_socket();
359    } else {
360        delete $self->{reuse_h}{(shift @{$self->{reuse_q}})->[1]}
361            while @{$self->{reuse_q}} && $self->{reuse_q}[0][0] <= $now;
362    }
363
364    while (1) {
365        $id = int rand(65536);
366        last if not defined $self->{reuse_h}{$id};
367    }
368
369    $id;
370}
371
372sub _register_unusable_id {
373    my ($self, $id) = @_;
374
375    push @{$self->{reuse_q}}, [ time + $self->{reuse}, $id ];
376    $self->{reuse_h}{$id} = 1;
377}
378
379sub parse_resolv_conf {
380    my ($self, $resolvconf) = @_;
381
382    $self->{server} = [];
383    $self->{search} = [];
384
385    my $attempts;
386    my $timeout;
387
388    for (split /\n/, $resolvconf) {
389        s/\s*[;#].*$//; # not quite legal, but many people insist
390
391        if (/^\s*nameserver\s+(\S+)\s*$/i) {
392            my $ip = $1;
393            if (my $ipn = parse_address($ip)) {
394                push @{ $self->{server} }, $ip;
395            } else {
396                warn "nameserver $ip invalid and ignored\n";
397            }
398        } elsif (/^\s*domain\s+(\S*)\s*$/i) {
399            $self->{search} = [$1];
400        } elsif (/^\s*search\s+(.*?)\s*$/i) {
401            $self->{search} = [split /\s+/, $1];
402        } elsif (/^\s*sortlist\s+(.*?)\s*$/i) {
403            # ignored, NYI
404        } elsif (/^\s*options\s+(.*?)\s*$/i) {
405            for (split /\s+/, $1) {
406                if (/^timeout:(\d+)$/) {
407                    $timeout = $1;
408                } elsif (/^attempts:(\d+)$/) {
409                    $attempts = $1;
410                } elsif (/^ndots:(\d+)$/) {
411                    $self->{ndots} = $1;
412                } else {
413                    # debug, rotate, no-check-names, inet6
414                }
415            }
416        }
417    }
418
419    if ( $timeout || $attempts ) {
420        $timeout ||= 5;
421        $attempts ||= 2;
422        $self->{timeout} = [ map { $timeout } 1..$attempts ];
423    }
424}
425
426sub _parse_resolv_conf_file {
427    my ($self, $resolv_conf) = @_;
428
429    open my $fh, '<', $resolv_conf
430        or Carp::croak "could not open file: $resolv_conf: $!";
431
432    $self->parse_resolv_conf(do { local $/; join '', <$fh> });
433}
434
435sub _enc_name($) {
436    pack "(C/a*)*", (split /\./, shift), ""
437}
438
439sub _enc_qd() {
440    no warnings;
441    (_enc_name $_->[0]) . pack "nn",
442        ($_->[1] > 0 ? $_->[1] : $type_id {$_->[1]}),
443        ($_->[2] > 0 ? $_->[2] : $class_id{$_->[2] || "in"})
444}
445
446sub _enc_rr() {
447    die "encoding of resource records is not supported";
448}
449
450sub dns_pack {
451    no warnings;
452    my ($req) = @_;
453
454    pack "nn nnnn a* a* a* a*",
455        $req->{id},
456
457        ! !$req->{qr}   * 0x8000
458        + $opcode_id{$req->{op}} * 0x0800
459        + ! !$req->{aa} * 0x0400
460        + ! !$req->{tc} * 0x0200
461        + ! !$req->{rd} * 0x0100
462        + ! !$req->{ra} * 0x0080
463        + ! !$req->{ad} * 0x0020
464        + ! !$req->{cd} * 0x0010
465        + $rcode_id{$req->{rc}} * 0x0001,
466
467        scalar @{ $req->{qd} || [] },
468        scalar @{ $req->{an} || [] },
469        scalar @{ $req->{ns} || [] },
470        scalar @{ $req->{ar} || [] },
471
472        (join "", map _enc_qd, @{ $req->{qd} || [] }),
473        (join "", map _enc_rr, @{ $req->{an} || [] }),
474        (join "", map _enc_rr, @{ $req->{ns} || [] }),
475        (join "", map _enc_rr, @{ $req->{ar} || [] })
476}
477
478our $ofs;
479our $pkt;
480
481# bitches
482sub _dec_name {
483   my @res;
484   my $redir;
485   my $ptr = $ofs;
486   my $cnt;
487
488   while () {
489      return undef if ++$cnt >= 256; # to avoid DoS attacks
490
491      my $len = ord substr $pkt, $ptr++, 1;
492
493      if ($len >= 0xc0) {
494         $ptr++;
495         $ofs = $ptr if $ptr > $ofs;
496         $ptr = (unpack "n", substr $pkt, $ptr - 2, 2) & 0x3fff;
497      } elsif ($len) {
498         push @res, substr $pkt, $ptr, $len;
499         $ptr += $len;
500      } else {
501         $ofs = $ptr if $ptr > $ofs;
502         return join ".", @res;
503      }
504   }
505}
506
507sub _dec_qd {
508   my $qname = _dec_name;
509   my ($qt, $qc) = unpack "nn", substr $pkt, $ofs; $ofs += 4;
510   [$qname, $type_str{$qt} || $qt, $class_str{$qc} || $qc]
511}
512
513our %dec_rr = (
514     1 => sub { join ".", unpack "C4", $_ }, # a     2 => sub { local $ofs = $ofs - length; _dec_name }, # ns
515     5 => sub { local $ofs = $ofs - length; _dec_name }, # cname
516     6 => sub {
517             local $ofs = $ofs - length;             my $mname = _dec_name;
518             my $rname = _dec_name;
519             ($mname, $rname, unpack "NNNNN", substr $pkt, $ofs)
520          }, # soa
521    11 => sub { ((join ".", unpack "C4", $_), unpack "C a*", substr $_, 4) }, # wks
522    12 => sub { local $ofs = $ofs - length; _dec_name }, # ptr
523    13 => sub { unpack "C/a* C/a*", $_ }, # hinfo
524    15 => sub { local $ofs = $ofs + 2 - length; ((unpack "n", $_), _dec_name) }, # mx
525    16 => sub { unpack "(C/a*)*", $_ }, # txt
526    28 => sub { format_ipv6 ($_) }, # aaaa
527    33 => sub { local $ofs = $ofs + 6 - length; ((unpack "nnn", $_), _dec_name) }, # srv
528    35 => sub { # naptr
529       # requires perl 5.10, sorry
530       my ($order, $preference, $flags, $service, $regexp, $offset) = unpack "nn C/a* C/a* C/a* .", $_;
531       local $ofs = $ofs + $offset - length;
532       ($order, $preference, $flags, $service, $regexp, _dec_name)
533    },
534    39 => sub { local $ofs = $ofs - length; _dec_name }, # dname
535    99 => sub { unpack "(C/a*)*", $_ }, # spf
536);
537
538sub _dec_rr {
539   my $name = _dec_name;
540
541   my ($rt, $rc, $ttl, $rdlen) = unpack "nn N n", substr $pkt, $ofs; $ofs += 10;
542   local $_ = substr $pkt, $ofs, $rdlen; $ofs += $rdlen;
543
544   [
545      $name,
546      $type_str{$rt}  || $rt,
547      $class_str{$rc} || $rc,
548      $ttl,
549      ($dec_rr{$rt} || sub { $_ })->(),
550   ]
551}
552
553sub dns_unpack {
554   local $pkt = shift;
555   my ($id, $flags, $qd, $an, $ns, $ar)
556      = unpack "nn nnnn A*", $pkt;
557
558   local $ofs = 6 * 2;
559
560   {
561      id => $id,
562      qr => ! ! ($flags & 0x8000),
563      aa => ! ! ($flags & 0x0400),
564      tc => ! ! ($flags & 0x0200),
565      rd => ! ! ($flags & 0x0100),
566      ra => ! ! ($flags & 0x0080),
567      ad => ! ! ($flags & 0x0020),
568      cd => ! ! ($flags & 0x0010),
569      op => $opcode_str{($flags & 0x001e) >> 11},
570      rc => $rcode_str{($flags & 0x000f)},
571
572      qd => [map _dec_qd, 1 .. $qd],
573      an => [map _dec_rr, 1 .. $an],
574      ns => [map _dec_rr, 1 .. $ns],
575      ar => [map _dec_rr, 1 .. $ar],
576   }
577}
578
579sub parse_address {
580    my $text = shift;
581    if (my $addr = parse_ipv6($text)) {
582        $addr =~ s/^\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff//;
583        return $addr;
584    } else {
585        return parse_ipv4($text);
586    }
587}
588
589sub parse_ipv4 {
590    $_[0] =~ /^      (?: 0x[0-9a-fA-F]+ | 0[0-7]* | [1-9][0-9]* )
591              (?:\. (?: 0x[0-9a-fA-F]+ | 0[0-7]* | [1-9][0-9]* ) ){0,3}$/x
592                  or return undef;
593
594    @_ = map /^0/ ? oct : $_, split /\./, $_[0];
595
596    # check leading parts against range
597    return undef if grep $_ >= 256, @_[0 .. @_ - 2];
598
599    # check trailing part against range
600    return undef if $_[-1] >= 2 ** (8 * (4 - $#_));
601
602    pack "N", (pop)
603        + ($_[0] << 24)
604        + ($_[1] << 16)
605        + ($_[2] <<  8);
606}
607
608sub parse_ipv6 {
609    # quick test to avoid longer processing
610    my $n = $_[0] =~ y/://;
611    return undef if $n < 2 || $n > 8;
612
613    my ($h, $t) = split /::/, $_[0], 2;
614
615    unless (defined $t) {
616        ($h, $t) = (undef, $h);
617    }
618
619    my @h = defined $h ? (split /:/, $h) : ();
620    my @t = split /:/, $t;
621
622    # check for ipv4 tail
623    if (@t && $t[-1]=~ /\./) {
624        return undef if $n > 6;
625
626        my $ipn = parse_ipv4(pop @t)
627            or return undef;
628
629        push @t, map +(sprintf "%x", $_), unpack "nn", $ipn;
630    }
631
632    # no :: then we need to have exactly 8 components
633    return undef unless @h + @t == 8 || $_[0] =~ /::/;
634
635    # now check all parts for validity
636    return undef if grep !/^[0-9a-fA-F]{1,4}$/, @h, @t;
637
638    # now pad...
639    push @h, 0 while @h + @t < 8;
640
641    # and done
642    pack "n*", map hex, @h, @t
643}
644
645our $resolver;
646
647sub RESOLVER() {
648    $resolver ||= Net::DNS::Lite->new;
649}
650
651sub inet_aton {
652    my $name = shift;
653    if (my $address = parse_address($name)) {
654        return $address;
655    }
656    my @rr = RESOLVER->resolve(
657        $name, 'a',
658        (@_ ? (timeout => $_[0]) : ()),
659    );
660    while (@rr) {
661        my $idx = int rand @rr;
662        my $address = parse_ipv4($rr[$idx][4]);
663        return $address if defined $address;
664        splice @rr, $idx, 1;
665    }
666    return undef;
667}
668
669sub format_ipv4($) {
670   join ".", unpack "C4", $_[0]
671}
672
673sub format_ipv6($) {
674   if ($_[0] =~ /^\x00\x00\x00\x00\x00\x00\x00\x00/) {
675      if (v0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0 eq $_[0]) {
676         return "::";
677      } elsif (v0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1 eq $_[0]) {
678         return "::1";
679      } elsif (v0.0.0.0.0.0.0.0.0.0.0.0 eq substr $_[0], 0, 12) {
680         # v4compatible
681         return "::" . format_ipv4 substr $_[0], 12;
682      } elsif (v0.0.0.0.0.0.0.0.0.0.255.255 eq substr $_[0], 0, 12) {
683         # v4mapped
684         return "::ffff:" . format_ipv4 substr $_[0], 12;
685      } elsif (v0.0.0.0.0.0.0.0.255.255.0.0 eq substr $_[0], 0, 12) {
686         # v4translated
687         return "::ffff:0:" . format_ipv4 substr $_[0], 12;
688      }
689   }
690
691   my $ip = sprintf "%x:%x:%x:%x:%x:%x:%x:%x", unpack "n8", $_[0];
692
693   # this is admittedly rather sucky
694      $ip =~ s/(?:^|:) 0:0:0:0:0:0:0 (?:$|:)/::/x
695   or $ip =~ s/(?:^|:)   0:0:0:0:0:0 (?:$|:)/::/x
696   or $ip =~ s/(?:^|:)     0:0:0:0:0 (?:$|:)/::/x
697   or $ip =~ s/(?:^|:)       0:0:0:0 (?:$|:)/::/x
698   or $ip =~ s/(?:^|:)         0:0:0 (?:$|:)/::/x
699   or $ip =~ s/(?:^|:)           0:0 (?:$|:)/::/x
700   or $ip =~ s/(?:^|:)             0 (?:$|:)/::/x;
701
702   $ip
703}
704
7051;
706__END__
707
708=head1 NAME
709
710Net::DNS::Lite - a pure-perl DNS resolver with support for timeout
711
712=head1 SYNOPSIS
713
714    use Net::DNS::Lite qw(inet_aton);
715
716    # drop-in replacement for Socket::inet_aton
717    $Net::DNS::Lite::TIMEOUT = 5; # global timeout variable
718    my $addr = inet_aton("www.google.com");
719
720    # or per-query timeout
721    my $addr = inet_aton("www.google.com", $timeout_in_seconds);
722
723=head1 DESCRIPTION
724
725This module provides a replacement function for L<Socket::inet_aton>, with support for timeouts.
726
727=head1 CONFIGURATION VARIABLES
728
729=head2 $Net::DNS::Lite::TIMEOUT
730
731maximum time (in seconds) inet_aton will block (default: 10)
732
733=head2 $Net::DNS::Lite::CACHE
734
735if set, Net::DNS::Lite will cache the DNS responses internally using the supplied cache object.  The cache object should support C<get>, C<set>, and C<remove> functions (default: none)
736
737=head2 $Net::DNS::Lite::CACHE_TTL
738
739maximum ttl of the cached entries (in seconds).  Only has effect when $Net::DNS::Lite::CACHE is set.
740
741=head1 AUTHOR
742
743Kazuho Oku
744
745The module is based on the excellent L<AnyEvent::DNS> by mlehmann.
746
747=head1 LICENSE
748
749This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
750
751See <http://www.perl.com/perl/misc/Artistic.html>
752
753=cut
754