our
$VERSION
= (
qw$Id: Base.pm 2011 2025-02-11 15:18:03Z willem $
)[2];
use
constant
OS_SPEC
=>
"Net::DNS::Resolver::$^O"
;
use
constant
OS_UNIX
=>
"Net::DNS::Resolver::UNIX"
;
use
constant
OS_CONF
=>
grep
eval
"require $_"
, OS_SPEC, OS_UNIX;
use
constant
USE_SOCKET_IP
=>
defined
eval
'use IO::Socket::IP 0.38; 1;'
;
use
constant
SOCKS
=>
scalar
eval
{
require
Config;
$Config::Config
{usesocks}; };
use
constant
TAINT
=>
eval
{ ${^TAINT} };
{
no
strict
'subs'
;
use
constant
AI_NUMERICHOST
=> Socket::AI_NUMERICHOST;
use
constant
IPPROTO_UDP
=> Socket::IPPROTO_UDP;
}
{
my
$defaults
=
bless
{
nameservers
=> [
qw(::1 127.0.0.1)
],
nameserver4
=> [
'127.0.0.1'
],
nameserver6
=> [
'::1'
],
port
=> 53,
srcaddr4
=>
'0.0.0.0'
,
srcaddr6
=>
'::'
,
srcport
=> 0,
searchlist
=> [],
retrans
=> 5,
retry
=> 4,
usevc
=> ( SOCKS ? 1 : 0 ),
igntc
=> 0,
recurse
=> 1,
defnames
=> 1,
dnsrch
=> 1,
ndots
=> 1,
debug
=> 0,
tcp_timeout
=> 120,
udp_timeout
=> 30,
persistent_tcp
=> ( SOCKS ? 1 : 0 ),
persistent_udp
=> 0,
dnssec
=> 0,
adflag
=> 0,
cdflag
=> 0,
udppacketsize
=> 0,
force_v4
=> 0,
force_v6
=> 0,
prefer_v4
=> 0,
prefer_v6
=> 0,
},
__PACKAGE__;
sub
_defaults {
return
$defaults
; }
}
my
%warned
;
sub
_deprecate {
my
(
undef
,
@note
) =
@_
;
carp
join
' '
,
'deprecated method;'
,
"@note"
unless
$warned
{
"@note"
}++;
return
;
}
sub
_untaint {
return
TAINT ?
map
{
ref
(
$_
) ? [_untaint(
@$_
)] :
do
{ /^(.*)$/; $1 } }
@_
:
@_
;
}
my
%public_attr
= (
map
{
$_
=>
$_
}
keys
%{
&_defaults
},
qw(domain nameserver srcaddr)
,
map
{
$_
=> 0 }
qw(nameserver4 nameserver6 srcaddr4 srcaddr6)
,
);
my
$initial
;
sub
new {
my
(
$class
,
%args
) =
@_
;
my
$self
;
my
$base
=
$class
->_defaults;
my
$init
=
$initial
;
$initial
||= [
%$base
];
if
(
my
$file
=
$args
{config_file} ) {
my
$conf
=
bless
{
@$initial
},
$class
;
$conf
->_read_config_file(
$file
);
$self
=
bless
{_untaint(
%$conf
)},
$class
;
%$base
=
%$self
unless
$init
;
}
elsif
(
$init
) {
$self
=
bless
{
%$base
},
$class
;
}
else
{
$class
->_init();
$self
=
bless
{
%$base
},
$class
;
}
while
(
my
(
$attr
,
$value
) =
each
%args
) {
next
unless
$public_attr
{
$attr
};
my
$ref
=
ref
(
$value
);
croak
"usage: $class->new( $attr => [...] )"
if
$ref
&& (
$ref
ne
'ARRAY'
);
$self
->
$attr
(
$ref
?
@$value
:
$value
);
}
return
$self
;
}
my
%resolv_conf
= (
attempts
=>
'retry'
,
inet6
=>
'prefer_v6'
,
timeout
=>
'retrans'
,
);
my
%res_option
= (
%public_attr
,
%resolv_conf
,
);
sub
_option {
my
(
$self
,
$name
,
@value
) =
@_
;
my
$attribute
=
$res_option
{
lc
$name
} ||
return
;
push
@value
, 1
unless
scalar
@value
;
return
$self
->
$attribute
(
@value
);
}
sub
_read_env {
my
$self
=
shift
;
$self
->searchlist(
map
{
split
}
$ENV
{LOCALDOMAIN} )
if
defined
$ENV
{LOCALDOMAIN};
$self
->nameservers(
map
{
split
}
$ENV
{RES_NAMESERVERS} )
if
defined
$ENV
{RES_NAMESERVERS};
$self
->searchlist(
map
{
split
}
$ENV
{RES_SEARCHLIST} )
if
defined
$ENV
{RES_SEARCHLIST};
foreach
(
map
{
split
}
$ENV
{RES_OPTIONS} ||
''
) {
$self
->_option(
split
m/:/ );
}
return
;
}
sub
_read_config_file {
my
(
$self
,
$file
) =
@_
;
my
$filehandle
= IO::File->new(
$file
,
'<'
) or croak
"$file: $!"
;
my
@nameserver
;
my
@searchlist
;
local
$_
;
while
(<
$filehandle
>) {
s/[;
/^nameserver/ &&
do
{
my
(
$keyword
,
@ip
) =
grep
{
defined
}
split
;
push
@nameserver
,
@ip
;
next
;
};
/^domain/ &&
do
{
my
(
$keyword
,
$domain
) =
grep
{
defined
}
split
;
$self
->domain(
$domain
);
next
;
};
/^search/ &&
do
{
my
(
$keyword
,
@domain
) =
grep
{
defined
}
split
;
push
@searchlist
,
@domain
;
next
;
};
/^option/ &&
do
{
my
(
$keyword
,
@option
) =
grep
{
defined
}
split
;
foreach
(
@option
) {
$self
->_option(
split
m/:/ );
}
};
}
close
(
$filehandle
);
$self
->nameservers(
@nameserver
)
if
@nameserver
;
$self
->searchlist(
@searchlist
)
if
@searchlist
;
return
;
}
sub
string {
my
$self
=
shift
;
$self
=
$self
->_defaults
unless
ref
(
$self
);
my
@nslist
=
$self
->nameservers();
my
(
$force
) = (
grep
( {
$self
->{
$_
} }
qw(force_v6 force_v4)
),
'force_v4'
);
my
(
$prefer
) = (
grep
( {
$self
->{
$_
} }
qw(prefer_v6 prefer_v4)
),
'prefer_v4'
);
return
<<END;
;; RESOLVER state:
;; nameservers = @nslist
;; searchlist = @{$self->{searchlist}}
;; defnames = $self->{defnames} dnsrch = $self->{dnsrch}
;; igntc = $self->{igntc} usevc = $self->{usevc}
;; recurse = $self->{recurse} port = $self->{port}
;; retrans = $self->{retrans} retry = $self->{retry}
;; tcp_timeout = $self->{tcp_timeout} persistent_tcp = $self->{persistent_tcp}
;; udp_timeout = $self->{udp_timeout} persistent_udp = $self->{persistent_udp}
;; ${prefer} = $self->{$prefer} ${force} = $self->{$force}
;; debug = $self->{debug} ndots = $self->{ndots}
END
}
sub
print
{
return
print
shift
->string;
}
sub
searchlist {
my
(
$self
,
@domain
) =
@_
;
$self
=
$self
->_defaults
unless
ref
(
$self
);
foreach
(
@domain
) {
$_
= Net::DNS::Domain->new(
$_
)->name }
$self
->{searchlist} = \
@domain
if
scalar
(
@domain
);
return
@{
$self
->{searchlist}};
}
sub
domain {
return
(
&searchlist
)[0];
}
sub
nameservers {
my
(
$self
,
@ns
) =
@_
;
$self
=
$self
->_defaults
unless
ref
(
$self
);
my
@ip
;
foreach
my
$ns
(
grep
{
defined
}
@ns
) {
if
( _ipv4(
$ns
) || _ipv6(
$ns
) ) {
push
@ip
,
$ns
;
}
else
{
my
$defres
=
ref
(
$self
)->new(
debug
=>
$self
->{debug} );
$defres
->{persistent} =
$self
->{persistent};
my
$names
= {};
my
$packet
=
$defres
->
send
(
$ns
,
'A'
);
my
@iplist
= _cname_addr(
$packet
,
$names
);
if
(IPv6) {
$packet
=
$defres
->
send
(
$ns
,
'AAAA'
);
push
@iplist
, _cname_addr(
$packet
,
$names
);
}
my
%unique
=
map
{
$_
=>
$_
}
@iplist
;
my
@address
=
values
(
%unique
);
carp
"unresolvable name: $ns"
unless
scalar
@address
;
push
@ip
,
@address
;
}
}
if
(
scalar
(
@ns
) || !
defined
(
wantarray
) ) {
my
@ipv4
=
grep
{ _ipv4(
$_
) }
@ip
;
my
@ipv6
=
grep
{ _ipv6(
$_
) }
@ip
;
my
@map4
=
map
{
"::FFFF:$_"
}
@ipv4
;
$self
->{nameservers} = \
@ip
;
$self
->{nameserver4} = \
@ipv4
;
$self
->{nameserver6} = \
@ipv6
;
$self
->{mapped_IPv4} = \
@map4
;
}
my
@IPv4
= @{
$self
->{nameserver4}};
my
@IPv6
= IPv6 ? @{
$self
->{nameserver6}} : ();
my
@IPlist
=
@IPv6
? @{
$self
->{nameservers}} :
@IPv4
;
@IPlist
= (
@IPv6
,
@IPv4
)
if
$self
->{prefer_v6};
@IPlist
= (
@IPv4
,
@IPv6
)
if
$self
->{prefer_v4};
@IPlist
=
@IPv6
if
$self
->{force_v6};
@IPlist
=
@IPv4
if
$self
->{force_v4};
$self
->errorstring(
'no nameservers'
)
unless
@IPlist
;
return
@IPlist
;
}
sub
nameserver {
return
&nameservers
; }
sub
_cname_addr {
my
@null
;
my
$packet
=
shift
||
return
@null
;
my
$names
=
shift
;
$names
->{
lc
(
$_
->qname )}++
foreach
$packet
->question;
$names
->{
lc
(
$_
->cname )}++
foreach
grep
{
$_
->can(
'cname'
) }
$packet
->answer;
my
@addr
=
grep
{
$_
->can(
'address'
) }
$packet
->answer;
return
map
{
$_
->address }
grep
{
$names
->{
lc
(
$_
->name )} }
@addr
;
}
sub
replyfrom {
return
shift
->{replyfrom};
}
sub
answerfrom {
return
&replyfrom
; }
sub
_reset_errorstring {
shift
->{errorstring} =
''
;
return
;
}
sub
errorstring {
my
(
$self
,
$text
) =
@_
;
$self
->_diag(
'errorstring:'
,
$self
->{errorstring} =
$text
)
if
$text
;
return
$self
->{errorstring};
}
sub
query {
my
(
$self
,
@argument
) =
@_
;
my
$name
=
shift
(
@argument
) ||
'.'
;
my
@sfix
=
$self
->{defnames} && (
$name
!~ m/[.:]/ ) ?
$self
->domain : ();
my
$fqdn
=
join
'.'
,
$name
,
@sfix
;
$self
->_diag(
'query('
,
$fqdn
,
@argument
,
')'
);
my
$packet
=
$self
->
send
(
$fqdn
,
@argument
) ||
return
;
return
$packet
->header->ancount ?
$packet
:
undef
;
}
sub
search {
my
(
$self
,
@argument
) =
@_
;
return
$self
->query(
@argument
)
unless
$self
->{dnsrch};
my
$name
=
shift
(
@argument
) ||
'.'
;
my
$dots
=
$name
=~
tr
/././;
my
@sfix
= (
$dots
<
$self
->{ndots} ) ? @{
$self
->{searchlist}} : ();
my
(
$one
,
@more
) = (
$name
=~ m/:|\.\d*$/ ) ? () : (
$dots
? (
undef
,
@sfix
) :
@sfix
);
foreach
my
$suffix
(
$one
,
@more
) {
my
$fqname
=
$suffix
?
join
(
'.'
,
$name
,
$suffix
) :
$name
;
$self
->_diag(
'search('
,
$fqname
,
@argument
,
')'
);
my
$packet
=
$self
->
send
(
$fqname
,
@argument
) ||
next
;
return
$packet
if
$packet
->header->ancount;
}
return
;
}
sub
send
{
my
(
$self
,
@argument
) =
@_
;
my
$packet
=
$self
->_make_query_packet(
@argument
);
my
$packet_data
=
$packet
->encode;
$self
->_reset_errorstring;
return
$self
->_send_tcp(
$packet
,
$packet_data
)
if
$self
->{usevc} ||
length
$packet_data
>
$self
->_packetsz;
my
$reply
=
$self
->_send_udp(
$packet
,
$packet_data
) ||
return
;
return
$reply
if
$self
->{igntc};
return
$reply
unless
$reply
->header->tc;
$self
->_diag(
'packet truncated: retrying using TCP'
);
return
$self
->_send_tcp(
$packet
,
$packet_data
);
}
sub
_send_tcp {
my
(
$self
,
$query
,
$query_data
) =
@_
;
my
$tcp_packet
=
pack
'n a*'
,
length
(
$query_data
),
$query_data
;
my
@ns
=
$self
->nameservers();
my
$fallback
;
my
$timeout
=
$self
->{tcp_timeout};
foreach
my
$ip
(
@ns
) {
$self
->_diag(
'tcp send'
,
"[$ip]"
);
my
$connection
=
$self
->_create_tcp_socket(
$ip
);
$self
->errorstring($!);
my
$select
= IO::Select->new(
$connection
||
next
);
$connection
->
send
(
$tcp_packet
);
$self
->errorstring($!);
my
@ready
=
$select
->can_read(
$timeout
);
next
unless
@ready
;
my
$socket
=
shift
@ready
;
my
$buffer
= _read_tcp(
$socket
);
$self
->{replyfrom} =
$ip
;
$self
->_diag(
'packet from'
,
"[$ip]"
,
length
(
$buffer
),
'octets'
);
my
$reply
= Net::DNS::Packet->decode( \
$buffer
,
$self
->{debug} );
$self
->errorstring($@);
next
unless
$self
->_accept_reply(
$reply
,
$query
);
$reply
->from(
$socket
->peerhost );
if
(
$self
->{tsig_rr} && !
$reply
->verify(
$query
) ) {
$self
->errorstring(
$reply
->verifyerr );
next
;
}
my
$rcode
=
$reply
->header->rcode;
return
$reply
if
$rcode
eq
'NOERROR'
;
return
$reply
if
$rcode
eq
'NXDOMAIN'
;
$fallback
=
$reply
;
}
$self
->errorstring(
$fallback
->header->rcode )
if
$fallback
;
$self
->errorstring(
'query timed out'
)
unless
$self
->errorstring;
return
$fallback
;
}
sub
_send_udp {
my
(
$self
,
$query
,
$query_data
) =
@_
;
my
@ns
=
$self
->nameservers;
my
$port
=
$self
->{port};
my
$retrans
=
$self
->{retrans} || 1;
my
$retry
=
$self
->{retry} || 1;
my
$servers
=
scalar
(
@ns
);
my
$timeout
=
$servers
?
do
{
no
integer;
$retrans
/
$servers
} : 0;
my
$fallback
;
RETRY:
for
( 1 ..
$retry
) {
my
$select
= IO::Select->new();
NAMESERVER:
foreach
my
$ns
(
@ns
) {
unless
(
ref
$ns
) {
my
$sockaddr
=
$self
->_create_dst_sockaddr(
$ns
,
$port
);
my
$socket
=
$self
->_create_udp_socket(
$ns
) ||
next
;
$ns
= [
$socket
,
$ns
,
$sockaddr
];
}
my
(
$socket
,
$ip
,
$sockaddr
,
$failed
) =
@$ns
;
next
if
$failed
;
$self
->_diag(
'udp send'
,
"[$ip]:$port"
);
$select
->add(
$socket
);
$socket
->
send
(
$query_data
, 0,
$sockaddr
);
$self
->errorstring(
$$ns
[3] = $! );
die
'Insecure dependency while running with -T switch'
if
TESTS && Scalar::Util::tainted(
$sockaddr
);
my
$reply
;
while
(
my
@ready
=
$select
->can_read(
$timeout
) ) {
my
$socket
=
shift
@ready
;
my
$buffer
= _read_udp(
$socket
);
$self
->{replyfrom} =
$ip
;
$self
->_diag(
'packet from'
,
"[$ip]"
,
length
(
$buffer
),
'octets'
);
my
$packet
= Net::DNS::Packet->decode( \
$buffer
,
$self
->{debug} );
$self
->errorstring($@);
next
unless
$self
->_accept_reply(
$packet
,
$query
);
$packet
->from(
$socket
->peerhost );
$reply
=
$packet
;
last
;
}
next
unless
$reply
;
if
(
$self
->{tsig_rr} && !
$reply
->verify(
$query
) ) {
$self
->errorstring(
$$ns
[3] =
$reply
->verifyerr );
next
;
}
my
$rcode
=
$reply
->header->rcode;
return
$reply
if
$rcode
eq
'NOERROR'
;
return
$reply
if
$rcode
eq
'NXDOMAIN'
;
$fallback
=
$reply
;
$$ns
[3] =
$rcode
;
}
no
integer;
$timeout
+=
$timeout
;
}
$self
->errorstring(
$fallback
->header->rcode )
if
$fallback
;
$self
->errorstring(
'query timed out'
)
unless
$self
->errorstring;
return
$fallback
;
}
sub
bgsend {
my
(
$self
,
@argument
) =
@_
;
my
$packet
=
$self
->_make_query_packet(
@argument
);
my
$packet_data
=
$packet
->encode;
$self
->_reset_errorstring;
return
$self
->_bgsend_tcp(
$packet
,
$packet_data
)
if
$self
->{usevc} ||
length
$packet_data
>
$self
->_packetsz;
return
$self
->_bgsend_udp(
$packet
,
$packet_data
);
}
sub
_bgsend_tcp {
my
(
$self
,
$packet
,
$packet_data
) =
@_
;
my
$tcp_packet
=
pack
'n a*'
,
length
(
$packet_data
),
$packet_data
;
foreach
my
$ip
(
$self
->nameservers ) {
$self
->_diag(
'bgsend'
,
"[$ip]"
);
my
$socket
=
$self
->_create_tcp_socket(
$ip
);
$self
->errorstring($!);
next
unless
$socket
;
$socket
->blocking(0);
$socket
->
send
(
$tcp_packet
);
$self
->errorstring($!);
$socket
->blocking(1);
my
$expire
=
time
() +
$self
->{tcp_timeout};
${
*$socket
}{net_dns_bg} = [
$expire
,
$packet
];
return
$socket
;
}
return
;
}
sub
_bgsend_udp {
my
(
$self
,
$packet
,
$packet_data
) =
@_
;
my
$port
=
$self
->{port};
foreach
my
$ip
(
$self
->nameservers ) {
my
$sockaddr
=
$self
->_create_dst_sockaddr(
$ip
,
$port
);
my
$socket
=
$self
->_create_udp_socket(
$ip
) ||
next
;
$self
->_diag(
'bgsend'
,
"[$ip]:$port"
);
$socket
->
send
(
$packet_data
, 0,
$sockaddr
);
$self
->errorstring($!);
die
'Insecure dependency while running with -T switch'
if
TESTS && Scalar::Util::tainted(
$sockaddr
);
my
$expire
=
time
() +
$self
->{udp_timeout};
${
*$socket
}{net_dns_bg} = [
$expire
,
$packet
];
return
$socket
;
}
return
;
}
sub
bgbusy {
my
(
$self
,
$handle
) =
@_
;
return
unless
$handle
;
my
$appendix
= ${
*$handle
}{net_dns_bg} ||= [
time
() +
$self
->{udp_timeout}];
my
(
$expire
,
$query
,
$read
) =
@$appendix
;
return
if
ref
(
$read
);
return
time
() <
$expire
unless
IO::Select->new(
$handle
)->can_read(0.02);
return
unless
$query
;
return
unless
$handle
->socktype() == SOCK_DGRAM;
my
$ans
=
$self
->_bgread(
$handle
);
$$appendix
[0] =
time
();
$$appendix
[2] = [
$ans
];
return
unless
$ans
;
return
if
$self
->{igntc};
return
unless
$ans
->header->tc;
$self
->_diag(
'packet truncated: retrying using TCP'
);
my
$tcp
=
$self
->_bgsend_tcp(
$query
,
$query
->encode ) ||
return
;
return
defined
(
$_
[1] =
$tcp
);
}
sub
bgisready {
__PACKAGE__->_deprecate(
'prefer ! bgbusy(...)'
);
return
!
&bgbusy
;
}
sub
bgread {
1
while
&bgbusy
;
return
&_bgread
;
}
sub
_bgread {
my
(
$self
,
$handle
) =
@_
;
return
unless
$handle
;
my
$appendix
= ${
*$handle
}{net_dns_bg};
my
(
$expire
,
$query
,
$read
) =
@$appendix
;
return
shift
(
@$read
)
if
ref
(
$read
);
return
unless
IO::Select->new(
$handle
)->can_read(0.2);
my
$dgram
=
$handle
->socktype() == SOCK_DGRAM;
my
$buffer
=
$dgram
? _read_udp(
$handle
) : _read_tcp(
$handle
);
my
$peerhost
=
$self
->{replyfrom} =
$handle
->peerhost;
$self
->_diag(
"packet from [$peerhost]"
,
length
(
$buffer
),
'octets'
);
my
$reply
= Net::DNS::Packet->decode( \
$buffer
,
$self
->{debug} );
$self
->errorstring($@);
return
unless
$self
->_accept_reply(
$reply
,
$query
);
$reply
->from(
$peerhost
);
return
$reply
unless
$self
->{tsig_rr} && !
$reply
->verify(
$query
);
$self
->errorstring(
$reply
->verifyerr );
return
;
}
sub
_accept_reply {
my
(
$self
,
$reply
,
$query
) =
@_
;
return
unless
$reply
;
my
$header
=
$reply
->header;
return
unless
$header
->
qr;
return if $query &
& (
$header
->id !=
$query
->header->id );
return
$self
->errorstring(
$header
->rcode );
}
sub
axfr {
my
(
$self
,
@argument
) =
@_
;
my
$zone
=
scalar
(
@argument
) ?
shift
@argument
:
$self
->domain;
my
@class
=
@argument
;
my
$request
=
$self
->_make_query_packet(
$zone
,
'AXFR'
,
@class
);
return
eval
{
$self
->_diag(
"axfr( $zone @class )"
);
my
(
$select
,
$verify
,
@rr
,
$soa
) =
$self
->_axfr_start(
$request
);
my
$iterator
=
sub
{
my
$rr
=
shift
(
@rr
);
if
(
ref
(
$rr
) eq
'Net::DNS::RR::SOA'
) {
if
(
$soa
) {
$select
=
undef
;
return
if
$rr
->canonical eq
$soa
->canonical;
croak
$self
->errorstring(
'mismatched final SOA'
);
}
$soa
=
$rr
;
}
unless
(
scalar
@rr
) {
my
$reply
;
(
$reply
,
$verify
) =
$self
->_axfr_next(
$select
,
$verify
);
@rr
=
$reply
->answer
if
$reply
;
}
return
$rr
;
};
return
$iterator
unless
wantarray
;
my
@zone
;
while
(
my
$rr
=
$iterator
->() ) {
push
@zone
,
$rr
,
@rr
;
@rr
=
pop
(
@zone
);
}
return
@zone
;
};
}
sub
axfr_start {
my
(
$self
,
@argument
) =
@_
;
$self
->_deprecate(
'prefer $iterator = $self->axfr(...)'
);
my
$iterator
=
$self
->axfr(
@argument
);
(
$self
->{axfr_iter} ) =
grep
{
defined
} (
$iterator
,
sub
{ } );
return
defined
(
$iterator
);
}
sub
axfr_next {
my
$self
=
shift
;
$self
->_deprecate(
'prefer $iterator->()'
);
return
$self
->{axfr_iter}->();
}
sub
_axfr_start {
my
(
$self
,
$request
) =
@_
;
my
$content
=
$request
->encode;
my
$TCP_msg
=
pack
'n a*'
,
length
(
$content
),
$content
;
my
(
$select
,
$reply
,
$rcode
);
foreach
my
$ns
(
$self
->nameservers ) {
$self
->_diag(
"axfr send [$ns]"
);
local
$self
->{persistent_tcp};
my
$socket
=
$self
->_create_tcp_socket(
$ns
);
$self
->errorstring($!);
$select
= IO::Select->new(
$socket
||
next
);
$socket
->
send
(
$TCP_msg
);
$self
->errorstring($!);
(
$reply
) =
$self
->_axfr_next(
$select
);
last
if
(
$rcode
=
$reply
->header->rcode ) eq
'NOERROR'
;
}
croak
$self
->errorstring
unless
$reply
;
$self
->errorstring(
$rcode
);
my
$verify
=
$request
->sigrr ?
$request
:
undef
;
unless
(
$verify
) {
croak
$self
->errorstring
unless
$rcode
eq
'NOERROR'
;
return
(
$select
,
$verify
,
$reply
->answer );
}
my
$verifyok
=
$reply
->verify(
$verify
);
croak
$self
->errorstring(
$reply
->verifyerr )
unless
$verifyok
;
croak
$self
->errorstring
if
$rcode
ne
'NOERROR'
;
return
(
$select
,
$verifyok
,
$reply
->answer );
}
sub
_axfr_next {
my
$self
=
shift
;
my
$select
=
shift
||
return
;
my
$verify
=
shift
;
my
(
$socket
) =
$select
->can_read(
$self
->{tcp_timeout} );
croak
$self
->errorstring(
'timed out'
)
unless
$socket
;
my
$buffer
= _read_tcp(
$socket
);
my
$packet
= Net::DNS::Packet->decode( \
$buffer
);
croak $@,
$self
->errorstring(
'corrupt packet'
)
if
$@;
return
(
$packet
,
$verify
)
unless
$verify
;
my
$verifyok
=
$packet
->verify(
$verify
);
croak
$self
->errorstring(
$packet
->verifyerr )
unless
$verifyok
;
return
(
$packet
,
$verifyok
);
}
sub
_read_socket {
my
(
$socket
,
$size
) =
@_
;
my
$buffer
=
''
;
$socket
->
recv
(
$buffer
,
$size
)
if
$size
;
return
$buffer
;
}
sub
_read_tcp {
my
$socket
=
shift
;
my
$buffer
=
''
;
my
$header
= _read_socket(
$socket
, 2 );
$header
.= _read_socket(
$socket
, 2 -
length
$header
);
return
$buffer
if
length
(
$header
) < 2;
my
$size
=
unpack
'n'
,
$header
;
while
(
my
$fragment
= _read_socket(
$socket
,
$size
-
length
$buffer
) ) {
$buffer
.=
$fragment
;
}
return
$buffer
;
}
sub
_read_udp {
return
_read_socket(
shift
(), 9000 );
}
sub
_create_tcp_socket {
my
(
$self
,
$ip
,
@sockopt
) =
@_
;
my
$socket
;
my
$sock_key
=
"TCP[$ip]"
;
if
(
$socket
=
$self
->{persistent}{
$sock_key
} ) {
$self
->_diag(
'using persistent socket'
,
$sock_key
);
return
$socket
if
$socket
->connected;
$self
->_diag(
'socket disconnected (trying to connect)'
);
}
my
$ip6_addr
= IPv6 && _ipv6(
$ip
);
$socket
= IO::Socket::IP->new(
LocalAddr
=>
$ip6_addr
?
$self
->{srcaddr6} :
$self
->{srcaddr4},
LocalPort
=>
$self
->{srcport},
PeerAddr
=>
$ip
,
PeerPort
=>
$self
->{port},
Proto
=>
'tcp'
,
Timeout
=>
$self
->{tcp_timeout},
GetAddrInfoFlags
=> AI_NUMERICHOST,
@sockopt
)
if
USE_SOCKET_IP;
unless
( USE_SOCKET_IP or
$ip6_addr
) {
$socket
= IO::Socket::INET->new(
LocalAddr
=>
$self
->{srcaddr4},
LocalPort
=>
$self
->{srcport} ||
undef
,
PeerAddr
=>
$ip
,
PeerPort
=>
$self
->{port},
Proto
=>
'tcp'
,
Timeout
=>
$self
->{tcp_timeout},
@sockopt
);
}
$self
->{persistent}{
$sock_key
} =
$socket
if
$self
->{persistent_tcp};
return
$socket
;
}
sub
_create_udp_socket {
my
(
$self
,
$ip
,
@sockopt
) =
@_
;
my
$socket
;
my
$sock_key
=
"UDP[$ip]"
;
return
$socket
if
$socket
=
$self
->{persistent}{
$sock_key
};
my
$ip6_addr
= IPv6 && _ipv6(
$ip
);
$socket
= IO::Socket::IP->new(
LocalAddr
=>
$ip6_addr
?
$self
->{srcaddr6} :
$self
->{srcaddr4},
LocalPort
=>
$self
->{srcport},
Proto
=>
'udp'
,
Type
=> SOCK_DGRAM,
GetAddrInfoFlags
=> AI_NUMERICHOST,
@sockopt
)
if
USE_SOCKET_IP;
unless
( USE_SOCKET_IP or
$ip6_addr
) {
$socket
= IO::Socket::INET->new(
LocalAddr
=>
$self
->{srcaddr4},
LocalPort
=>
$self
->{srcport} ||
undef
,
Proto
=>
'udp'
,
Type
=> SOCK_DGRAM,
@sockopt
);
}
$self
->{persistent}{
$sock_key
} =
$socket
if
$self
->{persistent_udp};
return
$socket
;
}
my
$ip4
= {
family
=> AF_INET,
flags
=> AI_NUMERICHOST,
protocol
=> IPPROTO_UDP,
socktype
=> SOCK_DGRAM
};
my
$ip6
= {
family
=> AF_INET6,
flags
=> AI_NUMERICHOST,
protocol
=> IPPROTO_UDP,
socktype
=> SOCK_DGRAM
};
sub
_create_dst_sockaddr {
my
(
$self
,
$ip
,
$port
) =
@_
;
unless
(USE_SOCKET_IP) {
return
_ipv6(
$ip
) ?
undef
: sockaddr_in(
$port
, inet_aton(
$ip
) );
}
my
@addrinfo
= Socket::getaddrinfo(
$ip
,
$port
, _ipv6(
$ip
) ?
$ip6
:
$ip4
);
return
(
grep
{
ref
}
@addrinfo
, {} )[0]->{addr};
}
sub
_ipv4 {
for
(
shift
) {
last
if
m/[^.0-9]/;
return
m/\.\d+\./;
}
return
;
}
sub
_ipv6 {
for
(
shift
) {
last
unless
m/:.*:/;
return
1
unless
m/[^:0-9A-Fa-f]/;
return
1
if
m/^[:.0-9A-Fa-f]+\%.+$/;
return
m/^[:0-9A-Fa-f]+:[.0-9]+$/;
}
return
;
}
sub
_make_query_packet {
my
(
$self
,
@argument
) =
@_
;
my
(
$packet
) =
@argument
;
unless
(
ref
(
$packet
) ) {
$packet
= Net::DNS::Packet->new(
@argument
);
$packet
->edns->udpsize(
$self
->{udppacketsize} );
my
$header
=
$packet
->header;
$header
->ad(
$self
->{adflag} );
$header
->cd(
$self
->{cdflag} );
$header
->
do
(1)
if
$self
->{dnssec};
$header
->rd(
$self
->{recurse} );
}
if
(
$self
->{tsig_rr} ) {
$packet
->sign_tsig(
$self
->{tsig_rr} )
unless
$packet
->sigrr;
}
return
$packet
;
}
sub
dnssec {
my
(
$self
,
@argument
) =
@_
;
for
(
@argument
) {
$self
->udppacketsize(1232);
$self
->{dnssec} =
$_
;
}
return
$self
->{dnssec};
}
sub
force_v6 {
my
(
$self
,
@value
) =
@_
;
for
(
@value
) {
$self
->{force_v4} = 0
if
$self
->{force_v6} =
$_
}
return
$self
->{force_v6} ? 1 : 0;
}
sub
force_v4 {
my
(
$self
,
@value
) =
@_
;
for
(
@value
) {
$self
->{force_v6} = 0
if
$self
->{force_v4} =
$_
}
return
$self
->{force_v4} ? 1 : 0;
}
sub
prefer_v6 {
my
(
$self
,
@value
) =
@_
;
for
(
@value
) {
$self
->{prefer_v4} = 0
if
$self
->{prefer_v6} =
$_
}
return
$self
->{prefer_v6} ? 1 : 0;
}
sub
prefer_v4 {
my
(
$self
,
@value
) =
@_
;
for
(
@value
) {
$self
->{prefer_v6} = 0
if
$self
->{prefer_v4} =
$_
}
return
$self
->{prefer_v4} ? 1 : 0;
}
sub
srcaddr {
my
(
$self
,
@value
) =
@_
;
for
(
@value
) {
my
$hashkey
= _ipv6(
$_
) ?
'srcaddr6'
:
'srcaddr4'
;
$self
->{
$hashkey
} =
$_
;
}
return
shift
@value
;
}
sub
tsig {
my
(
$self
,
$arg
,
@etc
) =
@_
;
return
$arg
unless
$arg
;
return
$arg
if
ref
(
$arg
) eq
'Net::DNS::RR::TSIG'
;
$self
->{tsig_rr} =
eval
{
local
$SIG
{__DIE__};
Net::DNS::RR::TSIG->create(
$arg
,
@etc
);
};
croak
"${@}unable to create TSIG record"
if
$@;
return
;
}
sub
_packetsz {
my
$udpsize
=
shift
->{udppacketsize} || 0;
return
$udpsize
> PACKETSZ ?
$udpsize
: PACKETSZ;
}
sub
udppacketsize {
my
(
$self
,
@value
) =
@_
;
for
(
@value
) {
$self
->{udppacketsize} =
$_
}
return
$self
->_packetsz;
}
sub
make_query_packet {
__PACKAGE__->_deprecate(
'see RT#37104'
); # uncoverable pod
return
&_make_query_packet
;
}
sub
_diag {
return
unless
shift
->{debug};
return
print
"\n;; @_\n"
;
}
{
my
$parse_dig
=
sub
{
my
$dug
= Net::DNS::ZoneFile->new( \
*DATA
);
my
@rr
=
$dug
->
read
;
my
@auth
=
grep
{
$_
->type eq
'NS'
}
@rr
;
my
%auth
=
map
{
lc
$_
->
nsdname
=> 1 }
@auth
;
my
%glue
;
my
@glue
=
grep
{
$auth
{
lc
$_
->name} }
@rr
;
foreach
(
grep
{
$_
->can(
'address'
) }
@glue
) {
push
@{
$glue
{
lc
$_
->name}},
$_
->address;
}
return
map
{
@$_
}
values
%glue
;
};
my
@ip
;
sub
_hints {
@ip
=
&$parse_dig
unless
scalar
@ip
;
splice
@ip
, 0, 0,
splice
(
@ip
,
int
(
rand
scalar
@ip
) );
return
@ip
;
}
}
sub
DESTROY { }
sub
AUTOLOAD {
my
(
$self
) =
@_
;
no
strict
'refs'
;
our
$AUTOLOAD
;
my
$name
=
$AUTOLOAD
;
$name
=~ s/.*://;
croak
qq[unknown method "$name"]
unless
$public_attr
{
$name
};
*{
$AUTOLOAD
} =
sub
{
my
$self
=
shift
;
$self
=
$self
->_defaults
unless
ref
(
$self
);
$self
->{
$name
} =
shift
|| 0
if
scalar
@_
;
return
$self
->{
$name
};
};
return
&$AUTOLOAD
;
}
1;