diff options
-rw-r--r-- | libpod/container.go | 2 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 93 | ||||
-rw-r--r-- | libpod/networking_linux.go | 27 | ||||
-rw-r--r-- | libpod/networking_slirp4netns.go | 92 | ||||
-rw-r--r-- | test/system/500-networking.bats | 21 |
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 |