|
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 |
|