aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBaron Lenardson <lenardson.baron@gmail.com>2021-01-27 21:13:23 -0600
committerBaron Lenardson <lenardson.baron@gmail.com>2021-05-17 08:21:22 -0500
commitc8dfcce6db0adfa2f91d979271e8c2725eb753a6 (patch)
treec509303139c828583537ce415d11c1edae5d7ad6
parentd8dc56ba6758e590d14fca0c733246454837faf9 (diff)
downloadpodman-c8dfcce6db0adfa2f91d979271e8c2725eb753a6.tar.gz
podman-c8dfcce6db0adfa2f91d979271e8c2725eb753a6.tar.bz2
podman-c8dfcce6db0adfa2f91d979271e8c2725eb753a6.zip
Add host.containers.internal entry into container's etc/hosts
This change adds the entry `host.containers.internal` to the `/etc/hosts` file within a new containers filesystem. The ip address is determined by the containers networking configuration and points to the gateway address for the containers networking namespace. Closes #5651 Signed-off-by: Baron Lenardson <lenardson.baron@gmail.com>
-rw-r--r--libpod/container.go2
-rw-r--r--libpod/container_internal_linux.go93
-rw-r--r--libpod/networking_linux.go27
-rw-r--r--libpod/networking_slirp4netns.go92
-rw-r--r--test/system/500-networking.bats21
5 files changed, 190 insertions, 45 deletions
diff --git a/libpod/container.go b/libpod/container.go
index c49d8feeb..bad91d54f 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -126,6 +126,8 @@ type Container struct {
// This is true if a container is restored from a checkpoint.
restoreFromCheckpoint bool
+
+ slirp4netnsSubnet *net.IPNet
}
// ContainerState contains the current state of the container
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 7d57e8965..17b894ce0 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -1358,6 +1358,34 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
return c.save()
}
+// Retrieves a container's "root" net namespace container dependency.
+func (c *Container) getRootNetNsDepCtr() (depCtr *Container, err error) {
+ containersVisited := map[string]int{c.config.ID: 1}
+ nextCtr := c.config.NetNsCtr
+ for nextCtr != "" {
+ // Make sure we aren't in a loop
+ if _, visited := containersVisited[nextCtr]; visited {
+ return nil, errors.New("loop encountered while determining net namespace container")
+ }
+ containersVisited[nextCtr] = 1
+
+ depCtr, err = c.runtime.state.Container(nextCtr)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error fetching dependency %s of container %s", c.config.NetNsCtr, c.ID())
+ }
+ // This should never happen without an error
+ if depCtr == nil {
+ break
+ }
+ nextCtr = depCtr.config.NetNsCtr
+ }
+
+ if depCtr == nil {
+ return nil, errors.New("unexpected error depCtr is nil without reported error from runtime state")
+ }
+ return depCtr, nil
+}
+
// Make standard bind mounts to include in the container
func (c *Container) makeBindMounts() error {
if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil {
@@ -1396,24 +1424,9 @@ func (c *Container) makeBindMounts() error {
// We want /etc/resolv.conf and /etc/hosts from the
// other container. Unless we're not creating both of
// them.
- var (
- depCtr *Container
- nextCtr string
- )
-
- // I don't like infinite loops, but I don't think there's
- // a serious risk of looping dependencies - too many
- // protections against that elsewhere.
- nextCtr = c.config.NetNsCtr
- for {
- depCtr, err = c.runtime.state.Container(nextCtr)
- if err != nil {
- return errors.Wrapf(err, "error fetching dependency %s of container %s", c.config.NetNsCtr, c.ID())
- }
- nextCtr = depCtr.config.NetNsCtr
- if nextCtr == "" {
- break
- }
+ depCtr, err := c.getRootNetNsDepCtr()
+ if err != nil {
+ return errors.Wrapf(err, "error fetching network namespace dependency container for container %s", c.ID())
}
// We need that container's bind mounts
@@ -1698,7 +1711,12 @@ func (c *Container) generateResolvConf() (string, error) {
nameservers = resolvconf.GetNameservers(resolv.Content)
// slirp4netns has a built in DNS server.
if c.config.NetMode.IsSlirp4netns() {
- nameservers = append([]string{slirp4netnsDNS}, nameservers...)
+ slirp4netnsDNS, err := GetSlirp4netnsDNS(c.slirp4netnsSubnet)
+ if err != nil {
+ logrus.Warn("failed to determine Slirp4netns DNS: ", err.Error())
+ } else {
+ nameservers = append([]string{slirp4netnsDNS.String()}, nameservers...)
+ }
}
}
@@ -1779,7 +1797,12 @@ func (c *Container) getHosts() string {
if c.Hostname() != "" {
if c.config.NetMode.IsSlirp4netns() {
// When using slirp4netns, the interface gets a static IP
- hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP, c.Hostname(), c.config.Name)
+ slirp4netnsIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet)
+ if err != nil {
+ logrus.Warn("failed to determine slirp4netnsIP: ", err.Error())
+ } else {
+ hosts += fmt.Sprintf("# used by slirp4netns\n%s\t%s %s\n", slirp4netnsIP.String(), c.Hostname(), c.config.Name)
+ }
} else {
hasNetNS := false
netNone := false
@@ -1802,6 +1825,36 @@ func (c *Container) getHosts() string {
}
}
}
+
+ // Add gateway entry
+ var depCtr *Container
+ if c.config.NetNsCtr != "" {
+ // ignoring the error because there isn't anything to do
+ depCtr, _ = c.getRootNetNsDepCtr()
+ } else if len(c.state.NetworkStatus) != 0 {
+ depCtr = c
+ } else {
+ depCtr = nil
+ }
+
+ if depCtr != nil {
+ for _, pluginResultsRaw := range depCtr.state.NetworkStatus {
+ pluginResult, _ := cnitypes.GetResult(pluginResultsRaw)
+ for _, ip := range pluginResult.IPs {
+ hosts += fmt.Sprintf("%s host.containers.internal\n", ip.Gateway)
+ }
+ }
+ } else if c.config.NetMode.IsSlirp4netns() {
+ gatewayIP, err := GetSlirp4netnsGateway(c.slirp4netnsSubnet)
+ if err != nil {
+ logrus.Warn("failed to determine gatewayIP: ", err.Error())
+ } else {
+ hosts += fmt.Sprintf("%s host.containers.internal\n", gatewayIP.String())
+ }
+ } else {
+ logrus.Debug("network configuration does not support host.containers.internal address")
+ }
+
return hosts
}
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index cfed5a1f2..1e763dac5 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -37,16 +37,12 @@ import (
)
const (
- // slirp4netnsIP is the IP used by slirp4netns to configure the tap device
- // inside the network namespace.
- slirp4netnsIP = "10.0.2.100"
-
- // slirp4netnsDNS is the IP for the built-in DNS server in the slirp network
- slirp4netnsDNS = "10.0.2.3"
-
// slirp4netnsMTU the default MTU override
slirp4netnsMTU = 65520
+ // default slirp4ns subnet
+ defaultSlirp4netnsSubnet = "10.0.2.0/24"
+
// rootlessCNINSName is the file name for the rootless network namespace bind mount
rootlessCNINSName = "rootless-cni-ns"
)
@@ -360,15 +356,20 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
}
// build a new resolv.conf file which uses the slirp4netns dns server address
- resolveIP := slirp4netnsDNS
+ resolveIP, err := GetSlirp4netnsDNS(nil)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to determine default slirp4netns DNS address")
+ }
+
if netOptions.cidr != "" {
_, cidr, err := net.ParseCIDR(netOptions.cidr)
if err != nil {
return nil, errors.Wrap(err, "failed to parse slirp4netns cidr")
}
- // the slirp dns ip is always the third ip in the subnet
- cidr.IP[len(cidr.IP)-1] = cidr.IP[len(cidr.IP)-1] + 3
- resolveIP = cidr.IP.String()
+ resolveIP, err = GetSlirp4netnsDNS(cidr)
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to determine slirp4netns DNS address from cidr: %s", cidr.String())
+ }
}
conf, err := resolvconf.Get()
if err != nil {
@@ -377,7 +378,7 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
searchDomains := resolvconf.GetSearchDomains(conf.Content)
dnsOptions := resolvconf.GetOptions(conf.Content)
- _, err = resolvconf.Build(filepath.Join(cniDir, "resolv.conf"), []string{resolveIP}, searchDomains, dnsOptions)
+ _, err = resolvconf.Build(filepath.Join(cniDir, "resolv.conf"), []string{resolveIP.String()}, searchDomains, dnsOptions)
if err != nil {
return nil, errors.Wrap(err, "failed to create rootless cni resolv.conf")
}
@@ -577,7 +578,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
// set up port forwarder for CNI-in-slirp4netns
netnsPath := ctr.state.NetNS.Path()
// TODO: support slirp4netns port forwarder as well
- return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, "")
+ return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
}
return nil
}
diff --git a/libpod/networking_slirp4netns.go b/libpod/networking_slirp4netns.go
index c46dc6972..74d390d29 100644
--- a/libpod/networking_slirp4netns.go
+++ b/libpod/networking_slirp4netns.go
@@ -308,15 +308,89 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error {
return err
}
+ // Set a default slirp subnet. Parsing a string with the net helper is easier than building the struct myself
+ _, ctr.slirp4netnsSubnet, _ = net.ParseCIDR(defaultSlirp4netnsSubnet)
+
+ // Set slirp4netnsSubnet addresses now that we are pretty sure the command executed
+ if netOptions.cidr != "" {
+ ipv4, ipv4network, err := net.ParseCIDR(netOptions.cidr)
+ if err != nil || ipv4.To4() == nil {
+ return errors.Errorf("invalid cidr %q", netOptions.cidr)
+ }
+ ctr.slirp4netnsSubnet = ipv4network
+ }
+
if havePortMapping {
if netOptions.isSlirpHostForward {
return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket)
}
- return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, netOptions.cidr)
+ return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
}
+
return nil
}
+// Get expected slirp ipv4 address based on subnet. If subnet is null use default subnet
+// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description
+func GetSlirp4netnsIP(subnet *net.IPNet) (*net.IP, error) {
+ _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet)
+ if subnet != nil {
+ slirpSubnet = subnet
+ }
+ expectedIP, err := addToIP(slirpSubnet, uint32(100))
+ if err != nil {
+ return nil, errors.Wrapf(err, "error calculating expected ip for slirp4netns")
+ }
+ return expectedIP, nil
+}
+
+// Get expected slirp Gateway ipv4 address based on subnet
+// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description
+func GetSlirp4netnsGateway(subnet *net.IPNet) (*net.IP, error) {
+ _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet)
+ if subnet != nil {
+ slirpSubnet = subnet
+ }
+ expectedGatewayIP, err := addToIP(slirpSubnet, uint32(2))
+ if err != nil {
+ return nil, errors.Wrapf(err, "error calculating expected gateway ip for slirp4netns")
+ }
+ return expectedGatewayIP, nil
+}
+
+// Get expected slirp DNS ipv4 address based on subnet
+// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description
+func GetSlirp4netnsDNS(subnet *net.IPNet) (*net.IP, error) {
+ _, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet)
+ if subnet != nil {
+ slirpSubnet = subnet
+ }
+ expectedDNSIP, err := addToIP(slirpSubnet, uint32(3))
+ if err != nil {
+ return nil, errors.Wrapf(err, "error calculating expected dns ip for slirp4netns")
+ }
+ return expectedDNSIP, nil
+}
+
+// Helper function to calculate slirp ip address offsets
+// Adapted from: https://github.com/signalsciences/ipv4/blob/master/int.go#L12-L24
+func addToIP(subnet *net.IPNet, offset uint32) (*net.IP, error) {
+ // I have no idea why I have to do this, but if I don't ip is 0
+ ipFixed := subnet.IP.To4()
+
+ ipInteger := uint32(ipFixed[3]) | uint32(ipFixed[2])<<8 | uint32(ipFixed[1])<<16 | uint32(ipFixed[0])<<24
+ ipNewRaw := ipInteger + offset
+ // Avoid overflows
+ if ipNewRaw < ipInteger {
+ return nil, errors.Errorf("integer overflow while calculating ip address offset, %s + %d", ipFixed, offset)
+ }
+ ipNew := net.IPv4(byte(ipNewRaw>>24), byte(ipNewRaw>>16&0xFF), byte(ipNewRaw>>8)&0xFF, byte(ipNewRaw&0xFF))
+ if !subnet.Contains(ipNew) {
+ return nil, errors.Errorf("calculated ip address %s is not within given subnet %s", ipNew.String(), subnet.String())
+ }
+ return &ipNew, nil
+}
+
func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error {
prog := filepath.Base(cmd.Path)
if len(cmd.Args) > 0 {
@@ -363,7 +437,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t
return nil
}
-func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath, slirp4CIDR string) error {
+func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath string) error {
syncR, syncW, err := os.Pipe()
if err != nil {
return errors.Wrapf(err, "failed to open pipe")
@@ -390,17 +464,11 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath, slir
}
}
- childIP := slirp4netnsIP
- // set the correct childIP when a custom cidr is set
- if slirp4CIDR != "" {
- _, cidr, err := net.ParseCIDR(slirp4CIDR)
- if err != nil {
- return errors.Wrap(err, "failed to parse slirp4netns cidr")
- }
- // the slirp container ip is always the hundredth ip in the subnet
- cidr.IP[len(cidr.IP)-1] = cidr.IP[len(cidr.IP)-1] + 100
- childIP = cidr.IP.String()
+ slirp4netnsIP, err := GetSlirp4netnsIP(ctr.slirp4netnsSubnet)
+ if err != nil {
+ return errors.Wrapf(err, "failed to get slirp4ns ip")
}
+ childIP := slirp4netnsIP.String()
outer:
for _, r := range ctr.state.NetworkStatus {
for _, i := range r.IPs {
diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats
index 34220829a..654bd5d8a 100644
--- a/test/system/500-networking.bats
+++ b/test/system/500-networking.bats
@@ -162,6 +162,27 @@ load helpers
done
}
+@test "podman run with slirp4ns assigns correct gateway address to host.containers.internal" {
+ CIDR="$(random_rfc1918_subnet)"
+ run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \
+ $IMAGE grep 'host.containers.internal' /etc/hosts
+ is "$output" "${CIDR}.2 host.containers.internal" "host.containers.internal should be the cidr+2 address"
+}
+
+@test "podman run with slirp4ns adds correct dns address to resolv.conf" {
+ CIDR="$(random_rfc1918_subnet)"
+ run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \
+ $IMAGE grep "${CIDR}" /etc/resolv.conf
+ is "$output" "nameserver ${CIDR}.3" "resolv.conf should have slirp4netns cidr+3 as a nameserver"
+}
+
+@test "podman run with slirp4ns assigns correct ip address container" {
+ CIDR="$(random_rfc1918_subnet)"
+ run_podman run --network slirp4netns:cidr="${CIDR}.0/24" \
+ $IMAGE sh -c "ip address | grep ${CIDR}"
+ is "$output" ".*inet ${CIDR}.100/24 \+" "container should have slirp4netns cidr+100 assigned to interface"
+}
+
# "network create" now works rootless, with the help of a special container
@test "podman network create" {
myport=54322