Repository: moby/moby (language: go)

Issue:
docker buildx fails to validate "--add-host=host.docker.internal:host-gateway"
### Description

When building an image with `docker buildx` on Linux and adding the `--add-host=host.docker.internal:host-gateway` option, it fails because the validation of the `host-gateway` is not an IP address, as required by https://github.com/moby/moby/blob/a263241ea16f1b018fc1c8f5c6564d84f1153543/builder/builder-next/builder.go#L562-L564

`docker buildx` output:

```
$ docker build --add-host=host.docker.internal:host-gateway .

[+] Building 0.0s (0/0)
ERROR: invalid host host.docker.internal:host-gateway
```

`docker build` is able to build the image correctly. In fact, it checks for the `host-gateway` special case: https://github.com/moby/moby/blob/f1dd6bf84e28930e1ccd903361f9284fb22d3b8a/opts/hosts.go#L180-L184

`docker build` output:

```
$ docker build --add-host=host.docker.internal:host-gateway --tag whatever . 

Sending build context to Docker daemon  1.38MB
Step 1/1 : FROM ubuntu:22.04
22.04: Pulling from library/ubuntu
e96e057aae67: Pull complete
Digest: sha256:4b1d0c4a2d2aaf63b37111f34eb9fa89fa1bf53dd6e4ca954d47caebca4005c2
Status: Downloaded newer image for ubuntu:22.04
 ---> a8780b506fa4
Successfully built a8780b506fa4
```

### Reproduce

1. Create a `Dockerfile` with the following contents:

```dockerfile
FROM ubuntu:22.04
```

2. Run `docker buildx --add-host=host.docker.internal:host-gateway .`

### Expected behavior

- `docker buildx` should be able to build the image
- The `host.docker.internal` name should point to the address of the machine running the container

### docker version

```bash
Client: Docker Engine - Community
 Version:           20.10.21
 API version:       1.41
 Go version:        go1.18.7
 Git commit:        baeda1f
 Built:             Tue Oct 25 18:02:28 2022
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.21
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.18.7
  Git commit:       3056208
  Built:            Tue Oct 25 18:00:19 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.9
  GitCommit:        1c90a442489720eec95342e1789ee8a5e1b9536f
 runc:
  Version:          1.1.4
  GitCommit:        v1.1.4-0-g5fd4c4d
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```


### docker info

```bash
Client:
 Context:    default
 Debug Mode: false
 Plugins:
  app: Docker App (Docker Inc., v0.9.1-beta3)
  buildx: Docker Buildx (Docker Inc., v0.9.1-docker)

Server:
 Containers: 14
  Running: 1
  Paused: 0
  Stopped: 13
 Images: 549
 Server Version: 20.10.21
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runtime.v1.linux runc io.containerd.runc.v2
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 1c90a442489720eec95342e1789ee8a5e1b9536f
 runc version: v1.1.4-0-g5fd4c4d
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: default
  cgroupns
 Kernel Version: 5.18.16-amd64
 Operating System: Debian GNU/Linux
 OSType: linux
 Architecture: x86_64
 CPUs: 48
 Total Memory: 177.1GiB
 Name: [redacted]
 ID: [redacted]
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: false
```


### Additional Info

_No response_


Discussion / hints:
/cc @crazy-max
Yes, Buildx checks for a valid IP address: https://github.com/docker/buildx/blob/23cabd67fb1c5e7ba2e3765e91bd5cc0f0311337/build/utils.go#L41-L56 and BuildKit as well: https://github.com/moby/buildkit/blob/7804e2c7fc09b4a536934f5607698bc3255cf0ed/frontend/dockerfile/builder/build.go#L751-L775.

BuildKit does not depend on libnetwork, and can't see overlay networks that are internal to Docker.

