# HG changeset patch
# User Aleksei Bavshin <a.bavs...@nginx.com>
# Date 1712098324 25200
#      Tue Apr 02 15:52:04 2024 -0700
# Node ID cf291bf98576d5ddd36ead2b7eacc0808120e66b
# Parent  b396d0c2c62c796c76921dcf20960f8ba2515aba
Tests: upstream configuration tests with re-resolvable servers.

Based on the NGINX Plus tests authored by Sergey Kandaurov.

diff --git a/stream_upstream_resolve.t b/stream_upstream_resolve.t
new file mode 100644
--- /dev/null
+++ b/stream_upstream_resolve.t
@@ -0,0 +1,379 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Stream tests for dynamic upstream configuration with re-resolvable servers.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+use Socket qw/ CRLF /;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::Stream qw/ stream /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/stream stream_upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+stream {
+    %%TEST_GLOBALS_STREAM%%
+
+    upstream u {
+        zone z 1m;
+        server example.net:%%PORT_8080%% max_fails=0 resolve;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8983_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    log_format test $upstream_addr;
+
+    server {
+        listen      127.0.0.1:8082;
+        proxy_pass  u;
+        access_log  %%TESTDIR%%/cc.log test;
+        proxy_next_upstream   on;
+        proxy_connect_timeout 50ms;
+    }
+}
+
+EOF
+
+port(8084);
+
+$t->run_daemon(\&dns_daemon, port(8983), $t)
+       ->waitforfile($t->testdir . '/' . port(8983));
+$t->try_run('no resolve in upstream server')->plan(11);
+
+###############################################################################
+
+my $p0 = port(8080);
+
+update_name({A => '127.0.0.201'});
+stream('127.0.0.1:' . port(8082))->read();
+
+# A changed
+
+update_name({A => '127.0.0.202'});
+stream('127.0.0.1:' . port(8082))->read();
+
+# 1 more A added
+
+update_name({A => '127.0.0.201 127.0.0.202'});
+stream('127.0.0.1:' . port(8082))->read();
+
+# 1 A removed, 2 AAAA added
+
+update_name({A => '127.0.0.201', AAAA => 'fe80::1 fe80::2'});
+stream('127.0.0.1:' . port(8082))->read();
+
+# all records removed
+
+update_name();
+stream('127.0.0.1:' . port(8082))->read();
+
+# A added after empty
+
+update_name({A => '127.0.0.201'});
+stream('127.0.0.1:' . port(8082))->read();
+
+# changed to CNAME
+
+update_name({CNAME => 'alias'}, 4);
+stream('127.0.0.1:' . port(8082))->read();
+
+# bad DNS reply should not affect existing upstream configuration
+
+update_name({ERROR => 'SERVFAIL'});
+stream('127.0.0.1:' . port(8082))->read();
+
+$t->stop();
+
+Test::Nginx::log_core('||', $t->read_file('cc.log'));
+
+open my $f, '<', "${\($t->testdir())}/cc.log" or die "Can't open cc.log: $!";
+my $line;
+
+like($f->getline(), qr/127.0.0.201:$p0/, 'log - A');
+
+# A changed
+
+like($f->getline(), qr/127.0.0.202:$p0/, 'log - A changed');
+
+# 1 more A added
+
+$line = $f->getline();
+like($line, qr/127.0.0.201:$p0/, 'log - A A 1');
+like($line, qr/127.0.0.202:$p0/, 'log - A A 2');
+
+# 1 A removed, 2 AAAA added
+
+$line = $f->getline();
+like($line, qr/127.0.0.201:$p0/, 'log - A AAAA AAAA 1');
+like($line, qr/\[fe80::1\]:$p0/, 'log - A AAAA AAAA 2');
+like($line, qr/\[fe80::2\]:$p0/, 'log - A AAAA AAAA 3');
+
+# all records removed
+
+like($f->getline(), qr/^u$/, 'log - empty response');
+
+# A added after empty
+
+like($f->getline(), qr/127.0.0.201:$p0/, 'log - A added 1');
+
+# changed to CNAME
+
+like($f->getline(), qr/127.0.0.203:$p0/, 'log - CNAME 1');
+
+# bad DNS reply should not affect existing upstream configuration
+
+like($f->getline(), qr/127.0.0.203:$p0/, 'log - ERROR 1');
+
+###############################################################################
+
+sub update_name {
+       my ($name, $plan) = @_;
+
+       $plan = 2 if !defined $plan;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8084)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{AAAA} = '' unless $name->{AAAA};
+       $name->{CNAME} = '' unless $name->{CNAME};
+       $name->{ERROR} = '' unless $name->{ERROR};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-AAAA: $name->{AAAA}
+X-CNAME: $name->{CNAME}
+X-ERROR: $name->{ERROR}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant CNAME      => 5;
+       use constant AAAA       => 28;
+       use constant DNAME      => 39;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 1);
+       $h = {A => [ "127.0.0.201" ]} unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR}) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq 'example.net') {
+               if ($type == A && $h->{A}) {
+                       map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+               }
+               if ($type == AAAA && $h->{AAAA}) {
+                       map { push @rdata, rd_addr6($ttl, $_) } @{$h->{AAAA}};
+               }
+               my $cname = defined $h->{CNAME} ? $h->{CNAME} : 0;
+               if ($cname) {
+                       push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl,
+                               8, 5, $cname, 0xc00c);
+               }
+
+       } elsif ($name eq 'alias.example.net') {
+               if ($type == A) {
+                       push @rdata, rd_addr($ttl, '127.0.0.203');
+               }
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub expand_ip6 {
+       my ($addr) = @_;
+
+       substr ($addr, index($addr, "::"), 2) =
+               join "0", map { ":" } (0 .. 8 - (split /:/, $addr) + 1);
+       map { hex "0" x (4 - length $_) . "$_" } split /:/, $addr;
+}
+
+sub rd_addr6 {
+       my ($ttl, $addr) = @_;
+
+       pack 'n3N nn8', 0xc00c, AAAA, IN, $ttl, 16, expand_ip6($addr);
+}
+
+sub dns_daemon {
+       my ($port, $t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => $port,
+               Proto => 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => '127.0.0.1:' . port(8084),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . $port;
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh) {
+                               my $new = $fh->accept;
+                               $new->autoflush(1);
+                               $sel->add($new);
+
+                       } elsif ($socket == $fh) {
+                               $fh->recv($recv_data, 65536);
+                               $data = reply_handler($recv_data, $h);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-AAAA: (.*)$/m;
+       map { push @{$h{AAAA}}, $_ } split(/ /, $1);
+       $headers =~ /X-CNAME: (.*)$/m;
+       $h{CNAME} = $1;
+       $headers =~ /X-ERROR: (.*)$/m;
+       $h{ERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/stream_upstream_resolve_reload.t b/stream_upstream_resolve_reload.t
new file mode 100644
--- /dev/null
+++ b/stream_upstream_resolve_reload.t
@@ -0,0 +1,336 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Stream tests for dynamic upstream configuration with re-resolvable servers.
+# Ensure that upstream configuration is inherited on reload.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::Stream qw/ stream /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+plan(skip_all => 'win32') if $^O eq 'MSWin32';
+
+my $t = Test::Nginx->new()->has(qw/stream stream_upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+stream {
+    %%TEST_GLOBALS_STREAM%%
+
+    upstream u {
+        zone z 1m;
+        server example.net:%%PORT_8081%% resolve;
+    }
+
+    upstream u2 {
+        zone z 1m;
+        server 127.0.0.203:%%PORT_8081%% max_fails=0;
+        server example.net:%%PORT_8081%% resolve max_fails=0;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8980_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    log_format test $upstream_addr;
+
+    server {
+        listen 127.0.0.1:8082;
+        proxy_pass u;
+        proxy_connect_timeout 50ms;
+        access_log %%TESTDIR%%/cc.log test;
+    }
+
+    server {
+        listen 127.0.0.1:8083;
+        proxy_pass u2;
+        proxy_connect_timeout 50ms;
+        access_log %%TESTDIR%%/cc2.log test;
+    }
+}
+
+EOF
+
+$t->run_daemon(\&dns_daemon, $t)->waitforfile($t->testdir . '/' . port(8980));
+$t->try_run('no resolve in upstream server')->plan(9);
+
+###############################################################################
+
+my $p = port(8081);
+
+update_name({A => '127.0.0.201'});
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8083))->read();
+
+update_name({ERROR => 'SERVFAIL'}, 0);
+
+my $conf = $t->read_file('nginx.conf');
+$conf =~ s/$p/port(8082)/gmse;
+$t->write_file('nginx.conf', $conf);
+
+$t->reload();
+waitforworker($t);
+
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8083))->read();
+
+update_name({A => '127.0.0.202'});
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8083))->read();
+
+$t->stop();
+
+Test::Nginx::log_core('||', $t->read_file('cc.log'));
+
+open my $f, '<', "${\($t->testdir())}/cc.log" or die "Can't open cc.log: $!";
+
+like($f->getline(), qr/127.0.0.201:$p/, 'log - before');
+like($f->getline(), qr/127.0.0.201:$p/, 'log - before 2');
+
+$p = port(8082);
+
+like($f->getline(), qr/127.0.0.201:$p/, 'log - preresolve');
+like($f->getline(), qr/127.0.0.201:$p/, 'log - preresolve 2');
+
+like($f->getline(), qr/127.0.0.202:$p/, 'log - update');
+like($f->getline(), qr/127.0.0.202:$p/, 'log - update 2');
+
+Test::Nginx::log_core('||', $t->read_file('cc2.log'));
+
+$p = port(8081);
+
+open $f, '<', "${\($t->testdir())}/cc2.log" or die "Can't open cc2.log: $!";
+
+like($f->getline(), qr/127.0.0.(201:$p, 127.0.0.203|203:$p, 127.0.0.201):$p/,
+       'log many - before');
+
+$p = port(8082);
+
+like($f->getline(), qr/127.0.0.(201:$p, 127.0.0.203|203:$p, 127.0.0.201):$p/,
+       'log many - preresolve');
+
+like($f->getline(), qr/127.0.0.(202:$p, 127.0.0.203|203:$p, 127.0.0.202):$p/,
+       'log many - update');
+
+###############################################################################
+
+sub waitforworker {
+       my ($t) = @_;
+
+       for (1 .. 30) {
+               last if $t->read_file('error.log') =~ /exited with code/;
+               select undef, undef, undef, 0.2;
+       }
+}
+
+sub update_name {
+       my ($name, $plan) = @_;
+
+       $plan = 2 if !defined $plan;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8081)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{ERROR} = '' unless $name->{ERROR};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-ERROR: $name->{ERROR}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 1);
+       $h = {A => [ "127.0.0.201" ]} unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR}) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq 'example.net' && $type == A && $h->{A}) {
+               map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub dns_daemon {
+       my ($t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => port(8980),
+               Proto => 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => "127.0.0.1:" . port(8081),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . port(8980);
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh) {
+                               my $new = $fh->accept;
+                               $new->autoflush(1);
+                               $sel->add($new);
+
+                       } elsif ($socket == $fh) {
+                               $fh->recv($recv_data, 65536);
+                               $data = reply_handler($recv_data, $h);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-ERROR: (.*)$/m;
+       $h{ERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/stream_upstream_resolver.t b/stream_upstream_resolver.t
new file mode 100644
--- /dev/null
+++ b/stream_upstream_resolver.t
@@ -0,0 +1,281 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for re-resolvable servers with resolver in stream upstream.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::Stream qw/ stream /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/stream stream_upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+stream {
+    %%TEST_GLOBALS_STREAM%%
+
+    resolver 127.0.0.1:%%PORT_8980_UDP%%;
+
+    upstream u {
+        zone z 1m;
+        server example.net:%%PORT_8080%% resolve;
+    }
+
+    upstream u1 {
+        zone z 1m;
+        server example.net:%%PORT_8080%% resolve;
+        resolver 127.0.0.1:%%PORT_8981_UDP%%;
+    }
+
+    upstream u2 {
+        zone z 1m;
+        server example.net:%%PORT_8080%% resolve;
+        resolver 127.0.0.1:%%PORT_8982_UDP%%;
+        resolver_timeout 200s; # for coverage
+    }
+
+    log_format test $upstream_addr;
+
+    proxy_connect_timeout 50ms;
+
+    server {
+        listen       127.0.0.1:8081;
+        proxy_pass   u;
+
+        access_log %%TESTDIR%%/access.log test;
+    }
+
+    server {
+        listen       127.0.0.1:8082;
+        proxy_pass   u1;
+
+        access_log %%TESTDIR%%/access1.log test;
+    }
+
+    server {
+        listen       127.0.0.1:8083;
+        proxy_pass   u2;
+
+        access_log %%TESTDIR%%/access2.log test;
+    }
+}
+
+EOF
+
+$t->run_daemon(\&dns_daemon, $t, port($_), port($_ + 10)) for (8980 .. 8982);
+$t->waitforfile($t->testdir . '/' . port($_)) for (8980 .. 8982);
+
+$t->try_run('no resolver in upstream')->plan(6);
+
+###############################################################################
+
+ok(waitfordns(8980), 'resolved');
+ok(waitfordns(8981), 'resolved in upstream 1');
+ok(waitfordns(8982), 'resolved in upstream 2');
+
+stream('127.0.0.1:' . port(8081))->read();
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8083))->read();
+
+$t->stop();
+
+like($t->read_file('access.log'), qr/127.0.0.200/, 'resolver');
+like($t->read_file('access1.log'), qr/127.0.0.201/, 'resolver upstream 1');
+like($t->read_file('access2.log'), qr/127.0.0.202/, 'resolver upstream 2');
+
+###############################################################################
+
+sub waitfordns {
+       my ($port, $plan) = @_;
+
+       $plan = 1 if !defined $plan;
+
+       sub sock {
+               my ($port) = @_;
+
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port($port + 10)
+               )
+                       or die "Can't connect to dns control socket: $!\n";
+       }
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+
+EOF
+
+       for (1 .. 10) {
+               my ($gen) = http($req, socket => sock($port)) =~ /X-Gen: (\d+)/;
+               select undef, undef, undef, 0.5;
+               return 1 if $gen >= $plan;
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $port) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant A          => 1;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 1);
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+
+       my $name = join('.', @name);
+       if ($name eq 'example.net' && $type == A) {
+               if ($port == port(8980)) {
+                       push @rdata, rd_addr($ttl, "127.0.0.200");
+               }
+
+               if ($port == port(8981)) {
+                       push @rdata, rd_addr($ttl, "127.0.0.201");
+               }
+
+               if ($port == port(8982)) {
+                       push @rdata, rd_addr($ttl, "127.0.0.202");
+               }
+       }
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub dns_daemon {
+       my ($t, $port, $control_port) = @_;
+
+       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 $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalAddr => '127.0.0.1',
+               LocalPort => $control_port,
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . $port;
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $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);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/stream_upstream_service.t b/stream_upstream_service.t
new file mode 100644
--- /dev/null
+++ b/stream_upstream_service.t
@@ -0,0 +1,544 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Stream tests for dynamic upstream configuration with service (SRV) feature.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::Stream qw/ stream /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/stream stream_upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+stream {
+    %%TEST_GLOBALS_STREAM%%
+
+    upstream u {
+        zone z 1m;
+        server example.net max_fails=0 resolve service=http;
+    }
+
+    upstream u2 {
+        zone z2 1m;
+        server example.net max_fails=0 resolve service=_http._tcp;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8981_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    log_format test $upstream_addr;
+
+    server {
+        listen 127.0.0.1:8081;
+        proxy_pass u;
+        proxy_next_upstream   on;
+        proxy_connect_timeout 50ms;
+        access_log %%TESTDIR%%/cc.log test;
+    }
+
+    server {
+        listen 127.0.0.1:8082;
+        proxy_pass u2;
+        proxy_next_upstream   on;
+        proxy_connect_timeout 50ms;
+        access_log %%TESTDIR%%/cc.log test;
+    }
+}
+
+EOF
+
+port(8080);
+port(8084);
+
+$t->write_file('t', '');
+
+$t->run_daemon(\&dns_daemon, $t)->waitforfile($t->testdir . '/' . port(8981));
+port(8981, socket => 1)->close();
+$t->try_run('no resolve in upstream server')->plan(20);
+
+###############################################################################
+
+my ($p0, $p2, $p3) = (port(8080), port(8082), port(8083));
+
+update_name({A => '127.0.0.201', SRV => "1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# fully specified service
+
+stream('127.0.0.1:' . port(8082))->read();
+
+# A changed
+
+update_name({A => '127.0.0.202', SRV => "1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# 1 more A added
+
+update_name({A => '127.0.0.201 127.0.0.202', SRV => "1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# 1 A removed, 2 AAAA added
+
+update_name({A => '127.0.0.201', AAAA => 'fe80::1 fe80::2',
+       SRV => "1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# all records removed
+
+update_name({SRV => "1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# all SRV records removed
+
+update_name();
+stream('127.0.0.1:' . port(8081))->read();
+
+# A added after empty
+
+update_name({A => '127.0.0.201', SRV => "1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# SRV changed its weight
+
+update_name({A => '127.0.0.201', SRV => "1 6 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# changed to CNAME
+
+update_name({CNAME => 'alias'}, 2, 2);
+stream('127.0.0.1:' . port(8081))->read();
+
+# bad SRV reply should not affect existing upstream configuration
+
+update_name({CNAME => 'alias', ERROR => 'SERVFAIL'}, 1, 0);
+stream('127.0.0.1:' . port(8081))->read();
+update_name({ERROR => ''}, 1, 0);
+
+# 2 equal SRV RR
+
+update_name({A => '127.0.0.201',
+       SRV => "1 5 $p0 example.net;1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+# all equal records removed
+
+update_name();
+stream('127.0.0.1:' . port(8081))->read();
+
+# 2 different SRV RR
+
+update_name({A => '127.0.0.201',
+       SRV => "1 5 $p2 example.net;2 6 $p3 alias.example.net"}, 1, 2);
+stream('127.0.0.1:' . port(8081))->read();
+
+# all different records removed
+
+update_name();
+stream('127.0.0.1:' . port(8081))->read();
+
+# bad subordinate reply should not affect existing upstream configuration
+
+update_name({A => '127.0.0.201',
+       SRV => "1 5 $p0 example.net;1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+update_name({A => '127.0.0.201', SERROR => 'SERVFAIL',
+       SRV => "1 5 $p0 example.net;1 5 $p0 example.net"});
+stream('127.0.0.1:' . port(8081))->read();
+
+$t->stop();
+
+Test::Nginx::log_core('||', $t->read_file('cc.log'));
+
+open my $f, '<', "${\($t->testdir())}/cc.log" or die "Can't open cc.log: $!";
+my $line;
+
+like($f->getline(), qr/127.0.0.201:$p0/, 'log - A');
+
+# fully specified service
+
+like($f->getline(), qr/127.0.0.201:$p0/, 'log - A full');
+
+# A changed
+
+like($f->getline(), qr/127.0.0.202:$p0/, 'log - A changed');
+
+# 1 more A added
+
+$line = $f->getline();
+like($line, qr/127.0.0.201:$p0/, 'log - A A 1');
+like($line, qr/127.0.0.202:$p0/, 'log - A A 2');
+
+# 1 A removed, 2 AAAA added
+
+$line = $f->getline();
+like($line, qr/127.0.0.201:$p0/, 'log - A AAAA AAAA 1');
+like($line, qr/\[fe80::1\]:$p0/, 'log - A AAAA AAAA 2');
+like($line, qr/\[fe80::2\]:$p0/, 'log - A AAAA AAAA 3');
+
+# all records removed
+
+like($f->getline(), qr/^u$/, 'log - empty response');
+
+# all SRV records removed
+
+like($f->getline(), qr/^u$/, 'log - empty response');
+
+# A added after empty
+
+like($f->getline(), qr/127.0.0.201:$p0/, 'log - A added 1');
+
+# SRV changed its weight
+
+like($f->getline(), qr/127.0.0.201:$p0/, 'log - SRV weight');
+
+# changed to CNAME
+
+like($f->getline(), qr/127.0.0.203:$p0/, 'log - CNAME');
+
+# bad SRV reply should not affect existing upstream configuration
+
+like($f->getline(), qr/127.0.0.203:$p0/, 'log - ERROR');
+
+# 2 equal SRV RR
+
+like($f->getline(), qr/127.0.0.201:$p0, 127.0.0.201:$p0/, 'log - SRV same');
+
+# all equal records removed
+
+like($f->getline(), qr/^u$/, 'log - SRV same removed');
+
+# 2 different SRV RR
+
+$line = $f->getline();
+like($line, qr/127.0.0.201:$p2, 127.0.0.203:$p3/, 'log - SRV diff');
+
+# all different records removed
+
+like($f->getline(), qr/^u$/, 'log - SRV diff removed');
+
+# bad subordinate reply should not affect existing upstream configuration
+
+like($f->getline(), qr/, /, 'log - subordinate good');
+like($f->getline(), qr/, /, 'log - subordinate error');
+
+###############################################################################
+
+sub update_name {
+       my ($name, $plan, $plan6) = @_;
+
+       $plan = 1, $plan6 = 0 if !defined $name;
+       $plan = $plan6 = 1 if !defined $plan;
+       $plan += $plan6 + $plan6;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8084)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{AAAA} = '' unless $name->{AAAA};
+       $name->{CNAME} = '' unless $name->{CNAME};
+       $name->{ERROR} = '' unless $name->{ERROR};
+       $name->{SERROR} = '' unless $name->{SERROR};
+       $name->{SRV} = '' unless $name->{SRV};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-AAAA: $name->{AAAA}
+X-CNAME: $name->{CNAME}
+X-ERROR: $name->{ERROR}
+X-SERROR: $name->{SERROR}
+X-SRV: $name->{SRV}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h, $cnt, $tcp) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant FORMERR    => 1;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant CNAME      => 5;
+       use constant AAAA       => 28;
+       use constant SRV        => 33;
+
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl, $port) = (0x8180, NOERROR, 3600, port(8080));
+       $h = {A => [ "127.0.0.1" ], SRV => [ "1 5 $port example.net" ]}
+               unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR} && $type == SRV) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       # subordinate error
+
+       if ($h->{SERROR} && $type != SRV) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq '_http._tcp.example.net') {
+               if ($type == SRV && $h->{SRV}) {
+                       map { push @rdata, rd_srv($ttl, (split ' ', $_)) }
+                               @{$h->{SRV}};
+               }
+
+               my $cname = defined $h->{CNAME} ? $h->{CNAME} : 0;
+               if ($cname) {
+                       push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl,
+                               8, 5, "alias", 0xc00c + length("_http._tcp "));
+               }
+
+       } elsif ($name eq '_http._tcp.trunc.example.net' && $type == SRV) {
+               push @rdata, $tcp
+                       ? rd_srv($ttl, 1, 1, $port, 'tcp.example.net')
+                       : rd_srv($ttl, 1, 1, $port, 'example.net');
+
+               $hdr |= 0x0300 if $name eq '_http._tcp.trunc.example.net'
+                       and !$tcp;
+
+       } elsif ($name eq 'example.net' || $name eq 'tcp.example.net') {
+               if ($type == A && $h->{A}) {
+                       map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+               }
+               if ($type == AAAA && $h->{AAAA}) {
+                       map { push @rdata, rd_addr6($ttl, $_) } @{$h->{AAAA}};
+               }
+               my $cname = defined $h->{CNAME} ? $h->{CNAME} : 0;
+               if ($cname) {
+                       push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl,
+                               8, 5, $cname, 0xc00c);
+               }
+
+       } elsif ($name eq 'alias.example.net') {
+               if ($type == SRV) {
+                       push @rdata, rd_srv($ttl, 1, 5, $port, 'example.net');
+               }
+               if ($type == A) {
+                       push @rdata, rd_addr($ttl, '127.0.0.203');
+               }
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $$cnt++ if $type == SRV || keys %$h;
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_srv {
+       my ($ttl, $pri, $w, $port, $name) = @_;
+       my @rdname = split /\./, $name;
+       my $rdlen = length(join '', @rdname) + @rdname + 7;     # pri w port x
+
+       pack 'n3N n n3 (C/a*)* x',
+               0xc00c, SRV, IN, $ttl, $rdlen, $pri, $w, $port, @rdname;
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub expand_ip6 {
+       my ($addr) = @_;
+
+       substr ($addr, index($addr, "::"), 2) =
+               join "0", map { ":" } (0 .. 8 - (split /:/, $addr) + 1);
+       map { hex "0" x (4 - length $_) . "$_" } split /:/, $addr;
+}
+
+sub rd_addr6 {
+       my ($ttl, $addr) = @_;
+
+       pack 'n3N nn8', 0xc00c, AAAA, IN, $ttl, 16, expand_ip6($addr);
+}
+
+sub dns_daemon {
+       my ($t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => port(8981),
+               Proto => 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => '127.0.0.1:' . port(8084),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $tcp = port(8981, socket => 1);
+       my $sel = IO::Select->new($socket, $control, $tcp);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . port(8981);
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh || $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, $h, \$cnt);
+                               $fh->send($data);
+
+                       } elsif ($fh->sockport() == port(8084)) {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+
+                       } elsif ($fh->sockport() == port(8981)) {
+                               $fh->recv($recv_data, 65536);
+                               unless (length $recv_data) {
+                                       $sel->remove($fh);
+                                       $fh->close;
+                                       next;
+                               }
+
+again:
+                               my $len = unpack("n", $recv_data);
+                               my $data = substr $recv_data, 2, $len;
+                               $data = reply_handler($data, $h, \$cnt, 1);
+                               $data = pack("n", length $data) . $data;
+                               $fh->send($data);
+                               $recv_data = substr $recv_data, 2 + $len;
+                               goto again if length $recv_data;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-AAAA: (.*)$/m;
+       map { push @{$h{AAAA}}, $_ } split(/ /, $1);
+       $headers =~ /X-SRV: (.*)$/m;
+       map { push @{$h{SRV}}, $_ } split(/;/, $1);
+       $headers =~ /X-CNAME: (.+)$/m and $h{CNAME} = $1;
+       $headers =~ /X-ERROR: (.+)$/m and $h{ERROR} = $1;
+       $headers =~ /X-SERROR: (.+)$/m and $h{SERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/stream_upstream_service_reload.t b/stream_upstream_service_reload.t
new file mode 100644
--- /dev/null
+++ b/stream_upstream_service_reload.t
@@ -0,0 +1,317 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Stream tests for dynamic upstream configuration with service (SRV) feature.
+# Ensure that upstream configuration is inherited on reload.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::Stream qw/ stream /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+plan(skip_all => 'win32') if $^O eq 'MSWin32';
+
+my $t = Test::Nginx->new()->has(qw/stream stream_upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+stream {
+    %%TEST_GLOBALS_STREAM%%
+
+    upstream u {
+        zone z 1m;
+        server example.net resolve service=http;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8980_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    log_format test $upstream_addr;
+
+    server {
+        listen 127.0.0.1:8082;
+        proxy_pass u;
+        proxy_connect_timeout 50ms;
+        access_log %%TESTDIR%%/cc.log test;
+    }
+}
+
+EOF
+
+port(8081);
+
+$t->run_daemon(\&dns_daemon, $t)->waitforfile($t->testdir . '/' . port(8980));
+$t->try_run('no resolve in upstream server')->plan(6);
+
+###############################################################################
+
+update_name({A => '127.0.0.201', SRV => "1 5 8080 example.net"});
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8082))->read();
+
+update_name({ERROR => 'SERVFAIL'}, 0);
+
+$t->reload();
+waitforworker($t);
+
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8082))->read();
+
+update_name({A => '127.0.0.202', SRV => "1 5 8080 example.net"});
+stream('127.0.0.1:' . port(8082))->read();
+stream('127.0.0.1:' . port(8082))->read();
+
+$t->stop();
+
+Test::Nginx::log_core('||', $t->read_file('cc.log'));
+
+open my $f, '<', "${\($t->testdir())}/cc.log" or die "Can't open cc.log: $!";
+
+like($f->getline(), qr/127.0.0.201:8080/, 'log - before');
+like($f->getline(), qr/127.0.0.201:8080/, 'log - before 2');
+
+like($f->getline(), qr/127.0.0.201:8080/, 'log - preresolve');
+like($f->getline(), qr/127.0.0.201:8080/, 'log - preresolve 2');
+
+like($f->getline(), qr/127.0.0.202:8080/, 'log - update');
+like($f->getline(), qr/127.0.0.202:8080/, 'log - update 2');
+
+###############################################################################
+
+sub waitforworker {
+       my ($t) = @_;
+
+       for (1 .. 30) {
+               last if $t->read_file('error.log') =~ /exited with code/;
+               select undef, undef, undef, 0.2;
+       }
+}
+
+sub update_name {
+       my ($name, $plan) = @_;
+
+       $plan = 3 if !defined $plan;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8081)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{ERROR} = '' unless $name->{ERROR};
+       $name->{SRV} = '' unless $name->{SRV};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-ERROR: $name->{ERROR}
+X-SRV: $name->{SRV}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant SRV        => 33;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl, $port) = (0x8180, NOERROR, 3600, port(8080));
+       $h = {A => [ "127.0.0.201" ], SRV => [ "1 5 $port example.net" ]}
+               unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR}) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq 'example.net' && $type == A && $h->{A}) {
+               map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+
+       }
+       if ($name eq '_http._tcp.example.net' && $type == SRV && $h->{SRV}) {
+               map { push @rdata, rd_srv($ttl, (split ' ', $_)) }
+                       @{$h->{SRV}};
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_srv {
+       my ($ttl, $pri, $w, $port, $name) = @_;
+       my @rdname = split /\./, $name;
+       my $rdlen = length(join '', @rdname) + @rdname + 7;     # pri w port x
+
+       pack 'n3N n n3 (C/a*)* x',
+               0xc00c, SRV, IN, $ttl, $rdlen, $pri, $w, $port, @rdname;
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub dns_daemon {
+       my ($t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => port(8980),
+               Proto => 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => "127.0.0.1:" . port(8081),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . port(8980);
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh) {
+                               my $new = $fh->accept;
+                               $new->autoflush(1);
+                               $sel->add($new);
+
+                       } elsif ($socket == $fh) {
+                               $fh->recv($recv_data, 65536);
+                               $data = reply_handler($recv_data, $h);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-SRV: (.*)$/m;
+       map { push @{$h{SRV}}, $_ } split(/;/, $1);
+       $headers =~ /X-ERROR: (.*)$/m;
+       $h{ERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/upstream_resolve.t b/upstream_resolve.t
new file mode 100644
--- /dev/null
+++ b/upstream_resolve.t
@@ -0,0 +1,368 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for dynamic upstream configuration with re-resolvable servers.
+# Ensure that dns updates are properly applied.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+use Socket qw/ CRLF /;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http proxy upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream u {
+        zone z 1m;
+        server example.net:%%PORT_8080%% resolve max_fails=0;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8982_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    server {
+        listen       127.0.0.1:8080;
+        listen       [::1]:%%PORT_8080%%;
+        server_name  localhost;
+
+        location / {
+            proxy_pass http://u/t;
+            proxy_connect_timeout 50ms;
+            add_header X-IP $upstream_addr;
+            error_page 502 504 redirect;
+        }
+
+        location /2 {
+            proxy_pass http://u/t;
+            add_header X-IP $upstream_addr;
+        }
+
+        location /t { }
+    }
+}
+
+EOF
+
+port(8083);
+
+$t->write_file('t', '');
+
+$t->run_daemon(\&dns_daemon, $t)->waitforfile($t->testdir . '/' . port(8982));
+$t->try_run('no resolve in upstream server')->plan(18);
+
+###############################################################################
+
+my ($r, @n);
+my $p0 = port(8080);
+
+update_name({A => '127.0.0.201'});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'A');
+like($r, qr/127.0.0.201:$p0/, 'A 1');
+
+# A changed
+
+update_name({A => '127.0.0.202'});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'A changed');
+like($r, qr/127.0.0.202:$p0/, 'A changed 1');
+
+# 1 more A added
+
+update_name({A => '127.0.0.201 127.0.0.202'});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 2, 'A A');
+like($r, qr/127.0.0.201:$p0/, 'A A 1');
+like($r, qr/127.0.0.202:$p0/, 'A A 2');
+
+# 1 A removed, 2 AAAA added
+
+update_name({A => '127.0.0.201', AAAA => 'fe80::1 fe80::2'});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 3, 'A AAAA AAAA responses');
+like($r, qr/127.0.0.201:$p0/, 'A AAAA AAAA 1');
+like($r, qr/\[fe80::1\]:$p0/, 'A AAAA AAAA 2');
+like($r, qr/\[fe80::1\]:$p0/, 'A AAAA AAAA 3');
+
+# all records removed
+
+update_name();
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 0, 'empty response');
+
+# A added after empty
+
+update_name({A => '127.0.0.201'});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'A added');
+like($r, qr/127.0.0.201:$p0/, 'A added 1');
+
+# changed to CNAME
+
+update_name({CNAME => 'alias'}, 4);
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'CNAME');
+like($r, qr/127.0.0.203:$p0/, 'CNAME 1');
+
+# bad DNS reply should not affect existing upstream configuration
+
+update_name({ERROR => 'SERVFAIL'});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'ERROR');
+like($r, qr/127.0.0.203:$p0/, 'ERROR 1');
+update_name({A => '127.0.0.1'});
+
+###############################################################################
+
+sub update_name {
+       my ($name, $plan) = @_;
+
+       $plan = 2 if !defined $plan;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8083)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{AAAA} = '' unless $name->{AAAA};
+       $name->{CNAME} = '' unless $name->{CNAME};
+       $name->{ERROR} = '' unless $name->{ERROR};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-AAAA: $name->{AAAA}
+X-CNAME: $name->{CNAME}
+X-ERROR: $name->{ERROR}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant CNAME      => 5;
+       use constant AAAA       => 28;
+       use constant DNAME      => 39;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 1);
+       $h = {A => [ "127.0.0.201" ]} unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR}) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq 'example.net') {
+               if ($type == A && $h->{A}) {
+                       map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+               }
+               if ($type == AAAA && $h->{AAAA}) {
+                       map { push @rdata, rd_addr6($ttl, $_) } @{$h->{AAAA}};
+               }
+               my $cname = defined $h->{CNAME} ? $h->{CNAME} : 0;
+               if ($cname) {
+                       push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl,
+                               8, 5, $cname, 0xc00c);
+               }
+
+       } elsif ($name eq 'alias.example.net') {
+               if ($type == A) {
+                       push @rdata, rd_addr($ttl, '127.0.0.203');
+               }
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub expand_ip6 {
+       my ($addr) = @_;
+
+       substr ($addr, index($addr, "::"), 2) =
+               join "0", map { ":" } (0 .. 8 - (split /:/, $addr) + 1);
+       map { hex "0" x (4 - length $_) . "$_" } split /:/, $addr;
+}
+
+sub rd_addr6 {
+       my ($ttl, $addr) = @_;
+
+       pack 'n3N nn8', 0xc00c, AAAA, IN, $ttl, 16, expand_ip6($addr);
+}
+
+sub dns_daemon {
+       my ($t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => port(8982),
+               Proto=> 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => "127.0.0.1:" . port(8083),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . port(8982);
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh) {
+                               my $new = $fh->accept;
+                               $new->autoflush(1);
+                               $sel->add($new);
+
+                       } elsif ($socket == $fh) {
+                               $fh->recv($recv_data, 65536);
+                               $data = reply_handler($recv_data, $h);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-AAAA: (.*)$/m;
+       map { push @{$h{AAAA}}, $_ } split(/ /, $1);
+       $headers =~ /X-CNAME: (.*)$/m;
+       $h{CNAME} = $1;
+       $headers =~ /X-ERROR: (.*)$/m;
+       $h{ERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/upstream_resolve_reload.t b/upstream_resolve_reload.t
new file mode 100644
--- /dev/null
+++ b/upstream_resolve_reload.t
@@ -0,0 +1,306 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for dynamic upstream configuration with re-resolvable servers.
+# Ensure that upstream configuration is inherited on reload.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+plan(skip_all => 'win32') if $^O eq 'MSWin32';
+
+my $t = Test::Nginx->new()->has(qw/http proxy upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream u {
+        zone z 1m;
+        server example.net:%%PORT_8081%% resolve;
+    }
+
+    upstream u2 {
+        zone z 1m;
+        server 127.0.0.203:%%PORT_8081%% max_fails=0;
+        server example.net:%%PORT_8081%% resolve max_fails=0;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8980_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location / {
+            proxy_pass http://u;
+            proxy_connect_timeout 50ms;
+            add_header X-IP $upstream_addr always;
+        }
+
+        location /2 {
+            proxy_pass http://u2;
+            proxy_connect_timeout 50ms;
+            add_header X-IP $upstream_addr always;
+        }
+    }
+}
+
+EOF
+
+$t->run_daemon(\&dns_daemon, $t)->waitforfile($t->testdir . '/' . port(8980));
+$t->try_run('no resolve in upstream server')->plan(9);
+
+###############################################################################
+
+my $p = port(8081);
+
+update_name({A => '127.0.0.201'});
+like(http_get('/'), qr/X-IP: 127.0.0.201:$p/, 'reload - before - request');
+like(http_get('/'), qr/X-IP: 127.0.0.201:$p/, 'reload - before - request 2');
+like(http_get('/2'), qr/127.0.0.(201:$p, 127.0.0.203|203:$p, 127.0.0.201):$p/,
+       'reload - before - many');
+
+update_name({ERROR => 'SERVFAIL'}, 0);
+
+my $conf = $t->read_file('nginx.conf');
+$conf =~ s/$p/port(8082)/gmse;
+$p = port(8082);
+$t->write_file('nginx.conf', $conf);
+
+$t->reload();
+waitforworker($t);
+
+like(http_get('/'), qr/X-IP: 127.0.0.201:$p/, 'reload - preresolve - request');
+like(http_get('/'), qr/X-IP: 127.0.0.201:$p/, 'reload - preresolve - request 
2');
+like(http_get('/2'), qr/127.0.0.(201:$p, 127.0.0.203|203:$p, 127.0.0.201):$p/,
+       'reload - preresolve - many');
+
+update_name({A => '127.0.0.202'});
+like(http_get('/'), qr/X-IP: 127.0.0.202:$p/, 'reload - update - request');
+like(http_get('/'), qr/X-IP: 127.0.0.202:$p/, 'reload - update - request 2');
+like(http_get('/2'), qr/127.0.0.(202:$p, 127.0.0.203|203:$p, 127.0.0.202):$p/,
+       'reload - update - many');
+
+###############################################################################
+
+sub waitforworker {
+       my ($t) = @_;
+
+       for (1 .. 30) {
+               last if $t->read_file('error.log') =~ /exited with code/;
+               select undef, undef, undef, 0.2;
+       }
+}
+
+sub update_name {
+       my ($name, $plan) = @_;
+
+       $plan = 2 if !defined $plan;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8081)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{ERROR} = '' unless $name->{ERROR};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-ERROR: $name->{ERROR}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 1);
+       $h = {A => [ "127.0.0.201" ]} unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR}) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq 'example.net' && $type == A && $h->{A}) {
+               map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub dns_daemon {
+       my ($t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => port(8980),
+               Proto => 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => "127.0.0.1:" . port(8081),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . port(8980);
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh) {
+                               my $new = $fh->accept;
+                               $new->autoflush(1);
+                               $sel->add($new);
+
+                       } elsif ($socket == $fh) {
+                               $fh->recv($recv_data, 65536);
+                               $data = reply_handler($recv_data, $h);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-ERROR: (.*)$/m;
+       $h{ERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/upstream_resolver.t b/upstream_resolver.t
new file mode 100644
--- /dev/null
+++ b/upstream_resolver.t
@@ -0,0 +1,265 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for re-resolvable servers with resolver in http upstream.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+use Socket qw/ CRLF /;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http proxy upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    resolver 127.0.0.1:%%PORT_8980_UDP%%;
+
+    upstream u {
+        zone z 1m;
+        server example.net:%%PORT_8080%% resolve;
+    }
+
+    upstream u1 {
+        zone z 1m;
+        server example.net:%%PORT_8080%% resolve;
+        resolver 127.0.0.1:%%PORT_8981_UDP%%;
+    }
+
+    upstream u2 {
+        zone z 1m;
+        server example.net:%%PORT_8080%% resolve;
+        resolver 127.0.0.1:%%PORT_8982_UDP%%;
+        resolver_timeout 200s; # for coverage
+    }
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location / {
+            proxy_pass http://$args/t;
+            proxy_connect_timeout 50ms;
+            add_header X-IP $upstream_addr;
+            error_page 502 504 redirect;
+        }
+
+    }
+}
+
+EOF
+
+$t->run_daemon(\&dns_daemon, $t, port($_), port($_ + 10)) for (8980 .. 8982);
+$t->waitforfile($t->testdir . '/' . port($_)) for (8980 .. 8982);
+
+$t->try_run('no resolver in upstream')->plan(6);
+
+###############################################################################
+
+ok(waitfordns(8980), 'resolved');
+ok(waitfordns(8981), 'resolved in upstream 1');
+ok(waitfordns(8982), 'resolved in upstream 2');
+
+like(http_get('/?u'), qr/127.0.0.200/, 'resolver');
+like(http_get('/?u1'), qr/127.0.0.201/, 'resolver upstream 1');
+like(http_get('/?u2'), qr/127.0.0.202/, 'resolver upstream 2');
+
+###############################################################################
+
+sub waitfordns {
+       my ($port, $plan) = @_;
+
+       $plan = 1 if !defined $plan;
+
+       sub sock {
+               my ($port) = @_;
+
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port($port + 10)
+               )
+                       or die "Can't connect to dns control socket: $!\n";
+       }
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+
+EOF
+
+       for (1 .. 10) {
+               my ($gen) = http($req, socket => sock($port)) =~ /X-Gen: (\d+)/;
+               select undef, undef, undef, 0.5;
+               return 1 if $gen >= $plan;
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $port) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant A          => 1;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 1);
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+
+       my $name = join('.', @name);
+       if ($name eq 'example.net' && $type == A) {
+               if ($port == port(8980)) {
+                       push @rdata, rd_addr($ttl, "127.0.0.200");
+               }
+
+               if ($port == port(8981)) {
+                       push @rdata, rd_addr($ttl, "127.0.0.201");
+               }
+
+               if ($port == port(8982)) {
+                       push @rdata, rd_addr($ttl, "127.0.0.202");
+               }
+       }
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub dns_daemon {
+       my ($t, $port, $control_port) = @_;
+
+       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 $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalAddr => '127.0.0.1',
+               LocalPort => $control_port,
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . $port;
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $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);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/upstream_service.t b/upstream_service.t
new file mode 100644
--- /dev/null
+++ b/upstream_service.t
@@ -0,0 +1,489 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for dynamic upstream configuration with service (SRV) feature.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+use Socket qw/ CRLF /;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http proxy upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream u {
+        zone z 1m;
+        server example.net resolve service=http max_fails=0;
+    }
+
+    upstream u2 {
+        zone z2 1m;
+        server example.net resolve service=_http._tcp;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8981_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        add_header X-IP $upstream_addr;
+        error_page 502 504 redirect;
+        proxy_connect_timeout 50ms;
+
+        location / {
+            proxy_pass http://u/t;
+        }
+
+        location /full {
+            proxy_pass http://u2/t;
+        }
+
+        location /t { }
+    }
+}
+
+EOF
+
+port(8084);
+
+$t->write_file('t', '');
+
+$t->run_daemon(\&dns_daemon, $t)->waitforfile($t->testdir . '/' . port(8981));
+port(8981, socket => 1)->close();
+$t->try_run('no service in upstream server')->plan(30);
+
+###############################################################################
+
+my ($r, @n);
+my ($p0, $p2, $p3) = (port(8080), port(8082), port(8083));
+
+update_name({A => '127.0.0.201', SRV => "1 5 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'A');
+like($r, qr/127.0.0.201:$p0/, 'A 1');
+
+# fully specified service
+
+$r = http_get('/full');
+is(@n = $r =~ /:$p0/g, 1, 'A full');
+like($r, qr/127.0.0.201:$p0/, 'A full 1');
+
+# A changed
+
+update_name({A => '127.0.0.202', SRV => "1 5 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'A changed');
+like($r, qr/127.0.0.202:$p0/, 'A changed 1');
+
+# 1 more A added
+
+update_name({A => '127.0.0.201 127.0.0.202', SRV => "1 5 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 2, 'A A');
+like($r, qr/127.0.0.201:$p0/, 'A A 1');
+like($r, qr/127.0.0.202:$p0/, 'A A 2');
+
+# 1 A removed, 2 AAAA added
+
+update_name({A => '127.0.0.201', AAAA => 'fe80::1 fe80::2',
+       SRV => "1 5 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 3, 'A AAAA AAAA responses');
+like($r, qr/127.0.0.201:$p0/, 'A AAAA AAAA 1');
+like($r, qr/\[fe80::1\]:$p0/, 'A AAAA AAAA 2');
+like($r, qr/\[fe80::1\]:$p0/, 'A AAAA AAAA 3');
+
+# all records removed
+
+update_name({SRV => "1 5 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 0, 'empty SRV response');
+
+# all SRV records removed
+
+update_name();
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 0, 'empty response');
+
+# A added after empty
+
+update_name({A => '127.0.0.201', SRV => "1 5 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'A added');
+like($r, qr/127.0.0.201:$p0/, 'A added 1');
+
+# SRV changed its weight
+
+update_name({A => '127.0.0.201', SRV => "1 6 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'SRV weight');
+like($r, qr/127.0.0.201:$p0/, 'SRV weight 1');
+
+# changed to CNAME
+
+update_name({CNAME => 'alias'}, 2, 2);
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'CNAME');
+like($r, qr/127.0.0.203:$p0/, 'CNAME 1');
+
+# bad SRV reply should not affect existing upstream configuration
+
+update_name({CNAME => 'alias', ERROR => 'SERVFAIL'}, 1, 0);
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 1, 'ERROR');
+like($r, qr/127.0.0.203:$p0/, 'ERROR 1');
+update_name({ERROR => ''}, 1, 0);
+
+# 2 equal SRV RR
+
+update_name({A => '127.0.0.201',
+       SRV => "1 5 $p0 example.net;1 5 $p0 example.net"});
+$r = http_get('/');
+is(@n = $r =~ /:$p0/g, 2, 'SRV same');
+like($r, qr/127.0.0.201:$p0, 127.0.0.201:$p0/, 'SRV same peers');
+
+# all equal records removed
+
+update_name();
+$r = http_get('/');
+is(@n = $r =~ /:($p0|$p2|$p3)/g, 0, 'SRV same removed');
+
+# 2 different SRV RR
+
+update_name({A => '127.0.0.201',
+       SRV => "1 5 $p2 example.net;2 6 $p3 alias.example.net"}, 1, 2);
+$r = http_get('/');
+is(@n = $r =~ /:($p2|$p3)/g, 2, 'SRV diff');
+like($r, qr/127.0.0.201:$p2/, 'SRV diff 1');
+like($r, qr/127.0.0.203:$p3/, 'SRV diff 2');
+
+# all different records removed
+
+update_name();
+$r = http_get('/');
+is(@n = $r =~ /:($p0|$p2|$p3)/g, 0, 'SRV diff removed');
+
+###############################################################################
+
+sub update_name {
+       my ($name, $plan, $plan6) = @_;
+
+       $plan = 1, $plan6 = 0 if !defined $name;
+       $plan = $plan6 = 1 if !defined $plan;
+       $plan += $plan6 + $plan6;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8084)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{AAAA} = '' unless $name->{AAAA};
+       $name->{CNAME} = '' unless $name->{CNAME};
+       $name->{ERROR} = '' unless $name->{ERROR};
+       $name->{SERROR} = '' unless $name->{SERROR};
+       $name->{SRV} = '' unless $name->{SRV};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-AAAA: $name->{AAAA}
+X-CNAME: $name->{CNAME}
+X-ERROR: $name->{ERROR}
+X-SERROR: $name->{SERROR}
+X-SRV: $name->{SRV}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h, $cnt, $tcp) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant FORMERR    => 1;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant CNAME      => 5;
+       use constant AAAA       => 28;
+       use constant SRV        => 33;
+
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl, $port) = (0x8180, NOERROR, 3600, port(8080));
+       $h = {A => [ "127.0.0.1" ], SRV => [ "1 5 $port example.net" ]}
+               unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR} && $type == SRV) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       # subordinate error
+
+       if ($h->{SERROR} && $type != SRV) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq '_http._tcp.example.net') {
+               if ($type == SRV && $h->{SRV}) {
+                       map { push @rdata, rd_srv($ttl, (split ' ', $_)) }
+                               @{$h->{SRV}};
+               }
+
+               my $cname = defined $h->{CNAME} ? $h->{CNAME} : 0;
+               if ($cname) {
+                       push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl,
+                               8, 5, "alias", 0xc00c + length("_http._tcp "));
+               }
+
+       } elsif ($name eq '_http._tcp.trunc.example.net' && $type == SRV) {
+               push @rdata, $tcp
+                       ? rd_srv($ttl, 1, 1, $port, 'tcp.example.net')
+                       : rd_srv($ttl, 1, 1, $port, 'example.net');
+
+               $hdr |= 0x0300 if $name eq '_http._tcp.trunc.example.net'
+                       and !$tcp;
+
+       } elsif ($name eq 'example.net' || $name eq 'tcp.example.net') {
+               if ($type == A && $h->{A}) {
+                       map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+               }
+               if ($type == AAAA && $h->{AAAA}) {
+                       map { push @rdata, rd_addr6($ttl, $_) } @{$h->{AAAA}};
+               }
+               my $cname = defined $h->{CNAME} ? $h->{CNAME} : 0;
+               if ($cname) {
+                       push @rdata, pack("n3N nCa5n", 0xc00c, CNAME, IN, $ttl,
+                               8, 5, $cname, 0xc00c);
+               }
+
+       } elsif ($name eq 'alias.example.net') {
+               if ($type == SRV) {
+                       push @rdata, rd_srv($ttl, 1, 5, $port, 'example.net');
+               }
+               if ($type == A) {
+                       push @rdata, rd_addr($ttl, '127.0.0.203');
+               }
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $$cnt++ if $type == SRV || keys %$h;
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_srv {
+       my ($ttl, $pri, $w, $port, $name) = @_;
+       my @rdname = split /\./, $name;
+       my $rdlen = length(join '', @rdname) + @rdname + 7;     # pri w port x
+
+       pack 'n3N n n3 (C/a*)* x',
+               0xc00c, SRV, IN, $ttl, $rdlen, $pri, $w, $port, @rdname;
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub expand_ip6 {
+       my ($addr) = @_;
+
+       substr ($addr, index($addr, "::"), 2) =
+               join "0", map { ":" } (0 .. 8 - (split /:/, $addr) + 1);
+       map { hex "0" x (4 - length $_) . "$_" } split /:/, $addr;
+}
+
+sub rd_addr6 {
+       my ($ttl, $addr) = @_;
+
+       pack 'n3N nn8', 0xc00c, AAAA, IN, $ttl, 16, expand_ip6($addr);
+}
+
+sub dns_daemon {
+       my ($t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => port(8981),
+               Proto => 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => '127.0.0.1:' . port(8084),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $tcp = port(8981, socket => 1);
+       my $sel = IO::Select->new($socket, $control, $tcp);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . port(8981);
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh || $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, $h, \$cnt);
+                               $fh->send($data);
+
+                       } elsif ($fh->sockport() == port(8084)) {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+
+                       } elsif ($fh->sockport() == port(8981)) {
+                               $fh->recv($recv_data, 65536);
+                               unless (length $recv_data) {
+                                       $sel->remove($fh);
+                                       $fh->close;
+                                       next;
+                               }
+
+again:
+                               my $len = unpack("n", $recv_data);
+                               my $data = substr $recv_data, 2, $len;
+                               $data = reply_handler($data, $h, \$cnt, 1);
+                               $data = pack("n", length $data) . $data;
+                               $fh->send($data);
+                               $recv_data = substr $recv_data, 2 + $len;
+                               goto again if length $recv_data;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-AAAA: (.*)$/m;
+       map { push @{$h{AAAA}}, $_ } split(/ /, $1);
+       $headers =~ /X-SRV: (.*)$/m;
+       map { push @{$h{SRV}}, $_ } split(/;/, $1);
+       $headers =~ /X-CNAME: (.+)$/m and $h{CNAME} = $1;
+       $headers =~ /X-ERROR: (.+)$/m and $h{ERROR} = $1;
+       $headers =~ /X-SERROR: (.+)$/m and $h{SERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
diff --git a/upstream_service_reload.t b/upstream_service_reload.t
new file mode 100644
--- /dev/null
+++ b/upstream_service_reload.t
@@ -0,0 +1,303 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for dynamic upstream configuration with service (SRV) feature.
+# Ensure that upstream configuration is inherited on reload.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use IO::Select;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+plan(skip_all => 'win32') if $^O eq 'MSWin32';
+
+my $t = Test::Nginx->new()->has(qw/http upstream_zone/);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    upstream u {
+        zone z 1m;
+        server example.net resolve service=http;
+    }
+
+    # lower the retry timeout after empty reply
+    resolver 127.0.0.1:%%PORT_8980_UDP%% valid=1s;
+    # retry query shortly after DNS is started
+    resolver_timeout 1s;
+
+    server {
+        listen       127.0.0.1:8080;
+        server_name  localhost;
+
+        location / {
+            proxy_pass http://u;
+            proxy_connect_timeout 50ms;
+            add_header X-IP $upstream_addr always;
+        }
+    }
+}
+
+EOF
+
+port(8081);
+
+$t->run_daemon(\&dns_daemon, $t)->waitforfile($t->testdir . '/' . port(8980));
+$t->try_run('no resolve in upstream server')->plan(6);
+
+###############################################################################
+
+update_name({A => '127.0.0.201', SRV => "1 5 42 example.net"});
+like(http_get('/'), qr/X-IP: 127.0.0.201:42/, 'reload - before - request');
+like(http_get('/'), qr/X-IP: 127.0.0.201:42/, 'reload - before - request 2');
+
+update_name({ERROR => 'SERVFAIL'}, 0);
+
+$t->reload();
+waitforworker($t);
+
+like(http_get('/'), qr/X-IP: 127.0.0.201:42/, 'reload - preresolve - request');
+like(http_get('/'), qr/X-IP: 127.0.0.201:42/, 'reload - preresolve - request 
2');
+
+update_name({A => '127.0.0.202', SRV => "1 5 42 example.net"});
+like(http_get('/'), qr/X-IP: 127.0.0.202:42/, 'reload - update - request');
+like(http_get('/'), qr/X-IP: 127.0.0.202:42/, 'reload - update - request 2');
+
+###############################################################################
+
+sub waitforworker {
+       my ($t) = @_;
+
+       for (1 .. 30) {
+               last if $t->read_file('error.log') =~ /exited with code/;
+               select undef, undef, undef, 0.2;
+       }
+}
+
+sub update_name {
+       my ($name, $plan) = @_;
+
+       $plan = 3 if !defined $plan;
+
+       sub sock {
+               IO::Socket::INET->new(
+                       Proto => 'tcp',
+                       PeerAddr => '127.0.0.1:' . port(8081)
+               )
+                       or die "Can't connect to nginx: $!\n";
+       }
+
+       $name->{A} = '' unless $name->{A};
+       $name->{ERROR} = '' unless $name->{ERROR};
+       $name->{SRV} = '' unless $name->{SRV};
+
+       my $req =<<EOF;
+GET / HTTP/1.0
+Host: localhost
+X-A: $name->{A}
+X-ERROR: $name->{ERROR}
+X-SRV: $name->{SRV}
+
+EOF
+
+       my ($gen) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+       for (1 .. 10) {
+               my ($gen2) = http($req, socket => sock()) =~ /X-Gen: (\d+)/;
+
+               # let resolver cache expire to finish upstream reconfiguration
+               select undef, undef, undef, 0.5;
+               last unless ($gen + $plan > $gen2);
+       }
+}
+
+###############################################################################
+
+sub reply_handler {
+       my ($recv_data, $h) = @_;
+
+       my (@name, @rdata);
+
+       use constant NOERROR    => 0;
+       use constant SERVFAIL   => 2;
+       use constant NXDOMAIN   => 3;
+
+       use constant A          => 1;
+       use constant SRV        => 33;
+       use constant IN         => 1;
+
+       # default values
+
+       my ($hdr, $rcode, $ttl, $port) = (0x8180, NOERROR, 3600, port(8080));
+       $h = {A => [ "127.0.0.201" ], SRV => [ "1 5 $port example.net" ]}
+               unless defined $h;
+
+       # decode name
+
+       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;
+       }
+
+       $offset -= 1;
+       my ($id, $type, $class) = unpack("n x$offset n2", $recv_data);
+       my $name = join('.', @name);
+
+       if ($h->{ERROR}) {
+               $rcode = SERVFAIL;
+               goto bad;
+       }
+
+       if ($name eq 'example.net' && $type == A && $h->{A}) {
+               map { push @rdata, rd_addr($ttl, $_) } @{$h->{A}};
+
+       }
+       if ($name eq '_http._tcp.example.net' && $type == SRV && $h->{SRV}) {
+               map { push @rdata, rd_srv($ttl, (split ' ', $_)) }
+                       @{$h->{SRV}};
+       }
+
+bad:
+
+       Test::Nginx::log_core('||', "DNS: $name $type $rcode");
+
+       $len = @name;
+       pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata,
+               0, 0, @name, $type, $class) . join('', @rdata);
+}
+
+sub rd_srv {
+       my ($ttl, $pri, $w, $port, $name) = @_;
+       my @rdname = split /\./, $name;
+       my $rdlen = length(join '', @rdname) + @rdname + 7;     # pri w port x
+
+       pack 'n3N n n3 (C/a*)* x',
+               0xc00c, SRV, IN, $ttl, $rdlen, $pri, $w, $port, @rdname;
+}
+
+sub rd_addr {
+       my ($ttl, $addr) = @_;
+
+       my $code = 'split(/\./, $addr)';
+
+       pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code);
+}
+
+sub dns_daemon {
+       my ($t) = @_;
+       my ($data, $recv_data, $h);
+
+       my $socket = IO::Socket::INET->new(
+               LocalAddr => '127.0.0.1',
+               LocalPort => port(8980),
+               Proto => 'udp',
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $control = IO::Socket::INET->new(
+               Proto => 'tcp',
+               LocalHost => "127.0.0.1:" . port(8081),
+               Listen => 5,
+               Reuse => 1
+       )
+               or die "Can't create listening socket: $!\n";
+
+       my $sel = IO::Select->new($socket, $control);
+
+       local $SIG{PIPE} = 'IGNORE';
+
+       # signal we are ready
+
+       open my $fh, '>', $t->testdir() . '/' . port(8980);
+       close $fh;
+       my $cnt = 0;
+
+       while (my @ready = $sel->can_read) {
+               foreach my $fh (@ready) {
+                       if ($control == $fh) {
+                               my $new = $fh->accept;
+                               $new->autoflush(1);
+                               $sel->add($new);
+
+                       } elsif ($socket == $fh) {
+                               $fh->recv($recv_data, 65536);
+                               $data = reply_handler($recv_data, $h);
+                               $fh->send($data);
+                               $cnt++;
+
+                       } else {
+                               $h = process_name($fh, $cnt);
+                               $sel->remove($fh);
+                               $fh->close;
+                       }
+               }
+       }
+}
+
+# parse dns update
+
+sub process_name {
+       my ($client, $cnt) = @_;
+       my $port = $client->sockport();
+
+       my $headers = '';
+       my $uri = '';
+       my %h;
+
+       while (<$client>) {
+               $headers .= $_;
+               last if (/^\x0d?\x0a?$/);
+       }
+       return 1 if $headers eq '';
+
+       $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+       return 1 if $uri eq '';
+
+       $headers =~ /X-A: (.*)$/m;
+       map { push @{$h{A}}, $_ } split(/ /, $1);
+       $headers =~ /X-SRV: (.*)$/m;
+       map { push @{$h{SRV}}, $_ } split(/;/, $1);
+       $headers =~ /X-ERROR: (.*)$/m;
+       $h{ERROR} = $1;
+
+       Test::Nginx::log_core('||', "$port: response, 200");
+       print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Gen: $cnt
+
+OK
+EOF
+
+       return \%h;
+}
+
+###############################################################################
_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
https://mailman.nginx.org/mailman/listinfo/nginx-devel

Reply via email to