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..5c7e1a71d 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,22 @@ 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 != "" { + f, err := os.OpenFile(ns.Path, os.O_RDONLY, 0) + if err != nil { + return err + } + err = system.Setns(f.Fd(), uintptr(namespaceInfo[ns.Name])) + f.Close() + if err != nil { + return err + } + } + } + return nil +} 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 bf60cd8f0..556ea6699 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 { @@ -15,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) @@ -26,13 +37,9 @@ 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 { + flag |= namespaceInfo[v.Name] } 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",