OTOH the [`docker` driver](https://docs.docker.com/build/building/drivers/docker/) (BuildKit in the docker engine) uses the libnetwork controller as sandbox netmode for its executor: https://github.com/moby/moby/blob/3df160a410b33b3253c7aff814a85322a0883274/builder/builder-next/executor_unix.go#L31

@tonistiigi I wonder if could have a special case for this driver: https://github.com/moby/moby/blob/3df160a410b33b3253c7aff814a85322a0883274/builder/builder-next/builder.go#L553-L568

@ferrarimarco What's your use case btw? You want to access a service on your host? If that's the case you can create a [container builder](https://docs.docker.com/build/building/drivers/docker-container/) with network host access:

```console
$ docker buildx create --use --bootstrap \
  --driver docker-container \
  --name mybuilder \
  --driver-opt network=host
```

@jedevc Maybe we should add an example for this use case in https://docs.docker.com/build/building/drivers/docker-container/?
@crazy-max we could do the conversion to IP address before it reaches buildkit I guess?
@thaJeztah Looks like the executor already carries the `DNSConfig`: https://github.com/moby/moby/blob/3df160a410b33b3253c7aff814a85322a0883274/builder/builder-next/executor_unix.go#L54-L63

So we could probably read the gateway IP address before executing the build request.

Fix patch:
diff --git a/builder/builder-next/builder.go b/builder/builder-next/builder.go
index a88e8b5751550..d770a2d7fdc15 100644
--- a/builder/builder-next/builder.go
+++ b/builder/builder-next/builder.go
@@ -18,6 +18,7 @@ import (
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/daemon/images"
 	"github.com/docker/docker/libnetwork"
+	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/streamformatter"
 	"github.com/docker/go-units"
@@ -81,6 +82,7 @@ type Opt struct {
 // Builder can build using BuildKit backend
 type Builder struct {
 	controller     *control.Controller
+	dnsconfig      config.DNSConfig
 	reqBodyHandler *reqBodyHandler
 
 	mu   sync.Mutex
@@ -97,6 +99,7 @@ func New(opt Opt) (*Builder, error) {
 	}
 	b := &Builder{
 		controller:     c,
+		dnsconfig:      opt.DNSConfig,
 		reqBodyHandler: reqHandler,
 		jobs:           map[string]*buildJob{},
 	}
@@ -311,7 +314,7 @@ func (b *Builder) Build(ctx context.Context, opt backend.BuildConfig) (*builder.
 		return nil, errors.Errorf("network mode %q not supported by buildkit", opt.Options.NetworkMode)
 	}
 
-	extraHosts, err := toBuildkitExtraHosts(opt.Options.ExtraHosts)
+	extraHosts, err := toBuildkitExtraHosts(opt.Options.ExtraHosts, b.dnsconfig.HostGatewayIP)
 	if err != nil {
 		return nil, err
 	}
@@ -551,18 +554,28 @@ func (j *buildJob) SetUpload(ctx context.Context, rc io.ReadCloser) error {
 }
 
 // toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
-func toBuildkitExtraHosts(inp []string) (string, error) {
+func toBuildkitExtraHosts(inp []string, hostGatewayIP net.IP) (string, error) {
 	if len(inp) == 0 {
 		return "", nil
 	}
 	hosts := make([]string, 0, len(inp))
 	for _, h := range inp {
-		parts := strings.Split(h, ":")
-
-		if len(parts) != 2 || parts[0] == "" || net.ParseIP(parts[1]) == nil {
+		host, ip, ok := strings.Cut(h, ":")
+		if !ok || host == "" || ip == "" {
 			return "", errors.Errorf("invalid host %s", h)
 		}
-		hosts = append(hosts, parts[0]+"="+parts[1])
+		// If the IP Address is a "host-gateway", replace this value with the
+		// IP address stored in the daemon level HostGatewayIP config variable.
+		if ip == opts.HostGatewayName {
+			gateway := hostGatewayIP.String()
+			if gateway == "" {
+				return "", fmt.Errorf("unable to derive the IP value for host-gateway")
+			}
+			ip = gateway
+		} else if net.ParseIP(ip) == nil {
+			return "", fmt.Errorf("invalid host %s", h)
+		}
+		hosts = append(hosts, host+"="+ip)
 	}
 	return strings.Join(hosts, ","), nil
 }


Test patch: