diff --git a/grpc.go b/grpc.go index c0cfe38c95..b4946bd8b8 100644 --- a/grpc.go +++ b/grpc.go @@ -648,13 +648,12 @@ func (a *agentGRPC) CreateContainer(ctx context.Context, req *pb.CreateContainer return emptyResp, err } - if ociSpec.Linux.Resources.CPU != nil && ociSpec.Linux.Resources.CPU.Cpus != "" { - availableCpuset, err := getAvailableCpusetList(ociSpec.Linux.Resources.CPU.Cpus) - if err != nil { - return emptyResp, err - } + if err := a.handleCPUSet(ociSpec); err != nil { + return emptyResp, err + } - ociSpec.Linux.Resources.CPU.Cpus = availableCpuset + if err := a.applyNetworkSysctls(ociSpec); err != nil { + return emptyResp, err } if a.sandbox.guestHooksPresent { @@ -699,6 +698,53 @@ func (a *agentGRPC) CreateContainer(ctx context.Context, req *pb.CreateContainer return a.finishCreateContainer(ctr, req, config) } +// Path overridden in unit tests +var procSysDir = "/proc/sys" + +// writeSystemProperty writes the value to a path under /proc/sys as determined from the key. +// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward. +func writeSystemProperty(key, value string) error { + keyPath := strings.Replace(key, ".", "/", -1) + return ioutil.WriteFile(filepath.Join(procSysDir, keyPath), []byte(value), 0644) +} + +func isNetworkSysctl(sysctl string) bool { + return strings.HasPrefix(sysctl, "net.") +} + +// libcontainer checks if the container is running in a separate network namespace +// before applying the network related sysctls. If it sees that the network namespace of the container +// is the same as the "host", it errors out. Since we do no create a new net namespace inside the guest, +// libcontainer would error out while verifying network sysctls. To overcome this, we dont pass +// network sysctls to libcontainer, we instead have the agent directly apply them. All other namespaced +// sysctls are applied by libcontainer. +func (a *agentGRPC) applyNetworkSysctls(ociSpec *specs.Spec) error { + sysctls := ociSpec.Linux.Sysctl + for key, value := range sysctls { + if isNetworkSysctl(key) { + if err := writeSystemProperty(key, value); err != nil { + return err + } + delete(sysctls, key) + } + } + + ociSpec.Linux.Sysctl = sysctls + return nil +} + +func (a *agentGRPC) handleCPUSet(ociSpec *specs.Spec) error { + if ociSpec.Linux.Resources.CPU != nil && ociSpec.Linux.Resources.CPU.Cpus != "" { + availableCpuset, err := getAvailableCpusetList(ociSpec.Linux.Resources.CPU.Cpus) + if err != nil { + return err + } + + ociSpec.Linux.Resources.CPU.Cpus = availableCpuset + } + return nil +} + func posixRlimitsToRlimits(posixRlimits []specs.POSIXRlimit) []configs.Rlimit { var rlimits []configs.Rlimit diff --git a/grpc_test.go b/grpc_test.go index 7979d97873..f4f414cbbc 100644 --- a/grpc_test.go +++ b/grpc_test.go @@ -357,6 +357,98 @@ func TestListProcesses(t *testing.T) { assert.NotEmpty(r.ProcessList) } +func TestIsNetworkSysctl(t *testing.T) { + assert := assert.New(t) + + sysctl := "net.core.somaxconn" + isNet := isNetworkSysctl(sysctl) + assert.True(isNet) + + sysctl = "kernel.shmmax" + isNet = isNetworkSysctl(sysctl) + assert.False(isNet) +} + +func TestWriteSystemProperty(t *testing.T) { + assert := assert.New(t) + + tmpDir, err := ioutil.TempDir("", "procsys") + assert.Nil(err) + defer os.RemoveAll(tmpDir) + + key := "net.core.somaxconn" + value := "1024" + procSysDir = filepath.Join(tmpDir, "proc", "sys") + err = os.MkdirAll(procSysDir, 0755) + assert.Nil(err) + + netCoreDir := filepath.Join(procSysDir, "net", "core") + err = os.MkdirAll(netCoreDir, 0755) + assert.Nil(err) + + sysFile := filepath.Join(netCoreDir, "somaxconn") + fd, err := os.Create(sysFile) + assert.Nil(err) + fd.Close() + + err = writeSystemProperty(key, value) + assert.Nil(err) + + // Read file and verify + content, err := ioutil.ReadFile(sysFile) + assert.Nil(err) + assert.Equal(value, string(content)) + + // Following checks require root privileges to remove a read-only dir + if os.Geteuid() != 0 { + return + } + + // Remove write permissions for procSysDir to what they normally are + // for /proc/sys so that files cannot be created + err = os.Chmod(procSysDir, 0555) + assert.Nil(err) + + // Nonexistent sys file + key = "net.ipv4.ip_forward" + value = "1" + err = writeSystemProperty(key, value) + assert.NotNil(err) +} + +func TestApplyNetworkSysctls(t *testing.T) { + assert := assert.New(t) + a := &agentGRPC{} + + spec := &specs.Spec{} + spec.Linux = &specs.Linux{} + + spec.Linux.Sysctl = make(map[string]string) + spec.Linux.Sysctl["kernel.shmmax"] = "512" + + err := a.applyNetworkSysctls(spec) + assert.Nil(err) + assert.Equal(len(spec.Linux.Sysctl), 1) + assert.Equal(spec.Linux.Sysctl["kernel.shmmax"], "512") + + // Check with network sysctl + spec.Linux.Sysctl["net.core.somaxconn"] = "1024" + tmpDir, err := ioutil.TempDir("", "procsys") + assert.Nil(err) + defer os.RemoveAll(tmpDir) + + procSysDir = filepath.Join(tmpDir, "proc", "sys") + netCoreDir := filepath.Join(procSysDir, "net", "core") + err = os.MkdirAll(netCoreDir, 0755) + assert.Nil(err) + + assert.Equal(len(spec.Linux.Sysctl), 2) + err = a.applyNetworkSysctls(spec) + assert.Nil(err) + assert.Equal(len(spec.Linux.Sysctl), 1) + assert.Equal(spec.Linux.Sysctl["kernel.shmmax"], "512") +} + func TestUpdateContainer(t *testing.T) { containerID := "1" assert := assert.New(t)