Allow IPv6 addresses to be enabled/disabled in runsc based on the sysctl.

By default in runsc, we have both IPv4 and IPv6 addresses enabled on all the
interfaces. However, in runc this is based on the sysctl
net.ipv6.conf.all.disable_ipv6. This CL will make runsc behave similar to runc.
- If net.ipv6.conf.all.disable_ipv6 is > 0, then only IPv4 addresses will be
enabled on the interfaces including loopback when network mode is "sandbox".

PiperOrigin-RevId: 737656607
This commit is contained in:
Nayana Bidari
2025-03-17 10:10:46 -07:00
committed by gVisor bot
parent 6b0a0af862
commit e6b6f2aa11
4 changed files with 144 additions and 21 deletions
+4 -4
View File
@@ -422,13 +422,13 @@ func (c *Container) Start(conf *config.Config) error {
func (c *Container) Restore(conf *config.Config, imagePath string, direct, background bool) error {
log.Debugf("Restore container, cid: %s", c.ID)
restore := func(conf *config.Config) error {
return c.Sandbox.Restore(conf, c.ID, imagePath, direct, background)
restore := func(conf *config.Config, spec *specs.Spec) error {
return c.Sandbox.Restore(conf, spec, c.ID, imagePath, direct, background)
}
return c.startImpl(conf, "restore", restore, c.Sandbox.RestoreSubcontainer)
}
func (c *Container) startImpl(conf *config.Config, action string, startRoot func(conf *config.Config) error, startSub func(spec *specs.Spec, conf *config.Config, cid string, stdios, goferFiles, goferFilestores []*os.File, devIOFile *os.File, goferConfs []boot.GoferMountConf) error) error {
func (c *Container) startImpl(conf *config.Config, action string, startRoot func(conf *config.Config, spec *specs.Spec) error, startSub func(spec *specs.Spec, conf *config.Config, cid string, stdios, goferFiles, goferFilestores []*os.File, devIOFile *os.File, goferConfs []boot.GoferMountConf) error) error {
if err := c.Saver.lock(BlockAcquire); err != nil {
return err
}
@@ -446,7 +446,7 @@ func (c *Container) startImpl(conf *config.Config, action string, startRoot func
}
if isRoot(c.Spec) {
if err := startRoot(conf); err != nil {
if err := startRoot(conf, c.Spec); err != nil {
return err
}
} else {
+85
View File
@@ -18,6 +18,7 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"math"
"math/rand"
"os"
@@ -4077,3 +4078,87 @@ func TestCheckpointResume(t *testing.T) {
})
}
}
func TestIPv6DisableAllSysctl(t *testing.T) {
tests := []struct {
name string
ipv6Disabled bool
}{
{"IPv6Disabled", true},
{"IPv6Enabled", false},
}
for name, conf := range configs(t, true /* noOverlay */) {
for _, test := range tests {
t.Run(test.name+name, func(t *testing.T) {
dir, err := os.MkdirTemp(testutil.TmpDir(), "ipv6-test")
if err != nil {
t.Fatalf("os.MkdirTemp failed: %v", err)
}
defer os.RemoveAll(dir)
if err := os.Chmod(dir, 0777); err != nil {
t.Fatalf("error chmoding file: %q, %v", dir, err)
}
outputPath := filepath.Join(dir, "output")
outputFile, err := createWriteableOutputFile(outputPath)
if err != nil {
t.Fatalf("error creating output file: %v", err)
}
defer outputFile.Close()
script := fmt.Sprintf("ip addr >> %q", outputPath)
spec := testutil.NewSpecWithArgs("bash", "-c", script)
conf.Network = config.NetworkSandbox
if test.ipv6Disabled {
spec.Linux = &specs.Linux{}
spec.Linux.Sysctl = make(map[string]string)
spec.Linux.Sysctl["net.ipv6.conf.all.disable_ipv6"] = "1"
}
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()
// Create and start the container.
args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
cont, err := New(conf, args)
if err != nil {
t.Fatalf("error creating container: %v", err)
}
if err := cont.Start(conf); err != nil {
t.Fatalf("error starting container: %v", err)
}
// Wait until application has ran.
if err := waitForFileNotEmpty(outputFile); err != nil {
// This can happen when the network does not
// have any network interfaces configured.
// We cannot test whether the sysctl works
// properly in this case. Log a warning and
// return.
t.Logf("No network interfaces are configured: %v", err)
return
}
content, err := ioutil.ReadFile(outputPath)
if err != nil {
fmt.Println("Error reading file:", err)
return
}
res := strings.Contains(string(content), "inet6")
if test.ipv6Disabled && res {
t.Fatalf("IPv6 address present when IPv6 is disabled on all interfaces")
}
if !test.ipv6Disabled && !res {
t.Fatalf("IPv6 address not present when IPv6 is enabled on all interfaces")
}
})
}
}
}
+26 -13
View File
@@ -52,7 +52,7 @@ import (
// Run the following container to test it:
//
// docker run -di --runtime=runsc -p 8080:80 -v $PWD:/usr/local/apache2/htdocs/ httpd:2.4
func setupNetwork(conn *urpc.Client, pid int, conf *config.Config) error {
func setupNetwork(conn *urpc.Client, pid int, conf *config.Config, disableIPv6 bool) error {
log.Infof("Setting up network")
switch conf.Network {
@@ -65,7 +65,7 @@ func setupNetwork(conn *urpc.Client, pid int, conf *config.Config) error {
// Build the path to the net namespace of the sandbox process.
// This is what we will copy.
nsPath := filepath.Join("/proc", strconv.Itoa(pid), "ns/net")
if err := createInterfacesAndRoutesFromNS(conn, nsPath, conf); err != nil {
if err := createInterfacesAndRoutesFromNS(conn, nsPath, conf, disableIPv6); err != nil {
return fmt.Errorf("creating interfaces from net namespace %q: %v", nsPath, err)
}
case config.NetworkHost:
@@ -125,7 +125,7 @@ func isRootNetNS() (bool, error) {
// createInterfacesAndRoutesFromNS scrapes the interface and routes from the
// net namespace with the given path, creates them in the sandbox, and removes
// them from the host.
func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *config.Config) error {
func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *config.Config, disableIPv6 bool) error {
switch conf.XDP.Mode {
case config.XDPModeOff:
case config.XDPModeNS:
@@ -168,6 +168,7 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *con
args := boot.CreateLinksAndRoutesArgs{
DisconnectOk: conf.NetDisconnectOk,
}
for _, iface := range ifaces {
if iface.Flags&net.FlagUp == 0 {
log.Infof("Skipping down interface: %+v", iface)
@@ -181,7 +182,7 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *con
// We build our own loopback device.
if iface.Flags&net.FlagLoopback != 0 {
link, err := loopbackLink(conf, iface, allAddrs)
link, err := loopbackLink(conf, iface, allAddrs, disableIPv6)
if err != nil {
return fmt.Errorf("getting loopback link for iface %q: %w", iface.Name, err)
}
@@ -195,6 +196,10 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *con
if !ok {
return fmt.Errorf("address is not IPNet: %+v", ifaddr)
}
// Do not add IPv6 addresses when IPv6 is disabled.
if disableIPv6 && ipNet.IP.To4() == nil {
continue
}
ipAddrs = append(ipAddrs, ipNet)
}
if len(ipAddrs) == 0 {
@@ -221,7 +226,7 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *con
// Scrape the routes before removing the address, since that
// will remove the routes as well.
routes, defv4, defv6, err := routesForIface(iface)
routes, defv4, defv6, err := routesForIface(iface, disableIPv6)
if err != nil {
return fmt.Errorf("getting routes for interface %q: %v", iface.Name, err)
}
@@ -460,7 +465,7 @@ func createSocket(iface net.Interface, ifaceLink netlink.Link, enableGSO bool) (
// loopbackLink returns the link with addresses and routes for a loopback
// interface.
func loopbackLink(conf *config.Config, iface net.Interface, addrs []net.Addr) (boot.LoopbackLink, error) {
func loopbackLink(conf *config.Config, iface net.Interface, addrs []net.Addr, disableIPv6 bool) (boot.LoopbackLink, error) {
link := boot.LoopbackLink{
Name: iface.Name,
GVisorGRO: conf.GVisorGRO,
@@ -471,6 +476,9 @@ func loopbackLink(conf *config.Config, iface net.Interface, addrs []net.Addr) (b
return boot.LoopbackLink{}, fmt.Errorf("address is not IPNet: %+v", addr)
}
if disableIPv6 && ipNet.IP.To4() == nil {
continue
}
prefix, _ := ipNet.Mask.Size()
link.Addresses = append(link.Addresses, boot.IPWithPrefix{
Address: ipNet.IP,
@@ -488,7 +496,7 @@ func loopbackLink(conf *config.Config, iface net.Interface, addrs []net.Addr) (b
// routesForIface iterates over all routes for the given interface and converts
// them to boot.Routes. It also returns the a default v4/v6 route if found.
func routesForIface(iface net.Interface) ([]boot.Route, *boot.Route, *boot.Route, error) {
func routesForIface(iface net.Interface, disableIPv6 bool) ([]boot.Route, *boot.Route, *boot.Route, error) {
link, err := netlink.LinkByIndex(iface.Index)
if err != nil {
return nil, nil, nil, err
@@ -524,12 +532,14 @@ func routesForIface(iface net.Interface) ([]boot.Route, *boot.Route, *boot.Route
return nil, nil, nil, fmt.Errorf("more than one default route found %q, def: %+v, route: %+v", iface.Name, defv6, r)
}
defv6 = &boot.Route{
Destination: net.IPNet{
IP: net.IPv6zero,
Mask: net.IPMask(net.IPv6zero),
},
Gateway: r.Gw,
if !disableIPv6 {
defv6 = &boot.Route{
Destination: net.IPNet{
IP: net.IPv6zero,
Mask: net.IPMask(net.IPv6zero),
},
Gateway: r.Gw,
}
}
default:
return nil, nil, nil, fmt.Errorf("unexpected address size for gateway: %+v for route: %+v", r.Gw, r)
@@ -538,6 +548,9 @@ func routesForIface(iface net.Interface) ([]boot.Route, *boot.Route, *boot.Route
}
dst := *r.Dst
if disableIPv6 && dst.IP.To4() == nil {
continue
}
dst.IP = dst.IP.Mask(dst.Mask)
routes = append(routes, boot.Route{
Destination: dst,
+29 -4
View File
@@ -407,8 +407,23 @@ func (s *Sandbox) CreateSubcontainer(conf *config.Config, cid string, tty *os.Fi
return nil
}
func getDisableIPv6(spec *specs.Spec) (bool, error) {
if spec.Linux == nil || spec.Linux.Sysctl == nil {
return false, nil
}
val, ok := spec.Linux.Sysctl["net.ipv6.conf.all.disable_ipv6"]
if !ok {
return false, nil
}
valInt, err := strconv.Atoi(val)
if err != nil {
return false, fmt.Errorf("getting net.ipv6.conf.all.disable_ipv6=%s: %w", val, err)
}
return valInt != 0, nil
}
// StartRoot starts running the root container process inside the sandbox.
func (s *Sandbox) StartRoot(conf *config.Config) error {
func (s *Sandbox) StartRoot(conf *config.Config, spec *specs.Spec) error {
if err := hostsettings.Handle(conf); err != nil {
return fmt.Errorf("host settings: %w (use --host-settings=ignore to bypass)", err)
}
@@ -420,8 +435,13 @@ func (s *Sandbox) StartRoot(conf *config.Config) error {
}
defer conn.Close()
var disableIPv6 bool
disableIPv6, err = getDisableIPv6(spec)
if err != nil {
return err
}
// Configure the network.
if err := setupNetwork(conn, pid, conf); err != nil {
if err := setupNetwork(conn, pid, conf, disableIPv6); err != nil {
return fmt.Errorf("setting up network: %w", err)
}
@@ -472,7 +492,7 @@ func (s *Sandbox) StartSubcontainer(spec *specs.Spec, conf *config.Config, cid s
}
// Restore sends the restore call for a container in the sandbox.
func (s *Sandbox) Restore(conf *config.Config, cid string, imagePath string, direct, background bool) error {
func (s *Sandbox) Restore(conf *config.Config, spec *specs.Spec, cid string, imagePath string, direct, background bool) error {
if err := hostsettings.Handle(conf); err != nil {
return fmt.Errorf("host settings: %w (use --host-settings=ignore to bypass)", err)
}
@@ -535,8 +555,13 @@ func (s *Sandbox) Restore(conf *config.Config, cid string, imagePath string, dir
}
defer conn.Close()
var disableIPv6 bool
disableIPv6, err = getDisableIPv6(spec)
if err != nil {
return err
}
// Configure the network.
if err := setupNetwork(conn, s.Pid.load(), conf); err != nil {
if err := setupNetwork(conn, s.Pid.load(), conf, disableIPv6); err != nil {
return fmt.Errorf("setting up network: %v", err)
}