Blame t/04-packet.t

Packit e6c8bb
# $Id: 04-packet.t 1449 2016-02-01 12:27:12Z willem $	-*-perl-*-
Packit e6c8bb
Packit e6c8bb
use strict;
Packit e6c8bb
Packit e6c8bb
BEGIN {
Packit e6c8bb
	use Test::More tests => 99;
Packit e6c8bb
Packit e6c8bb
	use_ok('Net::DNS');
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	new() class constructor method must return object of appropriate class
Packit e6c8bb
my $object = Net::DNS::Packet->new();
Packit e6c8bb
ok( $object->isa('Net::DNS::Packet'), 'new() object' );
Packit e6c8bb
Packit e6c8bb
ok( $object->header,			      'header() method works' );
Packit e6c8bb
ok( $object->header->isa('Net::DNS::Header'), 'header() returns header object' );
Packit e6c8bb
Packit e6c8bb
ok( $object->edns,			     'edns() method works' );
Packit e6c8bb
ok( $object->edns->isa('Net::DNS::RR::OPT'), 'edns() returns OPT RR object' );
Packit e6c8bb
Packit e6c8bb
like( $object->string, '/HEADER/', 'string() returns representation of packet' );
Packit e6c8bb
$object->header->opcode('UPDATE');
Packit e6c8bb
like( $object->string, '/UPDATE/', 'string() returns representation of update' );
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	Empty packet created when new() arguments omitted
Packit e6c8bb
my $empty = Net::DNS::Packet->new();
Packit e6c8bb
ok( $empty, 'create empty packet' );
Packit e6c8bb
foreach my $method ( qw(question answer authority additional), qw(zone pre prerequisite update) ) {
Packit e6c8bb
	my @result = $empty->$method;
Packit e6c8bb
	ok( @result == 0, "$method() returns empty list" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	Create a DNS query packet
Packit e6c8bb
my ( $domain, $type, $class ) = qw(example.test MX IN);
Packit e6c8bb
my $question = Net::DNS::Question->new( $domain, $type, $class );
Packit e6c8bb
Packit e6c8bb
my $packet = Net::DNS::Packet->new( $domain, $type, $class );
Packit e6c8bb
like( $packet->string, "/$class\t$type/", 'create query packet' );
Packit e6c8bb
Packit e6c8bb
my @question = $packet->question;
Packit e6c8bb
ok( @question && @question == 1, 'packet->question() returns single element list' );
Packit e6c8bb
my ($q) = @question;
Packit e6c8bb
ok( $q->isa('Net::DNS::Question'), 'list element is a question object' );
Packit e6c8bb
is( $q->string, $question->string, 'question object correct' );
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	data() method returns non-empty scalar
Packit e6c8bb
my $packet_data = $packet->data;
Packit e6c8bb
ok( $packet_data, 'packet->data() method works' );
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	new(\$data) class constructor method returns object of appropriate class
Packit e6c8bb
my $packet2 = Net::DNS::Packet->new( \$packet_data );
Packit e6c8bb
ok( $packet2->isa('Net::DNS::Packet'), 'new(\$data) object' );
Packit e6c8bb
is( $packet2->string, $packet->string, 'decoded packet matches original' );
Packit e6c8bb
is( unpack( 'H*', $packet2->data ), unpack( 'H*', $packet_data ), 'retransmitted packet matches original' );
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	new(\$data) class constructor captures exception text when data truncated
Packit e6c8bb
my @data = unpack 'C*', $packet->data;
Packit e6c8bb
while (@data) {
Packit e6c8bb
	pop(@data);
Packit e6c8bb
	my $truncated = pack 'C*', @data;
Packit e6c8bb
	my $length    = length $truncated;
Packit e6c8bb
	my $object    = Net::DNS::Packet->new( \$truncated );
Packit e6c8bb
	my $exception = $@;
Packit e6c8bb
	$exception =~ s/\n.*$//g;
Packit e6c8bb
	ok( $exception, "truncated ($length octets):\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	Use push() to add RRs to each section
Packit e6c8bb
my $update = Net::DNS::Packet->new('.');
Packit e6c8bb
my $index;
Packit e6c8bb
foreach my $section (qw(answer authority additional)) {
Packit e6c8bb
	my $i	= ++$index;
Packit e6c8bb
	my $rr1 = Net::DNS::RR->new(
Packit e6c8bb
		Name	=> "$section$i.example.test",
Packit e6c8bb
		Type	=> "A",
Packit e6c8bb
		Address => "10.0.0.$i"
Packit e6c8bb
		);
Packit e6c8bb
	my $string1 = $rr1->string;
Packit e6c8bb
	my $count1 = $update->push( $section, $rr1 );
Packit e6c8bb
	like( $update->string, "/$string1/", "push first RR into $section section" );
Packit e6c8bb
	is( $count1, 1, "push() returns $section RR count" );
Packit e6c8bb
Packit e6c8bb
	my $j	= ++$index;
Packit e6c8bb
	my $rr2 = Net::DNS::RR->new(
Packit e6c8bb
		Name	=> "$section$j.example.test",
Packit e6c8bb
		Type	=> "A",
Packit e6c8bb
		Address => "10.0.0.$j"
Packit e6c8bb
		);
Packit e6c8bb
	my $string2 = $rr2->string;
Packit e6c8bb
	my $count2 = $update->push( $section, $rr2 );
Packit e6c8bb
	like( $update->string, "/$string2/", "push second RR into $section section" );
Packit e6c8bb
	is( $count2, 2, "push() returns $section RR count" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
# Add enough distinct labels to render compression unusable at some point
Packit e6c8bb
for ( 0 .. 255 ) {
Packit e6c8bb
	$update->push( 'answer', Net::DNS::RR->new( "X$_ TXT \"" . pack( "A255", "x" ) . '"' ) );
Packit e6c8bb
}
Packit e6c8bb
$update->push( 'answer', Net::DNS::RR->new('XY TXT ""') );
Packit e6c8bb
$update->push( 'answer', Net::DNS::RR->new('VW.XY TXT ""') );
Packit e6c8bb
Packit e6c8bb
#	Decode data buffer and compare with original
Packit e6c8bb
my $buffer = $update->data;
Packit e6c8bb
my $decoded = eval { Net::DNS::Packet->new( \$buffer ) };
Packit e6c8bb
ok( $decoded, 'new() from data buffer works' );
Packit e6c8bb
is( $decoded->answersize, length($buffer), '$decoded->answersize() works' );
Packit e6c8bb
$decoded->answerfrom('local');
Packit e6c8bb
ok( $decoded->answerfrom(), '$decoded->answerfrom() works' );
Packit e6c8bb
ok( $decoded->string(),	    '$decoded->string() works' );
Packit e6c8bb
foreach my $count (qw(qdcount ancount nscount arcount)) {
Packit e6c8bb
	is( $decoded->header->$count, $update->header->$count, "check header->$count correct" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
foreach my $section (qw(question)) {
Packit e6c8bb
	my @original = map { $_->string } $update->$section;
Packit e6c8bb
	my @content  = map { $_->string } $decoded->$section;
Packit e6c8bb
	is_deeply( \@content, \@original, "check content of $section section" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
foreach my $section (qw(answer authority additional)) {
Packit e6c8bb
	my @original = map { $_->ttl(0); $_->string } $update->$section;    # almost! need TTL defined
Packit e6c8bb
	my @content = map { $_->string } $decoded->$section;
Packit e6c8bb
	is_deeply( \@content, \@original, "check content of $section section" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	check that pop() removes RR from section	Memo to self: no RR in question section!
Packit e6c8bb
foreach my $section (qw(answer authority additional)) {
Packit e6c8bb
	my $c1 = $update->push( $section, Net::DNS::RR->new('X TXT ""') );
Packit e6c8bb
	my $rr = $update->pop($section);
Packit e6c8bb
	my $c2 = $update->push($section);
Packit e6c8bb
	is( $c2, $c1 - 1, "pop() RR from $section section" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
#	Test using a predefined answer.
Packit e6c8bb
#	This is an answer that was generated by a bind server, with an option munged on the end.
Packit e6c8bb
Packit e6c8bb
my $BIND = pack( 'H*',
Packit e6c8bb
'22cc85000001000000010001056461636874036e657400001e0001c00c0006000100000e100025026e730472697065c012046f6c6166c02a7754e1ae0000a8c0000038400005460000001c2000002910000000800000050000000130'
Packit e6c8bb
	);
Packit e6c8bb
Packit e6c8bb
my $bind = Net::DNS::Packet->new( \$BIND );
Packit e6c8bb
Packit e6c8bb
is( $bind->header->qdcount, 1, 'check question count in synthetic packet header' );
Packit e6c8bb
is( $bind->header->ancount, 0, 'check answer count in synthetic packet header' );
Packit e6c8bb
is( $bind->header->nscount, 1, 'check authority count in synthetic packet header' );
Packit e6c8bb
is( $bind->header->adcount, 1, 'check additional count in synthetic packet header' );
Packit e6c8bb
Packit e6c8bb
my ($rr) = $bind->additional;
Packit e6c8bb
Packit e6c8bb
is( $rr->type, 'OPT',  'Additional section packet is EDNS0 type' );
Packit e6c8bb
is( $rr->size, '4096', 'EDNS0 packet size correct' );
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{					## check tolerance of invalid pop
Packit e6c8bb
	my $packet = new Net::DNS::Packet('example.com');
Packit e6c8bb
	my $case1  = $packet->pop('');
Packit e6c8bb
	my $case2  = $packet->pop('bogus');
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{					## check $packet->reply()
Packit e6c8bb
	my $packet = new Net::DNS::Packet('example.com');
Packit e6c8bb
	my $reply  = $packet->reply();
Packit e6c8bb
	ok( $reply->isa('Net::DNS::Packet'), '$packet->reply() returns packet' );
Packit e6c8bb
	eval { $reply->reply(); };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "reply->reply()\t[$exception]" );
Packit e6c8bb
	my $udpmax = 2048;
Packit e6c8bb
	$packet->edns->size($udpmax);
Packit e6c8bb
	$packet->data;
Packit e6c8bb
	is( $packet->reply($udpmax)->edns->size(), $udpmax, 'packet->reply() supports EDNS' );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{					## check $packet->sigrr
Packit e6c8bb
	my $packet = new Net::DNS::Packet();
Packit e6c8bb
	is( $packet->sigrr(), undef, 'sigrr() undef for empty packet' );
Packit e6c8bb
	$packet->push( additional => new Net::DNS::RR( type => 'OPT' ) );
Packit e6c8bb
	is( $packet->sigrr(),  undef, 'sigrr() undef for unsigned packet' );
Packit e6c8bb
	is( $packet->verify(), undef, 'verify() fails for unsigned packet' );
Packit e6c8bb
	ok( $packet->verifyerr(), 'verifyerr() returned for unsigned packet' );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{					## go through the motions of SIG0
Packit e6c8bb
	my $packet = new Net::DNS::Packet('example.com');
Packit e6c8bb
	my $sig = new Net::DNS::RR( type => 'SIG' );
Packit e6c8bb
	ok( $packet->sign_sig0($sig), 'sign_sig0() returns SIG0 record' );
Packit e6c8bb
	is( ref( $packet->sigrr() ), ref($sig), 'sigrr() returns SIG RR' );
Packit e6c8bb
Packit e6c8bb
	eval { $packet->sign_sig0( [] ); };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "sign_sig0([])\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
{					## check exception raised for bad TSIG
Packit e6c8bb
	my $packet = new Net::DNS::Packet('example.com');
Packit e6c8bb
	my $bogus = new Net::DNS::RR( type => 'NULL' );
Packit e6c8bb
	eval { $packet->sign_tsig($bogus); };
Packit e6c8bb
	my $exception = $1 if $@ =~ /^(.+)\n/;
Packit e6c8bb
	ok( $exception ||= '', "sign_tsig([])\t[$exception]" );
Packit e6c8bb
}
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
eval {					## exercise but do not test print
Packit e6c8bb
	require Data::Dumper;
Packit e6c8bb
	local $Data::Dumper::Maxdepth;
Packit e6c8bb
	local $Data::Dumper::Sortkeys;
Packit e6c8bb
	my $object   = new Net::DNS::Packet('example.com');
Packit e6c8bb
	my $buffer   = $object->data;
Packit e6c8bb
	my $corrupt  = substr $buffer, 0, 10;
Packit e6c8bb
	my $filename = '04-packet.txt';
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
	select( ( select(TEMP), Net::DNS::Packet->new( \$buffer, 1 ) )[0] );
Packit e6c8bb
	select( ( select(TEMP), Net::DNS::Packet->new( \$corrupt, 1 ) )[0] );
Packit e6c8bb
	close(TEMP);
Packit e6c8bb
	unlink($filename);
Packit e6c8bb
};
Packit e6c8bb
Packit e6c8bb
Packit e6c8bb
exit;
Packit e6c8bb