#
# Net::DNS::Resolver::Programmable
# A Net::DNS::Resolver descendant class for offline emulation of DNS
#
# (C) 2006-2007 Julian Mehnle <julian@mehnle.net>
# Maintained from 2017 by David Precious (BIGPRESH) <davidp@preshweb.co.uk>
#
##############################################################################
package Net::DNS::Resolver::Programmable;
=head1 NAME
Net::DNS::Resolver::Programmable - programmable DNS resolver class for offline
emulation of DNS
=cut
our $VERSION;
$VERSION = '0.008';
use warnings;
use strict;
use Net::DNS;
use base 'Net::DNS::Resolver';
use constant TRUE => (0 == 0);
use constant FALSE => not TRUE;
my %rcode = map { $_ => 1 } qw(NOERROR FORMERR SERVFAIL NXDOMAIN NOTIMP
REFUSED YXDOMAIN YXRRSET NXRRSET NOTAUTH NOTZONE BADVERS
BADSIG BADKEY BADTIME BADMODE BADNAME BADALG BADTRUNC);
# Interface:
##############################################################################
=head1 SYNOPSIS
use Net::DNS::Resolver::Programmable;
use Net::DNS::RR;
my $resolver = Net::DNS::Resolver::Programmable->new(
records => {
'example.com' => [
Net::DNS::RR->new('example.com. NS ns.example.org.'),
Net::DNS::RR->new('example.com. A 192.168.0.1')
],
'ns.example.org' => [
Net::DNS::RR->new('ns.example.org. A 192.168.1.1')
]
},
resolver_code => sub {
my ($domain, $rr_type, $class) = @_;
...
return ($result, $aa, @rrs);
}
);
=cut
# Implementation:
##############################################################################
=head1 DESCRIPTION
B<Net::DNS::Resolver::Programmable> is a B<Net::DNS::Resolver> descendant
class that allows a virtual DNS to be emulated instead of querying the real
DNS. A set of static DNS records may be supplied, or arbitrary code may be
specified as a means for retrieving DNS records, or even generating them on the
fly.
=head2 Constructor
The following constructor is provided:
=over
=item B<new(%options)>: returns I<Net::DNS::Resolver::Programmable>
Creates a new programmed DNS resolver object.
%options is a list of key/value pairs representing any of the following
options:
=over
=item B<records>
A reference to a hash of arrays containing a static set of I<Net::DNS::RR>
objects. The hash entries must be indexed by fully qualified domain names
(lower-case, without any trailing dots), and the entries themselves must be
arrays of the RR objects pertaining to these domain names. For example:
records => {
'example.com' => [
Net::DNS::RR->new('example.com. NS ns.example.org.'),
Net::DNS::RR->new('example.com. A 192.168.0.1')
],
'www.example.com' => [
Net::DNS::RR->new('www.example.com. A 192.168.0.2')
],
'ns.example.org' => [
Net::DNS::RR->new('ns.example.org. A 192.168.1.1')
]
}
If this option is specified, the resolver retrieves requested RRs from this
data structure.
=item B<resolver_code>
A code reference used as a call-back for dynamically retrieving requested RRs.
The code must take the following query parameters as arguments: the I<domain>,
I<RR type>, and I<class>.
It must return a list composed of: the response's I<RCODE> (by name, as
returned by L<< Net::DNS::Header->rcode|Net::DNS::Header/rcode >>), the
I<< C<aa> (authoritative answer) flag >> (I<boolean>, use B<undef> if you don't
care), and the I<Net::DNS::RR answer objects>. If an error string is returned
instead of a valid RCODE, a I<Net::DNS::Packet> object is not constructed but
an error condition for the resolver is signaled instead.
For example:
resolver_code => sub {
my ($domain, $rr_type, $class) = @_;
...
return ($result, $aa, @rrs);
}
If both this and the C<records> option are specified, then statically
programmed records are used in addition to any that are returned by the
configured resolver code.
=item B<defnames>
=item B<dnsrch>
=item B<domain>
=item B<searchlist>
=item B<debug>
These Net::DNS::Resolver options are also meaningful with
Net::DNS::Resolver::Programmable. See L<Net::DNS::Resolver> for their
descriptions.
=back
=cut
sub new {
my ($class, %options) = @_;
# Create new object:
my $self = $class->SUPER::new(%options);
$self->{records} = $options{records};
$self->{resolver_code} = $options{resolver_code};
return $self;
}
=back
=head2 Instance methods
The following instance methods of I<Net::DNS::Resolver> are also supported by
I<Net::DNS::Resolver::Programmable>:
=over
=item B<search>: returns I<Net::DNS::Packet>
=item B<query>: returns I<Net::DNS::Packet>
=item B<send>: returns I<Net::DNS::Packet>
Performs an offline DNS query, using the statically programmed DNS RRs and/or
the configured dynamic resolver code. See the L</new> constructor's C<records>
and C<resolver_code> options. See the descriptions of L<search, query, and
send|Net::DNS::Resolver/search> for details about the calling syntax of these
methods.
=cut
sub send {
my $self = shift;
# We could be passed a Net::DNS::Packet object, or a set of strings; handle
# both
my ($packet, $question);
if (Scalar::Util::blessed($_[0]) && $_[0]->isa('Net::DNS::Packet')) {
$packet = $_[0];
# TODO: is it a safe assumption that a packet we're passed will only
# contain one Question object?
($question) = $packet->question;
} else {
$question = Net::DNS::Question->new(@_);
}
my $domain = lc($question->qname);
my $rr_type = $question->qtype;
my $class = $question->qclass;
$self->_reset_errorstring;
my ($result, $aa, @answer_rrs);
if (defined(my $resolver_code = $self->{resolver_code})) {
($result, $aa, @answer_rrs) = $resolver_code->($domain, $rr_type, $class);
}
if ( not defined($result) or defined($rcode{$result}) ) {
# Valid RCODE, return a packet:
$aa = TRUE if not defined($aa);
$result = 'NOERROR' if not defined($result);
if (defined(my $records = $self->{records})) {
if (ref(my $rrs_for_domain = $records->{$domain}) eq 'ARRAY') {
foreach my $rr (@$rrs_for_domain) {
push(@answer_rrs, $rr)
if $rr->name eq $domain
and $rr->type eq $rr_type
and $rr->class eq $class;
}
}
}
my $response_packet = Net::DNS::Packet->new($domain, $rr_type, $class);
$response_packet->header->qr(TRUE);
$response_packet->header->rcode($result);
$response_packet->header->aa($aa);
$response_packet->push(answer => @answer_rrs);
return $response_packet;
}
else {
# Invalid RCODE, signal error condition by not returning a packet:
$self->errorstring($result);
return undef;
}
}
=item B<print>
=item B<string>: returns I<string>
=item B<searchlist>: returns I<list> of I<string>
=item B<defnames>: returns I<boolean>
=item B<dnsrch>: returns I<boolean>
=item B<debug>: returns I<boolean>
=item B<errorstring>: returns I<string>
=item B<answerfrom>: returns I<string>
=item B<answersize>: returns I<integer>
See L<Net::DNS::Resolver/METHODS>.
=back
Currently the following methods of I<Net::DNS::Resolver> are B<not> supported:
B<axfr>, B<axfr_start>, B<axfr_next>, B<nameservers>, B<port>, B<srcport>,
B<srcaddr>, B<bgsend>, B<bgread>, B<bgisready>, B<tsig>, B<retrans>, B<retry>,
B<recurse>, B<usevc>, B<tcp_timeout>, B<udp_timeout>, B<persistent_tcp>,
B<persistent_udp>, B<igntc>, B<dnssec>, B<cdflag>, B<udppacketsize>.
The effects of using these on I<Net::DNS::Resolver::Programmable> objects are
undefined.
=head1 SEE ALSO
L<Net::DNS::Resolver>
For availability, support, and license information, see the README file
included with Net::DNS::Resolver::Programmable.
=head1 AUTHORS
David Precious (BIGPRESH) C<< <davidp@preshweb.co.uk> >> took on maintainership
in July 2017
Original author Julian Mehnle C<< <julian@mehnle.net> >>
=head1 ACKNOWLEDGEMENTS
Dick Franks (rwfranks)
(This section was added by BIGPRESH in July 2017, so currently omits
acknowledgements for those who contributed things in the past; I may
retrospectively add them in future.)
=cut
TRUE;
# vim:sts=4 sw=4 et