AnyEvent

 view release on metacpan or  search on metacpan

lib/AnyEvent/DNS.pm  view on Meta::CPAN

=head1 NAME

AnyEvent::DNS - fully asynchronous DNS resolution

=head1 SYNOPSIS

   use AnyEvent::DNS;
   
   my $cv = AnyEvent->condvar;
   AnyEvent::DNS::a "www.google.de", $cv;
   # ... later
   my @addrs = $cv->recv;

=head1 DESCRIPTION

This module offers both a number of DNS convenience functions as well
as a fully asynchronous and high-performance pure-perl stub resolver.

The stub resolver supports DNS over IPv4 and IPv6, UDP and TCP, optional
EDNS0 support for up to 4kiB datagrams and automatically falls back to
virtual circuit mode for large responses.

=head2 CONVENIENCE FUNCTIONS

=over 4

=cut

package AnyEvent::DNS;

use Carp ();
use Socket qw(AF_INET SOCK_DGRAM SOCK_STREAM);

use AnyEvent (); BEGIN { AnyEvent::common_sense }
use AnyEvent::Util qw(AF_INET6);

our $VERSION = $AnyEvent::VERSION;
our @DNS_FALLBACK; # some public dns servers as fallback

{
   my $prep = sub {
      $_ = $_->[rand @$_] for @_;
      push @_, splice @_, rand $_, 1 for reverse 1..@_; # shuffle
      $_ = pack "H*", $_ for @_;
      \@_
   };

   my $ipv4 = $prep->(
      ["08080808", "08080404"], # 8.8.8.8, 8.8.4.4 - google public dns
      ["01010101", "01000001"], # 1.1.1.1, 1.0.0.1 - cloudflare public dns
      ["50505050", "50505151"], # 80.80.80.80, 80.80.81.81 - freenom.world
##      ["d1f40003", "d1f30004"], # v209.244.0.3/4 - resolver1/2.level3.net - status unknown
##      ["04020201", "04020203", "04020204", "04020205", "04020206"], # v4.2.2.1/3/4/5/6 - vnsc-pri.sys.gtei.net - effectively public
##      ["cdd22ad2", "4044c8c8"], # 205.210.42.205, 64.68.200.200 - cache1/2.dnsresolvers.com - verified public
#      ["8d010101"], # 141.1.1.1 - cable&wireless, now vodafone - status unknown
# 84.200.69.80      # dns.watch
# 84.200.70.40      # dns.watch
# 37.235.1.174      # freedns.zone
# 37.235.1.177      # freedns.zone
# 213.73.91.35      # dnscache.berlin.ccc.de
# 194.150.168.168   # dns.as250.net; Berlin/Frankfurt
# 85.214.20.141     # FoeBud (digitalcourage.de)
# 77.109.148.136    # privacyfoundation.ch
# 77.109.148.137    # privacyfoundation.ch
# 91.239.100.100    # anycast.censurfridns.dk
# 89.233.43.71      # ns1.censurfridns.dk
# 204.152.184.76    # f.6to4-servers.net, ISC, USA
   );

   my $ipv6 = $prep->(
      ["20014860486000000000000000008888", "20014860486000000000000000008844"], # 2001:4860:4860::8888/8844 - google ipv6
      ["26064700470000000000000000001111", "26064700470000000000000000001001"], # 2606:4700:4700::1111/1001 - cloudflare dns
   );

   undef $ipv4 unless $AnyEvent::PROTOCOL{ipv4};
   undef $ipv6 unless $AnyEvent::PROTOCOL{ipv6};

lib/AnyEvent/DNS.pm  view on Meta::CPAN

=back

=cut

sub new {
   my ($class, %arg) = @_;

   my $self = bless {
      server  => [],
      timeout => [2, 5, 5],
      search  => [],
      ndots   => 1,
      max_outstanding => 10,
      reuse   => 300,
      %arg,
      inhibit => 0,
      reuse_q => [],
   }, $class;

   # search should default to gethostname's domain
   # but perl lacks a good posix module

   # try to create an ipv4 and an ipv6 socket
   # only fail when we cannot create either
   my $got_socket;

   Scalar::Util::weaken (my $wself = $self);

   if (socket my $fh4, AF_INET , Socket::SOCK_DGRAM(), 0) {
      ++$got_socket;

      AnyEvent::fh_unblock $fh4;
      $self->{fh4} = $fh4;
      $self->{rw4} = AE::io $fh4, 0, sub {
         if (my $peer = recv $fh4, my $pkt, MAX_PKT, 0) {
            $wself->_recv ($pkt, $peer);
         }
      };
   }

   if (AF_INET6 && socket my $fh6, AF_INET6, Socket::SOCK_DGRAM(), 0) {
      ++$got_socket;

      $self->{fh6} = $fh6;
      AnyEvent::fh_unblock $fh6;
      $self->{rw6} = AE::io $fh6, 0, sub {
         if (my $peer = recv $fh6, my $pkt, MAX_PKT, 0) {
            $wself->_recv ($pkt, $peer);
         }
      };
   }

   $got_socket
      or Carp::croak "unable to create either an IPv4 or an IPv6 socket";

   $self->_compile;

   $self
}

# called to start asynchronous configuration
sub _config_begin {
   ++$_[0]{inhibit};
}

# called when done with async config
sub _config_done {
   --$_[0]{inhibit};
   $_[0]->_compile;
   $_[0]->_scheduler;
}

=item $resolver->parse_resolv_conf ($string)

Parses the given string as if it were a F<resolv.conf> file. The following
directives are supported (but not necessarily implemented).

C<#>- and C<;>-style comments, C<nameserver>, C<domain>, C<search>, C<sortlist>,
C<options> (C<timeout>, C<attempts>, C<ndots>).

Everything else is silently ignored.

=cut

sub parse_resolv_conf {
   my ($self, $resolvconf) = @_;

   $self->{server} = [];
   $self->{search} = [];

   my $attempts;

   for (split /\n/, $resolvconf) {
      s/\s*[;#].*$//; # not quite legal, but many people insist

      if (/^\s*nameserver\s+(\S+)\s*$/i) {
         my $ip = $1;
         if (my $ipn = AnyEvent::Socket::parse_address ($ip)) {
            push @{ $self->{server} }, $ipn;
         } else {
            AE::log 5 => "nameserver $ip invalid and ignored, while parsing resolver config.";
         }
      } elsif (/^\s*domain\s+(\S*)\s*$/i) {
         $self->{search} = [$1];
      } elsif (/^\s*search\s+(.*?)\s*$/i) {
         $self->{search} = [split /\s+/, $1];
      } elsif (/^\s*sortlist\s+(.*?)\s*$/i) {
         # ignored, NYI
      } elsif (/^\s*options\s+(.*?)\s*$/i) {
         for (split /\s+/, $1) {
            if (/^timeout:(\d+)$/) {
               $self->{timeout} = [$1];
            } elsif (/^attempts:(\d+)$/) {
               $attempts = $1;
            } elsif (/^ndots:(\d+)$/) {
               $self->{ndots} = $1;
            } else {
               # debug, rotate, no-check-names, inet6
            }
         }
      } else {



( run in 0.672 second using v1.01-cache-2.11-cpan-39bf76dae61 )