summaryrefslogtreecommitdiffhomepage
path: root/runsc/config
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/config')
-rw-r--r--runsc/config/BUILD28
-rw-r--r--runsc/config/config.go339
-rw-r--r--runsc/config/config_test.go272
-rw-r--r--runsc/config/flags.go205
4 files changed, 844 insertions, 0 deletions
diff --git a/runsc/config/BUILD b/runsc/config/BUILD
new file mode 100644
index 000000000..b1672bb9d
--- /dev/null
+++ b/runsc/config/BUILD
@@ -0,0 +1,28 @@
+load("//tools:defs.bzl", "go_library", "go_test")
+
+package(licenses = ["notice"])
+
+go_library(
+ name = "config",
+ srcs = [
+ "config.go",
+ "flags.go",
+ ],
+ visibility = ["//:sandbox"],
+ deps = [
+ "//pkg/refs",
+ "//pkg/sentry/watchdog",
+ "//pkg/sync",
+ "//runsc/flag",
+ ],
+)
+
+go_test(
+ name = "config_test",
+ size = "small",
+ srcs = [
+ "config_test.go",
+ ],
+ library = ":config",
+ deps = ["//runsc/flag"],
+)
diff --git a/runsc/config/config.go b/runsc/config/config.go
new file mode 100644
index 000000000..f30f79f68
--- /dev/null
+++ b/runsc/config/config.go
@@ -0,0 +1,339 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package config provides basic infrastructure to set configuration settings
+// for runsc. The configuration is set by flags to the command line. They can
+// also propagate to a different process using the same flags.
+package config
+
+import (
+ "fmt"
+
+ "gvisor.dev/gvisor/pkg/refs"
+ "gvisor.dev/gvisor/pkg/sentry/watchdog"
+)
+
+// Config holds configuration that is not part of the runtime spec.
+//
+// Follow these steps to add a new flag:
+// 1. Create a new field in Config.
+// 2. Add a field tag with the flag name
+// 3. Register a new flag in flags.go, with name and description
+// 4. Add any necessary validation into validate()
+// 5. If adding an enum, follow the same pattern as FileAccessType
+//
+type Config struct {
+ // RootDir is the runtime root directory.
+ RootDir string `flag:"root"`
+
+ // Debug indicates that debug logging should be enabled.
+ Debug bool `flag:"debug"`
+
+ // LogFilename is the filename to log to, if not empty.
+ LogFilename string `flag:"log"`
+
+ // LogFormat is the log format.
+ LogFormat string `flag:"log-format"`
+
+ // DebugLog is the path to log debug information to, if not empty.
+ DebugLog string `flag:"debug-log"`
+
+ // PanicLog is the path to log GO's runtime messages, if not empty.
+ PanicLog string `flag:"panic-log"`
+
+ // DebugLogFormat is the log format for debug.
+ DebugLogFormat string `flag:"debug-log-format"`
+
+ // FileAccess indicates how the filesystem is accessed.
+ FileAccess FileAccessType `flag:"file-access"`
+
+ // Overlay is whether to wrap the root filesystem in an overlay.
+ Overlay bool `flag:"overlay"`
+
+ // FSGoferHostUDS enables the gofer to mount a host UDS.
+ FSGoferHostUDS bool `flag:"fsgofer-host-uds"`
+
+ // Network indicates what type of network to use.
+ Network NetworkType `flag:"network"`
+
+ // EnableRaw indicates whether raw sockets should be enabled. Raw
+ // sockets are disabled by stripping CAP_NET_RAW from the list of
+ // capabilities.
+ EnableRaw bool `flag:"net-raw"`
+
+ // HardwareGSO indicates that hardware segmentation offload is enabled.
+ HardwareGSO bool `flag:"gso"`
+
+ // SoftwareGSO indicates that software segmentation offload is enabled.
+ SoftwareGSO bool `flag:"software-gso"`
+
+ // TXChecksumOffload indicates that TX Checksum Offload is enabled.
+ TXChecksumOffload bool `flag:"tx-checksum-offload"`
+
+ // RXChecksumOffload indicates that RX Checksum Offload is enabled.
+ RXChecksumOffload bool `flag:"rx-checksum-offload"`
+
+ // QDisc indicates the type of queuening discipline to use by default
+ // for non-loopback interfaces.
+ QDisc QueueingDiscipline `flag:"qdisc"`
+
+ // LogPackets indicates that all network packets should be logged.
+ LogPackets bool `flag:"log-packets"`
+
+ // Platform is the platform to run on.
+ Platform string `flag:"platform"`
+
+ // Strace indicates that strace should be enabled.
+ Strace bool `flag:"strace"`
+
+ // StraceSyscalls is the set of syscalls to trace (comma-separated values).
+ // If StraceEnable is true and this string is empty, then all syscalls will
+ // be traced.
+ StraceSyscalls string `flag:"strace-syscalls"`
+
+ // StraceLogSize is the max size of data blobs to display.
+ StraceLogSize uint `flag:"strace-log-size"`
+
+ // DisableSeccomp indicates whether seccomp syscall filters should be
+ // disabled. Pardon the double negation, but default to enabled is important.
+ DisableSeccomp bool
+
+ // WatchdogAction sets what action the watchdog takes when triggered.
+ WatchdogAction watchdog.Action `flag:"watchdog-action"`
+
+ // PanicSignal registers signal handling that panics. Usually set to
+ // SIGUSR2(12) to troubleshoot hangs. -1 disables it.
+ PanicSignal int `flag:"panic-signal"`
+
+ // ProfileEnable is set to prepare the sandbox to be profiled.
+ ProfileEnable bool `flag:"profile"`
+
+ // RestoreFile is the path to the saved container image
+ RestoreFile string
+
+ // NumNetworkChannels controls the number of AF_PACKET sockets that map
+ // to the same underlying network device. This allows netstack to better
+ // scale for high throughput use cases.
+ NumNetworkChannels int `flag:"num-network-channels"`
+
+ // Rootless allows the sandbox to be started with a user that is not root.
+ // Defense is depth measures are weaker with rootless. Specifically, the
+ // sandbox and Gofer process run as root inside a user namespace with root
+ // mapped to the caller's user.
+ Rootless bool `flag:"rootless"`
+
+ // AlsoLogToStderr allows to send log messages to stderr.
+ AlsoLogToStderr bool `flag:"alsologtostderr"`
+
+ // ReferenceLeakMode sets reference leak check mode
+ ReferenceLeak refs.LeakMode `flag:"ref-leak-mode"`
+
+ // OverlayfsStaleRead instructs the sandbox to assume that the root mount
+ // is on a Linux overlayfs mount, which does not necessarily preserve
+ // coherence between read-only and subsequent writable file descriptors
+ // representing the "same" file.
+ OverlayfsStaleRead bool `flag:"overlayfs-stale-read"`
+
+ // CPUNumFromQuota sets CPU number count to available CPU quota, using
+ // least integer value greater than or equal to quota.
+ //
+ // E.g. 0.2 CPU quota will result in 1, and 1.9 in 2.
+ CPUNumFromQuota bool `flag:"cpu-num-from-quota"`
+
+ // Enables VFS2.
+ VFS2 bool `flag:"vfs2"`
+
+ // Enables FUSE usage.
+ FUSE bool `flag:"fuse"`
+
+ // Allows overriding of flags in OCI annotations.
+ AllowFlagOverride bool `flag:"allow-flag-override"`
+
+ // Enables seccomp inside the sandbox.
+ OCISeccomp bool `flag:"oci-seccomp"`
+
+ // TestOnlyAllowRunAsCurrentUserWithoutChroot should only be used in
+ // tests. It allows runsc to start the sandbox process as the current
+ // user, and without chrooting the sandbox process. This can be
+ // necessary in test environments that have limited capabilities.
+ TestOnlyAllowRunAsCurrentUserWithoutChroot bool `flag:"TESTONLY-unsafe-nonroot"`
+
+ // TestOnlyTestNameEnv should only be used in tests. It looks up for the
+ // test name in the container environment variables and adds it to the debug
+ // log file name. This is done to help identify the log with the test when
+ // multiple tests are run in parallel, since there is no way to pass
+ // parameters to the runtime from docker.
+ TestOnlyTestNameEnv string `flag:"TESTONLY-test-name-env"`
+}
+
+func (c *Config) validate() error {
+ if c.FileAccess == FileAccessShared && c.Overlay {
+ return fmt.Errorf("overlay flag is incompatible with shared file access")
+ }
+ if c.NumNetworkChannels <= 0 {
+ return fmt.Errorf("num_network_channels must be > 0, got: %d", c.NumNetworkChannels)
+ }
+ return nil
+}
+
+// FileAccessType tells how the filesystem is accessed.
+type FileAccessType int
+
+const (
+ // FileAccessExclusive is the same as FileAccessShared, but enables
+ // extra caching for improved performance. It should only be used if
+ // the sandbox has exclusive access to the filesystem.
+ FileAccessExclusive FileAccessType = iota
+
+ // FileAccessShared sends IO requests to a Gofer process that validates the
+ // requests and forwards them to the host.
+ FileAccessShared
+)
+
+func fileAccessTypePtr(v FileAccessType) *FileAccessType {
+ return &v
+}
+
+// Set implements flag.Value.
+func (f *FileAccessType) Set(v string) error {
+ switch v {
+ case "shared":
+ *f = FileAccessShared
+ case "exclusive":
+ *f = FileAccessExclusive
+ default:
+ return fmt.Errorf("invalid file access type %q", v)
+ }
+ return nil
+}
+
+// Get implements flag.Value.
+func (f *FileAccessType) Get() interface{} {
+ return *f
+}
+
+// String implements flag.Value.
+func (f *FileAccessType) String() string {
+ switch *f {
+ case FileAccessShared:
+ return "shared"
+ case FileAccessExclusive:
+ return "exclusive"
+ }
+ panic(fmt.Sprintf("Invalid file access type %v", *f))
+}
+
+// NetworkType tells which network stack to use.
+type NetworkType int
+
+const (
+ // NetworkSandbox uses internal network stack, isolated from the host.
+ NetworkSandbox NetworkType = iota
+
+ // NetworkHost redirects network related syscalls to the host network.
+ NetworkHost
+
+ // NetworkNone sets up just loopback using netstack.
+ NetworkNone
+)
+
+func networkTypePtr(v NetworkType) *NetworkType {
+ return &v
+}
+
+// Set implements flag.Value.
+func (n *NetworkType) Set(v string) error {
+ switch v {
+ case "sandbox":
+ *n = NetworkSandbox
+ case "host":
+ *n = NetworkHost
+ case "none":
+ *n = NetworkNone
+ default:
+ return fmt.Errorf("invalid network type %q", v)
+ }
+ return nil
+}
+
+// Get implements flag.Value.
+func (n *NetworkType) Get() interface{} {
+ return *n
+}
+
+// String implements flag.Value.
+func (n *NetworkType) String() string {
+ switch *n {
+ case NetworkSandbox:
+ return "sandbox"
+ case NetworkHost:
+ return "host"
+ case NetworkNone:
+ return "none"
+ }
+ panic(fmt.Sprintf("Invalid network type %v", *n))
+}
+
+// QueueingDiscipline is used to specify the kind of Queueing Discipline to
+// apply for a give FDBasedLink.
+type QueueingDiscipline int
+
+const (
+ // QDiscNone disables any queueing for the underlying FD.
+ QDiscNone QueueingDiscipline = iota
+
+ // QDiscFIFO applies a simple fifo based queue to the underlying FD.
+ QDiscFIFO
+)
+
+func queueingDisciplinePtr(v QueueingDiscipline) *QueueingDiscipline {
+ return &v
+}
+
+// Set implements flag.Value.
+func (q *QueueingDiscipline) Set(v string) error {
+ switch v {
+ case "none":
+ *q = QDiscNone
+ case "fifo":
+ *q = QDiscFIFO
+ default:
+ return fmt.Errorf("invalid qdisc %q", v)
+ }
+ return nil
+}
+
+// Get implements flag.Value.
+func (q *QueueingDiscipline) Get() interface{} {
+ return *q
+}
+
+// String implements flag.Value.
+func (q *QueueingDiscipline) String() string {
+ switch *q {
+ case QDiscNone:
+ return "none"
+ case QDiscFIFO:
+ return "fifo"
+ }
+ panic(fmt.Sprintf("Invalid qdisc %v", *q))
+}
+
+func leakModePtr(v refs.LeakMode) *refs.LeakMode {
+ return &v
+}
+
+func watchdogActionPtr(v watchdog.Action) *watchdog.Action {
+ return &v
+}
diff --git a/runsc/config/config_test.go b/runsc/config/config_test.go
new file mode 100644
index 000000000..fb162b7eb
--- /dev/null
+++ b/runsc/config/config_test.go
@@ -0,0 +1,272 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+ "strings"
+ "testing"
+
+ "gvisor.dev/gvisor/runsc/flag"
+)
+
+func init() {
+ RegisterFlags()
+}
+
+func TestDefault(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ // "--root" is always set to something different than the default. Reset it
+ // to make it easier to test that default values do not generate flags.
+ c.RootDir = ""
+
+ // All defaults doesn't require setting flags.
+ flags := c.ToFlags()
+ if len(flags) > 0 {
+ t.Errorf("default flags not set correctly for: %s", flags)
+ }
+}
+
+func setDefault(name string) {
+ fl := flag.CommandLine.Lookup(name)
+ fl.Value.Set(fl.DefValue)
+}
+
+func TestFromFlags(t *testing.T) {
+ flag.CommandLine.Lookup("root").Value.Set("some-path")
+ flag.CommandLine.Lookup("debug").Value.Set("true")
+ flag.CommandLine.Lookup("num-network-channels").Value.Set("123")
+ flag.CommandLine.Lookup("network").Value.Set("none")
+ defer func() {
+ setDefault("root")
+ setDefault("debug")
+ setDefault("num-network-channels")
+ setDefault("network")
+ }()
+
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want := "some-path"; c.RootDir != want {
+ t.Errorf("RootDir=%v, want: %v", c.RootDir, want)
+ }
+ if want := true; c.Debug != want {
+ t.Errorf("Debug=%v, want: %v", c.Debug, want)
+ }
+ if want := 123; c.NumNetworkChannels != want {
+ t.Errorf("NumNetworkChannels=%v, want: %v", c.NumNetworkChannels, want)
+ }
+ if want := NetworkNone; c.Network != want {
+ t.Errorf("Network=%v, want: %v", c.Network, want)
+ }
+}
+
+func TestToFlags(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.RootDir = "some-path"
+ c.Debug = true
+ c.NumNetworkChannels = 123
+ c.Network = NetworkNone
+
+ flags := c.ToFlags()
+ if len(flags) != 4 {
+ t.Errorf("wrong number of flags set, want: 4, got: %d: %s", len(flags), flags)
+ }
+ t.Logf("Flags: %s", flags)
+ fm := map[string]string{}
+ for _, f := range flags {
+ kv := strings.Split(f, "=")
+ fm[kv[0]] = kv[1]
+ }
+ for name, want := range map[string]string{
+ "--root": "some-path",
+ "--debug": "true",
+ "--num-network-channels": "123",
+ "--network": "none",
+ } {
+ if got, ok := fm[name]; ok {
+ if got != want {
+ t.Errorf("flag %q, want: %q, got: %q", name, want, got)
+ }
+ } else {
+ t.Errorf("flag %q not set", name)
+ }
+ }
+}
+
+// TestInvalidFlags checks that enum flags fail when value is not in enum set.
+func TestInvalidFlags(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ error string
+ }{
+ {
+ name: "file-access",
+ error: "invalid file access type",
+ },
+ {
+ name: "network",
+ error: "invalid network type",
+ },
+ {
+ name: "qdisc",
+ error: "invalid qdisc",
+ },
+ {
+ name: "watchdog-action",
+ error: "invalid watchdog action",
+ },
+ {
+ name: "ref-leak-mode",
+ error: "invalid ref leak mode",
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ defer setDefault(tc.name)
+ if err := flag.CommandLine.Lookup(tc.name).Value.Set("invalid"); err == nil || !strings.Contains(err.Error(), tc.error) {
+ t.Errorf("flag.Value.Set(invalid) wrong error reported: %v", err)
+ }
+ })
+ }
+}
+
+func TestValidationFail(t *testing.T) {
+ for _, tc := range []struct {
+ name string
+ flags map[string]string
+ error string
+ }{
+ {
+ name: "shared+overlay",
+ flags: map[string]string{
+ "file-access": "shared",
+ "overlay": "true",
+ },
+ error: "overlay flag is incompatible",
+ },
+ {
+ name: "network-channels",
+ flags: map[string]string{
+ "num-network-channels": "-1",
+ },
+ error: "num_network_channels must be > 0",
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ for name, val := range tc.flags {
+ defer setDefault(name)
+ if err := flag.CommandLine.Lookup(name).Value.Set(val); err != nil {
+ t.Errorf("%s=%q: %v", name, val, err)
+ }
+ }
+ if _, err := NewFromFlags(); err == nil || !strings.Contains(err.Error(), tc.error) {
+ t.Errorf("NewFromFlags() wrong error reported: %v", err)
+ }
+ })
+ }
+}
+
+func TestOverride(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.AllowFlagOverride = true
+
+ t.Run("string", func(t *testing.T) {
+ c.RootDir = "foobar"
+ if err := c.Override("root", "bar"); err != nil {
+ t.Fatalf("Override(root, bar) failed: %v", err)
+ }
+ defer setDefault("root")
+ if c.RootDir != "bar" {
+ t.Errorf("Override(root, bar) didn't work: %+v", c)
+ }
+ })
+
+ t.Run("bool", func(t *testing.T) {
+ c.Debug = true
+ if err := c.Override("debug", "false"); err != nil {
+ t.Fatalf("Override(debug, false) failed: %v", err)
+ }
+ defer setDefault("debug")
+ if c.Debug {
+ t.Errorf("Override(debug, false) didn't work: %+v", c)
+ }
+ })
+
+ t.Run("enum", func(t *testing.T) {
+ c.FileAccess = FileAccessShared
+ if err := c.Override("file-access", "exclusive"); err != nil {
+ t.Fatalf("Override(file-access, exclusive) failed: %v", err)
+ }
+ defer setDefault("file-access")
+ if c.FileAccess != FileAccessExclusive {
+ t.Errorf("Override(file-access, exclusive) didn't work: %+v", c)
+ }
+ })
+}
+
+func TestOverrideDisabled(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ const errMsg = "flag override disabled"
+ if err := c.Override("root", "path"); err == nil || !strings.Contains(err.Error(), errMsg) {
+ t.Errorf("Override() wrong error: %v", err)
+ }
+}
+
+func TestOverrideError(t *testing.T) {
+ c, err := NewFromFlags()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.AllowFlagOverride = true
+ for _, tc := range []struct {
+ name string
+ value string
+ error string
+ }{
+ {
+ name: "invalid",
+ value: "valid",
+ error: `flag "invalid" not found`,
+ },
+ {
+ name: "debug",
+ value: "invalid",
+ error: "error setting flag debug",
+ },
+ {
+ name: "file-access",
+ value: "invalid",
+ error: "invalid file access type",
+ },
+ } {
+ t.Run(tc.name, func(t *testing.T) {
+ if err := c.Override(tc.name, tc.value); err == nil || !strings.Contains(err.Error(), tc.error) {
+ t.Errorf("Override(%q, %q) wrong error: %v", tc.name, tc.value, err)
+ }
+ })
+ }
+}
diff --git a/runsc/config/flags.go b/runsc/config/flags.go
new file mode 100644
index 000000000..a5f25cfa2
--- /dev/null
+++ b/runsc/config/flags.go
@@ -0,0 +1,205 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package config
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "reflect"
+ "strconv"
+
+ "gvisor.dev/gvisor/pkg/refs"
+ "gvisor.dev/gvisor/pkg/sentry/watchdog"
+ "gvisor.dev/gvisor/pkg/sync"
+ "gvisor.dev/gvisor/runsc/flag"
+)
+
+var registration sync.Once
+
+// This is the set of flags used to populate Config.
+func RegisterFlags() {
+ registration.Do(func() {
+ // Although these flags are not part of the OCI spec, they are used by
+ // Docker, and thus should not be changed.
+ flag.String("root", "", "root directory for storage of container state.")
+ flag.String("log", "", "file path where internal debug information is written, default is stdout.")
+ flag.String("log-format", "text", "log format: text (default), json, or json-k8s.")
+ flag.Bool("debug", false, "enable debug logging.")
+
+ // These flags are unique to runsc, and are used to configure parts of the
+ // system that are not covered by the runtime spec.
+
+ // Debugging flags.
+ flag.String("debug-log", "", "additional location for logs. If it ends with '/', log files are created inside the directory with default names. The following variables are available: %TIMESTAMP%, %COMMAND%.")
+ flag.String("panic-log", "", "file path were panic reports and other Go's runtime messages are written.")
+ flag.Bool("log-packets", false, "enable network packet logging.")
+ flag.String("debug-log-format", "text", "log format: text (default), json, or json-k8s.")
+ flag.Bool("alsologtostderr", false, "send log messages to stderr.")
+ flag.Bool("allow-flag-override", false, "allow OCI annotations (dev.gvisor.flag.<name>) to override flags for debugging.")
+
+ // Debugging flags: strace related
+ flag.Bool("strace", false, "enable strace.")
+ flag.String("strace-syscalls", "", "comma-separated list of syscalls to trace. If --strace is true and this list is empty, then all syscalls will be traced.")
+ flag.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs.")
+
+ // Flags that control sandbox runtime behavior.
+ flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm.")
+ flag.Var(watchdogActionPtr(watchdog.LogWarning), "watchdog-action", "sets what action the watchdog takes when triggered: log (default), panic.")
+ flag.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.")
+ flag.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).")
+ flag.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.")
+ flag.Var(leakModePtr(refs.NoLeakChecking), "ref-leak-mode", "sets reference leak check mode: disabled (default), log-names, log-traces.")
+ flag.Bool("cpu-num-from-quota", false, "set cpu number to cpu quota (least integer greater or equal to quota value, but not less than 2)")
+ flag.Bool("oci-seccomp", false, "Enables loading OCI seccomp filters inside the sandbox.")
+
+ // Flags that control sandbox runtime behavior: FS related.
+ flag.Var(fileAccessTypePtr(FileAccessExclusive), "file-access", "specifies which filesystem to use for the root mount: exclusive (default), shared. Volume mounts are always shared.")
+ flag.Bool("overlay", false, "wrap filesystem mounts with writable overlay. All modifications are stored in memory inside the sandbox.")
+ flag.Bool("overlayfs-stale-read", true, "assume root mount is an overlay filesystem")
+ flag.Bool("fsgofer-host-uds", false, "allow the gofer to mount Unix Domain Sockets.")
+ flag.Bool("vfs2", false, "TEST ONLY; use while VFSv2 is landing. This uses the new experimental VFS layer.")
+ flag.Bool("fuse", false, "TEST ONLY; use while FUSE in VFSv2 is landing. This allows the use of the new experimental FUSE filesystem.")
+
+ // Flags that control sandbox runtime behavior: network related.
+ flag.Var(networkTypePtr(NetworkSandbox), "network", "specifies which network to use: sandbox (default), host, none. Using network inside the sandbox is more secure because it's isolated from the host network.")
+ flag.Bool("net-raw", false, "enable raw sockets. When false, raw sockets are disabled by removing CAP_NET_RAW from containers (`runsc exec` will still be able to utilize raw sockets). Raw sockets allow malicious containers to craft packets and potentially attack the network.")
+ flag.Bool("gso", true, "enable hardware segmentation offload if it is supported by a network device.")
+ flag.Bool("software-gso", true, "enable software segmentation offload when hardware offload can't be enabled.")
+ flag.Bool("tx-checksum-offload", false, "enable TX checksum offload.")
+ flag.Bool("rx-checksum-offload", true, "enable RX checksum offload.")
+ flag.Var(queueingDisciplinePtr(QDiscFIFO), "qdisc", "specifies which queueing discipline to apply by default to the non loopback nics used by the sandbox.")
+ flag.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.")
+
+ // Test flags, not to be used outside tests, ever.
+ flag.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.")
+ flag.String("TESTONLY-test-name-env", "", "TEST ONLY; do not ever use! Used for automated tests to improve logging.")
+ })
+}
+
+// NewFromFlags creates a new Config with values coming from command line flags.
+func NewFromFlags() (*Config, error) {
+ conf := &Config{}
+
+ obj := reflect.ValueOf(conf).Elem()
+ st := obj.Type()
+ for i := 0; i < st.NumField(); i++ {
+ f := st.Field(i)
+ name, ok := f.Tag.Lookup("flag")
+ if !ok {
+ // No flag set for this field.
+ continue
+ }
+ fl := flag.CommandLine.Lookup(name)
+ if fl == nil {
+ panic(fmt.Sprintf("Flag %q not found", name))
+ }
+ x := reflect.ValueOf(flag.Get(fl.Value))
+ obj.Field(i).Set(x)
+ }
+
+ if len(conf.RootDir) == 0 {
+ // If not set, set default root dir to something (hopefully) user-writeable.
+ conf.RootDir = "/var/run/runsc"
+ if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
+ conf.RootDir = filepath.Join(runtimeDir, "runsc")
+ }
+ }
+
+ if err := conf.validate(); err != nil {
+ return nil, err
+ }
+ return conf, nil
+}
+
+// ToFlags returns a slice of flags that correspond to the given Config.
+func (c *Config) ToFlags() []string {
+ var rv []string
+
+ obj := reflect.ValueOf(c).Elem()
+ st := obj.Type()
+ for i := 0; i < st.NumField(); i++ {
+ f := st.Field(i)
+ name, ok := f.Tag.Lookup("flag")
+ if !ok {
+ // No flag set for this field.
+ continue
+ }
+ val := getVal(obj.Field(i))
+
+ flag := flag.CommandLine.Lookup(name)
+ if flag == nil {
+ panic(fmt.Sprintf("Flag %q not found", name))
+ }
+ if val == flag.DefValue {
+ continue
+ }
+ rv = append(rv, fmt.Sprintf("--%s=%s", flag.Name, val))
+ }
+ return rv
+}
+
+// Override writes a new value to a flag.
+func (c *Config) Override(name string, value string) error {
+ if !c.AllowFlagOverride {
+ return fmt.Errorf("flag override disabled, use --allow-flag-override to enable it")
+ }
+
+ obj := reflect.ValueOf(c).Elem()
+ st := obj.Type()
+ for i := 0; i < st.NumField(); i++ {
+ f := st.Field(i)
+ fieldName, ok := f.Tag.Lookup("flag")
+ if !ok || fieldName != name {
+ // Not a flag field, or flag name doesn't match.
+ continue
+ }
+ fl := flag.CommandLine.Lookup(name)
+ if fl == nil {
+ // Flag must exist if there is a field match above.
+ panic(fmt.Sprintf("Flag %q not found", name))
+ }
+
+ // Use flag to convert the string value to the underlying flag type, using
+ // the same rules as the command-line for consistency.
+ if err := fl.Value.Set(value); err != nil {
+ return fmt.Errorf("error setting flag %s=%q: %w", name, value, err)
+ }
+ x := reflect.ValueOf(flag.Get(fl.Value))
+ obj.Field(i).Set(x)
+
+ // Validates the config again to ensure it's left in a consistent state.
+ return c.validate()
+ }
+ return fmt.Errorf("flag %q not found. Cannot set it to %q", name, value)
+}
+
+func getVal(field reflect.Value) string {
+ if str, ok := field.Addr().Interface().(fmt.Stringer); ok {
+ return str.String()
+ }
+ switch field.Kind() {
+ case reflect.Bool:
+ return strconv.FormatBool(field.Bool())
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return strconv.FormatInt(field.Int(), 10)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return strconv.FormatUint(field.Uint(), 10)
+ case reflect.String:
+ return field.String()
+ default:
+ panic("unknown type " + field.Kind().String())
+ }
+}