blob: 634414701ae822eeade3795137198561e29ceac7 [file]
#!/usr/bin/perl
# (C) Sergey Kandaurov
# (C) Nginx, Inc.
# Tests for http resolver.
###############################################################################
use warnings;
use strict;
use Test::More;
BEGIN { use FindBin; chdir($FindBin::Bin); }
use lib 'lib';
use Test::Nginx;
###############################################################################
select STDERR; $| = 1;
select STDOUT; $| = 1;
eval { require Net::DNS::Nameserver; };
plan(skip_all => "Net::DNS::Nameserver not installed") if $@;
my $t = Test::Nginx->new()->has(qw/http proxy rewrite ipv6/);
$t->write_file_expand('nginx.conf', <<'EOF');
%%TEST_GLOBALS%%
daemon off;
events {
}
http {
%%TEST_GLOBALS_HTTP%%
server {
listen 127.0.0.1:8080;
listen [::1]:8080;
server_name localhost;
location / {
resolver 127.0.0.1:8081;
# to lower resolving delay for unsupported AAAA
resolver_timeout 1s;
proxy_pass http://$host:8080/backend;
}
location /cached {
resolver 127.0.0.1:8081 127.0.0.1:8082;
proxy_pass http://$host:8080/backend;
}
location /two {
resolver 127.0.0.1:8081 127.0.0.1:8082;
resolver_timeout 2s;
proxy_pass http://$host:8080/backend;
}
location /valid {
resolver 127.0.0.1:8081 127.0.0.1:8082 valid=3s;
resolver_timeout 2s;
proxy_pass http://$host:8080/backend;
}
location /invalid {
proxy_pass http://$host:8080/backend;
}
location /many {
resolver 127.0.0.1:8081 127.0.0.1:8082;
resolver_timeout 2s;
proxy_pass http://$host:8080/backend;
proxy_next_upstream http_504 timeout error;
proxy_intercept_errors on;
proxy_connect_timeout 2s;
error_page 504 502 /50x;
}
location /backend {
return 200;
}
location /50x {
return 200 $upstream_addr;
}
}
}
EOF
$t->run_daemon(\&dns_daemon, 8081);
$t->run_daemon(\&dns_daemon, 8082);
eval {
open OLDERR, ">&", \*STDERR; close STDERR;
$t->run();
open STDERR, ">&", \*OLDERR;
};
plan(skip_all => 'no inet6 support') if $@;
$t->waitforsocket('127.0.0.1:8081');
$t->waitforsocket('127.0.0.1:8082');
$t->plan(28);
###############################################################################
like(http_host_header('a.example.net', '/'), qr/200 OK/, 'A');
like(http_host_header('short.example.net', '/'), qr/502 Bad/,
'A short dns response');
TODO: {
local $TODO = 'support for AAAA';
like(http_host_header('aaaa.example.net', '/'), qr/200 OK/, 'AAAA');
}
like(http_host_header('nx.example.net', '/'), qr/502 Bad/, 'NXDOMAIN');
like(http_host_header('cname.example.net', '/cached'), qr/200 OK/, 'CNAME');
like(http_host_header('cname.example.net', '/cached'), qr/200 OK/,
'CNAME cached');
# CNAME + A combined answer
# demonstrates the name in answer section different from what is asked
like(http_host_header('cname_a.example.net', '/'), qr/200 OK/, 'CNAME + A');
# CNAME refers to non-existing A
like(http_host_header('cname2.example.net', '/'), qr/502 Bad/, 'CNAME bad');
like(http_host_header('long.example.net', '/'), qr/200 OK/, 'long label');
like(http_host_header('long2.example.net', '/'), qr/200 OK/, 'long name');
# take into account DNAME
like(http_host_header('alias.example.com', '/'), qr/200 OK/, 'DNAME');
# many A records in round robin
# nonexisting IPs enumerated with proxy_next_upstream
like(http_host_header('many.example.net', '/many'),
qr/^127.0.0.20(1:8080, 127.0.0.202:8080|2:8080, 127.0.0.201:8080)$/m,
'A many');
like(http_host_header('many.example.net', '/many'),
qr/^127.0.0.20(1:8080, 127.0.0.202:8080|2:8080, 127.0.0.201:8080)$/m,
'A many cached');
# several resolver addresses with 1st ns bad
# query bad ns, negative responses are not cached
like(http_host_header('2.example.net', '/two'), qr/502 Bad/, 'two ns bad');
# query alive ns
like(http_host_header('2.example.net', '/two'), qr/200 OK/, 'two ns good');
# cached response prevents querying the next (bad) ns again
like(http_host_header('2.example.net', '/two'), qr/200 OK/, 'two ns cached');
# ttl tested with 1st ns good and 2nd ns bad
# query good ns and cache response
like(http_host_header('ttl.example.net', '/two'), qr/200 OK/, 'ttl');
# cached response prevents querying the next (bad) ns
like(http_host_header('ttl.example.net', '/two'), qr/200 OK/, 'ttl cached 1');
like(http_host_header('ttl.example.net', '/two'), qr/200 OK/, 'ttl cached 2');
sleep 2;
# expired ttl causes nginx to query the next (bad) ns
like(http_host_header('ttl.example.net', '/two'), qr/502 Bad/, 'ttl expired');
# zero ttl prohibits response caching
like(http_host_header('ttl0.example.net', '/two'), qr/200 OK/, 'zero ttl');
TODO: {
local $TODO = 'support for zero ttl';
like(http_host_header('ttl0.example.net', '/two'), qr/502 Bad/,
'zero ttl not cached');
}
# "valid" parameter tested with 1st alive ns and 2nd bad ns
# query alive ns, and cache response
like(http_host_header('ttl.example.net', '/valid'), qr/200 OK/, 'valid');
# cached response prevents querying the next (bad) ns
like(http_host_header('ttl.example.net', '/valid'), qr/200 OK/,
'valid cached 1');
like(http_host_header('ttl.example.net', '/valid'), qr/200 OK/,
'valid cached 2');
sleep 2;
# expired ttl is overridden with "valid" parameter
# response is taken from cache
like(http_host_header('ttl.example.net', '/valid'), qr/200 OK/,
'valid overrides ttl');
sleep 2;
# expired "valid" value causes nginx to query the next (bad) ns
like(http_host_header('ttl.example.net', '/valid'), qr/502 Bad/,
'valid expired');
like(http_host_header('example.net', '/invalid'), qr/502 Bad/, 'no resolver');
###############################################################################
sub http_host_header {
my ($host, $uri) = @_;
return http(<<EOF);
GET $uri HTTP/1.0
Host: $host
EOF
}
###############################################################################
sub reply_handler {
my ($name, $class, $type, $peerhost, $query, $conn) = @_;
my ($rcode, @ans, $ttl, $rdata);
$rcode = 'NOERROR';
$ttl = 3600;
if (($name eq 'a.example.net') || ($name eq 'alias.example.net')) {
($rdata) = ('127.0.0.1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif (($name eq 'many.example.net')) {
if ($conn->{sockport} == 8082) {
return 'SERVFAIL';
}
($rdata) = ('127.0.0.201');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
($rdata) = ('127.0.0.202');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif (($name eq 'aaaa.example.net')) {
($type, $rdata) = ('AAAA', '::1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif (($name eq 'short.example.net')) {
# zero length RDATA in DNS response
($name, $rdata) = ($name, '');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif (($name eq 'alias.example.com')) {
my $dname = 'example.com';
($type, $rdata) = ('DNAME', 'example.net');
push @ans, Net::DNS::RR->new("$dname $ttl $class $type $rdata");
($type, $rdata) = ('CNAME', 'alias.example.net');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq 'cname.example.net') {
if ($conn->{sockport} == 8082) {
return 'SERVFAIL';
}
($type, $rdata) = ('CNAME', 'alias.example.net');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq 'cname_a.example.net') {
($type, $rdata) = ('CNAME', 'alias.example.net');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
($name, $type, $rdata) = ('alias.example.net', 'A', '127.0.0.1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq 'cname2.example.net') {
# points to non-existing A
($type, $rdata) = ('CNAME', 'nx.example.net');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq 'long.example.net') {
($type, $rdata) = ('CNAME', 'a' x 63);
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif (($name eq 'a' x 63)) {
($rdata) = ('127.0.0.1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq 'long2.example.net') {
($type, $rdata) = ('CNAME', 'a' x 63 . '.' . 'a' x 63 . '.'
. 'a' x 63 . '.' . 'a' x 63);
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif (($name eq 'a' x 63 . '.' . 'a' x 63 . '.' . 'a' x 63 . '.'
. 'a' x 63))
{
($rdata) = ('127.0.0.1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq 'ttl.example.net') {
if ($conn->{sockport} == 8082) {
return 'SERVFAIL';
}
($ttl, $rdata) = (1, '127.0.0.1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq 'ttl0.example.net') {
if ($conn->{sockport} == 8082) {
return 'SERVFAIL';
}
($ttl, $rdata) = (0, '127.0.0.1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} elsif ($name eq '2.example.net') {
if ($conn->{sockport} == 8081) {
return 'SERVFAIL';
}
($rdata) = ('127.0.0.1');
push @ans, Net::DNS::RR->new("$name $ttl $class $type $rdata");
} else {
$rcode = 'NXDOMAIN';
}
return ($rcode, \@ans);
}
sub dns_daemon {
my ($port) = @_;
my $ns = Net::DNS::Nameserver->new(
LocalAddr => '127.0.0.1',
LocalPort => $port,
Proto => 'udp',
ReplyHandler => \&reply_handler,
)
or die "Can't create nameserver object: $!\n";
$ns->main_loop;
}
###############################################################################