From 549f508d5b1109791fca25498e37e8f3bd0120e2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Nov 2014 17:39:32 -0500 Subject: [PATCH 1/2] Change namespaces config to include path for setns This changes the namespace configuration on the config to include the name of the namespace along with an optional path. This path is used to point to a file of another namespace for the namespace so that it can be joined in place of the empty, initialized namespace. Signed-off-by: Michael Crosby --- config.go | 12 ++++-- config_test.go | 13 ++++++- integration/exec_test.go | 23 ++++++++--- integration/template_test.go | 12 +++--- ipc/ipc.go | 29 -------------- namespaces/init.go | 30 +++++++++++--- namespaces/utils.go | 12 +++--- network/netns.go | 39 ------------------- network/strategy.go | 1 - network/types.go | 5 --- sample_configs/apparmor.json | 14 +++---- sample_configs/attach_to_bridge.json | 14 +++---- sample_configs/minimal.json | 14 +++---- .../route_source_address_selection.json | 14 +++---- sample_configs/selinux.json | 14 +++---- 15 files changed, 108 insertions(+), 138 deletions(-) delete mode 100644 ipc/ipc.go delete mode 100644 network/netns.go diff --git a/config.go b/config.go index 915e00660..94c2bd981 100644 --- a/config.go +++ b/config.go @@ -10,6 +10,13 @@ type MountConfig mount.MountConfig type Network network.Network +// Namespace defines configuration for each namespace. It specifies an +// alternate path that is able to be joined via setns. +type Namespace struct { + Name string `json:"name"` + Path string `json:"path,omitempty"` +} + // Config defines configuration options for executing a process inside a contained environment. type Config struct { // Mount specific options. @@ -38,7 +45,7 @@ type Config struct { // Namespaces specifies the container's namespaces that it should setup when cloning the init process // If a namespace is not provided that namespace is shared from the container's parent process - Namespaces map[string]bool `json:"namespaces,omitempty"` + Namespaces []Namespace `json:"namespaces,omitempty"` // Capabilities specify the capabilities to keep when executing the process inside the container // All capbilities not specified will be dropped from the processes capability mask @@ -47,9 +54,6 @@ type Config struct { // Networks specifies the container's network setup to be created Networks []*Network `json:"networks,omitempty"` - // Ipc specifies the container's ipc setup to be created - IpcNsPath string `json:"ipc,omitempty"` - // Routes can be specified to create entries in the route table as the container is started Routes []*Route `json:"routes,omitempty"` diff --git a/config_test.go b/config_test.go index 598128115..b4e16bf01 100644 --- a/config_test.go +++ b/config_test.go @@ -64,12 +64,12 @@ func TestConfigJsonFormat(t *testing.T) { t.Fail() } - if !container.Namespaces["NEWNET"] { + if getNamespaceIndex(container, "NEWNET") == -1 { t.Log("namespaces should contain NEWNET") t.Fail() } - if container.Namespaces["NEWUSER"] { + if getNamespaceIndex(container, "NEWUSER") != -1 { t.Log("namespaces should not contain NEWUSER") t.Fail() } @@ -158,3 +158,12 @@ func TestSelinuxLabels(t *testing.T) { t.Fatalf("expected mount label %q but received %q", label, container.MountConfig.MountLabel) } } + +func getNamespaceIndex(config *Config, name string) int { + for i, v := range config.Namespaces { + if v.Name == name { + return i + } + } + return -1 +} diff --git a/integration/exec_test.go b/integration/exec_test.go index 8f4dae0f9..cf749efbf 100644 --- a/integration/exec_test.go +++ b/integration/exec_test.go @@ -4,6 +4,8 @@ import ( "os" "strings" "testing" + + "github.com/docker/libcontainer" ) func TestExecPS(t *testing.T) { @@ -55,7 +57,6 @@ func TestIPCPrivate(t *testing.T) { } config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = true buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") if err != nil { t.Fatal(err) @@ -87,7 +88,8 @@ func TestIPCHost(t *testing.T) { } config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = false + i := getNamespaceIndex(config, "NEWIPC") + config.Namespaces = append(config.Namespaces[:i], config.Namespaces[i+1:]...) buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") if err != nil { t.Fatal(err) @@ -119,8 +121,8 @@ func TestIPCJoinPath(t *testing.T) { } config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = false - config.IpcNsPath = "/proc/1/ns/ipc" + i := getNamespaceIndex(config, "NEWIPC") + config.Namespaces[i].Path = "/proc/1/ns/ipc" buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") if err != nil { @@ -148,8 +150,8 @@ func TestIPCBadPath(t *testing.T) { defer remove(rootfs) config := newTemplateConfig(rootfs) - config.Namespaces["NEWIPC"] = false - config.IpcNsPath = "/proc/1/ns/ipcc" + i := getNamespaceIndex(config, "NEWIPC") + config.Namespaces[i].Path = "/proc/1/ns/ipcc" _, _, err = runContainer(config, "", "true") if err == nil { @@ -177,3 +179,12 @@ func TestRlimit(t *testing.T) { t.Fatalf("expected rlimit to be 1024, got %s", limit) } } + +func getNamespaceIndex(config *libcontainer.Config, name string) int { + for i, v := range config.Namespaces { + if v.Name == name { + return i + } + } + return -1 +} diff --git a/integration/template_test.go b/integration/template_test.go index efcf6d5b9..f37070ffb 100644 --- a/integration/template_test.go +++ b/integration/template_test.go @@ -32,12 +32,12 @@ func newTemplateConfig(rootfs string) *libcontainer.Config { "KILL", "AUDIT_WRITE", }, - Namespaces: map[string]bool{ - "NEWNS": true, - "NEWUTS": true, - "NEWIPC": true, - "NEWPID": true, - "NEWNET": true, + Namespaces: []libcontainer.Namespace{ + {Name: "NEWNS"}, + {Name: "NEWUTS"}, + {Name: "NEWIPC"}, + {Name: "NEWPID"}, + {Name: "NEWNET"}, }, Cgroups: &cgroups.Cgroup{ Parent: "integration", diff --git a/ipc/ipc.go b/ipc/ipc.go deleted file mode 100644 index 147cf5571..000000000 --- a/ipc/ipc.go +++ /dev/null @@ -1,29 +0,0 @@ -package ipc - -import ( - "fmt" - "os" - "syscall" - - "github.com/docker/libcontainer/system" -) - -// Join the IPC Namespace of specified ipc path if it exists. -// If the path does not exist then you are not joining a container. -func Initialize(nsPath string) error { - if nsPath == "" { - return nil - } - f, err := os.OpenFile(nsPath, os.O_RDONLY, 0) - if err != nil { - return fmt.Errorf("failed get IPC namespace fd: %v", err) - } - - err = system.Setns(f.Fd(), syscall.CLONE_NEWIPC) - f.Close() - - if err != nil { - return fmt.Errorf("failed to setns current IPC namespace: %v", err) - } - return nil -} diff --git a/namespaces/init.go b/namespaces/init.go index 7c83b1376..d357d1641 100644 --- a/namespaces/init.go +++ b/namespaces/init.go @@ -13,7 +13,6 @@ import ( "github.com/docker/libcontainer" "github.com/docker/libcontainer/apparmor" "github.com/docker/libcontainer/console" - "github.com/docker/libcontainer/ipc" "github.com/docker/libcontainer/label" "github.com/docker/libcontainer/mount" "github.com/docker/libcontainer/netlink" @@ -65,7 +64,10 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip if err := json.NewDecoder(pipe).Decode(&networkState); err != nil { return err } - + // join any namespaces via a path to the namespace fd if provided + if err := joinExistingNamespaces(container.Namespaces); err != nil { + return err + } if consolePath != "" { if err := console.OpenAndDup(consolePath); err != nil { return err @@ -79,9 +81,7 @@ func Init(container *libcontainer.Config, uncleanRootfs, consolePath string, pip return fmt.Errorf("setctty %s", err) } } - if err := ipc.Initialize(container.IpcNsPath); err != nil { - return fmt.Errorf("setup IPC %s", err) - } + if err := setupNetwork(container, networkState); err != nil { return fmt.Errorf("setup networking %s", err) } @@ -308,3 +308,23 @@ func LoadContainerEnvironment(container *libcontainer.Config) error { } return nil } + +// joinExistingNamespaces gets all the namespace paths specified for the container and +// does a setns on the namespace fd so that the current process joins the namespace. +func joinExistingNamespaces(namespaces []libcontainer.Namespace) error { + for _, ns := range namespaces { + if ns.Path != "" { + nsf := GetNamespace(ns.Name) + f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0) + if err != nil { + return err + } + err = system.Setns(f.Fd(), uintptr(nsf.Value)) + f.Close() + if err != nil { + return err + } + } + } + return nil +} diff --git a/namespaces/utils.go b/namespaces/utils.go index bf60cd8f0..88420fe52 100644 --- a/namespaces/utils.go +++ b/namespaces/utils.go @@ -5,6 +5,8 @@ package namespaces import ( "os" "syscall" + + "github.com/docker/libcontainer" ) type initError struct { @@ -26,12 +28,10 @@ func newInitPipe() (parent *os.File, child *os.File, err error) { // GetNamespaceFlags parses the container's Namespaces options to set the correct // flags on clone, unshare, and setns -func GetNamespaceFlags(namespaces map[string]bool) (flag int) { - for key, enabled := range namespaces { - if enabled { - if ns := GetNamespace(key); ns != nil { - flag |= ns.Value - } +func GetNamespaceFlags(namespaces []libcontainer.Namespace) (flag int) { + for _, v := range namespaces { + if ns := GetNamespace(v.Name); ns != nil { + flag |= ns.Value } } return flag diff --git a/network/netns.go b/network/netns.go deleted file mode 100644 index 73cd8de53..000000000 --- a/network/netns.go +++ /dev/null @@ -1,39 +0,0 @@ -// +build linux - -package network - -import ( - "fmt" - "os" - "syscall" - - "github.com/docker/libcontainer/system" -) - -// crosbymichael: could make a network strategy that instead of returning veth pair names it returns a pid to an existing network namespace -type NetNS struct { -} - -func (v *NetNS) Create(n *Network, nspid int, networkState *NetworkState) error { - networkState.NsPath = n.NsPath - return nil -} - -func (v *NetNS) Initialize(config *Network, networkState *NetworkState) error { - if networkState.NsPath == "" { - return fmt.Errorf("nspath does is not specified in NetworkState") - } - - f, err := os.OpenFile(networkState.NsPath, os.O_RDONLY, 0) - if err != nil { - return fmt.Errorf("failed get network namespace fd: %v", err) - } - - if err := system.Setns(f.Fd(), syscall.CLONE_NEWNET); err != nil { - f.Close() - return fmt.Errorf("failed to setns current network namespace: %v", err) - } - - f.Close() - return nil -} diff --git a/network/strategy.go b/network/strategy.go index be5ec93b7..019fe62f4 100644 --- a/network/strategy.go +++ b/network/strategy.go @@ -13,7 +13,6 @@ var ( var strategies = map[string]NetworkStrategy{ "veth": &Veth{}, "loopback": &Loopback{}, - "netns": &NetNS{}, } // NetworkStrategy represents a specific network configuration for diff --git a/network/types.go b/network/types.go index ea0741be1..dcf00420f 100644 --- a/network/types.go +++ b/network/types.go @@ -8,9 +8,6 @@ type Network struct { // Type sets the networks type, commonly veth and loopback Type string `json:"type,omitempty"` - // Path to network namespace - NsPath string `json:"ns_path,omitempty"` - // The bridge to use. Bridge string `json:"bridge,omitempty"` @@ -50,6 +47,4 @@ type NetworkState struct { VethHost string `json:"veth_host,omitempty"` // The name of the veth interface created inside the container for the child. VethChild string `json:"veth_child,omitempty"` - // Net namespace path. - NsPath string `json:"ns_path,omitempty"` } diff --git a/sample_configs/apparmor.json b/sample_configs/apparmor.json index f739df100..50421ec88 100644 --- a/sample_configs/apparmor.json +++ b/sample_configs/apparmor.json @@ -176,13 +176,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"name":"NEWIPC"}, + {"name": "NEWNET"}, + {"name": "NEWNS"}, + {"name": "NEWPID"}, + {"name": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/sample_configs/attach_to_bridge.json b/sample_configs/attach_to_bridge.json index 0795e6c14..9b190293a 100644 --- a/sample_configs/attach_to_bridge.json +++ b/sample_configs/attach_to_bridge.json @@ -175,13 +175,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"name": "NEWIPC"}, + {"name": "NEWNET"}, + {"name": "NEWNS"}, + {"name": "NEWPID"}, + {"name": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/sample_configs/minimal.json b/sample_configs/minimal.json index 8d85ddf7d..720be64f9 100644 --- a/sample_configs/minimal.json +++ b/sample_configs/minimal.json @@ -181,13 +181,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"name": "NEWIPC"}, + {"name": "NEWNET"}, + {"name": "NEWNS"}, + {"name": "NEWPID"}, + {"name": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/sample_configs/route_source_address_selection.json b/sample_configs/route_source_address_selection.json index d4baf94cd..f403996dc 100644 --- a/sample_configs/route_source_address_selection.json +++ b/sample_configs/route_source_address_selection.json @@ -175,13 +175,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"name": "NEWIPC"}, + {"name": "NEWNET"}, + {"name": "NEWNS"}, + {"name": "NEWPID"}, + {"name": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", diff --git a/sample_configs/selinux.json b/sample_configs/selinux.json index ce383e2cc..cfb83e09f 100644 --- a/sample_configs/selinux.json +++ b/sample_configs/selinux.json @@ -177,13 +177,13 @@ "TERM=xterm" ], "hostname": "koye", - "namespaces": { - "NEWIPC": true, - "NEWNET": true, - "NEWNS": true, - "NEWPID": true, - "NEWUTS": true - }, + "namespaces": [ + {"name": "NEWIPC"}, + {"name": "NEWNET"}, + {"name": "NEWNS"}, + {"name": "NEWPID"}, + {"name": "NEWUTS"} + ], "networks": [ { "address": "127.0.0.1/0", From 12124f731da29c99df1c4d33ad604c002a775499 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 3 Dec 2014 15:47:26 -0800 Subject: [PATCH 2/2] Remove unused namespace types This removes the unsued namespace types from the namespace package and simplifies getting the values for the namespaces. Signed-off-by: Michael Crosby --- namespaces/init.go | 3 +-- namespaces/types.go | 50 --------------------------------------- namespaces/types_linux.go | 16 ------------- namespaces/types_test.go | 30 ----------------------- namespaces/utils.go | 13 +++++++--- 5 files changed, 11 insertions(+), 101 deletions(-) delete mode 100644 namespaces/types.go delete mode 100644 namespaces/types_linux.go delete mode 100644 namespaces/types_test.go diff --git a/namespaces/init.go b/namespaces/init.go index d357d1641..5c7e1a71d 100644 --- a/namespaces/init.go +++ b/namespaces/init.go @@ -314,12 +314,11 @@ func LoadContainerEnvironment(container *libcontainer.Config) error { func joinExistingNamespaces(namespaces []libcontainer.Namespace) error { for _, ns := range namespaces { if ns.Path != "" { - nsf := GetNamespace(ns.Name) f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0) if err != nil { return err } - err = system.Setns(f.Fd(), uintptr(nsf.Value)) + err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Name])) f.Close() if err != nil { return err diff --git a/namespaces/types.go b/namespaces/types.go deleted file mode 100644 index 16ce981e8..000000000 --- a/namespaces/types.go +++ /dev/null @@ -1,50 +0,0 @@ -package namespaces - -import "errors" - -type ( - Namespace struct { - Key string `json:"key,omitempty"` - Value int `json:"value,omitempty"` - File string `json:"file,omitempty"` - } - Namespaces []*Namespace -) - -// namespaceList is used to convert the libcontainer types -// into the names of the files located in /proc//ns/* for -// each namespace -var ( - namespaceList = Namespaces{} - ErrUnkownNamespace = errors.New("Unknown namespace") - ErrUnsupported = errors.New("Unsupported method") -) - -func (ns *Namespace) String() string { - return ns.Key -} - -func GetNamespace(key string) *Namespace { - for _, ns := range namespaceList { - if ns.Key == key { - cpy := *ns - return &cpy - } - } - return nil -} - -// Contains returns true if the specified Namespace is -// in the slice -func (n Namespaces) Contains(ns string) bool { - return n.Get(ns) != nil -} - -func (n Namespaces) Get(ns string) *Namespace { - for _, nsp := range n { - if nsp != nil && nsp.Key == ns { - return nsp - } - } - return nil -} diff --git a/namespaces/types_linux.go b/namespaces/types_linux.go deleted file mode 100644 index d3079944c..000000000 --- a/namespaces/types_linux.go +++ /dev/null @@ -1,16 +0,0 @@ -package namespaces - -import ( - "syscall" -) - -func init() { - namespaceList = Namespaces{ - {Key: "NEWNS", Value: syscall.CLONE_NEWNS, File: "mnt"}, - {Key: "NEWUTS", Value: syscall.CLONE_NEWUTS, File: "uts"}, - {Key: "NEWIPC", Value: syscall.CLONE_NEWIPC, File: "ipc"}, - {Key: "NEWUSER", Value: syscall.CLONE_NEWUSER, File: "user"}, - {Key: "NEWPID", Value: syscall.CLONE_NEWPID, File: "pid"}, - {Key: "NEWNET", Value: syscall.CLONE_NEWNET, File: "net"}, - } -} diff --git a/namespaces/types_test.go b/namespaces/types_test.go deleted file mode 100644 index 4d0a72c9b..000000000 --- a/namespaces/types_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package namespaces - -import ( - "testing" -) - -func TestNamespacesContains(t *testing.T) { - ns := Namespaces{ - GetNamespace("NEWPID"), - GetNamespace("NEWNS"), - GetNamespace("NEWUTS"), - } - - if ns.Contains("NEWNET") { - t.Fatal("namespaces should not contain NEWNET") - } - - if !ns.Contains("NEWPID") { - t.Fatal("namespaces should contain NEWPID but does not") - } - - withNil := Namespaces{ - GetNamespace("UNDEFINED"), // this element will be nil - GetNamespace("NEWPID"), - } - - if !withNil.Contains("NEWPID") { - t.Fatal("namespaces should contain NEWPID but does not") - } -} diff --git a/namespaces/utils.go b/namespaces/utils.go index 88420fe52..556ea6699 100644 --- a/namespaces/utils.go +++ b/namespaces/utils.go @@ -17,6 +17,15 @@ func (i initError) Error() string { return i.Message } +var namespaceInfo = map[string]int{ + "NEWNET": syscall.CLONE_NEWNET, + "NEWNS": syscall.CLONE_NEWNS, + "NEWUSER": syscall.CLONE_NEWUSER, + "NEWIPC": syscall.CLONE_NEWIPC, + "NEWUTS": syscall.CLONE_NEWUTS, + "NEWPID": syscall.CLONE_NEWPID, +} + // New returns a newly initialized Pipe for communication between processes func newInitPipe() (parent *os.File, child *os.File, err error) { fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) @@ -30,9 +39,7 @@ func newInitPipe() (parent *os.File, child *os.File, err error) { // flags on clone, unshare, and setns func GetNamespaceFlags(namespaces []libcontainer.Namespace) (flag int) { for _, v := range namespaces { - if ns := GetNamespace(v.Name); ns != nil { - flag |= ns.Value - } + flag |= namespaceInfo[v.Name] } return flag }