Browse code

Better selection of DNS server

Commit e353e7e3f0ce8eceeff657393cba2876375403fa updated selection of the
`resolv.conf` file to use in situations where systemd-resolvd is used as
a resolver.

If a host uses `systemd-resolvd`, the system's `/etc/resolv.conf` file is
updated to set `127.0.0.53` as DNS, which is the local IP address for
systemd-resolvd. The DNS servers that are configured by the user will now
be stored in `/run/systemd/resolve/resolv.conf`, and systemd-resolvd acts
as a forwarding DNS for those.

Originally, Docker copied the DNS servers as configured in `/etc/resolv.conf`
as default DNS servers in containers, which failed to work if systemd-resolvd
is used (as `127.0.0.53` is not available inside the container's networking
namespace). To resolve this, e353e7e3f0ce8eceeff657393cba2876375403fa instead
detected if systemd-resolvd is in use, and in that case copied the "upstream"
DNS servers from the `/run/systemd/resolve/resolv.conf` configuration.

While this worked for most situations, it had some downsides, among which:

- we're skipping systemd-resolvd altogether, which means that we cannot take
advantage of addition functionality provided by it (such as per-interface
DNS servers)
- when updating DNS servers in the system's configuration, those changes were
not reflected in the container configuration, which could be problematic in
"developer" scenarios, when switching between networks.

This patch changes the way we select which resolv.conf to use as template
for the container's resolv.conf;

- in situations where a custom network is attached to the container, and the
embedded DNS is available, we use `/etc/resolv.conf` unconditionally. If
systemd-resolvd is used, the embedded DNS forwards external DNS lookups to
systemd-resolvd, which in turn is responsible for forwarding requests to
the external DNS servers configured by the user.
- if the container is running in "host mode" networking, we also use the
DNS server that's configured in `/etc/resolv.conf`. In this situation, no
embedded DNS server is available, but the container runs in the host's
networking namespace, and can use the same DNS servers as the host (which
could be systemd-resolvd or DNSMasq
- if the container uses the default (bridge) network, no embedded DNS is
available, and the container has its own networking namespace. In this
situation we check if systemd-resolvd is used, in which case we skip
systemd-resolvd, and configure the upstream DNS servers as DNS for the
container. This situation is the same as is used currently, which means
that dynamically switching DNS servers won't be supported for these
containers.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2020/05/25 21:02:18
Showing 1 changed files
... ...
@@ -391,13 +391,65 @@ func serviceDiscoveryOnDefaultNetwork() bool {
391 391
 func (daemon *Daemon) setupPathsAndSandboxOptions(container *container.Container, sboxOptions *[]libnetwork.SandboxOption) error {
392 392
 	var err error
393 393
 
394
-	if container.HostConfig.NetworkMode.IsHost() {
395
-		// Point to the host files, so that will be copied into the container running in host mode
396
-		*sboxOptions = append(*sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts"))
397
-	}
394
+	// Set the correct paths for /etc/hosts and /etc/resolv.conf, based on the
395
+	// networking-mode of the container. Note that containers with "container"
396
+	// networking are already handled in "initializeNetworking()" before we reach
397
+	// this function, so do not have to be accounted for here.
398
+	switch {
399
+	case container.HostConfig.NetworkMode.IsHost():
400
+		// In host-mode networking, the container does not have its own networking
401
+		// namespace, so both `/etc/hosts` and `/etc/resolv.conf` should be the same
402
+		// as on the host itself. The container gets a copy of these files, but they
403
+		// may be symlinked, so resolve the original path first.
404
+		etcHosts, err := filepath.EvalSymlinks("/etc/hosts")
405
+		if err != nil {
406
+			return err
407
+		}
408
+		resolvConf, err := filepath.EvalSymlinks("/etc/resolv.conf")
409
+		if err != nil {
410
+			return err
411
+		}
398 412
 
399
-	// Copy the host's resolv.conf for the container (/etc/resolv.conf or /run/systemd/resolve/resolv.conf)
400
-	*sboxOptions = append(*sboxOptions, libnetwork.OptionOriginResolvConfPath(daemon.configStore.GetResolvConf()))
413
+		*sboxOptions = append(
414
+			*sboxOptions,
415
+			libnetwork.OptionOriginHostsPath(etcHosts),
416
+			libnetwork.OptionOriginResolvConfPath(resolvConf),
417
+		)
418
+	case container.HostConfig.NetworkMode.IsUserDefined():
419
+		// The container uses a user-defined network. We use the embedded DNS
420
+		// server for container name resolution and to act as a DNS forwarder
421
+		// for external DNS resolution.
422
+		// We parse the DNS server(s) that are defined in /etc/resolv.conf on
423
+		// the host, which may be a local DNS server (for example, if DNSMasq or
424
+		// systemd-resolvd are in use). The embedded DNS server forwards DNS
425
+		// resolution to the DNS server configured on the host, which in itself
426
+		// may act as a forwarder for external DNS servers.
427
+		// If systemd-resolvd is used, the "upstream" DNS servers can be found in
428
+		// /run/systemd/resolve/resolv.conf. We do not query those DNS servers
429
+		// directly, as they can be dynamically reconfigured.
430
+		resolvConf, err := filepath.EvalSymlinks("/etc/resolv.conf")
431
+		if err != nil {
432
+			return err
433
+		}
434
+		*sboxOptions = append(*sboxOptions, libnetwork.OptionOriginResolvConfPath(resolvConf))
435
+	default:
436
+		// For other situations, such as the default bridge network, container
437
+		// discovery / name resolution is handled through /etc/hosts, and no
438
+		// embedded DNS server is available. Without the embedded DNS, we
439
+		// cannot use local DNS servers on the host (for example, if DNSMasq or
440
+		// systemd-resolvd is used). If systemd-resolvd is used, we try to
441
+		// determine the external DNS servers that are used on the host.
442
+		// This situation is not ideal, because DNS servers configured in the
443
+		// container are not updated after the container is created, but the
444
+		// DNS servers on the host can be dynamically updated.
445
+		//
446
+		// Copy the host's resolv.conf for the container (/run/systemd/resolve/resolv.conf or /etc/resolv.conf)
447
+		resolvConf, err := filepath.EvalSymlinks(daemon.configStore.GetResolvConf())
448
+		if err != nil {
449
+			return err
450
+		}
451
+		*sboxOptions = append(*sboxOptions, libnetwork.OptionOriginResolvConfPath(resolvConf))
452
+	}
401 453
 
402 454
 	container.HostsPath, err = container.GetRootResourcePath("hosts")
403 455
 	if err != nil {