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 <arko.dasgupta@docker.com>
This commit is contained in:
parent
de5a67156b
commit
92e809a680
8 changed files with 95 additions and 5 deletions
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]))
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
8
daemon/network/constants.go
Normal file
8
daemon/network/constants.go
Normal file
|
@ -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"
|
||||
)
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue