Internal resolver for default bridge network

Until now, containers on the default bridge network have been configured
to talk directly to external DNS servers - their resolv.conf files have
either been populated with nameservers from the host's resolv.conf, or
with servers from '--dns' (or with Google's nameservers as a fallback).

This change makes the internal bridge more like other networks by using
the internal resolver.  But, the internal resolver is not populated with
container names or aliases - it's only for external DNS lookups.

Containers on the default network, on a host that has a loopback
resolver (like systemd's on 127.0.0.53) will now use that resolver
via the internal resolver. So, the logic used to find systemd's current
set of resolvers is no longer needed by the daemon.

Legacy links work just as they did before, using '/etc/hosts' and magic.

(Buildkit does not use libnetwork, so it can't use the internal resolver.
But it does use libnetwork/resolvconf's logic to configure resolv.conf.
So, code to set up resolv.conf for a legacy networking without an internal
resolver can't be removed yet.)

Signed-off-by: Rob Murray <rob.murray@docker.com>
This commit is contained in:
Rob Murray
2024-03-20 17:44:19 +00:00
parent 56a43a7618
commit 18f4f775ed
13 changed files with 96 additions and 227 deletions

View File

@@ -27,7 +27,6 @@ import (
"github.com/docker/docker/integration-cli/cli/build"
"github.com/docker/docker/integration-cli/daemon"
"github.com/docker/docker/internal/testutils/specialimage"
"github.com/docker/docker/libnetwork/resolvconf"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/testutil"
@@ -1249,45 +1248,8 @@ func (s *DockerCLIRunSuite) TestRunDisallowBindMountingRootToRoot(c *testing.T)
}
}
// Verify that a container gets default DNS when only localhost resolvers exist
func (s *DockerCLIRunSuite) TestRunDNSDefaultOptions(c *testing.T) {
// Not applicable on Windows as this is testing Unix specific functionality
testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
// preserve original resolv.conf for restoring after test
origResolvConf, err := os.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) {
c.Fatalf("/etc/resolv.conf does not exist")
}
// defer restored original conf
defer func() {
if err := os.WriteFile("/etc/resolv.conf", origResolvConf, 0o644); err != nil {
c.Fatal(err)
}
}()
// test 3 cases: standard IPv4 localhost, commented out localhost, and IPv6 localhost
// 2 are removed from the file at container start, and the 3rd (commented out) one is ignored by
// GetNameservers(), leading to a replacement of nameservers with the default set
tmpResolvConf := []byte("nameserver 127.0.0.1\n#nameserver 127.0.2.1\nnameserver ::1")
if err := os.WriteFile("/etc/resolv.conf", tmpResolvConf, 0o644); err != nil {
c.Fatal(err)
}
actual := cli.DockerCmd(c, "run", "busybox", "cat", "/etc/resolv.conf").Combined()
actual = regexp.MustCompile("(?m)^#.*$").ReplaceAllString(actual, "")
actual = strings.ReplaceAll(strings.Trim(actual, "\r\n"), "\n", " ")
// NOTE: if we ever change the defaults from google dns, this will break
expected := "nameserver 8.8.8.8 nameserver 8.8.4.4"
if actual != expected {
c.Fatalf("expected resolv.conf be: %q, but was: %q", expected, actual)
}
}
func (s *DockerCLIRunSuite) TestRunDNSOptions(c *testing.T) {
// Not applicable on Windows as Windows does not support --dns*, or
// the Unix-specific functionality of resolv.conf.
// Not applicable on Windows as Windows does not support the Unix-specific functionality of resolv.conf.
testRequires(c, DaemonIsLinux)
result := cli.DockerCmd(c, "run", "--dns=127.0.0.1", "--dns-search=mydomain", "--dns-opt=ndots:9", "busybox", "cat", "/etc/resolv.conf")
@@ -1298,16 +1260,22 @@ func (s *DockerCLIRunSuite) TestRunDNSOptions(c *testing.T) {
actual := regexp.MustCompile("(?m)^#.*$").ReplaceAllString(result.Stdout(), "")
actual = strings.ReplaceAll(strings.Trim(actual, "\r\n"), "\n", " ")
if actual != "nameserver 127.0.0.1 search mydomain options ndots:9" {
c.Fatalf("nameserver 127.0.0.1 expected 'search mydomain options ndots:9', but says: %q", actual)
if actual != "nameserver 127.0.0.11 search mydomain options ndots:9" {
c.Fatalf("nameserver 127.0.0.11 expected 'search mydomain options ndots:9', but says: %q", actual)
}
if !strings.Contains(result.Stdout(), "ExtServers: [127.0.0.1]") {
c.Fatalf("expected 'ExtServers: [127.0.0.1]' was not found in %q", result.Stdout())
}
out := cli.DockerCmd(c, "run", "--dns=1.1.1.1", "--dns-search=.", "--dns-opt=ndots:3", "busybox", "cat", "/etc/resolv.conf").Combined()
actual = regexp.MustCompile("(?m)^#.*$").ReplaceAllString(out, "")
actual = strings.ReplaceAll(strings.Trim(strings.Trim(actual, "\r\n"), " "), "\n", " ")
if actual != "nameserver 1.1.1.1 options ndots:3" {
c.Fatalf("expected 'nameserver 1.1.1.1 options ndots:3', but says: %q", actual)
if actual != "nameserver 127.0.0.11 options ndots:3" {
c.Fatalf("expected 'nameserver 127.0.0.11 options ndots:3', but says: %q", actual)
}
if !strings.Contains(out, "ExtServers: [1.1.1.1]") {
c.Fatalf("expected 'ExtServers: [1.1.1.1]' was not found in %q", out)
}
}
@@ -1317,87 +1285,11 @@ func (s *DockerCLIRunSuite) TestRunDNSRepeatOptions(c *testing.T) {
actual := regexp.MustCompile("(?m)^#.*$").ReplaceAllString(out, "")
actual = strings.ReplaceAll(strings.Trim(actual, "\r\n"), "\n", " ")
if actual != "nameserver 1.1.1.1 nameserver 2.2.2.2 search mydomain mydomain2 options ndots:9 timeout:3" {
c.Fatalf("expected 'nameserver 1.1.1.1 nameserver 2.2.2.2 search mydomain mydomain2 options ndots:9 timeout:3', but says: %q", actual)
if actual != "nameserver 127.0.0.11 search mydomain mydomain2 options ndots:9 timeout:3" {
c.Fatalf("expected 'nameserver 127.0.0.11 search mydomain mydomain2 options ndots:9 timeout:3', but says: %q", actual)
}
}
func (s *DockerCLIRunSuite) TestRunDNSOptionsBasedOnHostResolvConf(c *testing.T) {
// Not applicable on Windows as testing Unix specific functionality
testRequires(c, testEnv.IsLocalDaemon, DaemonIsLinux)
origResolvConf, err := os.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) {
c.Fatalf("/etc/resolv.conf does not exist")
}
hostNameservers := resolvconf.GetNameservers(origResolvConf, resolvconf.IP)
hostSearch := resolvconf.GetSearchDomains(origResolvConf)
out := cli.DockerCmd(c, "run", "--dns=127.0.0.1", "busybox", "cat", "/etc/resolv.conf").Combined()
if actualNameservers := resolvconf.GetNameservers([]byte(out), resolvconf.IP); actualNameservers[0] != "127.0.0.1" {
c.Fatalf("expected '127.0.0.1', but says: %q", actualNameservers[0])
}
actualSearch := resolvconf.GetSearchDomains([]byte(out))
if len(actualSearch) != len(hostSearch) {
c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch))
}
for i := range actualSearch {
if actualSearch[i] != hostSearch[i] {
c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i])
}
}
out = cli.DockerCmd(c, "run", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf").Combined()
actualNameservers := resolvconf.GetNameservers([]byte(out), resolvconf.IP)
if len(actualNameservers) != len(hostNameservers) {
c.Fatalf("expected %q nameserver(s), but it has: %q", len(hostNameservers), len(actualNameservers))
}
for i := range actualNameservers {
if actualNameservers[i] != hostNameservers[i] {
c.Fatalf("expected %q nameserver, but says: %q", actualNameservers[i], hostNameservers[i])
}
}
if actualSearch = resolvconf.GetSearchDomains([]byte(out)); actualSearch[0] != "mydomain" {
c.Fatalf("expected 'mydomain', but says: %q", actualSearch[0])
}
// test with file
tmpResolvConf := []byte("search example.com\nnameserver 12.34.56.78\nnameserver 127.0.0.1")
if err := os.WriteFile("/etc/resolv.conf", tmpResolvConf, 0o644); err != nil {
c.Fatal(err)
}
// put the old resolvconf back
defer func() {
if err := os.WriteFile("/etc/resolv.conf", origResolvConf, 0o644); err != nil {
c.Fatal(err)
}
}()
resolvConf, err := os.ReadFile("/etc/resolv.conf")
if os.IsNotExist(err) {
c.Fatalf("/etc/resolv.conf does not exist")
}
hostSearch = resolvconf.GetSearchDomains(resolvConf)
out = cli.DockerCmd(c, "run", "busybox", "cat", "/etc/resolv.conf").Combined()
if actualNameservers = resolvconf.GetNameservers([]byte(out), resolvconf.IP); actualNameservers[0] != "12.34.56.78" || len(actualNameservers) != 1 {
c.Fatalf("expected '12.34.56.78', but has: %v", actualNameservers)
}
actualSearch = resolvconf.GetSearchDomains([]byte(out))
if len(actualSearch) != len(hostSearch) {
c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch))
}
for i := range actualSearch {
if actualSearch[i] != hostSearch[i] {
c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i])
}
if !strings.Contains(out, "ExtServers: [1.1.1.1 2.2.2.2]") {
c.Fatalf("expected 'ExtServers: [1.1.1.1 2.2.2.2]' was not found in %q", out)
}
}