From 92e809a6807210a3d1ecd7949314367e82f5b683 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 1 Nov 2019 17:09:40 -0700 Subject: [PATCH] Support host.docker.internal in dockerd on Linux Docker Desktop (on MAC and Windows hosts) allows containers running inside a Linux VM to connect to the host using the host.docker.internal DNS name, which is implemented by VPNkit (DNS proxy on the host) This PR allows containers to connect to Linux hosts by appending a special string "host-gateway" to --add-host e.g. "--add-host=host.docker.internal:host-gateway" which adds host.docker.internal DNS entry in /etc/hosts and maps it to host-gateway-ip This PR also add a daemon flag call host-gateway-ip which defaults to the default bridge IP Docker Desktop will need to set this field to the Host Proxy IP so DNS requests for host.docker.internal can be routed to VPNkit Addresses: https://github.com/docker/for-linux/issues/264 Signed-off-by: Arko Dasgupta --- cmd/dockerd/config.go | 1 + daemon/config/config.go | 8 ++-- daemon/container_operations.go | 10 +++++ daemon/daemon_unix.go | 13 +++++++ daemon/network/constants.go | 8 ++++ integration/container/daemon_linux_test.go | 44 ++++++++++++++++++++++ integration/internal/container/ops.go | 8 ++++ opts/hosts.go | 8 +++- 8 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 daemon/network/constants.go diff --git a/cmd/dockerd/config.go b/cmd/dockerd/config.go index f4b48a4097..f75ea3c38e 100644 --- a/cmd/dockerd/config.go +++ b/cmd/dockerd/config.go @@ -64,6 +64,7 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) error { flags.Var(opts.NewListOptsRef(&conf.DNS, opts.ValidateIPAddress), "dns", "DNS server to use") flags.Var(opts.NewNamedListOptsRef("dns-opts", &conf.DNSOptions, nil), "dns-opt", "DNS options to use") flags.Var(opts.NewListOptsRef(&conf.DNSSearch, opts.ValidateDNSSearch), "dns-search", "DNS search domains to use") + flags.Var(opts.NewIPOpt(&conf.HostGatewayIP, ""), "host-gateway-ip", "IP address that the special 'host-gateway' string in --add-host resolves to. Defaults to the IP address of the default bridge") flags.Var(opts.NewNamedListOptsRef("labels", &conf.Labels, opts.ValidateLabel), "label", "Set key=value labels to the daemon") flags.StringVar(&conf.LogConfig.Type, "log-driver", "json-file", "Default driver for container logs") flags.Var(opts.NewNamedMapOpts("log-opts", conf.LogConfig.Config, nil), "log-opt", "Default log driver options for containers") diff --git a/daemon/config/config.go b/daemon/config/config.go index 0fbd81f021..247ac41cb9 100644 --- a/daemon/config/config.go +++ b/daemon/config/config.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "net" "os" "reflect" "strings" @@ -115,9 +116,10 @@ type CommonTLSOptions struct { // DNSConfig defines the DNS configurations. type DNSConfig struct { - DNS []string `json:"dns,omitempty"` - DNSOptions []string `json:"dns-opts,omitempty"` - DNSSearch []string `json:"dns-search,omitempty"` + DNS []string `json:"dns,omitempty"` + DNSOptions []string `json:"dns-opts,omitempty"` + DNSSearch []string `json:"dns-search,omitempty"` + HostGatewayIP net.IP `json:"host-gateway-ip,omitempty"` } // CommonConfig defines the configuration of a docker daemon which is diff --git a/daemon/container_operations.go b/daemon/container_operations.go index 205c090501..d488d09f84 100644 --- a/daemon/container_operations.go +++ b/daemon/container_operations.go @@ -115,6 +115,16 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container) ([]lib return nil, err } parts := strings.SplitN(extraHost, ":", 2) + // If the IP Address is a string called "host-gateway", replace this + // value with the IP address stored in the daemon level HostGatewayIP + // config variable + if parts[1] == network.HostGatewayName { + gateway := daemon.configStore.HostGatewayIP.String() + if gateway == "" { + return nil, fmt.Errorf("unable to derive the IP value for host-gateway") + } + parts[1] = gateway + } sboxOptions = append(sboxOptions, libnetwork.OptionExtraHost(parts[0], parts[1])) } diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index f2d1fc932f..9f1422295d 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -925,6 +925,19 @@ func (daemon *Daemon) initNetworkController(config *config.Config, activeSandbox removeDefaultBridgeInterface() } + // Set HostGatewayIP to the default bridge's IP if it is empty + if daemon.configStore.HostGatewayIP == nil && controller != nil { + if n, err := controller.NetworkByName("bridge"); err == nil { + v4Info, v6Info := n.Info().IpamInfo() + var gateway net.IP + if len(v4Info) > 0 { + gateway = v4Info[0].Gateway.IP + } else if len(v6Info) > 0 { + gateway = v6Info[0].Gateway.IP + } + daemon.configStore.HostGatewayIP = gateway + } + } return controller, nil } diff --git a/daemon/network/constants.go b/daemon/network/constants.go new file mode 100644 index 0000000000..e7d97018fd --- /dev/null +++ b/daemon/network/constants.go @@ -0,0 +1,8 @@ +package network + +const ( + // HostGatewayName is the string value that can be passed + // to the IPAddr section in --add-host that is replaced by + // the value of HostGatewayIP daemon config value + HostGatewayName = "host-gateway" +) diff --git a/integration/container/daemon_linux_test.go b/integration/container/daemon_linux_test.go index 39f056fabe..5e260a495d 100644 --- a/integration/container/daemon_linux_test.go +++ b/integration/container/daemon_linux_test.go @@ -120,3 +120,47 @@ func TestDaemonRestartIpcMode(t *testing.T) { assert.NilError(t, err) assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "shareable")) } + +// TestDaemonHostGatewayIP verifies that when a magic string "host-gateway" is passed +// to ExtraHosts (--add-host) instead of an IP address, its value is set to +// 1. Daemon config flag value specified by host-gateway-ip or +// 2. IP of the default bridge network +// and is added to the /etc/hosts file +func TestDaemonHostGatewayIP(t *testing.T) { + skip.If(t, testEnv.IsRemoteDaemon) + skip.If(t, testEnv.DaemonInfo.OSType == "windows") + t.Parallel() + + // Verify the IP in /etc/hosts is same as host-gateway-ip + d := daemon.New(t) + // Verify the IP in /etc/hosts is same as the default bridge's IP + d.StartWithBusybox(t) + c := d.NewClientT(t) + ctx := context.Background() + cID := container.Run(ctx, t, c, + container.WithExtraHost("host.docker.internal:host-gateway"), + ) + res, err := container.Exec(ctx, c, cID, []string{"cat", "/etc/hosts"}) + assert.NilError(t, err) + assert.Assert(t, is.Len(res.Stderr(), 0)) + assert.Equal(t, 0, res.ExitCode) + inspect, err := c.NetworkInspect(ctx, "bridge", types.NetworkInspectOptions{}) + assert.NilError(t, err) + assert.Check(t, is.Contains(res.Stdout(), inspect.IPAM.Config[0].Gateway)) + c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) + d.Stop(t) + + // Verify the IP in /etc/hosts is same as host-gateway-ip + d.StartWithBusybox(t, "--host-gateway-ip=6.7.8.9") + cID = container.Run(ctx, t, c, + container.WithExtraHost("host.docker.internal:host-gateway"), + ) + res, err = container.Exec(ctx, c, cID, []string{"cat", "/etc/hosts"}) + assert.NilError(t, err) + assert.Assert(t, is.Len(res.Stderr(), 0)) + assert.Equal(t, 0, res.ExitCode) + assert.Check(t, is.Contains(res.Stdout(), "6.7.8.9")) + c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) + d.Stop(t) + +} diff --git a/integration/internal/container/ops.go b/integration/internal/container/ops.go index b9ba8c4e85..c829ad1717 100644 --- a/integration/internal/container/ops.go +++ b/integration/internal/container/ops.go @@ -180,3 +180,11 @@ func WithCgroupnsMode(mode string) func(*TestContainerConfig) { c.HostConfig.CgroupnsMode = containertypes.CgroupnsMode(mode) } } + +// WithExtraHost sets the user defined IP:Host mappings in the container's +// /etc/hosts file +func WithExtraHost(extraHost string) func(*TestContainerConfig) { + return func(c *TestContainerConfig) { + c.HostConfig.ExtraHosts = append(c.HostConfig.ExtraHosts, extraHost) + } +} diff --git a/opts/hosts.go b/opts/hosts.go index a6f2662df0..41caae3072 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/docker/docker/daemon/network" "github.com/docker/docker/pkg/homedir" ) @@ -169,8 +170,11 @@ func ValidateExtraHost(val string) (string, error) { if len(arr) != 2 || len(arr[0]) == 0 { return "", fmt.Errorf("bad format for add-host: %q", val) } - if _, err := ValidateIPAddress(arr[1]); err != nil { - return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + // Skip IPaddr validation for special "host-gateway" string + if arr[1] != network.HostGatewayName { + if _, err := ValidateIPAddress(arr[1]); err != nil { + return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + } } return val, nil }