diff options
21 files changed, 777 insertions, 275 deletions
diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md index 5793ecae5..406dfcd89 100644 --- a/docs/source/markdown/podman-build.1.md +++ b/docs/source/markdown/podman-build.1.md @@ -439,9 +439,9 @@ with a new set of cached layers. #### **--no-hosts** Do not create _/etc/hosts_ for the container. - -By default, Buildah manages _/etc/hosts_, adding the container's own IP address. -**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. Conflicts with the --add-host option. +By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. +This option conflicts with **--add-host**. #### **--os**=*string* diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index ea31428fd..15ae28dc3 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -743,9 +743,9 @@ Disable any defined healthchecks for container. #### **--no-hosts** -Do not create /etc/hosts for the container. -By default, Podman will manage /etc/hosts, adding the container's own IP address and any hosts from **--add-host**. -#### **--no-hosts** disables this, and the image's **/etc/host** will be preserved unmodified. +Do not create _/etc/hosts_ for the container. +By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. This option conflicts with **--add-host**. #### **--oom-kill-disable** diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md index 471c17f91..8b56d109a 100644 --- a/docs/source/markdown/podman-play-kube.1.md +++ b/docs/source/markdown/podman-play-kube.1.md @@ -216,7 +216,10 @@ Valid _mode_ values are: #### **--no-hosts** -Do not create /etc/hosts within the pod's containers, instead use the version from the image +Do not create /etc/hosts for the pod. +By default, Podman will manage /etc/hosts, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's **/etc/host** will be preserved unmodified. +This option conflicts with host added in the Kubernetes YAML. #### **--quiet**, **-q** diff --git a/docs/source/markdown/podman-pod-create.1.md b/docs/source/markdown/podman-pod-create.1.md index f24458dc6..403a317ea 100644 --- a/docs/source/markdown/podman-pod-create.1.md +++ b/docs/source/markdown/podman-pod-create.1.md @@ -17,7 +17,11 @@ containers added to it. The pod id is printed to STDOUT. You can then use #### **--add-host**=_host_:_ip_ -Add a host to the /etc/hosts file shared between all containers in the pod. +Add a custom host-to-IP mapping (host:ip) + +Add a line to /etc/hosts. The format is hostname:ip. The **--add-host** +option can be set multiple times. +The /etc/hosts file is shared between all containers in the pod. #### **--cgroup-parent**=*path* @@ -187,7 +191,10 @@ NOTE: A container will only have access to aliases on the first network that it #### **--no-hosts** -Disable creation of /etc/hosts for the pod. +Do not create _/etc/hosts_ for the pod. +By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. +This option conflicts with **--add-host**. #### **--pid**=*pid* diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 52143c934..578acf379 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -85,8 +85,10 @@ and specified with a _tag_. ## OPTIONS #### **--add-host**=_host_:_ip_ -Add a line to container's _/etc/hosts_ for custom host-to-IP mapping. -This option can be set multiple times. +Add a custom host-to-IP mapping (host:ip) + +Add a line to /etc/hosts. The format is hostname:ip. The **--add-host** +option can be set multiple times. #### **--annotation**=_key_=_value_ @@ -768,9 +770,8 @@ Disable any defined healthchecks for container. #### **--no-hosts** Do not create _/etc/hosts_ for the container. - By default, Podman will manage _/etc/hosts_, adding the container's own IP address and any hosts from **--add-host**. -#### **--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. +**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. This option conflicts with **--add-host**. #### **--oom-kill-disable** diff --git a/libpod/container.go b/libpod/container.go index bc3cab439..6e2b7f528 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -628,6 +628,15 @@ func (c *Container) RuntimeName() string { // Hostname gets the container's hostname func (c *Container) Hostname() string { + if c.config.UTSNsCtr != "" { + utsNsCtr, err := c.runtime.GetContainer(c.config.UTSNsCtr) + if err != nil { + // should we return an error here? + logrus.Errorf("unable to lookup uts namespace for container %s: %v", c.ID(), err) + return "" + } + return utsNsCtr.Hostname() + } if c.config.Spec.Hostname != "" { return c.config.Spec.Hostname } diff --git a/libpod/container_internal.go b/libpod/container_internal.go index b051b7f2d..6c0d51df3 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1,7 +1,6 @@ package libpod import ( - "bufio" "bytes" "context" "fmt" @@ -17,8 +16,10 @@ import ( "github.com/containers/buildah/copier" "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" + "github.com/containers/common/libnetwork/etchosts" "github.com/containers/common/pkg/cgroups" "github.com/containers/common/pkg/chown" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/ctime" @@ -31,6 +32,7 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/lockfile" "github.com/containers/storage/pkg/mount" "github.com/coreos/go-systemd/v22/daemon" securejoin "github.com/cyphar/filepath-securejoin" @@ -1006,17 +1008,14 @@ func (c *Container) completeNetworkSetup() error { } } // check if we have a bindmount for /etc/hosts - if hostsBindMount, ok := state.BindMounts["/etc/hosts"]; ok && len(c.cniHosts()) > 0 { - ctrHostPath := filepath.Join(c.state.RunDir, "hosts") - if hostsBindMount == ctrHostPath { - // read the existing hosts - b, err := ioutil.ReadFile(hostsBindMount) - if err != nil { - return err - } - if err := ioutil.WriteFile(hostsBindMount, append(b, []byte(c.cniHosts())...), 0644); err != nil { - return err - } + if hostsBindMount, ok := state.BindMounts[config.DefaultHostsFile]; ok { + entries, err := c.getHostsEntries() + if err != nil { + return err + } + // add new container ips to the hosts file + if err := etchosts.Add(hostsBindMount, entries); err != nil { + return err } } @@ -1041,18 +1040,6 @@ func (c *Container) completeNetworkSetup() error { return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644) } -func (c *Container) cniHosts() string { - var hosts string - for _, status := range c.getNetworkStatus() { - for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Subnets { - hosts += fmt.Sprintf("%s\t%s %s\n", netAddress.IPNet.IP.String(), c.Hostname(), c.config.Name) - } - } - } - return hosts -} - // Initialize a container, creating it in the runtime func (c *Container) init(ctx context.Context, retainRetries bool) error { // Unconditionally remove conmon temporary files. @@ -1894,6 +1881,24 @@ func (c *Container) cleanup(ctx context.Context) error { lastError = errors.Wrapf(err, "error removing container %s network", c.ID()) } + // cleanup host entry if it is shared + if c.config.NetNsCtr != "" { + if hoststFile, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + if _, err := os.Stat(hoststFile); err == nil { + // we cannot use the dependency container lock due ABBA deadlocks + if lock, err := lockfile.GetLockfile(hoststFile); err == nil { + lock.Lock() + // make sure to ignore ENOENT error in case the netns container was cleanup before this one + if err := etchosts.Remove(hoststFile, getLocalhostHostEntry(c)); err != nil && !errors.Is(err, os.ErrNotExist) { + // this error is not fatal we still want to do proper cleanup + logrus.Errorf("failed to remove hosts entry from the netns containers /etc/hosts: %v", err) + } + lock.Unlock() + } + } + } + } + // Remove the container from the runtime, if necessary. // Do this *before* unmounting storage - some runtimes (e.g. Kata) // apparently object to having storage removed while the container still @@ -2030,33 +2035,6 @@ func (c *Container) writeStringToStaticDir(filename, contents string) (string, e return destFileName, nil } -// appendStringToRunDir appends the provided string to the runtimedir file -func (c *Container) appendStringToRunDir(destFile, output string) (string, error) { - destFileName := filepath.Join(c.state.RunDir, destFile) - - f, err := os.OpenFile(destFileName, os.O_APPEND|os.O_RDWR, 0600) - if err != nil { - return "", err - } - defer f.Close() - - compareStr := strings.TrimRight(output, "\n") - scanner := bufio.NewScanner(f) - scanner.Split(bufio.ScanLines) - - for scanner.Scan() { - if strings.Compare(scanner.Text(), compareStr) == 0 { - return filepath.Join(c.state.RunDir, destFile), nil - } - } - - if _, err := f.WriteString(output); err != nil { - return "", errors.Wrapf(err, "unable to write %s", destFileName) - } - - return filepath.Join(c.state.RunDir, destFile), nil -} - // saveSpec saves the OCI spec to disk, replacing any existing specs for the container func (c *Container) saveSpec(spec *spec.Spec) error { // If the OCI spec already exists, we need to replace it diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 6ffd99649..9d0c8ff8c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -28,6 +28,7 @@ import ( "github.com/containers/buildah/pkg/chrootuser" "github.com/containers/buildah/pkg/overlay" butil "github.com/containers/buildah/util" + "github.com/containers/common/libnetwork/etchosts" "github.com/containers/common/libnetwork/types" "github.com/containers/common/pkg/apparmor" "github.com/containers/common/pkg/cgroups" @@ -49,6 +50,7 @@ import ( "github.com/containers/podman/v4/version" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/lockfile" securejoin "github.com/cyphar/filepath-securejoin" runcuser "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -2079,23 +2081,25 @@ func (c *Container) makeBindMounts() error { // check if dependency container has an /etc/hosts file. // It may not have one, so only use it if it does. - hostsPath, exists := bindMounts["/etc/hosts"] + hostsPath, exists := bindMounts[config.DefaultHostsFile] if !c.config.UseImageHosts && exists { - depCtr.lock.Lock() - // generate a hosts file for the dependency container, - // based on either its old hosts file, or the default, - // and add the relevant information from the new container (hosts and IP) - hostsPath, err = depCtr.appendHosts(hostsPath, c) + // we cannot use the dependency container lock due ABBA deadlocks in cleanup() + lock, err := lockfile.GetLockfile(hostsPath) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + lock.Lock() + // add the newly added container to the hosts file + // we always use 127.0.0.1 as ip since they have the same netns + err = etchosts.Add(hostsPath, getLocalhostHostEntry(c)) + lock.Unlock() if err != nil { - depCtr.lock.Unlock() return errors.Wrapf(err, "error creating hosts file for container %s which depends on container %s", c.ID(), depCtr.ID()) } - depCtr.lock.Unlock() // finally, save it in the new container - err := c.mountIntoRootDirs("/etc/hosts", hostsPath) - + err = c.mountIntoRootDirs(config.DefaultHostsFile, hostsPath) if err != nil { return errors.Wrapf(err, "error assigning mounts to container %s", c.ID()) } @@ -2123,7 +2127,7 @@ func (c *Container) makeBindMounts() error { } if !c.config.UseImageHosts { - if err := c.updateHosts("/etc/hosts"); err != nil { + if err := c.createHosts(); err != nil { return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) } } @@ -2142,7 +2146,7 @@ func (c *Container) makeBindMounts() error { } } else { if !c.config.UseImageHosts && c.state.BindMounts["/etc/hosts"] == "" { - if err := c.updateHosts("/etc/hosts"); err != nil { + if err := c.createHosts(); err != nil { return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) } } @@ -2528,169 +2532,79 @@ func (c *Container) removeNameserver(ips []string) error { return nil } -// updateHosts updates the container's hosts file -func (c *Container) updateHosts(path string) error { - var hosts string - - orig, err := ioutil.ReadFile(path) - if err != nil { - // Ignore if the path does not exist - if !os.IsNotExist(err) { - return err - } - } else { - hosts = string(orig) - } - - hosts += c.getHosts() - hosts = c.appendLocalhost(hosts) - - newHosts, err := c.writeStringToRundir("hosts", hosts) - if err != nil { - return err - } - - if err = c.mountIntoRootDirs("/etc/hosts", newHosts); err != nil { - return err - } - - return nil +func getLocalhostHostEntry(c *Container) etchosts.HostEntries { + return etchosts.HostEntries{{IP: "127.0.0.1", Names: []string{c.Hostname(), c.config.Name}}} } -// based on networking mode we may want to append the localhost -// if there isn't any record for it and also this should happen -// in slirp4netns and similar network modes. -func (c *Container) appendLocalhost(hosts string) string { - if !strings.Contains(hosts, "localhost") && - !c.config.NetMode.IsHost() { - hosts += "127.0.0.1\tlocalhost\n::1\tlocalhost\n" - } - - return hosts -} - -// appendHosts appends a container's config and state pertaining to hosts to a container's -// local hosts file. netCtr is the container from which the netNS information is -// taken. -// path is the basis of the hosts file, into which netCtr's netNS information will be appended. -// FIXME. Path should be used by this function,but I am not sure what is correct; remove //lint -// once this is fixed -func (c *Container) appendHosts(path string, netCtr *Container) (string, error) { //nolint - return c.appendStringToRunDir("hosts", netCtr.getHosts()) -} - -// getHosts finds the pertinent information for a container's host file in its config and state -// and returns a string in a format that can be written to the host file -func (c *Container) getHosts() string { - var hosts string - - if len(c.config.HostAdd) > 0 { - for _, host := range c.config.HostAdd { - // the host format has already been verified at this point - fields := strings.SplitN(host, ":", 2) - hosts += fmt.Sprintf("%s %s\n", fields[1], fields[0]) - } - } - - hosts += c.cniHosts() - - // Add hostname for slirp4netns - if c.Hostname() != "" { - if c.config.NetMode.IsSlirp4netns() { - // When using slirp4netns, the interface gets a static IP - slirp4netnsIP, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) - if err != nil { - logrus.Warnf("Failed to determine slirp4netnsIP: %v", err.Error()) - } else { - hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP.String(), c.Hostname(), c.config.Name) - } +// getHostsEntries returns the container ip host entries for the correct netmode +func (c *Container) getHostsEntries() (etchosts.HostEntries, error) { + var entries etchosts.HostEntries + names := []string{c.Hostname(), c.config.Name} + switch { + case c.config.NetMode.IsBridge(): + entries = etchosts.GetNetworkHostEntries(c.state.NetworkStatus, names...) + case c.config.NetMode.IsSlirp4netns(): + ip, err := GetSlirp4netnsIP(c.slirp4netnsSubnet) + if err != nil { + return nil, err } - - // Do we have a network namespace? - netNone := false - if c.config.NetNsCtr == "" && !c.config.CreateNetNS { + entries = etchosts.HostEntries{{IP: ip.String(), Names: names}} + default: + // check for net=none + if !c.config.CreateNetNS { for _, ns := range c.config.Spec.Linux.Namespaces { if ns.Type == spec.NetworkNamespace { if ns.Path == "" { - netNone = true + entries = etchosts.HostEntries{{IP: "127.0.0.1", Names: names}} } break } } } - // If we are net=none (have a network namespace, but not connected to - // anything) add the container's name and hostname to localhost. - if netNone { - hosts += fmt.Sprintf("127.0.0.1 %s %s\n", c.Hostname(), c.config.Name) - } } + return entries, nil +} - // Add gateway entry if we are not in a machine. If we use podman machine - // the gvproxy dns server will take care of host.containers.internal. - // https://github.com/containers/gvisor-tap-vsock/commit/1108ea45162281046d239047a6db9bc187e64b08 - if !c.runtime.config.Engine.MachineEnabled { - var depCtr *Container - netStatus := c.getNetworkStatus() - if c.config.NetNsCtr != "" { - // ignoring the error because there isn't anything to do - depCtr, _ = c.getRootNetNsDepCtr() - } else if len(netStatus) != 0 { - depCtr = c +func (c *Container) createHosts() error { + var containerIPsEntries etchosts.HostEntries + var err error + // if we configure the netns after the container create we should not add + // the hosts here since we have no information about the actual ips + // instead we will add them in c.completeNetworkSetup() + if !c.config.PostConfigureNetNS { + containerIPsEntries, err = c.getHostsEntries() + if err != nil { + return fmt.Errorf("failed to get container ip host entries: %w", err) } + } + baseHostFile, err := etchosts.GetBaseHostFile(c.runtime.config.Containers.BaseHostsFile, c.state.Mountpoint) + if err != nil { + return err + } - // getLocalIP returns the non loopback local IP of the host - getLocalIP := func() string { - addrs, err := net.InterfaceAddrs() - if err != nil { - return "" - } - for _, address := range addrs { - // check the address type and if it is not a loopback the display it - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - return ipnet.IP.String() - } - } - } - return "" - } + targetFile := filepath.Join(c.state.RunDir, "hosts") + err = etchosts.New(&etchosts.Params{ + BaseFile: baseHostFile, + ExtraHosts: c.config.HostAdd, + ContainerIPs: containerIPsEntries, + HostContainersInternalIP: etchosts.GetHostContainersInternalIP(c.runtime.config, c.state.NetworkStatus, c.runtime.network), + TargetFile: targetFile, + }) + if err != nil { + return err + } - if depCtr != nil { - host := "" - outer: - for net, status := range depCtr.getNetworkStatus() { - network, err := c.runtime.network.NetworkInspect(net) - // only add the host entry for bridge networks - // ip/macvlan gateway is normally not on the host - if err != nil || network.Driver != types.BridgeNetworkDriver { - continue - } - for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Subnets { - if netAddress.Gateway != nil { - host = fmt.Sprintf("%s host.containers.internal\n", netAddress.Gateway.String()) - break outer - } - } - } - } - // if no bridge gw was found try to use a local ip - if host == "" { - if ip := getLocalIP(); ip != "" { - host = fmt.Sprintf("%s\t%s\n", ip, "host.containers.internal") - } - } - hosts += host - } else if c.config.NetMode.IsSlirp4netns() { - if ip := getLocalIP(); ip != "" { - hosts += fmt.Sprintf("%s\t%s\n", ip, "host.containers.internal") - } - } else { - logrus.Debug("Network configuration does not support host.containers.internal address") - } + if err := os.Chown(targetFile, c.RootUID(), c.RootGID()); err != nil { + return err + } + if err := label.Relabel(targetFile, c.MountLabel(), false); err != nil { + return err } - return hosts + if err = c.mountIntoRootDirs(config.DefaultHostsFile, targetFile); err != nil { + return err + } + return nil } // generateGroupEntry generates an entry or entries into /etc/group as diff --git a/libpod/container_internal_linux_test.go b/libpod/container_internal_linux_test.go index a7dd0fc31..2c1193445 100644 --- a/libpod/container_internal_linux_test.go +++ b/libpod/container_internal_linux_test.go @@ -8,7 +8,6 @@ import ( "os" "testing" - "github.com/containers/podman/v4/pkg/namespaces" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/stretchr/testify/assert" ) @@ -70,30 +69,3 @@ func TestGenerateUserGroupEntry(t *testing.T) { } assert.Equal(t, group, "567:x:567:567\n") } - -func TestAppendLocalhost(t *testing.T) { - { - c := Container{ - config: &ContainerConfig{ - ContainerNetworkConfig: ContainerNetworkConfig{ - NetMode: namespaces.NetworkMode("slirp4netns"), - }, - }, - } - - assert.Equal(t, "127.0.0.1\tlocalhost\n::1\tlocalhost\n", c.appendLocalhost("")) - assert.Equal(t, "127.0.0.1\tlocalhost", c.appendLocalhost("127.0.0.1\tlocalhost")) - } - { - c := Container{ - config: &ContainerConfig{ - ContainerNetworkConfig: ContainerNetworkConfig{ - NetMode: namespaces.NetworkMode("host"), - }, - }, - } - - assert.Equal(t, "", c.appendLocalhost("")) - assert.Equal(t, "127.0.0.1\tlocalhost", c.appendLocalhost("127.0.0.1\tlocalhost")) - } -} diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 71e29f18f..41beaf41d 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -20,7 +20,9 @@ import ( "time" "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/common/libnetwork/etchosts" "github.com/containers/common/libnetwork/types" + "github.com/containers/common/pkg/config" "github.com/containers/common/pkg/netns" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/events" @@ -1282,15 +1284,35 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro for _, ip := range oldStatus.DNSServerIPs { stringIPs = append(stringIPs, ip.String()) } - if len(stringIPs) == 0 { - return nil + if len(stringIPs) > 0 { + logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs) + if err := c.removeNameserver(stringIPs); err != nil { + return err + } } - logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs) - if err := c.removeNameserver(stringIPs); err != nil { - return err + + // update /etc/hosts file + if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + // sync the names with c.getHostsEntries() + names := []string{c.Hostname(), c.config.Name} + rm := etchosts.GetNetworkHostEntries(map[string]types.StatusBlock{netName: oldStatus}, names...) + if len(rm) > 0 { + // make sure to lock this file to prevent concurrent writes when + // this is used a net dependency container + lock, err := lockfile.GetLockfile(file) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + logrus.Debugf("Remove /etc/hosts entries %v", rm) + lock.Lock() + err = etchosts.Remove(file, rm) + lock.Unlock() + if err != nil { + return err + } + } } } - return nil } @@ -1361,6 +1383,13 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe return errors.New("when adding aliases, results must be of length 1") } + // we need to get the old host entries before we add the new one to the status + // if we do not add do it here we will get the wrong existing entries which will throw of the logic + // we could also copy the map but this does not seem worth it + // sync the hostNames with c.getHostsEntries() + hostNames := []string{c.Hostname(), c.config.Name} + oldHostEntries := etchosts.GetNetworkHostEntries(networkStatus, hostNames...) + // update network status if networkStatus == nil { networkStatus = make(map[string]types.StatusBlock, 1) @@ -1394,12 +1423,31 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe } stringIPs = append(stringIPs, ip.String()) } - if len(stringIPs) == 0 { - return nil + if len(stringIPs) > 0 { + logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs) + if err := c.addNameserver(stringIPs); err != nil { + return err + } } - logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs) - if err := c.addNameserver(stringIPs); err != nil { - return err + + // update /etc/hosts file + if file, ok := c.state.BindMounts[config.DefaultHostsFile]; ok { + // make sure to lock this file to prevent concurrent writes when + // this is used a net dependency container + lock, err := lockfile.GetLockfile(file) + if err != nil { + return fmt.Errorf("failed to lock hosts file: %w", err) + } + new := etchosts.GetNetworkHostEntries(results, hostNames...) + logrus.Debugf("Add /etc/hosts entries %v", new) + // use special AddIfExists API to make sure we only add new entries if an old one exists + // see the AddIfExists() comment for more information + lock.Lock() + err = etchosts.AddIfExists(file, oldHostEntries, new) + lock.Unlock() + if err != nil { + return err + } } return nil diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 42b70e334..e06cd9a29 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -38,6 +38,13 @@ func (s *SpecGenerator) Validate() error { if len(s.PortMappings) > 0 || s.PublishExposedPorts { return errors.Wrap(define.ErrNetworkOnPodContainer, "published or exposed ports must be defined when the pod is created") } + if len(s.HostAdd) > 0 { + return errors.Wrap(define.ErrNetworkOnPodContainer, "extra host entries must be specified on the pod") + } + } + + if s.NetNS.IsContainer() && len(s.HostAdd) > 0 { + return errors.Wrap(ErrInvalidSpecConfig, "cannot set extra host entries when the container is joined to another containers network namespace") } // diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index db366b612..6373b949a 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -377,21 +377,19 @@ var _ = Describe("Podman pod create", func() { Expect(result.OutputToString()).To(ContainSubstring(infraID)) }) - It("podman run --add-host in pod", func() { - session := podmanTest.Podman([]string{"pod", "create"}) + It("podman run --add-host in pod should fail", func() { + session := podmanTest.Podman([]string{"pod", "create", "--add-host", "host1:127.0.0.1"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) podID := session.OutputToString() - // verify we can add a host to the infra's /etc/hosts - // N/B: Using alpine for ping, since BB ping throws - // permission denied error as of Fedora 33. - session = podmanTest.Podman([]string{"run", "--pod", podID, "--add-host", "foobar:127.0.0.1", ALPINE, "ping", "-c", "1", "foobar"}) + session = podmanTest.Podman([]string{"create", "--pod", podID, "--add-host", "foobar:127.0.0.1", ALPINE, "ping", "-c", "1", "foobar"}) session.WaitWithDefaultTimeout() - Expect(session).Should(Exit(0)) + Expect(session).Should(ExitWithError()) + Expect(session.ErrorToString()).To(ContainSubstring("extra host entries must be specified on the pod: network cannot be configured when it is shared with a pod")) - // verify we can see the other hosts of infra's /etc/hosts - session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "ping", "-c", "1", "foobar"}) + // verify we can see the pods hosts + session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "ping", "-c", "1", "host1"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) }) diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index a313e8d40..7561a2e85 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -78,8 +78,7 @@ var _ = Describe("Podman run dns", func() { session := podmanTest.Podman([]string{"run", "--add-host=foobar:1.1.1.1", "--add-host=foobaz:2001:db8::68", ALPINE, "cat", "/etc/hosts"}) session.WaitWithDefaultTimeout() Expect(session).Should(Exit(0)) - Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("1.1.1.1 foobar"))) - Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("2001:db8::68 foobaz"))) + Expect(session.OutputToStringArray()).To(ContainElements(HavePrefix("1.1.1.1\tfoobar"), HavePrefix("2001:db8::68\tfoobaz"))) }) It("podman run add hostname", func() { diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 696668e52..49c387227 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -608,6 +608,18 @@ EXPOSE 2004-2005/tcp`, ALPINE) Expect(ctr2).Should(Exit(0)) }) + It("podman run --net container: and --add-host should fail", func() { + ctrName := "ctrToJoin" + ctr1 := podmanTest.RunTopContainer(ctrName) + ctr1.WaitWithDefaultTimeout() + Expect(ctr1).Should(Exit(0)) + + ctr2 := podmanTest.Podman([]string{"run", "-d", "--net=container:" + ctrName, "--add-host", "host1:127.0.0.1", ALPINE, "true"}) + ctr2.WaitWithDefaultTimeout() + Expect(ctr2).Should(ExitWithError()) + Expect(ctr2.ErrorToString()).Should(ContainSubstring("cannot set extra host entries when the container is joined to another containers network namespace: invalid configuration")) + }) + It("podman run --net container: copies hosts and resolv", func() { ctrName := "ctr1" ctr1 := podmanTest.RunTopContainer(ctrName) diff --git a/test/system/030-run.bats b/test/system/030-run.bats index 087f74690..283c3aea9 100644 --- a/test/system/030-run.bats +++ b/test/system/030-run.bats @@ -786,7 +786,7 @@ EOF mv $hosts_tmp /etc/hosts assert "$status" = 0 \ "podman run without /etc/hosts file should work" - assert "$output" =~ "^1\.2\.3\.4 foo.com.*" \ + assert "$output" =~ "^1\.2\.3\.4[[:blank:]]foo\.com.*" \ "users can add hosts even without /etc/hosts" } diff --git a/test/system/200-pod.bats b/test/system/200-pod.bats index 56449dcad..ef4bf1a6c 100644 --- a/test/system/200-pod.bats +++ b/test/system/200-pod.bats @@ -250,7 +250,7 @@ EOF is "$output" ".*invalid config provided: cannot set hostname when joining the pod UTS namespace: invalid configuration" "--hostname should not be allowed in share UTS pod" run_podman run --rm --pod $pod_id $IMAGE cat /etc/hosts - is "$output" ".*$add_host_ip $add_host_n" "--add-host was added" + is "$output" ".*$add_host_ip[[:blank:]]$add_host_n" "--add-host was added" is "$output" ".* $hostname" "--hostname is in /etc/hosts" # ^^^^ this must be a tab, not a space diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index 108abdf23..0c3062a7e 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -134,14 +134,50 @@ load helpers done } +@test "podman pod manages /etc/hosts correctly" { + local pod_name=pod-$(random_string 10) + local infra_name=infra-$(random_string 10) + local con1_name=con1-$(random_string 10) + local con2_name=con2-$(random_string 10) + run_podman pod create --name $pod_name --infra-name $infra_name + pid="$output" + run_podman run --pod $pod_name --name $con1_name $IMAGE cat /etc/hosts + is "$output" ".*\s$pod_name $infra_name.*" "Pod hostname in /etc/hosts" + is "$output" ".*127.0.0.1\s$con1_name.*" "Container1 name in /etc/hosts" + # get the length of the hosts file + old_lines=${#lines[@]} + + # since the first container should be cleaned up now we should only see the + # new host entry and the old one should be removed (lines check) + run_podman run --pod $pod_name --name $con2_name $IMAGE cat /etc/hosts + is "$output" ".*\s$pod_name $infra_name.*" "Pod hostname in /etc/hosts" + is "$output" ".*127.0.0.1\s$con2_name.*" "Container2 name in /etc/hosts" + is "${#lines[@]}" "$old_lines" "Number of hosts lines is equal" + + run_podman run --pod $pod_name $IMAGE sh -c "hostname && cat /etc/hostname" + is "${lines[0]}" "$pod_name" "hostname is the pod hostname" + is "${lines[1]}" "$pod_name" "/etc/hostname contains correct pod hostname" + + run_podman pod rm $pod_name + is "$output" "$pid" "Only ID in output (no extra errors)" +} + @test "podman run with slirp4ns assigns correct addresses to /etc/hosts" { CIDR="$(random_rfc1918_subnet)" IP=$(hostname -I | cut -f 1 -d " ") local conname=con-$(random_string 10) run_podman run --rm --network slirp4netns:cidr="${CIDR}.0/24" \ --name $conname --hostname $conname $IMAGE cat /etc/hosts - is "$output" ".*${IP} host.containers.internal" "host.containers.internal should be the cidr+2 address" + is "$output" ".*${IP} host.containers.internal" "host.containers.internal should be host address" is "$output" ".*${CIDR}.100 $conname $conname" "$conname should be the cidr+100 address" + + if is_rootless; then + # check the slirp ip also works correct with userns + run_podman run --rm --userns keep-id --network slirp4netns:cidr="${CIDR}.0/24" \ + --name $conname --hostname $conname $IMAGE cat /etc/hosts + is "$output" ".*${IP} host.containers.internal" "host.containers.internal should be host address" + is "$output" ".*${CIDR}.100 $conname $conname" "$conname should be the cidr+100 address" + fi } @test "podman run with slirp4ns adds correct dns address to resolv.conf" { @@ -434,9 +470,17 @@ load helpers run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").Aliases}}" is "$output" "[${cid:0:12}]" "short container id in network aliases" + # check /etc/hosts for our entry + run_podman exec $cid cat /etc/hosts + is "$output" ".*$ip.*" "hosts contain expected ip" + run_podman network disconnect $netname $cid is "$output" "" "Output should be empty (no errors)" + # check /etc/hosts again, the entry should be gone now + run_podman exec $cid cat /etc/hosts + assert "$output" !~ "$ip" "IP ($ip) should no longer be in /etc/hosts" + # check that we cannot curl (timeout after 3 sec) run curl --max-time 3 -s $SERVER/index.txt assert $status -ne 0 \ @@ -452,13 +496,18 @@ load helpers # check that we have a new ip and mac # if the ip is still the same this whole test turns into a nop run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}" - assert "$output" != "$ip" \ + new_ip="$output" + assert "$new_ip" != "$ip" \ "IP address did not change after podman network disconnect/connect" run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}" assert "$output" != "$mac" \ "MAC address did not change after podman network disconnect/connect" + # check /etc/hosts for the new entry + run_podman exec $cid cat /etc/hosts + is "$output" ".*$new_ip.*" "hosts contain expected new ip" + # Disconnect/reconnect of a container *with no ports* should succeed quietly run_podman network disconnect $netname $background_cid is "$output" "" "disconnect of container with no open ports" @@ -629,4 +678,48 @@ EOF done } +@test "podman run CONTAINERS_CONF /etc/hosts options" { + skip_if_remote "CONTAINERS_CONF redirect does not work on remote" + + containersconf=$PODMAN_TMPDIR/containers.conf + basehost=$PODMAN_TMPDIR/host + + ip1="$(random_rfc1918_subnet).$((RANDOM % 256))" + name1=host1$(random_string) + ip2="$(random_rfc1918_subnet).$((RANDOM % 256))" + name2=host2$(random_string) + + cat >$basehost <<EOF +$ip1 $name1 +$ip2 $name2 #some comment +EOF + + containersinternal_ip="$(random_rfc1918_subnet).$((RANDOM % 256))" + cat >$containersconf <<EOF +[containers] + base_hosts_file = "$basehost" + host_containers_internal_ip = "$containersinternal_ip" +EOF + + ip3="$(random_rfc1918_subnet).$((RANDOM % 256))" + name3=host3$(random_string) + + CONTAINERS_CONF=$containersconf run_podman run --rm --add-host $name3:$ip3 $IMAGE cat /etc/hosts + is "$output" ".*$ip3[[:blank:]]$name3.*" "--add-host entry in /etc/host" + is "$output" ".*$ip1[[:blank:]]$name1.*" "first base entry in /etc/host" + is "$output" ".*$ip2[[:blank:]]$name2.*" "second base entry in /etc/host" + is "$output" ".*127.0.0.1[[:blank:]]localhost.*" "ipv4 localhost entry added" + is "$output" ".*::1[[:blank:]]localhost.*" "ipv6 localhost entry added" + is "$output" ".*$containersinternal_ip[[:blank:]]host\.containers\.internal.*" "host.containers.internal ip from config in /etc/host" + is "${#lines[@]}" "7" "expect 7 host entries in /etc/hosts" + + # now try again with container name and hostname == host entry name + # in this case podman should not add its own entry thus we only have 5 entries (-1 for the removed --add-host) + CONTAINERS_CONF=$containersconf run_podman run --rm --name $name1 --hostname $name1 $IMAGE cat /etc/hosts + is "$output" ".*$ip1[[:blank:]]$name1.*" "first base entry in /etc/host" + is "$output" ".*$ip2[[:blank:]]$name2.*" "second base entry in /etc/host" + is "$output" ".*$containersinternal_ip[[:blank:]]host\.containers\.internal.*" "host.containers.internal ip from config in /etc/host" + is "${#lines[@]}" "5" "expect 5 host entries in /etc/hosts" +} + # vim: filetype=sh diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go b/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go new file mode 100644 index 000000000..ce248a181 --- /dev/null +++ b/vendor/github.com/containers/common/libnetwork/etchosts/hosts.go @@ -0,0 +1,339 @@ +package etchosts + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/util" +) + +const ( + hostContainersInternal = "host.containers.internal" + localhost = "localhost" +) + +type HostEntries []HostEntry + +type HostEntry struct { + IP string + Names []string +} + +// Params for the New() function call +type Params struct { + // BaseFile is the file where we read entries from and add entries to + // the target hosts file. If the name is empty it will not read any entries. + BaseFile string + // ExtraHosts is a slice of entries in the "hostname:ip" format. + // Optional. + ExtraHosts []string + // ContainerIPs should contain the main container ipv4 and ipv6 if available + // with the container name and host name as names set. + // Optional. + ContainerIPs HostEntries + // HostContainersInternalIP is the IP for the host.containers.internal entry. + // Optional. + HostContainersInternalIP string + // TargetFile where the hosts are written to. + TargetFile string +} + +// New will create a new hosts file and write this to the target file. +// This function does not prevent any kind of concurrency problems, it is +// the callers responsibility to avoid concurrent writes to this file. +// The extraHosts are written first, then the hosts from the file baseFile and the +// containerIps. The container ip entry is only added when the name was not already +// added before. +func New(params *Params) error { + if err := new(params); err != nil { + return fmt.Errorf("failed to create new hosts file: %w", err) + } + return nil +} + +// Add adds the given entries to the hosts file, entries are only added if +// they are not already present. +// Add is not atomic because it will keep the current file inode. This is +// required to keep bind mounts for containers working. +func Add(file string, entries HostEntries) error { + if err := add(file, entries); err != nil { + return fmt.Errorf("failed to add entries to hosts file: %w", err) + } + return nil +} + +// AddIfExists will add the given entries only if one of the existsEntries +// is in the hosts file. This API is required for podman network connect. +// Since we want to add the same host name for each network ip we want to +// add duplicates and the normal Add() call prevents us from doing so. +// However since we also do not want to overwrite potential entries that +// were added by users manually we first have to check if there are the +// current expected entries in the file. Note that this will only check +// for one match not all. It will also only check that the ip and one of +// the hostnames match like Remove(). +func AddIfExists(file string, existsEntries, newEntries HostEntries) error { + if err := addIfExists(file, existsEntries, newEntries); err != nil { + return fmt.Errorf("failed to add entries to hosts file: %w", err) + } + return nil +} + +// Remove will remove the given entries from the file. An entry will be +// removed when the ip and at least one name matches. Not all names have +// to match. If the given entries are not present in the file no error is +// returned. +// Remove is not atomic because it will keep the current file inode. This is +// required to keep bind mounts for containers working. +func Remove(file string, entries HostEntries) error { + if err := remove(file, entries); err != nil { + return fmt.Errorf("failed to remove entries from hosts file: %w", err) + } + return nil +} + +// new see comment on New() +func new(params *Params) error { + entries, err := parseExtraHosts(params.ExtraHosts) + if err != nil { + return err + } + entries2, err := parseHostsFile(params.BaseFile) + if err != nil { + return err + } + entries = append(entries, entries2...) + + // preallocate the slice with enough space for the 3 special entries below + containerIPs := make(HostEntries, 0, len(params.ContainerIPs)+3) + + // if localhost was not added we add it + // https://github.com/containers/podman/issues/11411 + lh := []string{localhost} + l1 := HostEntry{IP: "127.0.0.1", Names: lh} + l2 := HostEntry{IP: "::1", Names: lh} + containerIPs = append(containerIPs, l1, l2) + if params.HostContainersInternalIP != "" { + e := HostEntry{IP: params.HostContainersInternalIP, Names: []string{hostContainersInternal}} + containerIPs = append(containerIPs, e) + } + containerIPs = append(containerIPs, params.ContainerIPs...) + + if err := writeHostFile(params.TargetFile, entries, containerIPs); err != nil { + return err + } + return nil +} + +// add see comment on Add() +func add(file string, entries HostEntries) error { + currentEntries, err := parseHostsFile(file) + if err != nil { + return err + } + + names := make(map[string]struct{}) + for _, entry := range currentEntries { + for _, name := range entry.Names { + names[name] = struct{}{} + } + } + + // open file in append mode since we only add, we do not have to write existing entries again + f, err := os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return err + } + defer f.Close() + + return addEntriesIfNotExists(f, entries, names) +} + +// addIfExists see comment on AddIfExists() +func addIfExists(file string, existsEntries, newEntries HostEntries) error { + // special case when there are no existing entries do a normal add + // this can happen when we connect a network which was not connected + // to any other networks before + if len(existsEntries) == 0 { + return add(file, newEntries) + } + + currentEntries, err := parseHostsFile(file) + if err != nil { + return err + } + + for _, entry := range currentEntries { + if !checkIfEntryExists(entry, existsEntries) { + // keep looking for existing entries + continue + } + // if we have a matching existing entry add the new entries + // open file in append mode since we only add, we do not have to write existing entries again + f, err := os.OpenFile(file, os.O_WRONLY|os.O_APPEND, 0o644) + if err != nil { + return err + } + defer f.Close() + + for _, e := range newEntries { + if _, err = f.WriteString(formatLine(e.IP, e.Names)); err != nil { + return err + } + } + return nil + } + // no match found is no error + return nil +} + +// remove see comment on Remove() +func remove(file string, entries HostEntries) error { + currentEntries, err := parseHostsFile(file) + if err != nil { + return err + } + + f, err := os.Create(file) + if err != nil { + return err + } + defer f.Close() + + for _, entry := range currentEntries { + if checkIfEntryExists(entry, entries) { + continue + } + if _, err = f.WriteString(formatLine(entry.IP, entry.Names)); err != nil { + return err + } + } + return nil +} + +func checkIfEntryExists(current HostEntry, entries HostEntries) bool { + // check if the current entry equals one of the given entries + for _, rm := range entries { + if current.IP == rm.IP { + // it is enough if one of the names match, in this case we remove the full entry + for _, name := range current.Names { + if util.StringInSlice(name, rm.Names) { + return true + } + } + } + } + return false +} + +// parseExtraHosts converts a slice of "name:ip" string to entries. +// Because podman and buildah both store the extra hosts in this format +// we convert it here instead of having to this on the caller side. +func parseExtraHosts(extraHosts []string) (HostEntries, error) { + entries := make(HostEntries, 0, len(extraHosts)) + for _, entry := range extraHosts { + values := strings.SplitN(entry, ":", 2) + if len(values) != 2 { + return nil, fmt.Errorf("unable to parse host entry %q: incorrect format", entry) + } + if values[0] == "" { + return nil, fmt.Errorf("hostname in host entry %q is empty", entry) + } + if values[1] == "" { + return nil, fmt.Errorf("IP address in host entry %q is empty", entry) + } + e := HostEntry{IP: values[1], Names: []string{values[0]}} + entries = append(entries, e) + } + return entries, nil +} + +// parseHostsFile parses a given host file and returns all entries in it. +// Note that this will remove all comments and spaces. +func parseHostsFile(file string) (HostEntries, error) { + // empty file is valid, in this case we skip adding entries from the file + if file == "" { + return nil, nil + } + + f, err := os.Open(file) + if err != nil { + // do not error when the default hosts file does not exists + // https://github.com/containers/podman/issues/12667 + if errors.Is(err, os.ErrNotExist) && file == config.DefaultHostsFile { + return nil, nil + } + return nil, err + } + defer f.Close() + + entries := HostEntries{} + scanner := bufio.NewScanner(f) + for scanner.Scan() { + // split of the comments + line := scanner.Text() + if c := strings.IndexByte(line, '#'); c != -1 { + line = line[:c] + } + fields := strings.Fields(line) + // if we only have a ip without names we skip it + if len(fields) < 2 { + continue + } + + e := HostEntry{IP: fields[0], Names: fields[1:]} + entries = append(entries, e) + } + + return entries, scanner.Err() +} + +// writeHostFile write the entries to the given file +func writeHostFile(file string, userEntries, containerIPs HostEntries) error { + f, err := os.Create(file) + if err != nil { + return err + } + defer f.Close() + + names := make(map[string]struct{}) + for _, entry := range userEntries { + for _, name := range entry.Names { + names[name] = struct{}{} + } + if _, err = f.WriteString(formatLine(entry.IP, entry.Names)); err != nil { + return err + } + } + + return addEntriesIfNotExists(f, containerIPs, names) +} + +// addEntriesIfNotExists only adds the entries for names that are not already +// in the hosts file, otherwise we start overwriting user entries +func addEntriesIfNotExists(f io.StringWriter, containerIPs HostEntries, names map[string]struct{}) error { + for _, entry := range containerIPs { + freeNames := make([]string, 0, len(entry.Names)) + for _, name := range entry.Names { + if _, ok := names[name]; !ok { + freeNames = append(freeNames, name) + } + } + if len(freeNames) > 0 { + if _, err := f.WriteString(formatLine(entry.IP, freeNames)); err != nil { + return err + } + } + } + return nil +} + +// formatLine converts the given ip and names to a valid hosts line. +// The returned string includes the newline. +func formatLine(ip string, names []string) string { + return ip + "\t" + strings.Join(names, " ") + "\n" +} diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/ip.go b/vendor/github.com/containers/common/libnetwork/etchosts/ip.go new file mode 100644 index 000000000..3d14b7147 --- /dev/null +++ b/vendor/github.com/containers/common/libnetwork/etchosts/ip.go @@ -0,0 +1,91 @@ +package etchosts + +import ( + "net" + + "github.com/containers/common/libnetwork/types" + "github.com/containers/common/libnetwork/util" + "github.com/containers/common/pkg/config" + "github.com/containers/storage/pkg/unshare" +) + +// GetHostContainersInternalIP return the host.containers.internal ip +// if netStatus is not nil then networkInterface also must be non nil otherwise this function panics +func GetHostContainersInternalIP(conf *config.Config, netStatus map[string]types.StatusBlock, networkInterface types.ContainerNetwork) string { + switch conf.Containers.HostContainersInternalIP { + case "": + // if empty (default) we will automatically choose one below + // if machine we let the gvproxy dns server handle the dns name so do not add it + if conf.Engine.MachineEnabled { + return "" + } + case "none": + return "" + default: + return conf.Containers.HostContainersInternalIP + } + ip := "" + // Only use the bridge ip when root, as rootless the interfaces are created + // inside the special netns and not the host so we cannot use them. + if unshare.IsRootless() { + return getLocalIP() + } + for net, status := range netStatus { + network, err := networkInterface.NetworkInspect(net) + // only add the host entry for bridge networks + // ip/macvlan gateway is normally not on the host + if err != nil || network.Driver != types.BridgeNetworkDriver { + continue + } + for _, netInt := range status.Interfaces { + for _, netAddress := range netInt.Subnets { + if netAddress.Gateway != nil { + if util.IsIPv4(netAddress.Gateway) { + return netAddress.Gateway.String() + } + // ipv6 address but keep looking since we prefer to use ipv4 + ip = netAddress.Gateway.String() + } + } + } + } + if ip != "" { + return ip + } + return getLocalIP() +} + +// getLocalIP returns the non loopback local IP of the host +func getLocalIP() string { + addrs, err := net.InterfaceAddrs() + if err != nil { + return "" + } + ip := "" + for _, address := range addrs { + // check the address type and if it is not a loopback the display it + if ipnet, ok := address.(*net.IPNet); ok && ipnet.IP.IsGlobalUnicast() { + if util.IsIPv4(ipnet.IP) { + return ipnet.IP.String() + } + // if ipv6 we keep looking for an ipv4 address + ip = ipnet.IP.String() + } + } + return ip +} + +// GetNetworkHostEntries returns HostEntries for all ips in the network status +// with the given hostnames. +func GetNetworkHostEntries(netStatus map[string]types.StatusBlock, names ...string) HostEntries { + hostEntries := make(HostEntries, 0, len(netStatus)) + for _, status := range netStatus { + for _, netInt := range status.Interfaces { + for _, netAddress := range netInt.Subnets { + e := HostEntry{IP: netAddress.IPNet.IP.String(), Names: names} + hostEntries = append(hostEntries, e) + } + } + } + return hostEntries +} diff --git a/vendor/github.com/containers/common/libnetwork/etchosts/util.go b/vendor/github.com/containers/common/libnetwork/etchosts/util.go new file mode 100644 index 000000000..d78284594 --- /dev/null +++ b/vendor/github.com/containers/common/libnetwork/etchosts/util.go @@ -0,0 +1,30 @@ +package etchosts + +import ( + "fmt" + + "github.com/containers/common/pkg/config" + securejoin "github.com/cyphar/filepath-securejoin" +) + +// GetBaseHostFile return the hosts file which should be used as base. +// The first param should be the config value config.Containers.BaseHostsFile +// The second param should be the root path to the mounted image. This is +// required when the user conf value is set to "image". +func GetBaseHostFile(confValue, imageRoot string) (string, error) { + switch confValue { + case "": + return config.DefaultHostsFile, nil + case "none": + return "", nil + case "image": + // use secure join to prevent problems with symlinks + path, err := securejoin.SecureJoin(imageRoot, config.DefaultHostsFile) + if err != nil { + return "", fmt.Errorf("failed to get /etc/hosts path in image: %w", err) + } + return path, nil + default: + return confValue, nil + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ca4f53bf6..137975ced 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -114,6 +114,7 @@ github.com/containers/buildah/util github.com/containers/common/libimage github.com/containers/common/libimage/manifests github.com/containers/common/libnetwork/cni +github.com/containers/common/libnetwork/etchosts github.com/containers/common/libnetwork/internal/util github.com/containers/common/libnetwork/netavark github.com/containers/common/libnetwork/network |