Blame t/03-rr.t

Packit e6c8bb
# $Id: 03-rr.t 1597 2017-09-22 08:04:02Z willem $	-*-perl-*-
Packit e6c8bb
Packit e6c8bb
use strict;
Packit e6c8bb
use Test::More tests => 108;
Packit e6c8bb
Packit e6c8bb
use Net::DNS::RR;
Packit e6c8bb
local $Net::DNS::Parameters::DNSEXTLANG;			# suppress Extlang type queries
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check exception raised for unparsable argument
Packit e6c8bb
	foreach my $testcase ( undef, '', ' ', '. NULL x', '. OPT x', '. ATMA x', [], {} ) {
Packit e6c8bb
		eval { new Net::DNS::RR($testcase) };
Packit e6c8bb
		my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
		my $test = defined $testcase ? "'$testcase'" : 'undef';
Packit e6c8bb
		ok( $exception ||= '', "new Net::DNS::RR($test)\t[$exception]" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check plausible ways to create empty record
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		'example.com	A',
Packit e6c8bb
		'example.com	IN',
Packit e6c8bb
		'example.com	IN A',
Packit e6c8bb
		'example.com	IN 123 A',
Packit e6c8bb
		'example.com	123 A',
Packit e6c8bb
		'example.com	123 IN A',
Packit e6c8bb
		'example.com	123 In Aaaa',
Packit e6c8bb
		'example.com	A \\# 0',
Packit e6c8bb
		) {
Packit e6c8bb
		my $rr = new Net::DNS::RR("$testcase");
Packit e6c8bb
		is( length( $rr->rdata ), 0, "new Net::DNS::RR( $testcase )" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check basic functions
Packit e6c8bb
        my ( $name, $class, $ttl, $type, $rdata ) = qw(example.com IN 123 A 192.0.2.1);
Packit e6c8bb
        my $rr = new Net::DNS::RR("$name $ttl $class $type $rdata");
Packit e6c8bb
	my $rdlen = length( $rr->rdata );
Packit e6c8bb
        is( $rr->owner,	   $name,  'expected value returned by $rr->owner' );
Packit e6c8bb
        is( $rr->type,	   $type,  'expected value returned by $rr->type' );
Packit e6c8bb
        is( $rr->class,	   $class, 'expected value returned by $rr->class' );
Packit e6c8bb
        is( $rr->ttl,	   $ttl,   'expected value returned by $rr->ttl' );
Packit e6c8bb
        is( $rr->rdstring, $rdata, 'expected value returned by $rr->rdstring' );
Packit e6c8bb
        is( $rr->rdlength, $rdlen, 'expected value returned by $rr->rdlength' );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check basic parsing of all acceptable forms of A record
Packit e6c8bb
	my $example  = new Net::DNS::RR('example.com. 0 IN A 192.0.2.1');
Packit e6c8bb
	my $expected = $example->string;
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		join( "\t", qw( example.com 0 IN A ), q(\# 4 c0 00 02 01) ),
Packit e6c8bb
		join( "\t", qw( example.com 0 IN A ), q(\# 4 c0000201 ) ),
Packit e6c8bb
		'example.com	0	IN	A	192.0.2.1',
Packit e6c8bb
		'example.com	0	IN	TYPE1	192.0.2.1',
Packit e6c8bb
		'example.com	0	CLASS1	A	192.0.2.1',
Packit e6c8bb
		'example.com	0	CLASS1	TYPE1	192.0.2.1',
Packit e6c8bb
		'example.com	0		A	192.0.2.1',
Packit e6c8bb
		'example.com	0		TYPE1	192.0.2.1',
Packit e6c8bb
		'example.com		IN	A	192.0.2.1',
Packit e6c8bb
		'example.com		IN	TYPE1	192.0.2.1',
Packit e6c8bb
		'example.com		CLASS1	A	192.0.2.1',
Packit e6c8bb
		'example.com		CLASS1	TYPE1	192.0.2.1',
Packit e6c8bb
		'example.com			A	192.0.2.1',
Packit e6c8bb
		'example.com			TYPE1	192.0.2.1',
Packit e6c8bb
		'example.com	IN	0	A	192.0.2.1',
Packit e6c8bb
		'example.com	IN	0	TYPE1	192.0.2.1',
Packit e6c8bb
		'example.com	CLASS1	0	A	192.0.2.1',
Packit e6c8bb
		'example.com	CLASS1	0	TYPE1	192.0.2.1',
Packit e6c8bb
		) {
Packit e6c8bb
		my $rr = new Net::DNS::RR("$testcase");
Packit e6c8bb
		$rr->ttl( $example->ttl );			# TTL only shown if defined
Packit e6c8bb
		is( $rr->string, $expected, "new Net::DNS::RR( $testcase )" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check parsing of comments, quotes and brackets
Packit e6c8bb
	my $example  = new Net::DNS::RR('example.com. 0 IN TXT "txt-data"');
Packit e6c8bb
	my $expected = $example->string;
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		q(example.com 0 IN TXT txt-data ; space delimited),
Packit e6c8bb
		q(example.com 0    TXT txt-data),
Packit e6c8bb
		q(example.com   IN TXT txt-data),
Packit e6c8bb
		q(example.com      TXT txt-data),
Packit e6c8bb
		q(example.com IN 0 TXT txt-data),
Packit e6c8bb
		q(example.com	0	IN	TXT	txt-data	; tab delimited),
Packit e6c8bb
		q(example.com	0		TXT	txt-data),
Packit e6c8bb
		q(example.com		IN	TXT	txt-data),
Packit e6c8bb
		q(example.com			TXT	txt-data),
Packit e6c8bb
		q(example.com	IN	0	TXT	txt-data),
Packit e6c8bb
		q(example.com	0	IN	TXT	"txt-data"	; "quoted"),
Packit e6c8bb
		q(example.com	0		TXT	"txt-data"),
Packit e6c8bb
		q(example.com		IN	TXT	"txt-data"),
Packit e6c8bb
		q(example.com			TXT	"txt-data"),
Packit e6c8bb
		q(example.com	IN	0	TXT	"txt-data"),
Packit e6c8bb
		'example.com (	0	IN	TXT	txt-data )	; bracketed',
Packit e6c8bb
		) {
Packit e6c8bb
		my $rr = new Net::DNS::RR("$testcase");
Packit e6c8bb
		$rr->ttl( $example->ttl );			# TTL only shown if defined
Packit e6c8bb
		is( $rr->string, $expected, "new Net::DNS::RR( $testcase )" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check parsing of implemented RR type with hexadecimal RDATA
Packit e6c8bb
	my @common   = qw( example.com. 3600 IN TXT );
Packit e6c8bb
	my $expected = join "\t", @common, q("two separate" "quoted strings");
Packit e6c8bb
	my $testcase = join "\t", @common, q(\# 28 0c74776f2073657061726174650e71756f74656420737472696e6773);
Packit e6c8bb
	my $rr	     = new Net::DNS::RR("$testcase");
Packit e6c8bb
	is( $rr->string, $expected, "new Net::DNS::RR( $testcase )" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check for exception if RFC3597 format hexadecimal data inconsistent
Packit e6c8bb
	foreach my $testcase ( '\# 0 c0 00 02 01', '\# 3 c0 00 02 01', '\# 5 c0 00 02 01' ) {
Packit e6c8bb
		eval { new Net::DNS::RR("example.com 3600 IN A $testcase") };
Packit e6c8bb
		my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
		ok( $exception ||= '', "mismatched length: $testcase\t[$exception]" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check object construction from attribute list
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		[ type => 'A', address => '192.0.2.1' ],
Packit e6c8bb
		[ type => 'A', address => ['192.0.2.1'] ],
Packit e6c8bb
		) {
Packit e6c8bb
		my $rr = new Net::DNS::RR(@$testcase);
Packit e6c8bb
		is( length( $rr->rdata ), 4, "new Net::DNS::RR( @$testcase )" );
Packit e6c8bb
	}
Packit e6c8bb
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		[ type => 'A', rdata => '' ],
Packit e6c8bb
		[ name => 'example.com', type => 'MX' ],
Packit e6c8bb
		[ type => 'MX', class => 'IN', ttl => 123 ],
Packit e6c8bb
		) {
Packit e6c8bb
		my $rr = new Net::DNS::RR(@$testcase);
Packit e6c8bb
		is( length( $rr->rdata ), 0, "new Net::DNS::RR( @$testcase )" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check for exception for nonexistent attribute
Packit e6c8bb
	my $method = 'bogus';
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		[ type => 'A' ],
Packit e6c8bb
		[ type => 'ATMA' ],
Packit e6c8bb
		[ type => 'ATMA', unimplemented => 'x' ],
Packit e6c8bb
		) {
Packit e6c8bb
		eval { new Net::DNS::RR( @$testcase )->$method('x') };
Packit e6c8bb
		my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
		ok( $exception ||= '', "unknown method:\t[$exception]" );
Packit e6c8bb
	}
Packit e6c8bb
	my $rr = new Net::DNS::RR( type => 'A' );
Packit e6c8bb
        is( $rr->$method, undef, 'suppress repeated unknown method exception' );
Packit e6c8bb
        is( $rr->DESTROY, undef, 'DESTROY() exists to defeat pre-5.18 AUTOLOAD' );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check for exception on bad class method
Packit e6c8bb
	eval { xxxx Net::DNS::RR( type => 'X' ); };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "unknown class method:\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check for exception if RR name not recognised
Packit e6c8bb
	eval { new Net::DNS::RR('example.com. IN BOGUS') };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "unrecognised RR type:\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check for exception when abusing $rr->type()
Packit e6c8bb
	my $rr = new Net::DNS::RR( type => 'A' );
Packit e6c8bb
	eval { $rr->type('X'); };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "cannot change type:\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check for exception when abusing $rr->ttl()
Packit e6c8bb
	my $rr = new Net::DNS::RR( type => 'A' );
Packit e6c8bb
	eval { $rr->ttl('1year'); };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "unknown time unit:\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check for exception when abusing $rr->rdata()
Packit e6c8bb
	my $rr = new Net::DNS::RR( type => 'SOA' );
Packit e6c8bb
	eval { $rr->rdata( pack 'H* H*', '00c000', '00000001' x 5 ); };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "compressed rdata:\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check propagation of exception in string()
Packit e6c8bb
				## (relies on bug that nobody cares enough to fix)
Packit e6c8bb
	my $rr = new Net::DNS::RR( type => 'MINFO', emailbx => '.' );
Packit e6c8bb
	eval {
Packit e6c8bb
		local $SIG{__WARN__} = sub { die @_ };
Packit e6c8bb
		$rr->string();
Packit e6c8bb
	};
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "exception in string:\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check propagation of exception in rdstring()
Packit e6c8bb
				## (relies on bug that nobody cares enough to fix)
Packit e6c8bb
	my $rr = new Net::DNS::RR( type => 'MINFO', emailbx => '.' );
Packit e6c8bb
	eval {
Packit e6c8bb
		local $SIG{__WARN__} = sub { die @_ };
Packit e6c8bb
		$rr->rdatastr();
Packit e6c8bb
	};
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "exception in rdstring:\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check encode/decode functions
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		'example.com	A',
Packit e6c8bb
		'example.com	IN',
Packit e6c8bb
		'example.com	IN A',
Packit e6c8bb
		'example.com	IN 123 A',
Packit e6c8bb
		'example.com	123 A',
Packit e6c8bb
		'example.com	123 IN A',
Packit e6c8bb
		'example.com	A 192.0.2.1',
Packit e6c8bb
		) {
Packit e6c8bb
		my $rr = new Net::DNS::RR("$testcase");
Packit e6c8bb
		my $encoded = $rr->encode;
Packit e6c8bb
		my $decoded = decode Net::DNS::RR(\$encoded);
Packit e6c8bb
		$rr->ttl( $decoded->ttl ) unless $rr->ttl;
Packit e6c8bb
		is( $decoded->string, $rr->string, "encode/decode $testcase" );
Packit e6c8bb
	}
Packit e6c8bb
Packit e6c8bb
	my $opt = new Net::DNS::RR( type => 'OPT' );
Packit e6c8bb
	my $encoded = $opt->encode;
Packit e6c8bb
	my ( $decoded, $offset ) = decode Net::DNS::RR(\$encoded);
Packit e6c8bb
	is( $decoded->string, $opt->string, "encode/decode OPT RR" );
Packit e6c8bb
	is( $offset, length($encoded), "decode returns offset of next RR" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{				## check canonical encode function
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		'example.com 123 IN A',
Packit e6c8bb
		'EXAMPLE.com 123 A 192.0.2.1',
Packit e6c8bb
		) {
Packit e6c8bb
		my $rr = new Net::DNS::RR("$testcase");
Packit e6c8bb
		my $expected  = unpack 'H*', $rr->encode(0);
Packit e6c8bb
		my $canonical = unpack 'H*', $rr->canonical;
Packit e6c8bb
		is( $canonical, $expected, "canonical encode $testcase" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{
Packit e6c8bb
	foreach my $testcase (
Packit e6c8bb
		'',
Packit e6c8bb
		'000001',
Packit e6c8bb
		'0000010001000000010004',
Packit e6c8bb
		) {
Packit e6c8bb
		my $wiredata = pack 'H*', $testcase;
Packit e6c8bb
		my $question = eval { decode Net::DNS::RR(\$wiredata); };
Packit e6c8bb
		my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
		ok( $exception ||= '', "corrupt wire-format\t[$exception]" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{					## check plain and generic formats
Packit e6c8bb
	my @testcase = (
Packit e6c8bb
		[owner => 'example.com.', type => 'A'],
Packit e6c8bb
		[owner => 'example.com.', type => 'A', rdata => ''],
Packit e6c8bb
		['example.com.	IN	NS	a.iana-servers.net.'],
Packit e6c8bb
		['example.com.	IN	SOA	(
Packit e6c8bb
				sns.dns.icann.org. noc.dns.icann.org.
Packit e6c8bb
				2015082417	;serial
Packit e6c8bb
				7200		;refresh
Packit e6c8bb
				3600		;retry
Packit e6c8bb
				1209600		;expire
Packit e6c8bb
				3600		;minimum
Packit e6c8bb
			)'],
Packit e6c8bb
		[owner => 'example.com.', type => 'ATMA'],	# unimplemented
Packit e6c8bb
		[owner => 'example.com.', type => 'ATMA', rdata => ''],
Packit e6c8bb
		[owner => 'example.com.', type => 'ATMA', rdata => 'octets'],
Packit e6c8bb
	);
Packit e6c8bb
	foreach my $testcase (@testcase) {
Packit e6c8bb
		my $rr = new Net::DNS::RR(@$testcase);
Packit e6c8bb
		my $type = $rr->type;
Packit e6c8bb
		my $plain = new Net::DNS::RR( $rr->plain );
Packit e6c8bb
		is( $plain->string, $rr->string, "parse rr->plain format $type" );
Packit e6c8bb
		my $rfc3597 = new Net::DNS::RR( $rr->generic );
Packit e6c8bb
		is( $rfc3597->string, $rr->string, "parse rr->generic format $type" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{					## check RR sorting functions
Packit e6c8bb
	foreach my $attr ( [], ['preference'], ['X'] ) {
Packit e6c8bb
		my $func = Net::DNS::RR::MX->get_rrsort_func(@$attr);
Packit e6c8bb
		is( ref($func), 'CODE', "MX->get_rrsort_func(@$attr)" );
Packit e6c8bb
	}
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
eval {					## exercise printing functions
Packit e6c8bb
	require Data::Dumper;
Packit e6c8bb
	local $Data::Dumper::Maxdepth;
Packit e6c8bb
	local $Data::Dumper::Sortkeys;
Packit e6c8bb
	my $object   = new Net::DNS::RR('example.com A 192.0.2.1');
Packit e6c8bb
	my $filename = "03-rr.tmp";
Packit e6c8bb
	open( TEMP, ">$filename" ) || die "Could not open $filename for writing";
Packit e6c8bb
	select( ( select(TEMP), $object->print )[0] );
Packit e6c8bb
	select( ( select(TEMP), $object->dump )[0] );
Packit e6c8bb
	$Data::Dumper::Maxdepth = 6;
Packit e6c8bb
	$Data::Dumper::Sortkeys = 1;
Packit e6c8bb
	select( ( select(TEMP), $object->dump )[0] );
Packit e6c8bb
	close(TEMP);
Packit e6c8bb
	unlink($filename);
Packit e6c8bb
};
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
exit;
Packit e6c8bb