Merge pull request #13918 from Luap99/hosts
use etchosts package from c/common
This commit is contained in:
commit
ad3da638ce
|
@ -439,9 +439,9 @@ with a new set of cached layers.
|
||||||
#### **--no-hosts**
|
#### **--no-hosts**
|
||||||
|
|
||||||
Do not create _/etc/hosts_ for the container.
|
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**.
|
||||||
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.
|
||||||
**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified. Conflicts with the --add-host option.
|
This option conflicts with **--add-host**.
|
||||||
|
|
||||||
#### **--os**=*string*
|
#### **--os**=*string*
|
||||||
|
|
||||||
|
|
|
@ -743,9 +743,9 @@ Disable any defined healthchecks for container.
|
||||||
|
|
||||||
#### **--no-hosts**
|
#### **--no-hosts**
|
||||||
|
|
||||||
Do not create /etc/hosts for the container.
|
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**.
|
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.
|
**--no-hosts** disables this, and the image's _/etc/hosts_ will be preserved unmodified.
|
||||||
This option conflicts with **--add-host**.
|
This option conflicts with **--add-host**.
|
||||||
|
|
||||||
#### **--oom-kill-disable**
|
#### **--oom-kill-disable**
|
||||||
|
|
|
@ -216,7 +216,10 @@ Valid _mode_ values are:
|
||||||
|
|
||||||
#### **--no-hosts**
|
#### **--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**
|
#### **--quiet**, **-q**
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,11 @@ containers added to it. The pod id is printed to STDOUT. You can then use
|
||||||
|
|
||||||
#### **--add-host**=_host_:_ip_
|
#### **--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*
|
#### **--cgroup-parent**=*path*
|
||||||
|
|
||||||
|
@ -187,7 +191,10 @@ NOTE: A container will only have access to aliases on the first network that it
|
||||||
|
|
||||||
#### **--no-hosts**
|
#### **--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*
|
#### **--pid**=*pid*
|
||||||
|
|
||||||
|
|
|
@ -85,8 +85,10 @@ and specified with a _tag_.
|
||||||
## OPTIONS
|
## OPTIONS
|
||||||
#### **--add-host**=_host_:_ip_
|
#### **--add-host**=_host_:_ip_
|
||||||
|
|
||||||
Add a line to container's _/etc/hosts_ for custom host-to-IP mapping.
|
Add a custom host-to-IP mapping (host:ip)
|
||||||
This option can be set multiple times.
|
|
||||||
|
Add a line to /etc/hosts. The format is hostname:ip. The **--add-host**
|
||||||
|
option can be set multiple times.
|
||||||
|
|
||||||
#### **--annotation**=_key_=_value_
|
#### **--annotation**=_key_=_value_
|
||||||
|
|
||||||
|
@ -768,9 +770,8 @@ Disable any defined healthchecks for container.
|
||||||
#### **--no-hosts**
|
#### **--no-hosts**
|
||||||
|
|
||||||
Do not create _/etc/hosts_ for the container.
|
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**.
|
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**.
|
This option conflicts with **--add-host**.
|
||||||
|
|
||||||
#### **--oom-kill-disable**
|
#### **--oom-kill-disable**
|
||||||
|
|
|
@ -628,6 +628,15 @@ func (c *Container) RuntimeName() string {
|
||||||
|
|
||||||
// Hostname gets the container's hostname
|
// Hostname gets the container's hostname
|
||||||
func (c *Container) Hostname() string {
|
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 != "" {
|
if c.config.Spec.Hostname != "" {
|
||||||
return c.config.Spec.Hostname
|
return c.config.Spec.Hostname
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package libpod
|
package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -17,8 +16,10 @@ import (
|
||||||
"github.com/containers/buildah/copier"
|
"github.com/containers/buildah/copier"
|
||||||
"github.com/containers/buildah/pkg/overlay"
|
"github.com/containers/buildah/pkg/overlay"
|
||||||
butil "github.com/containers/buildah/util"
|
butil "github.com/containers/buildah/util"
|
||||||
|
"github.com/containers/common/libnetwork/etchosts"
|
||||||
"github.com/containers/common/pkg/cgroups"
|
"github.com/containers/common/pkg/cgroups"
|
||||||
"github.com/containers/common/pkg/chown"
|
"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/define"
|
||||||
"github.com/containers/podman/v4/libpod/events"
|
"github.com/containers/podman/v4/libpod/events"
|
||||||
"github.com/containers/podman/v4/pkg/ctime"
|
"github.com/containers/podman/v4/pkg/ctime"
|
||||||
|
@ -31,6 +32,7 @@ import (
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
"github.com/containers/storage/pkg/idtools"
|
"github.com/containers/storage/pkg/idtools"
|
||||||
|
"github.com/containers/storage/pkg/lockfile"
|
||||||
"github.com/containers/storage/pkg/mount"
|
"github.com/containers/storage/pkg/mount"
|
||||||
"github.com/coreos/go-systemd/v22/daemon"
|
"github.com/coreos/go-systemd/v22/daemon"
|
||||||
securejoin "github.com/cyphar/filepath-securejoin"
|
securejoin "github.com/cyphar/filepath-securejoin"
|
||||||
|
@ -1006,19 +1008,16 @@ func (c *Container) completeNetworkSetup() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if we have a bindmount for /etc/hosts
|
// check if we have a bindmount for /etc/hosts
|
||||||
if hostsBindMount, ok := state.BindMounts["/etc/hosts"]; ok && len(c.cniHosts()) > 0 {
|
if hostsBindMount, ok := state.BindMounts[config.DefaultHostsFile]; ok {
|
||||||
ctrHostPath := filepath.Join(c.state.RunDir, "hosts")
|
entries, err := c.getHostsEntries()
|
||||||
if hostsBindMount == ctrHostPath {
|
|
||||||
// read the existing hosts
|
|
||||||
b, err := ioutil.ReadFile(hostsBindMount)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(hostsBindMount, append(b, []byte(c.cniHosts())...), 0644); err != nil {
|
// add new container ips to the hosts file
|
||||||
|
if err := etchosts.Add(hostsBindMount, entries); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// check if we have a bindmount for resolv.conf
|
// check if we have a bindmount for resolv.conf
|
||||||
resolvBindMount := state.BindMounts["/etc/resolv.conf"]
|
resolvBindMount := state.BindMounts["/etc/resolv.conf"]
|
||||||
|
@ -1041,18 +1040,6 @@ func (c *Container) completeNetworkSetup() error {
|
||||||
return ioutil.WriteFile(resolvBindMount, []byte(strings.Join(outResolvConf, "\n")), 0644)
|
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
|
// Initialize a container, creating it in the runtime
|
||||||
func (c *Container) init(ctx context.Context, retainRetries bool) error {
|
func (c *Container) init(ctx context.Context, retainRetries bool) error {
|
||||||
// Unconditionally remove conmon temporary files.
|
// 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())
|
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.
|
// Remove the container from the runtime, if necessary.
|
||||||
// Do this *before* unmounting storage - some runtimes (e.g. Kata)
|
// Do this *before* unmounting storage - some runtimes (e.g. Kata)
|
||||||
// apparently object to having storage removed while the container still
|
// 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
|
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
|
// saveSpec saves the OCI spec to disk, replacing any existing specs for the container
|
||||||
func (c *Container) saveSpec(spec *spec.Spec) error {
|
func (c *Container) saveSpec(spec *spec.Spec) error {
|
||||||
// If the OCI spec already exists, we need to replace it
|
// If the OCI spec already exists, we need to replace it
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/containers/buildah/pkg/chrootuser"
|
"github.com/containers/buildah/pkg/chrootuser"
|
||||||
"github.com/containers/buildah/pkg/overlay"
|
"github.com/containers/buildah/pkg/overlay"
|
||||||
butil "github.com/containers/buildah/util"
|
butil "github.com/containers/buildah/util"
|
||||||
|
"github.com/containers/common/libnetwork/etchosts"
|
||||||
"github.com/containers/common/libnetwork/types"
|
"github.com/containers/common/libnetwork/types"
|
||||||
"github.com/containers/common/pkg/apparmor"
|
"github.com/containers/common/pkg/apparmor"
|
||||||
"github.com/containers/common/pkg/cgroups"
|
"github.com/containers/common/pkg/cgroups"
|
||||||
|
@ -49,6 +50,7 @@ import (
|
||||||
"github.com/containers/podman/v4/version"
|
"github.com/containers/podman/v4/version"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
"github.com/containers/storage/pkg/idtools"
|
"github.com/containers/storage/pkg/idtools"
|
||||||
|
"github.com/containers/storage/pkg/lockfile"
|
||||||
securejoin "github.com/cyphar/filepath-securejoin"
|
securejoin "github.com/cyphar/filepath-securejoin"
|
||||||
runcuser "github.com/opencontainers/runc/libcontainer/user"
|
runcuser "github.com/opencontainers/runc/libcontainer/user"
|
||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
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.
|
// check if dependency container has an /etc/hosts file.
|
||||||
// It may not have one, so only use it if it does.
|
// 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 {
|
if !c.config.UseImageHosts && exists {
|
||||||
depCtr.lock.Lock()
|
// we cannot use the dependency container lock due ABBA deadlocks in cleanup()
|
||||||
// generate a hosts file for the dependency container,
|
lock, err := lockfile.GetLockfile(hostsPath)
|
||||||
// based on either its old hosts file, or the default,
|
if err != nil {
|
||||||
// and add the relevant information from the new container (hosts and IP)
|
return fmt.Errorf("failed to lock hosts file: %w", err)
|
||||||
hostsPath, err = depCtr.appendHosts(hostsPath, c)
|
}
|
||||||
|
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 {
|
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())
|
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
|
// finally, save it in the new container
|
||||||
err := c.mountIntoRootDirs("/etc/hosts", hostsPath)
|
err = c.mountIntoRootDirs(config.DefaultHostsFile, hostsPath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error assigning mounts to container %s", c.ID())
|
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 !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())
|
return errors.Wrapf(err, "error creating hosts file for container %s", c.ID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2142,7 +2146,7 @@ func (c *Container) makeBindMounts() error {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !c.config.UseImageHosts && c.state.BindMounts["/etc/hosts"] == "" {
|
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())
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateHosts updates the container's hosts file
|
func getLocalhostHostEntry(c *Container) etchosts.HostEntries {
|
||||||
func (c *Container) updateHosts(path string) error {
|
return etchosts.HostEntries{{IP: "127.0.0.1", Names: []string{c.Hostname(), c.config.Name}}}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on networking mode we may want to append the localhost
|
// getHostsEntries returns the container ip host entries for the correct netmode
|
||||||
// if there isn't any record for it and also this should happen
|
func (c *Container) getHostsEntries() (etchosts.HostEntries, error) {
|
||||||
// in slirp4netns and similar network modes.
|
var entries etchosts.HostEntries
|
||||||
func (c *Container) appendLocalhost(hosts string) string {
|
names := []string{c.Hostname(), c.config.Name}
|
||||||
if !strings.Contains(hosts, "localhost") &&
|
switch {
|
||||||
!c.config.NetMode.IsHost() {
|
case c.config.NetMode.IsBridge():
|
||||||
hosts += "127.0.0.1\tlocalhost\n::1\tlocalhost\n"
|
entries = etchosts.GetNetworkHostEntries(c.state.NetworkStatus, names...)
|
||||||
}
|
case c.config.NetMode.IsSlirp4netns():
|
||||||
|
ip, err := GetSlirp4netnsIP(c.slirp4netnsSubnet)
|
||||||
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 {
|
if err != nil {
|
||||||
logrus.Warnf("Failed to determine slirp4netnsIP: %v", err.Error())
|
return nil, err
|
||||||
} else {
|
|
||||||
hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP.String(), c.Hostname(), c.config.Name)
|
|
||||||
}
|
}
|
||||||
}
|
entries = etchosts.HostEntries{{IP: ip.String(), Names: names}}
|
||||||
|
default:
|
||||||
// Do we have a network namespace?
|
// check for net=none
|
||||||
netNone := false
|
if !c.config.CreateNetNS {
|
||||||
if c.config.NetNsCtr == "" && !c.config.CreateNetNS {
|
|
||||||
for _, ns := range c.config.Spec.Linux.Namespaces {
|
for _, ns := range c.config.Spec.Linux.Namespaces {
|
||||||
if ns.Type == spec.NetworkNamespace {
|
if ns.Type == spec.NetworkNamespace {
|
||||||
if ns.Path == "" {
|
if ns.Path == "" {
|
||||||
netNone = true
|
entries = etchosts.HostEntries{{IP: "127.0.0.1", Names: names}}
|
||||||
}
|
}
|
||||||
break
|
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
|
func (c *Container) createHosts() error {
|
||||||
// the gvproxy dns server will take care of host.containers.internal.
|
var containerIPsEntries etchosts.HostEntries
|
||||||
// https://github.com/containers/gvisor-tap-vsock/commit/1108ea45162281046d239047a6db9bc187e64b08
|
var err error
|
||||||
if !c.runtime.config.Engine.MachineEnabled {
|
// if we configure the netns after the container create we should not add
|
||||||
var depCtr *Container
|
// the hosts here since we have no information about the actual ips
|
||||||
netStatus := c.getNetworkStatus()
|
// instead we will add them in c.completeNetworkSetup()
|
||||||
if c.config.NetNsCtr != "" {
|
if !c.config.PostConfigureNetNS {
|
||||||
// ignoring the error because there isn't anything to do
|
containerIPsEntries, err = c.getHostsEntries()
|
||||||
depCtr, _ = c.getRootNetNsDepCtr()
|
|
||||||
} else if len(netStatus) != 0 {
|
|
||||||
depCtr = c
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLocalIP returns the non loopback local IP of the host
|
|
||||||
getLocalIP := func() string {
|
|
||||||
addrs, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return fmt.Errorf("failed to get container ip host entries: %w", err)
|
||||||
}
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
baseHostFile, err := etchosts.GetBaseHostFile(c.runtime.config.Containers.BaseHostsFile, c.state.Mountpoint)
|
||||||
return ""
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if depCtr != nil {
|
targetFile := filepath.Join(c.state.RunDir, "hosts")
|
||||||
host := ""
|
err = etchosts.New(&etchosts.Params{
|
||||||
outer:
|
BaseFile: baseHostFile,
|
||||||
for net, status := range depCtr.getNetworkStatus() {
|
ExtraHosts: c.config.HostAdd,
|
||||||
network, err := c.runtime.network.NetworkInspect(net)
|
ContainerIPs: containerIPsEntries,
|
||||||
// only add the host entry for bridge networks
|
HostContainersInternalIP: etchosts.GetHostContainersInternalIP(c.runtime.config, c.state.NetworkStatus, c.runtime.network),
|
||||||
// ip/macvlan gateway is normally not on the host
|
TargetFile: targetFile,
|
||||||
if err != nil || network.Driver != types.BridgeNetworkDriver {
|
})
|
||||||
continue
|
if err != nil {
|
||||||
}
|
return err
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return hosts
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.mountIntoRootDirs(config.DefaultHostsFile, targetFile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateGroupEntry generates an entry or entries into /etc/group as
|
// generateGroupEntry generates an entry or entries into /etc/group as
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containers/podman/v4/pkg/namespaces"
|
|
||||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -70,30 +69,3 @@ func TestGenerateUserGroupEntry(t *testing.T) {
|
||||||
}
|
}
|
||||||
assert.Equal(t, group, "567:x:567:567\n")
|
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"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,7 +20,9 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
"github.com/containers/common/libnetwork/etchosts"
|
||||||
"github.com/containers/common/libnetwork/types"
|
"github.com/containers/common/libnetwork/types"
|
||||||
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/common/pkg/netns"
|
"github.com/containers/common/pkg/netns"
|
||||||
"github.com/containers/podman/v4/libpod/define"
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
"github.com/containers/podman/v4/libpod/events"
|
"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 {
|
for _, ip := range oldStatus.DNSServerIPs {
|
||||||
stringIPs = append(stringIPs, ip.String())
|
stringIPs = append(stringIPs, ip.String())
|
||||||
}
|
}
|
||||||
if len(stringIPs) == 0 {
|
if len(stringIPs) > 0 {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
|
logrus.Debugf("Removing DNS Servers %v from resolv.conf", stringIPs)
|
||||||
if err := c.removeNameserver(stringIPs); err != nil {
|
if err := c.removeNameserver(stringIPs); err != nil {
|
||||||
return err
|
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
|
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")
|
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
|
// update network status
|
||||||
if networkStatus == nil {
|
if networkStatus == nil {
|
||||||
networkStatus = make(map[string]types.StatusBlock, 1)
|
networkStatus = make(map[string]types.StatusBlock, 1)
|
||||||
|
@ -1394,13 +1423,32 @@ func (c *Container) NetworkConnect(nameOrID, netName string, netOpts types.PerNe
|
||||||
}
|
}
|
||||||
stringIPs = append(stringIPs, ip.String())
|
stringIPs = append(stringIPs, ip.String())
|
||||||
}
|
}
|
||||||
if len(stringIPs) == 0 {
|
if len(stringIPs) > 0 {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
|
logrus.Debugf("Adding DNS Servers %v to resolv.conf", stringIPs)
|
||||||
if err := c.addNameserver(stringIPs); err != nil {
|
if err := c.addNameserver(stringIPs); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,13 @@ func (s *SpecGenerator) Validate() error {
|
||||||
if len(s.PortMappings) > 0 || s.PublishExposedPorts {
|
if len(s.PortMappings) > 0 || s.PublishExposedPorts {
|
||||||
return errors.Wrap(define.ErrNetworkOnPodContainer, "published or exposed ports must be defined when the pod is created")
|
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")
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -377,21 +377,19 @@ var _ = Describe("Podman pod create", func() {
|
||||||
Expect(result.OutputToString()).To(ContainSubstring(infraID))
|
Expect(result.OutputToString()).To(ContainSubstring(infraID))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman run --add-host in pod", func() {
|
It("podman run --add-host in pod should fail", func() {
|
||||||
session := podmanTest.Podman([]string{"pod", "create"})
|
session := podmanTest.Podman([]string{"pod", "create", "--add-host", "host1:127.0.0.1"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session).Should(Exit(0))
|
Expect(session).Should(Exit(0))
|
||||||
podID := session.OutputToString()
|
podID := session.OutputToString()
|
||||||
|
|
||||||
// verify we can add a host to the infra's /etc/hosts
|
session = podmanTest.Podman([]string{"create", "--pod", podID, "--add-host", "foobar:127.0.0.1", ALPINE, "ping", "-c", "1", "foobar"})
|
||||||
// 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.WaitWithDefaultTimeout()
|
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
|
// verify we can see the pods hosts
|
||||||
session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "ping", "-c", "1", "foobar"})
|
session = podmanTest.Podman([]string{"run", "--pod", podID, ALPINE, "ping", "-c", "1", "host1"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session).Should(Exit(0))
|
Expect(session).Should(Exit(0))
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 := podmanTest.Podman([]string{"run", "--add-host=foobar:1.1.1.1", "--add-host=foobaz:2001:db8::68", ALPINE, "cat", "/etc/hosts"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session).Should(Exit(0))
|
Expect(session).Should(Exit(0))
|
||||||
Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("1.1.1.1 foobar")))
|
Expect(session.OutputToStringArray()).To(ContainElements(HavePrefix("1.1.1.1\tfoobar"), HavePrefix("2001:db8::68\tfoobaz")))
|
||||||
Expect(session.OutputToStringArray()).To(ContainElement(HavePrefix("2001:db8::68 foobaz")))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman run add hostname", func() {
|
It("podman run add hostname", func() {
|
||||||
|
|
|
@ -608,6 +608,18 @@ EXPOSE 2004-2005/tcp`, ALPINE)
|
||||||
Expect(ctr2).Should(Exit(0))
|
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() {
|
It("podman run --net container: copies hosts and resolv", func() {
|
||||||
ctrName := "ctr1"
|
ctrName := "ctr1"
|
||||||
ctr1 := podmanTest.RunTopContainer(ctrName)
|
ctr1 := podmanTest.RunTopContainer(ctrName)
|
||||||
|
|
|
@ -786,7 +786,7 @@ EOF
|
||||||
mv $hosts_tmp /etc/hosts
|
mv $hosts_tmp /etc/hosts
|
||||||
assert "$status" = 0 \
|
assert "$status" = 0 \
|
||||||
"podman run without /etc/hosts file should work"
|
"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"
|
"users can add hosts even without /etc/hosts"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
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
|
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"
|
is "$output" ".* $hostname" "--hostname is in /etc/hosts"
|
||||||
# ^^^^ this must be a tab, not a space
|
# ^^^^ this must be a tab, not a space
|
||||||
|
|
||||||
|
|
|
@ -134,14 +134,50 @@ load helpers
|
||||||
done
|
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" {
|
@test "podman run with slirp4ns assigns correct addresses to /etc/hosts" {
|
||||||
CIDR="$(random_rfc1918_subnet)"
|
CIDR="$(random_rfc1918_subnet)"
|
||||||
IP=$(hostname -I | cut -f 1 -d " ")
|
IP=$(hostname -I | cut -f 1 -d " ")
|
||||||
local conname=con-$(random_string 10)
|
local conname=con-$(random_string 10)
|
||||||
run_podman run --rm --network slirp4netns:cidr="${CIDR}.0/24" \
|
run_podman run --rm --network slirp4netns:cidr="${CIDR}.0/24" \
|
||||||
--name $conname --hostname $conname $IMAGE cat /etc/hosts
|
--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"
|
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" {
|
@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}}"
|
run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").Aliases}}"
|
||||||
is "$output" "[${cid:0:12}]" "short container id in network 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
|
run_podman network disconnect $netname $cid
|
||||||
is "$output" "" "Output should be empty (no errors)"
|
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)
|
# check that we cannot curl (timeout after 3 sec)
|
||||||
run curl --max-time 3 -s $SERVER/index.txt
|
run curl --max-time 3 -s $SERVER/index.txt
|
||||||
assert $status -ne 0 \
|
assert $status -ne 0 \
|
||||||
|
@ -452,13 +496,18 @@ load helpers
|
||||||
# check that we have a new ip and mac
|
# check that we have a new ip and mac
|
||||||
# if the ip is still the same this whole test turns into a nop
|
# if the ip is still the same this whole test turns into a nop
|
||||||
run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").IPAddress}}"
|
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"
|
"IP address did not change after podman network disconnect/connect"
|
||||||
|
|
||||||
run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}"
|
run_podman inspect $cid --format "{{(index .NetworkSettings.Networks \"$netname\").MacAddress}}"
|
||||||
assert "$output" != "$mac" \
|
assert "$output" != "$mac" \
|
||||||
"MAC address did not change after podman network disconnect/connect"
|
"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
|
# Disconnect/reconnect of a container *with no ports* should succeed quietly
|
||||||
run_podman network disconnect $netname $background_cid
|
run_podman network disconnect $netname $background_cid
|
||||||
is "$output" "" "disconnect of container with no open ports"
|
is "$output" "" "disconnect of container with no open ports"
|
||||||
|
@ -629,4 +678,48 @@ EOF
|
||||||
done
|
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
|
# vim: filetype=sh
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,6 +114,7 @@ github.com/containers/buildah/util
|
||||||
github.com/containers/common/libimage
|
github.com/containers/common/libimage
|
||||||
github.com/containers/common/libimage/manifests
|
github.com/containers/common/libimage/manifests
|
||||||
github.com/containers/common/libnetwork/cni
|
github.com/containers/common/libnetwork/cni
|
||||||
|
github.com/containers/common/libnetwork/etchosts
|
||||||
github.com/containers/common/libnetwork/internal/util
|
github.com/containers/common/libnetwork/internal/util
|
||||||
github.com/containers/common/libnetwork/netavark
|
github.com/containers/common/libnetwork/netavark
|
||||||
github.com/containers/common/libnetwork/network
|
github.com/containers/common/libnetwork/network
|
||||||
|
|
Loading…
Reference in New Issue