AnyEvent
view release on metacpan or search on metacpan
lib/AnyEvent/DNS.pm view on Meta::CPAN
($res) = $res =~ /^(.*)$/s
if AnyEvent::TAINT && $self->{untaint};
$res = dns_unpack $res
or return;
my $id = $self->{id}{$res->{id}};
return unless ref $id;
$NOW = time;
$id->[1]->($res);
}
sub _recv {
my ($self, $pkt, $peer) = @_;
# we ignore errors (often one gets port unreachable, but there is
# no good way to take advantage of that.
my ($port, $host) = AnyEvent::Socket::unpack_sockaddr ($peer);
return unless $port == DOMAIN_PORT && grep $_ eq $host, @{ $self->{server} };
$self->_feed ($pkt);
}
sub _free_id {
my ($self, $id, $timeout) = @_;
if ($timeout) {
# we need to block the id for a while
$self->{id}{$id} = 1;
push @{ $self->{reuse_q} }, [$NOW + $self->{reuse}, $id];
} else {
# we can quickly recycle the id
delete $self->{id}{$id};
}
--$self->{outstanding};
$self->_scheduler;
}
# execute a single request, involves sending it with timeouts to multiple servers
sub _exec {
my ($self, $req) = @_;
my $retry; # of retries
my $do_retry;
$do_retry = sub {
my $retry_cfg = $self->{retry}[$retry++]
or do {
# failure
$self->_free_id ($req->[2], $retry > 1);
undef $do_retry; return $req->[1]->();
};
my ($server, $timeout) = @$retry_cfg;
$self->{id}{$req->[2]} = [(AE::timer $timeout, 0, sub {
$NOW = time;
# timeout, try next
&$do_retry if $do_retry;
}), sub {
my ($res) = @_;
if ($res->{tc}) {
# success, but truncated, so use tcp
AnyEvent::Socket::tcp_connect (AnyEvent::Socket::format_address ($server), DOMAIN_PORT, sub {
return unless $do_retry; # some other request could have invalidated us already
my ($fh) = @_
or return &$do_retry;
require AnyEvent::Handle;
my $handle; $handle = new AnyEvent::Handle
fh => $fh,
timeout => $timeout,
on_error => sub {
undef $handle;
return unless $do_retry; # some other request could have invalidated us already
# failure, try next
&$do_retry;
};
$handle->push_write (pack "n/a*", $req->[0]);
$handle->push_read (chunk => 2, sub {
$handle->unshift_read (chunk => (unpack "n", $_[1]), sub {
undef $handle;
$self->_feed ($_[1]);
});
});
}, sub { $timeout });
} else {
# success
$self->_free_id ($req->[2], $retry > 1);
undef $do_retry; return $req->[1]->($res);
}
}];
my $sa = AnyEvent::Socket::pack_sockaddr (DOMAIN_PORT, $server);
my $fh = AF_INET == AnyEvent::Socket::sockaddr_family ($sa)
? $self->{fh4} : $self->{fh6}
or return &$do_retry;
send $fh, $req->[0], 0, $sa;
};
&$do_retry;
}
sub _scheduler {
my ($self) = @_;
return if $self->{inhibit};
#no strict 'refs';
$NOW = time;
# first clear id reuse queue
delete $self->{id}{ (shift @{ $self->{reuse_q} })->[1] }
while @{ $self->{reuse_q} } && $self->{reuse_q}[0][0] <= $NOW;
while ($self->{outstanding} < $self->{max_outstanding}) {
if (@{ $self->{reuse_q} } >= 30000) {
# we ran out of ID's, wait a bit
$self->{reuse_to} ||= AE::timer $self->{reuse_q}[0][0] - $NOW, 0, sub {
delete $self->{reuse_to};
$self->_scheduler;
};
last;
}
if (my $req = shift @{ $self->{queue} }) {
# found a request in the queue, execute it
while () {
$req->[2] = int rand 65536;
last unless exists $self->{id}{$req->[2]};
}
++$self->{outstanding};
$self->{id}{$req->[2]} = 1;
substr $req->[0], 0, 2, pack "n", $req->[2];
$self->_exec ($req);
} elsif (my $cb = shift @{ $self->{wait} }) {
# found a wait_for_slot callback
$cb->($self);
} else {
# nothing to do, just exit
last;
}
}
}
=item $resolver->request ($req, $cb->($res))
This is the main low-level workhorse for sending DNS requests.
This function sends a single request (a hash-ref formated as specified
for C<dns_pack>) to the configured nameservers in turn until it gets a
response. It handles timeouts, retries and automatically falls back to
virtual circuit mode (TCP) when it receives a truncated reply. It does not
handle anything else, such as the domain searchlist or relative names -
use C<< ->resolve >> for that.
Calls the callback with the decoded response packet if a reply was
received, or no arguments in case none of the servers answered.
=cut
sub request($$) {
my ($self, $req, $cb) = @_;
# _enc_name barfs on names that are too long, which is often outside
# program control, so check for too long names here.
for (@{ $req->{qd} }) {
return AE::postpone sub { $cb->(undef) }
if 255 < length $_->[0];
}
push @{ $self->{queue} }, [dns_pack $req, $cb];
$self->_scheduler;
}
( run in 1.271 second using v1.01-cache-2.11-cpan-39bf76dae61 )