Skip to content

Commit

Permalink
Add host.containers.internal entry into container's etc/hosts
Browse files Browse the repository at this point in the history
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 containers#5651

Signed-off-by: Baron Lenardson <[email protected]>
  • Loading branch information
Baron Lenardson committed May 17, 2021
1 parent d8dc56b commit c8dfcce
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 45 deletions.
2 changes: 2 additions & 0 deletions libpod/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
93 changes: 73 additions & 20 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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...)
}
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down
27 changes: 14 additions & 13 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand All @@ -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")
}
Expand Down Expand Up @@ -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
}
Expand Down
92 changes: 80 additions & 12 deletions libpod/networking_slirp4netns.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand All @@ -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 {
Expand Down
21 changes: 21 additions & 0 deletions test/system/500-networking.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit c8dfcce

Please sign in to comment.