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>
This commit is contained in:
parent
d8dc56ba67
commit
c8dfcce6db
|
@ -126,6 +126,8 @@ type Container struct {
|
||||||
|
|
||||||
// This is true if a container is restored from a checkpoint.
|
// This is true if a container is restored from a checkpoint.
|
||||||
restoreFromCheckpoint bool
|
restoreFromCheckpoint bool
|
||||||
|
|
||||||
|
slirp4netnsSubnet *net.IPNet
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerState contains the current state of the container
|
// ContainerState contains the current state of the container
|
||||||
|
|
|
@ -1358,6 +1358,34 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
|
||||||
return c.save()
|
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
|
// Make standard bind mounts to include in the container
|
||||||
func (c *Container) makeBindMounts() error {
|
func (c *Container) makeBindMounts() error {
|
||||||
if err := os.Chown(c.state.RunDir, c.RootUID(), c.RootGID()); err != nil {
|
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
|
// We want /etc/resolv.conf and /etc/hosts from the
|
||||||
// other container. Unless we're not creating both of
|
// other container. Unless we're not creating both of
|
||||||
// them.
|
// them.
|
||||||
var (
|
depCtr, err := c.getRootNetNsDepCtr()
|
||||||
depCtr *Container
|
if err != nil {
|
||||||
nextCtr string
|
return errors.Wrapf(err, "error fetching network namespace dependency container for container %s", c.ID())
|
||||||
)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need that container's bind mounts
|
// We need that container's bind mounts
|
||||||
|
@ -1698,7 +1711,12 @@ func (c *Container) generateResolvConf() (string, error) {
|
||||||
nameservers = resolvconf.GetNameservers(resolv.Content)
|
nameservers = resolvconf.GetNameservers(resolv.Content)
|
||||||
// slirp4netns has a built in DNS server.
|
// slirp4netns has a built in DNS server.
|
||||||
if c.config.NetMode.IsSlirp4netns() {
|
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.Hostname() != "" {
|
||||||
if c.config.NetMode.IsSlirp4netns() {
|
if c.config.NetMode.IsSlirp4netns() {
|
||||||
// When using slirp4netns, the interface gets a static IP
|
// 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 {
|
} else {
|
||||||
hasNetNS := false
|
hasNetNS := false
|
||||||
netNone := 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
|
return hosts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,16 +37,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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 the default MTU override
|
||||||
slirp4netnsMTU = 65520
|
slirp4netnsMTU = 65520
|
||||||
|
|
||||||
|
// default slirp4ns subnet
|
||||||
|
defaultSlirp4netnsSubnet = "10.0.2.0/24"
|
||||||
|
|
||||||
// rootlessCNINSName is the file name for the rootless network namespace bind mount
|
// rootlessCNINSName is the file name for the rootless network namespace bind mount
|
||||||
rootlessCNINSName = "rootless-cni-ns"
|
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
|
// 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 != "" {
|
if netOptions.cidr != "" {
|
||||||
_, cidr, err := net.ParseCIDR(netOptions.cidr)
|
_, cidr, err := net.ParseCIDR(netOptions.cidr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to parse slirp4netns cidr")
|
return nil, errors.Wrap(err, "failed to parse slirp4netns cidr")
|
||||||
}
|
}
|
||||||
// the slirp dns ip is always the third ip in the subnet
|
resolveIP, err = GetSlirp4netnsDNS(cidr)
|
||||||
cidr.IP[len(cidr.IP)-1] = cidr.IP[len(cidr.IP)-1] + 3
|
if err != nil {
|
||||||
resolveIP = cidr.IP.String()
|
return nil, errors.Wrapf(err, "failed to determine slirp4netns DNS address from cidr: %s", cidr.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
conf, err := resolvconf.Get()
|
conf, err := resolvconf.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -377,7 +378,7 @@ func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
|
||||||
searchDomains := resolvconf.GetSearchDomains(conf.Content)
|
searchDomains := resolvconf.GetSearchDomains(conf.Content)
|
||||||
dnsOptions := resolvconf.GetOptions(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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to create rootless cni resolv.conf")
|
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
|
// set up port forwarder for CNI-in-slirp4netns
|
||||||
netnsPath := ctr.state.NetNS.Path()
|
netnsPath := ctr.state.NetNS.Path()
|
||||||
// TODO: support slirp4netns port forwarder as well
|
// TODO: support slirp4netns port forwarder as well
|
||||||
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, "")
|
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,15 +308,89 @@ func (r *Runtime) setupSlirp4netns(ctr *Container) error {
|
||||||
return err
|
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 havePortMapping {
|
||||||
if netOptions.isSlirpHostForward {
|
if netOptions.isSlirpHostForward {
|
||||||
return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket)
|
return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket)
|
||||||
}
|
}
|
||||||
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, netOptions.cidr)
|
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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 {
|
func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error {
|
||||||
prog := filepath.Base(cmd.Path)
|
prog := filepath.Base(cmd.Path)
|
||||||
if len(cmd.Args) > 0 {
|
if len(cmd.Args) > 0 {
|
||||||
|
@ -363,7 +437,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t
|
||||||
return nil
|
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()
|
syncR, syncW, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "failed to open pipe")
|
return errors.Wrapf(err, "failed to open pipe")
|
||||||
|
@ -390,17 +464,11 @@ func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath, slir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
childIP := slirp4netnsIP
|
slirp4netnsIP, err := GetSlirp4netnsIP(ctr.slirp4netnsSubnet)
|
||||||
// set the correct childIP when a custom cidr is set
|
if err != nil {
|
||||||
if slirp4CIDR != "" {
|
return errors.Wrapf(err, "failed to get slirp4ns ip")
|
||||||
_, 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()
|
|
||||||
}
|
}
|
||||||
|
childIP := slirp4netnsIP.String()
|
||||||
outer:
|
outer:
|
||||||
for _, r := range ctr.state.NetworkStatus {
|
for _, r := range ctr.state.NetworkStatus {
|
||||||
for _, i := range r.IPs {
|
for _, i := range r.IPs {
|
||||||
|
|
|
@ -162,6 +162,27 @@ load helpers
|
||||||
done
|
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
|
# "network create" now works rootless, with the help of a special container
|
||||||
@test "podman network create" {
|
@test "podman network create" {
|
||||||
myport=54322
|
myport=54322
|
||||||
|
|
Loading…
Reference in New Issue