@@ -10,20 +10,20 @@ use warnings;
1010use strict;
1111
1212use Test::More;
13- # use Test::Simple 'no_plan';
13+
14+ use IO::Select;
1415
1516BEGIN { use FindBin; chdir ($FindBin::Bin ); }
1617
1718use lib ' lib' ;
1819use Test::Nginx;
19- use Net::DNS::Nameserver;
2020
2121# ##############################################################################
2222
2323select STDERR ; $| = 1;
2424select STDOUT ; $| = 1;
2525
26- my $t = Test::Nginx-> new()-> has(qw/ http proxy/ ); # ->plan(12 );
26+ my $t = Test::Nginx-> new()-> has(qw/ http proxy/ )-> plan(22 );
2727
2828# ##############################################################################
2929
@@ -37,9 +37,6 @@ print("+ test_enable_rewrite_phase: $test_enable_rewrite_phase\n");
3737
3838# --- init DNS server ---
3939
40- my $bind_pid ;
41- my $bind_server_port = 18085;
42-
4340# SRV record, not used
4441my %route_map ;
4542
@@ -52,19 +49,6 @@ my %aroute_map = (
5249 ' set-response-status.com' => [[300, " 127.0.0.1" ]],
5350);
5451
55- # AAAA record (ipv6)
56- my %aaaaroute_map ;
57- # my %aaaaroute_map = (
58- # 'www.test-a.com' => [[300, "[::1]"]],
59- # 'www.test-b.com' => [[300, "[::1]"]],
60- # #'www.test-a.com' => [[300, "127.0.0.1"]],
61- # #'www.test-b.com' => [[300, "127.0.0.1"]],
62- # );
63-
64- start_bind();
65-
66- # --- end ---
67-
6852# ##############################################################################
6953
7054my $nginx_conf = <<'EOF' ;
8872
8973 access_log %%TESTDIR%%/connect.log connect;
9074
91- resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x.
75+ resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x.
9276
9377 server {
9478 listen 127.0.0.1:8081;
157141
158142$t -> write_file_expand(' nginx.conf' , $nginx_conf );
159143
144+ $t -> run_daemon(\&dns_daemon, port(8981), $t );
145+ $t -> waitforfile($t -> testdir . ' /' . port(8981));
146+
160147eval {
161148 $t -> run();
162149};
@@ -313,7 +300,7 @@ http {
313300
314301 access_log off;
315302
316- resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x.
303+ resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x.
317304
318305 server {
319306 listen 127.0.0.1:8080;
@@ -380,7 +367,7 @@ http {
380367
381368 access_log off;
382369
383- resolver 127.0.0.1:18085 ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x.
370+ resolver 127.0.0.1:%%PORT_8981_UDP%% ipv6=off; # NOTE: cannot connect ipv6 address ::1 in mac os x.
384371
385372 server {
386373 listen 127.0.0.1:8080;
@@ -410,17 +397,8 @@ if ($test_enable_rewrite_phase) {
410397
411398$t -> stop();
412399
413-
414-
415- # --- stop DNS server ---
416-
417- stop_bind();
418-
419- done_testing();
420-
421400# ##############################################################################
422401
423-
424402sub http_connect_request {
425403 my ($host , $port , $url ) = @_ ;
426404 my $r = http_connect($host , $port , <<EOF );
@@ -523,111 +501,147 @@ EOF
523501 return $reply ;
524502}
525503
526- # --- DNS Server ---
504+ # ##############################################################################
527505
528506sub reply_handler {
529- my ($qname , $qclass , $qtype , $peerhost , $query , $conn ) = @_ ;
530- my ($rcode , @ans , @auth , @add );
531- # print("DNS reply: receive query=$qname, $qclass, $qtype, $peerhost, $query, $conn\n");
532-
533- if ($qtype eq " SRV" && exists ($route_map {$qname })) {
534- my @records = @{$route_map {$qname }};
535- for (my $i = 0; $i < scalar (@records ); $i ++) {
536- my ($ttl , $weight , $priority , $port , $origin_addr ) = @{$records [$i ]};
537- my $rr = new Net::DNS::RR(" $qname $ttl $qclass $qtype $priority $weight $port $origin_addr " );
538- push @ans , $rr ;
539- # print("DNS reply: $qname $ttl $qclass $qtype $origin_addr\n");
540- }
507+ my ($recv_data , $port , $state , %extra ) = @_ ;
541508
542- $rcode = " NOERROR" ;
543- } elsif (($qtype eq " A" ) && exists ($aroute_map {$qname })) {
544- my @records = @{$aroute_map {$qname }};
545- for (my $i = 0; $i < scalar (@records ); $i ++) {
546- my ($ttl , $origin_addr ) = @{$records [$i ]};
547- my $rr = new Net::DNS::RR(" $qname $ttl $qclass $qtype $origin_addr " );
548- push @ans , $rr ;
549- # print("DNS reply: $qname $ttl $qclass $qtype $origin_addr\n");
550- }
509+ my (@name , @rdata );
551510
552- $rcode = " NOERROR" ;
553- } elsif (($qtype eq " AAAA" ) && exists ($aaaaroute_map {$qname })) {
554- my @records = @{$aaaaroute_map {$qname }};
555- for (my $i = 0; $i < scalar (@records ); $i ++) {
556- my ($ttl , $origin_addr ) = @{$records [$i ]};
557- my $rr = new Net::DNS::RR(" $qname $ttl $qclass $qtype $origin_addr " );
558- push @ans , $rr ;
559- # print("DNS reply: $qname $ttl $qclass $qtype $origin_addr\n");
560- }
511+ use constant NOERROR => 0;
512+ use constant FORMERR => 1;
513+ use constant SERVFAIL => 2;
514+ use constant NXDOMAIN => 3;
561515
562- $rcode = " NOERROR" ;
563- } else {
564- $rcode = " NXDOMAIN" ;
565- }
516+ use constant A => 1;
517+ use constant CNAME => 5;
518+ use constant DNAME => 39;
566519
567- # mark the answer as authoritative (by setting the 'aa' flag)
568- my $headermask = { ra => 1 };
520+ use constant IN => 1;
569521
570- # specify EDNS options { option => value }
571- my $optionmask = { };
522+ # default values
572523
573- return ($rcode , \@ans , \@auth , \@add , $headermask , $optionmask );
574- }
524+ my ($hdr , $rcode , $ttl ) = (0x8180, NOERROR, 3600);
575525
576- sub bind_daemon {
577- my $ns = new Net::DNS::Nameserver(
578- LocalAddr => [' 127.0.0.1' ],
579- LocalPort => $bind_server_port ,
580- ReplyHandler => \&reply_handler,
581- Verbose => 0, # Verbose = 1 to print debug info
582- Truncate => 0
583- ) || die " [D] DNS server: couldn't create nameserver object\n " ;
526+ # decode name
584527
585- $ns -> main_loop;
586- }
587-
588- sub start_bind {
589- if (defined $bind_server_port ) {
528+ my ($len , $offset ) = (undef , 12);
529+ while (1) {
530+ $len = unpack (" \@ $offset C" , $recv_data );
531+ last if $len == 0;
532+ $offset ++;
533+ push @name , unpack (" \@ $offset A$len " , $recv_data );
534+ $offset += $len ;
535+ }
590536
591- print " + DNS server: try to bind server port: $bind_server_port \n " ;
537+ $offset -= 1;
538+ my ($id , $type , $class ) = unpack (" n x$offset n2" , $recv_data );
592539
593- $t -> run_daemon(\&bind_daemon);
594- $bind_pid = pop @{$t -> {_daemons }};
540+ my $name = join (' .' , @name );
595541
596- print " + DNS server: daemon pid: $bind_pid \n " ;
542+ if (( $type == A) && exists ( $aroute_map { $name })) {
597543
598- my $s ;
599- my $i = 1;
600- while (not $s ) {
601- $s = IO::Socket::INET-> new(
602- Proto => ' tcp' ,
603- PeerAddr => " 127.0.0.1" ,
604- PeerPort => $bind_server_port
605- );
606- sleep 0.1;
607- $i ++ > 20 and last ;
608- }
609- sleep 0.1;
610- $s or die " cannot connect to DNS server" ;
611- close ($s ) or die ' can not connect to DNS server' ;
544+ my @records = @{$aroute_map {$name }};
612545
613- print " + DNS server: working\n " ;
546+ for (my $i = 0; $i < scalar (@records ); $i ++) {
547+ my ($ttl , $origin_addr ) = @{$records [$i ]};
548+ push @rdata , rd_addr($ttl , $origin_addr );
614549
615- END {
616- print (" + try to stop\n " );
617- stop_bind();
550+ # print("dns reply: $name $ttl $class $type $origin_addr\n");
551+ }
618552 }
619- }
553+
554+ $len = @name ;
555+ pack (" n6 (C/a*)$len x n2" , $id , $hdr | $rcode , 1, scalar @rdata ,
556+ 0, 0, @name , $type , $class ) . join (' ' , @rdata );
620557}
621558
622- sub stop_bind {
623- if (defined $bind_pid ) {
624- # kill dns daemon
625- kill $^O eq ' MSWin32' ? 15 : ' TERM' , $bind_pid ;
626- wait ;
559+ sub rd_addr {
560+ my ($ttl , $addr ) = @_ ;
627561
628- $bind_pid = undef ;
629- print (" + DNS server: stop\n " );
630- }
562+ my $code = ' split(/\./, $addr)' ;
563+
564+ return pack ' n3N' , 0xc00c, A, IN, $ttl if $addr eq ' ' ;
565+
566+ pack ' n3N nC4' , 0xc00c, A, IN, $ttl , eval " scalar $code " , eval ($code );
567+ }
568+
569+ sub dns_daemon {
570+ my ($port , $t , %extra ) = @_ ;
571+
572+ print (" + dns daemon: try to listen on 127.0.0.1:$port \n " );
573+
574+ my ($data , $recv_data );
575+ my $socket = IO::Socket::INET-> new(
576+ LocalAddr => ' 127.0.0.1' ,
577+ LocalPort => $port ,
578+ Proto => ' udp' ,
579+ )
580+ or die " Can't create listening socket: $! \n " ;
581+
582+ my $sel = IO::Select-> new($socket );
583+ my $tcp = 0;
584+
585+ if ($extra {tcp }) {
586+ $tcp = port(8983, socket => 1);
587+ $sel -> add($tcp );
588+ }
589+
590+ local $SIG {PIPE } = ' IGNORE' ;
591+
592+ # track number of relevant queries
593+
594+ my %state = (
595+ cnamecnt => 0,
596+ twocnt => 0,
597+ ttlcnt => 0,
598+ ttl0cnt => 0,
599+ cttlcnt => 0,
600+ cttl2cnt => 0,
601+ manycnt => 0,
602+ casecnt => 0,
603+ idcnt => 0,
604+ fecnt => 0,
605+ );
606+
607+ # signal we are ready
608+
609+ open my $fh , ' >' , $t -> testdir() . ' /' . $port ;
610+ close $fh ;
611+
612+ while (my @ready = $sel -> can_read) {
613+ foreach my $fh (@ready ) {
614+ if ($tcp == $fh ) {
615+ my $new = $fh -> accept;
616+ $new -> autoflush(1);
617+ $sel -> add($new );
618+
619+ } elsif ($socket == $fh ) {
620+ $fh -> recv ($recv_data , 65536);
621+ $data = reply_handler($recv_data , $port ,
622+ \%state );
623+ $fh -> send ($data );
624+
625+ } else {
626+ $fh -> recv ($recv_data , 65536);
627+ unless (length $recv_data ) {
628+ $sel -> remove($fh );
629+ $fh -> close ;
630+ next ;
631+ }
632+
633+ again:
634+ my $len = unpack (" n" , $recv_data );
635+ $data = substr $recv_data , 2, $len ;
636+ $data = reply_handler($data , $port , \%state ,
637+ tcp => 1);
638+ $data = pack (" n" , length $data ) . $data ;
639+ $fh -> send ($data );
640+ $recv_data = substr $recv_data , 2 + $len ;
641+ goto again if length $recv_data ;
642+ }
643+ }
644+ }
631645}
632646
633647# ##############################################################################
0 commit comments