diff --git a/runsc/boot/controller.go b/runsc/boot/controller.go index e667e9908..4435d3bba 100644 --- a/runsc/boot/controller.go +++ b/runsc/boot/controller.go @@ -144,11 +144,6 @@ const ( CgroupsWriteControlFiles = "Cgroups.WriteControlFiles" ) -// ControlSocketAddr generates an abstract unix socket name for the given ID. -func ControlSocketAddr(id string) string { - return fmt.Sprintf("\x00runsc-sandbox.%s", id) -} - // controller holds the control server, and is used for communication into the // sandbox. type controller struct { diff --git a/runsc/boot/loader_test.go b/runsc/boot/loader_test.go index 4ef88dacd..3aec336f6 100644 --- a/runsc/boot/loader_test.go +++ b/runsc/boot/loader_test.go @@ -104,7 +104,7 @@ func startGofer(root string) (int, func(), error) { } func createLoader(spec *specs.Spec) (*Loader, func(), error) { - fd, err := server.CreateSocket(ControlSocketAddr(fmt.Sprintf("%010d", rand.Int())[:10])) + fd, err := server.CreateSocket(fmt.Sprintf("\x00loader-test.%010d", rand.Int())[:10]) if err != nil { return nil, nil, err } diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index 6efce8949..7591dfe89 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -24,6 +24,7 @@ import ( "path" "path/filepath" "reflect" + "runtime" "strconv" "strings" "testing" @@ -2683,3 +2684,43 @@ func TestSaveSystemdCgroup(t *testing.T) { t.Errorf("CompatCgroup not properly saved: want %v, got %v", cont.CompatCgroup, loadCont.CompatCgroup) } } + +// TestSandboxCommunicationUnshare checks that communication with sandboxes do +// not require being in the same network namespace. This is required to allow +// Kubernetes daemonsets/containers to communicate with sandboxes without the +// need to join the host network namespaces. +func TestSandboxCommunicationUnshare(t *testing.T) { + spec, conf := sleepSpecConf(t) + _, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf) + if err != nil { + t.Fatalf("error setting up container: %v", err) + } + defer cleanup() + + args := Args{ + ID: testutil.RandomContainerID(), + Spec: spec, + BundleDir: bundleDir, + } + + cont, err := New(conf, args) + if err != nil { + t.Fatalf("Creating container: %v", err) + } + defer cont.Destroy() + + if err := cont.Start(conf); err != nil { + t.Fatalf("starting container: %v", err) + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + if err := unix.Unshare(unix.CLONE_NEWNET); err != nil { + t.Fatalf("unix.Unshare(): %v", err) + } + + // Send a simple command to test that the sandbox can be reached. + if err := cont.SignalContainer(0, true); err != nil { + t.Errorf("SignalContainer(): %v", err) + } +} diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 0d0600d44..5a2525d12 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -24,6 +24,7 @@ import ( "math" "os" "os/exec" + "path/filepath" "strconv" "strings" "syscall" @@ -65,6 +66,30 @@ const ( namespaceAnnotation = "io.kubernetes.cri.sandbox-namespace" ) +// createControlSocket finds a location and creates the socket used to +// communicate with the sandbox. +func createControlSocket(rootDir, id string) (string, int, error) { + name := fmt.Sprintf("runsc-%s.sock", id) + + // Only use absolute paths to guarantee resolution from anywhere. + var paths []string + for _, dir := range []string{rootDir, "/var/run", "/run", "/tmp"} { + paths = append(paths, filepath.Join(dir, name)) + } + // If nothing else worked, use the abstract namespace. + paths = append(paths, fmt.Sprintf("\x00runsc-sandbox.%s", id)) + + for _, path := range paths { + log.Debugf("Attempting to create socket file %q", path) + fd, err := server.CreateSocket(path) + if err == nil { + log.Debugf("Using socket file %q", path) + return path, fd, nil + } + } + return "", -1, fmt.Errorf("unable to find location to write socket file") +} + // pid is an atomic type that implements JSON marshal/unmarshal interfaces. type pid struct { val atomicbitops.Int64 @@ -144,6 +169,9 @@ type Sandbox struct { // created. MetricServerAddress string `json:"metricServerAddress"` + // ControlAddress is the uRPC address used to connect to the sandbox. + ControlAddress string `json:"control_address"` + // child is set if a sandbox process is a child of the current process. // // This field isn't saved to json, because only a creator of sandbox @@ -225,6 +253,7 @@ func New(conf *config.Config, args *Args) (*Sandbox, error) { s.PodName = args.Spec.Annotations[podNameAnnotation] s.Namespace = args.Spec.Annotations[namespaceAnnotation] } + // The Cleanup object cleans up partially created sandboxes when an error // occurs. Any errors occurring during cleanup itself are ignored. c := cleanup.Make(func() { @@ -514,7 +543,7 @@ func (s *Sandbox) Event(cid string) (*boot.EventOut, error) { func (s *Sandbox) sandboxConnect() (*urpc.Client, error) { log.Debugf("Connecting to sandbox %q", s.ID) - conn, err := client.ConnectTo(boot.ControlSocketAddr(s.ID)) + conn, err := client.ConnectTo(s.ControlAddress) if err != nil { return nil, s.connError(err) } @@ -622,12 +651,12 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn } // Create a socket for the control server and donate it to the sandbox. - addr := boot.ControlSocketAddr(s.ID) - sockFD, err := server.CreateSocket(addr) - log.Infof("Creating sandbox process with addr: %s", addr[1:]) // skip "\00". + controlAddress, sockFD, err := createControlSocket(conf.RootDir, s.ID) if err != nil { - return fmt.Errorf("creating control server socket for sandbox %q: %v", s.ID, err) + return fmt.Errorf("creating control socket %q: %v", s.ControlAddress, err) } + log.Infof("Control socket: %q", s.ControlAddress) + s.ControlAddress = controlAddress donations.DonateAndClose("controller-fd", os.NewFile(uintptr(sockFD), "control_server_socket")) specFile, err := specutils.OpenSpec(args.BundleDir) @@ -1000,6 +1029,12 @@ func (s *Sandbox) IsRootContainer(cid string) bool { // is idempotent. func (s *Sandbox) destroy() error { log.Debugf("Destroying sandbox %q", s.ID) + // Only delete the control file if it exists and is not an abstract UDS. + if len(s.ControlAddress) > 0 && s.ControlAddress[0] != 0 { + if err := os.Remove(s.ControlAddress); err != nil { + log.Warningf("failed to delete control socket file %q: %v", s.ControlAddress, err) + } + } pid := s.Pid.load() if pid != 0 { log.Debugf("Killing sandbox %q", s.ID)