From 8fef9901ed8f82b46c3473219cfd4c3736b47ae7 Mon Sep 17 00:00:00 2001 From: Xiaochen Wang Date: Mon, 29 May 2023 11:08:04 +0800 Subject: [PATCH] Fixed test cases that failed to start the DNS daemon * Refactored the logic of the DNS daemon to follows the coding style of the nginx-tests repository. * When attempting to bind UDP ports fails, it retries other random ports. --- t/http_proxy_connect.t | 246 ++++++++++++----------- t/http_proxy_connect_lua.t | 242 +++++++++++----------- t/http_proxy_connect_resolve_variables.t | 229 +++++++++++---------- t/http_proxy_connect_timeout.t | 2 +- 4 files changed, 385 insertions(+), 334 deletions(-) diff --git a/t/http_proxy_connect.t b/t/http_proxy_connect.t index 672c604..53d0c7b 100644 --- a/t/http_proxy_connect.t +++ b/t/http_proxy_connect.t @@ -10,20 +10,20 @@ use warnings; use strict; use Test::More; -# use Test::Simple 'no_plan'; + +use IO::Select; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; use Test::Nginx; -use Net::DNS::Nameserver; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy/); #->plan(12); +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(22); ############################################################################### @@ -37,9 +37,6 @@ print("+ test_enable_rewrite_phase: $test_enable_rewrite_phase\n"); # --- init DNS server --- -my $bind_pid; -my $bind_server_port = 18085; - # SRV record, not used my %route_map; @@ -52,19 +49,6 @@ my %aroute_map = ( 'set-response-status.com' => [[300, "127.0.0.1"]], ); -# AAAA record (ipv6) -my %aaaaroute_map; -# my %aaaaroute_map = ( -# 'www.test-a.com' => [[300, "[::1]"]], -# 'www.test-b.com' => [[300, "[::1]"]], -# #'www.test-a.com' => [[300, "127.0.0.1"]], -# #'www.test-b.com' => [[300, "127.0.0.1"]], -# ); - -start_bind(); - -# --- end --- - ############################################################################### my $nginx_conf = <<'EOF'; @@ -88,7 +72,7 @@ http { access_log %%TESTDIR%%/connect.log connect; - resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. + resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. server { listen 127.0.0.1:8081; @@ -157,6 +141,9 @@ EOF $t->write_file_expand('nginx.conf', $nginx_conf); +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + eval { $t->run(); }; @@ -313,7 +300,7 @@ http { access_log off; - resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. + resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. server { listen 127.0.0.1:8080; @@ -380,7 +367,7 @@ http { access_log off; - resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. + resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. server { listen 127.0.0.1:8080; @@ -410,17 +397,8 @@ if ($test_enable_rewrite_phase) { $t->stop(); - - -# --- stop DNS server --- - -stop_bind(); - -done_testing(); - ############################################################################### - sub http_connect_request { my ($host, $port, $url) = @_; my $r = http_connect($host, $port, < 0; + use constant FORMERR => 1; + use constant SERVFAIL => 2; + use constant NXDOMAIN => 3; - $rcode = "NOERROR"; - } else { - $rcode = "NXDOMAIN"; - } + use constant A => 1; + use constant CNAME => 5; + use constant DNAME => 39; - # mark the answer as authoritative (by setting the 'aa' flag) - my $headermask = { ra => 1 }; + use constant IN => 1; - # specify EDNS options { option => value } - my $optionmask = { }; + # default values - return ($rcode, \@ans, \@auth, \@add, $headermask, $optionmask); -} + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); -sub bind_daemon { - my $ns = new Net::DNS::Nameserver( - LocalAddr => ['127.0.0.1'], - LocalPort => $bind_server_port, - ReplyHandler => \&reply_handler, - Verbose => 0, # Verbose = 1 to print debug info - Truncate => 0 - ) || die "[D] DNS server: couldn't create nameserver object\n"; + # decode name - $ns->main_loop; -} - -sub start_bind { - if (defined $bind_server_port) { + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } - print "+ DNS server: try to bind server port: $bind_server_port\n"; + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); - $t->run_daemon(\&bind_daemon); - $bind_pid = pop @{$t->{_daemons}}; + my $name = join('.', @name); - print "+ DNS server: daemon pid: $bind_pid\n"; + if (($type == A) && exists($aroute_map{$name})) { - my $s; - my $i = 1; - while (not $s) { - $s = IO::Socket::INET->new( - Proto => 'tcp', - PeerAddr => "127.0.0.1", - PeerPort => $bind_server_port - ); - sleep 0.1; - $i++ > 20 and last; - } - sleep 0.1; - $s or die "cannot connect to DNS server"; - close($s) or die 'can not connect to DNS server'; + my @records = @{$aroute_map{$name}}; - print "+ DNS server: working\n"; + for (my $i = 0; $i < scalar(@records); $i++) { + my ($ttl, $origin_addr) = @{$records[$i]}; + push @rdata, rd_addr($ttl, $origin_addr); - END { - print("+ try to stop\n"); - stop_bind(); + #print("dns reply: $name $ttl $class $type $origin_addr\n"); + } } - } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); } -sub stop_bind { - if (defined $bind_pid) { - # kill dns daemon - kill $^O eq 'MSWin32' ? 15 : 'TERM', $bind_pid; - wait; +sub rd_addr { + my ($ttl, $addr) = @_; - $bind_pid = undef; - print ("+ DNS server: stop\n"); - } + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t, %extra) = @_; + + print("+ dns daemon: try to listen on 127.0.0.1:$port\n"); + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + my $sel = IO::Select->new($socket); + my $tcp = 0; + + if ($extra{tcp}) { + $tcp = port(8983, socket => 1); + $sel->add($tcp); + } + + local $SIG{PIPE} = 'IGNORE'; + + # track number of relevant queries + + my %state = ( + cnamecnt => 0, + twocnt => 0, + ttlcnt => 0, + ttl0cnt => 0, + cttlcnt => 0, + cttl2cnt => 0, + manycnt => 0, + casecnt => 0, + idcnt => 0, + fecnt => 0, + ); + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (my @ready = $sel->can_read) { + foreach my $fh (@ready) { + if ($tcp == $fh) { + my $new = $fh->accept; + $new->autoflush(1); + $sel->add($new); + + } elsif ($socket == $fh) { + $fh->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port, + \%state); + $fh->send($data); + + } else { + $fh->recv($recv_data, 65536); + unless (length $recv_data) { + $sel->remove($fh); + $fh->close; + next; + } + +again: + my $len = unpack("n", $recv_data); + $data = substr $recv_data, 2, $len; + $data = reply_handler($data, $port, \%state, + tcp => 1); + $data = pack("n", length $data) . $data; + $fh->send($data); + $recv_data = substr $recv_data, 2 + $len; + goto again if length $recv_data; + } + } + } } ############################################################################### diff --git a/t/http_proxy_connect_lua.t b/t/http_proxy_connect_lua.t index 154b9d1..4c68337 100644 --- a/t/http_proxy_connect_lua.t +++ b/t/http_proxy_connect_lua.t @@ -8,20 +8,20 @@ use warnings; use strict; use Test::More; -# use Test::Simple 'no_plan'; + +use IO::Select; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; use Test::Nginx; -use Net::DNS::Nameserver; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy lua/); #->plan(12); +my $t = Test::Nginx->new()->has(qw/http proxy lua/)->plan(1); ############################################################################### @@ -35,9 +35,6 @@ print("+ test_enable_rewrite_phase: $test_enable_rewrite_phase\n"); # --- init DNS server --- -my $bind_pid; -my $bind_server_port = 18085; - # SRV record, not used my %route_map; @@ -49,19 +46,6 @@ my %aroute_map = ( 'set-response-status.com' => [[300, "127.0.0.1"]], ); -# AAAA record (ipv6) -my %aaaaroute_map; -# my %aaaaroute_map = ( -# 'www.test-a.com' => [[300, "[::1]"]], -# 'www.test-b.com' => [[300, "[::1]"]], -# #'www.test-a.com' => [[300, "127.0.0.1"]], -# #'www.test-b.com' => [[300, "127.0.0.1"]], -# ); - -start_bind(); - -# --- end --- - ############################################################################### $t->write_file_expand('nginx.conf', <<'EOF'); @@ -84,7 +68,7 @@ http { access_log off; - resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. + resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. server { listen 127.0.0.1:8080; @@ -111,6 +95,9 @@ EOF # test $proxy_connect_response variable via lua-nginx-module +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + $t->run(); if ($test_enable_rewrite_phase) { @@ -119,17 +106,8 @@ if ($test_enable_rewrite_phase) { $t->stop(); - - -# --- stop DNS server --- - -stop_bind(); - -done_testing(); - ############################################################################### - sub http_connect_request { my ($host, $port, $url) = @_; my $r = http_connect($host, $port, < 0; + use constant FORMERR => 1; + use constant SERVFAIL => 2; + use constant NXDOMAIN => 3; - $rcode = "NOERROR"; - } else { - $rcode = "NXDOMAIN"; - } + use constant A => 1; + use constant CNAME => 5; + use constant DNAME => 39; - # mark the answer as authoritative (by setting the 'aa' flag) - my $headermask = { ra => 1 }; + use constant IN => 1; - # specify EDNS options { option => value } - my $optionmask = { }; + # default values - return ($rcode, \@ans, \@auth, \@add, $headermask, $optionmask); -} + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); -sub bind_daemon { - my $ns = new Net::DNS::Nameserver( - LocalAddr => ['127.0.0.1'], - LocalPort => $bind_server_port, - ReplyHandler => \&reply_handler, - Verbose => 0, # Verbose = 1 to print debug info - Truncate => 0 - ) || die "[D] DNS server: couldn't create nameserver object\n"; + # decode name - $ns->main_loop; -} - -sub start_bind { - if (defined $bind_server_port) { + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } - print "+ DNS server: try to bind server port: $bind_server_port\n"; + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); - $t->run_daemon(\&bind_daemon); - $bind_pid = pop @{$t->{_daemons}}; + my $name = join('.', @name); - print "+ DNS server: daemon pid: $bind_pid\n"; + if (($type == A) && exists($aroute_map{$name})) { - my $s; - my $i = 1; - while (not $s) { - $s = IO::Socket::INET->new( - Proto => 'tcp', - PeerAddr => "127.0.0.1", - PeerPort => $bind_server_port - ); - sleep 0.1; - $i++ > 20 and last; - } - sleep 0.1; - $s or die "cannot connect to DNS server"; - close($s) or die 'can not connect to DNS server'; + my @records = @{$aroute_map{$name}}; - print "+ DNS server: working\n"; + for (my $i = 0; $i < scalar(@records); $i++) { + my ($ttl, $origin_addr) = @{$records[$i]}; + push @rdata, rd_addr($ttl, $origin_addr); - END { - print("+ try to stop $bind_pid\n"); - stop_bind(); + #print("dns reply: $name $ttl $class $type $origin_addr\n"); + } } - } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); } -sub stop_bind { - if (defined $bind_pid) { - # kill dns daemon - kill $^O eq 'MSWin32' ? 15 : 'TERM', $bind_pid; - wait; +sub rd_addr { + my ($ttl, $addr) = @_; - $bind_pid = undef; - print ("+ DNS server: stop\n"); - } + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t, %extra) = @_; + + print("+ dns daemon: try to listen on 127.0.0.1:$port\n"); + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + my $sel = IO::Select->new($socket); + my $tcp = 0; + + if ($extra{tcp}) { + $tcp = port(8983, socket => 1); + $sel->add($tcp); + } + + local $SIG{PIPE} = 'IGNORE'; + + # track number of relevant queries + + my %state = ( + cnamecnt => 0, + twocnt => 0, + ttlcnt => 0, + ttl0cnt => 0, + cttlcnt => 0, + cttl2cnt => 0, + manycnt => 0, + casecnt => 0, + idcnt => 0, + fecnt => 0, + ); + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (my @ready = $sel->can_read) { + foreach my $fh (@ready) { + if ($tcp == $fh) { + my $new = $fh->accept; + $new->autoflush(1); + $sel->add($new); + + } elsif ($socket == $fh) { + $fh->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port, + \%state); + $fh->send($data); + + } else { + $fh->recv($recv_data, 65536); + unless (length $recv_data) { + $sel->remove($fh); + $fh->close; + next; + } + +again: + my $len = unpack("n", $recv_data); + $data = substr $recv_data, 2, $len; + $data = reply_handler($data, $port, \%state, + tcp => 1); + $data = pack("n", length $data) . $data; + $fh->send($data); + $recv_data = substr $recv_data, 2 + $len; + goto again if length $recv_data; + } + } + } } ############################################################################### diff --git a/t/http_proxy_connect_resolve_variables.t b/t/http_proxy_connect_resolve_variables.t index 4e94f8e..7116028 100644 --- a/t/http_proxy_connect_resolve_variables.t +++ b/t/http_proxy_connect_resolve_variables.t @@ -23,7 +23,7 @@ use Net::DNS::Nameserver; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/http proxy/); #->plan(12); +my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(14); ############################################################################### @@ -39,9 +39,6 @@ plan(skip_all => 'No rewrite phase enabled') if ($test_enable_rewrite_phase == 0 # --- init DNS server --- -my $bind_pid; -my $bind_server_port = 18085; - # SRV record, not used my %route_map; @@ -60,10 +57,6 @@ my %aaaaroute_map; # #'www.test-b.com' => [[300, "127.0.0.1"]], # ); -start_bind(); - -# --- end --- - ############################################################################### my $nginx_conf = <<'EOF'; @@ -95,7 +88,7 @@ http { access_log %%TESTDIR%%/connect.log connect; error_log %%TESTDIR%%/connect_error.log info; - resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. + resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. server { listen 127.0.0.1:8080; @@ -182,6 +175,9 @@ EOF $t->write_file_expand('nginx.conf', $nginx_conf); +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + eval { $t->run(); }; @@ -267,16 +263,8 @@ like($t->read_file('connect_error.log'), $t->stop(); - -# --- stop DNS server --- - -stop_bind(); - -done_testing(); - ############################################################################### - sub http_connect_request { my ($host, $port, $url) = @_; my $r = http_connect($host, $port, < 0; + use constant FORMERR => 1; + use constant SERVFAIL => 2; + use constant NXDOMAIN => 3; - $rcode = "NOERROR"; - } else { - #$rcode = "NXDOMAIN"; - $rcode = "NOERROR"; - } - - # mark the answer as authoritative (by setting the 'aa' flag) - my $headermask = { ra => 1 }; + use constant A => 1; + use constant CNAME => 5; + use constant DNAME => 39; - # specify EDNS options { option => value } - my $optionmask = { }; + use constant IN => 1; - return ($rcode, \@ans, \@auth, \@add, $headermask, $optionmask); -} + # default values -sub bind_daemon { - my $ns = new Net::DNS::Nameserver( - LocalAddr => ['127.0.0.1'], - LocalPort => $bind_server_port, - ReplyHandler => \&reply_handler, - Verbose => 0, # Verbose = 1 to print debug info - Truncate => 0 - ) || die "[D] DNS server: couldn't create nameserver object\n"; + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); - $ns->main_loop; -} + # decode name -sub start_bind { - if (defined $bind_server_port) { + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } - print "+ DNS server: try to bind server port: $bind_server_port\n"; + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); - $t->run_daemon(\&bind_daemon); - $bind_pid = pop @{$t->{_daemons}}; + my $name = join('.', @name); - print "+ DNS server: daemon pid: $bind_pid\n"; + if (($type == A) && exists($aroute_map{$name})) { - my $s; - my $i = 1; - while (not $s) { - $s = IO::Socket::INET->new( - Proto => 'tcp', - PeerAddr => "127.0.0.1", - PeerPort => $bind_server_port - ); - sleep 0.1; - $i++ > 20 and last; - } - sleep 0.1; - $s or die "cannot connect to DNS server"; - close($s) or die 'can not connect to DNS server'; + my @records = @{$aroute_map{$name}}; - print "+ DNS server: working\n"; + for (my $i = 0; $i < scalar(@records); $i++) { + my ($ttl, $origin_addr) = @{$records[$i]}; + push @rdata, rd_addr($ttl, $origin_addr); - END { - print("+ try to stop\n"); - stop_bind(); + #print("dns reply: $name $ttl $class $type $origin_addr\n"); + } } - } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); } -sub stop_bind { - if (defined $bind_pid) { - # kill dns daemon - kill $^O eq 'MSWin32' ? 15 : 'TERM', $bind_pid; - wait; +sub rd_addr { + my ($ttl, $addr) = @_; - $bind_pid = undef; - print ("+ DNS server: stop\n"); - } + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t, %extra) = @_; + + print("+ dns daemon: try to listen on 127.0.0.1:$port\n"); + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + my $sel = IO::Select->new($socket); + my $tcp = 0; + + if ($extra{tcp}) { + $tcp = port(8983, socket => 1); + $sel->add($tcp); + } + + local $SIG{PIPE} = 'IGNORE'; + + # track number of relevant queries + + my %state = ( + cnamecnt => 0, + twocnt => 0, + ttlcnt => 0, + ttl0cnt => 0, + cttlcnt => 0, + cttl2cnt => 0, + manycnt => 0, + casecnt => 0, + idcnt => 0, + fecnt => 0, + ); + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (my @ready = $sel->can_read) { + foreach my $fh (@ready) { + if ($tcp == $fh) { + my $new = $fh->accept; + $new->autoflush(1); + $sel->add($new); + + } elsif ($socket == $fh) { + $fh->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port, + \%state); + $fh->send($data); + + } else { + $fh->recv($recv_data, 65536); + unless (length $recv_data) { + $sel->remove($fh); + $fh->close; + next; + } + +again: + my $len = unpack("n", $recv_data); + $data = substr $recv_data, 2, $len; + $data = reply_handler($data, $port, \%state, + tcp => 1); + $data = pack("n", length $data) . $data; + $fh->send($data); + $recv_data = substr $recv_data, 2 + $len; + goto again if length $recv_data; + } + } + } } ############################################################################### diff --git a/t/http_proxy_connect_timeout.t b/t/http_proxy_connect_timeout.t index a7fc969..1d00933 100644 --- a/t/http_proxy_connect_timeout.t +++ b/t/http_proxy_connect_timeout.t @@ -145,7 +145,7 @@ http { access_log %%TESTDIR%%/connect.log connect; error_log %%TESTDIR%%/connect_timeout_error.log debug; - resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. + resolver 127.0.0.1:8085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x. server { listen 127.0.0.1:8080;