Compare commits
14 Commits
v0.3.0-rc0
...
v0.3.0-rc3
Author | SHA1 | Date | |
---|---|---|---|
35f3a090b2 | |||
131ecc4055 | |||
d582c9ce8f | |||
72337159c1 | |||
7f90f9d559 | |||
6f63d9d707 | |||
3bab8a2805 | |||
6fb30a6700 | |||
d6751cea24 | |||
c43ccc703a | |||
76ea259ff9 | |||
c29cd52628 | |||
2de97b7e98 | |||
b23895a7c7 |
82
pkg/ns/ns.go
82
pkg/ns/ns.go
@ -20,7 +20,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@ -58,6 +60,7 @@ type NetNS interface {
|
|||||||
type netNS struct {
|
type netNS struct {
|
||||||
file *os.File
|
file *os.File
|
||||||
mounted bool
|
mounted bool
|
||||||
|
closed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCurrentThreadNetNSPath() string {
|
func getCurrentThreadNetNSPath() string {
|
||||||
@ -72,12 +75,63 @@ func GetCurrentNS() (NetNS, error) {
|
|||||||
return GetNS(getCurrentThreadNetNSPath())
|
return GetNS(getCurrentThreadNetNSPath())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
|
||||||
|
NSFS_MAGIC = 0x6e736673
|
||||||
|
PROCFS_MAGIC = 0x9fa0
|
||||||
|
)
|
||||||
|
|
||||||
|
type NSPathNotExistErr struct{ msg string }
|
||||||
|
|
||||||
|
func (e NSPathNotExistErr) Error() string { return e.msg }
|
||||||
|
|
||||||
|
type NSPathNotNSErr struct{ msg string }
|
||||||
|
|
||||||
|
func (e NSPathNotNSErr) Error() string { return e.msg }
|
||||||
|
|
||||||
|
func IsNSorErr(nspath string) error {
|
||||||
|
stat := syscall.Statfs_t{}
|
||||||
|
if err := syscall.Statfs(nspath, &stat); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch stat.Type {
|
||||||
|
case PROCFS_MAGIC:
|
||||||
|
// Kernel < 3.19
|
||||||
|
|
||||||
|
validPathContent := "ns/"
|
||||||
|
validName := strings.Contains(nspath, validPathContent)
|
||||||
|
if !validName {
|
||||||
|
return NSPathNotNSErr{msg: fmt.Sprintf("path %q doesn't contain %q", nspath, validPathContent)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
case NSFS_MAGIC:
|
||||||
|
// Kernel >= 3.19
|
||||||
|
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Returns an object representing the namespace referred to by @path
|
// Returns an object representing the namespace referred to by @path
|
||||||
func GetNS(nspath string) (NetNS, error) {
|
func GetNS(nspath string) (NetNS, error) {
|
||||||
|
err := IsNSorErr(nspath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
fd, err := os.Open(nspath)
|
fd, err := os.Open(nspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &netNS{file: fd}, nil
|
return &netNS{file: fd}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,8 +219,22 @@ func (ns *netNS) Fd() uintptr {
|
|||||||
return ns.file.Fd()
|
return ns.file.Fd()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ns *netNS) errorIfClosed() error {
|
||||||
|
if ns.closed {
|
||||||
|
return fmt.Errorf("%q has already been closed", ns.file.Name())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ns *netNS) Close() error {
|
func (ns *netNS) Close() error {
|
||||||
ns.file.Close()
|
if err := ns.errorIfClosed(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ns.file.Close(); err != nil {
|
||||||
|
return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
|
||||||
|
}
|
||||||
|
ns.closed = true
|
||||||
|
|
||||||
if ns.mounted {
|
if ns.mounted {
|
||||||
if err := unix.Unmount(ns.file.Name(), unix.MNT_DETACH); err != nil {
|
if err := unix.Unmount(ns.file.Name(), unix.MNT_DETACH); err != nil {
|
||||||
@ -175,11 +243,17 @@ func (ns *netNS) Close() error {
|
|||||||
if err := os.RemoveAll(ns.file.Name()); err != nil {
|
if err := os.RemoveAll(ns.file.Name()); err != nil {
|
||||||
return fmt.Errorf("Failed to clean up namespace %s: %v", ns.file.Name(), err)
|
return fmt.Errorf("Failed to clean up namespace %s: %v", ns.file.Name(), err)
|
||||||
}
|
}
|
||||||
|
ns.mounted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *netNS) Do(toRun func(NetNS) error) error {
|
func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||||
|
if err := ns.errorIfClosed(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
containedCall := func(hostNS NetNS) error {
|
containedCall := func(hostNS NetNS) error {
|
||||||
threadNS, err := GetNS(getCurrentThreadNetNSPath())
|
threadNS, err := GetNS(getCurrentThreadNetNSPath())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -218,6 +292,10 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ns *netNS) Set() error {
|
func (ns *netNS) Set() error {
|
||||||
|
if err := ns.errorIfClosed(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if _, _, err := unix.Syscall(unix.SYS_SETNS, ns.Fd(), uintptr(unix.CLONE_NEWNET), 0); err != 0 {
|
if _, _, err := unix.Syscall(unix.SYS_SETNS, ns.Fd(), uintptr(unix.CLONE_NEWNET), 0); err != 0 {
|
||||||
return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
|
return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
|
||||||
}
|
}
|
||||||
@ -230,7 +308,7 @@ func (ns *netNS) Set() error {
|
|||||||
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
|
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
|
||||||
ns, err := GetNS(nspath)
|
ns, err := GetNS(nspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to open %v: %v", nspath, err)
|
return err
|
||||||
}
|
}
|
||||||
defer ns.Close()
|
defer ns.Close()
|
||||||
return ns.Do(toRun)
|
return ns.Do(toRun)
|
||||||
|
@ -17,6 +17,7 @@ package ns_test
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@ -169,6 +170,76 @@ var _ = Describe("Linux namespace operations", func() {
|
|||||||
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
|
Expect(netnsInode).NotTo(Equal(createdNetNSInode))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("fails when the path is not a namespace", func() {
|
||||||
|
tempFile, err := ioutil.TempFile("", "nstest")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
nspath := tempFile.Name()
|
||||||
|
defer os.Remove(nspath)
|
||||||
|
|
||||||
|
_, err = ns.GetNS(nspath)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
|
||||||
|
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("closing a network namespace", func() {
|
||||||
|
It("should prevent further operations", func() {
|
||||||
|
createdNetNS, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = createdNetNS.Close()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = createdNetNS.Do(func(ns.NetNS) error { return nil })
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
err = createdNetNS.Set()
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should only work once", func() {
|
||||||
|
createdNetNS, err := ns.NewNS()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = createdNetNS.Close()
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = createdNetNS.Close()
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("IsNSorErr", func() {
|
||||||
|
It("should detect a namespace", func() {
|
||||||
|
createdNetNS, err := ns.NewNS()
|
||||||
|
err = ns.IsNSorErr(createdNetNS.Path())
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should refuse other paths", func() {
|
||||||
|
tempFile, err := ioutil.TempFile("", "nstest")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
nspath := tempFile.Name()
|
||||||
|
defer os.Remove(nspath)
|
||||||
|
|
||||||
|
err = ns.IsNSorErr(nspath)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
|
||||||
|
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should error on non-existing paths", func() {
|
||||||
|
err := ns.IsNSorErr("/tmp/IDoNotExist")
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err).To(BeAssignableToTypeOf(ns.NSPathNotExistErr{}))
|
||||||
|
Expect(err).NotTo(BeAssignableToTypeOf(ns.NSPathNotNSErr{}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -36,28 +36,72 @@ type CmdArgs struct {
|
|||||||
StdinData []byte
|
StdinData []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type reqForCmdEntry map[string]bool
|
||||||
|
|
||||||
// PluginMain is the "main" for a plugin. It accepts
|
// PluginMain is the "main" for a plugin. It accepts
|
||||||
// two callback functions for add and del commands.
|
// two callback functions for add and del commands.
|
||||||
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) {
|
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error) {
|
||||||
var cmd, contID, netns, ifName, args, path string
|
var cmd, contID, netns, ifName, args, path string
|
||||||
|
|
||||||
vars := []struct {
|
vars := []struct {
|
||||||
name string
|
name string
|
||||||
val *string
|
val *string
|
||||||
req bool
|
reqForCmd reqForCmdEntry
|
||||||
}{
|
}{
|
||||||
{"CNI_COMMAND", &cmd, true},
|
{
|
||||||
{"CNI_CONTAINERID", &contID, false},
|
"CNI_COMMAND",
|
||||||
{"CNI_NETNS", &netns, true},
|
&cmd,
|
||||||
{"CNI_IFNAME", &ifName, true},
|
reqForCmdEntry{
|
||||||
{"CNI_ARGS", &args, false},
|
"ADD": true,
|
||||||
{"CNI_PATH", &path, true},
|
"DEL": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CNI_CONTAINERID",
|
||||||
|
&contID,
|
||||||
|
reqForCmdEntry{
|
||||||
|
"ADD": false,
|
||||||
|
"DEL": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CNI_NETNS",
|
||||||
|
&netns,
|
||||||
|
reqForCmdEntry{
|
||||||
|
"ADD": true,
|
||||||
|
"DEL": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CNI_IFNAME",
|
||||||
|
&ifName,
|
||||||
|
reqForCmdEntry{
|
||||||
|
"ADD": true,
|
||||||
|
"DEL": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CNI_ARGS",
|
||||||
|
&args,
|
||||||
|
reqForCmdEntry{
|
||||||
|
"ADD": false,
|
||||||
|
"DEL": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CNI_PATH",
|
||||||
|
&path,
|
||||||
|
reqForCmdEntry{
|
||||||
|
"ADD": true,
|
||||||
|
"DEL": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
argsMissing := false
|
argsMissing := false
|
||||||
for _, v := range vars {
|
for _, v := range vars {
|
||||||
*v.val = os.Getenv(v.name)
|
*v.val = os.Getenv(v.name)
|
||||||
if v.req && *v.val == "" {
|
if v.reqForCmd[cmd] && *v.val == "" {
|
||||||
log.Printf("%v env variable missing", v.name)
|
log.Printf("%v env variable missing", v.name)
|
||||||
argsMissing = true
|
argsMissing = true
|
||||||
}
|
}
|
||||||
|
@ -71,5 +71,14 @@ var _ = Describe("Skel", func() {
|
|||||||
// Expect(err).NotTo(HaveOccurred())
|
// Expect(err).NotTo(HaveOccurred())
|
||||||
// PluginMain(fErr, nil)
|
// PluginMain(fErr, nil)
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
It("should not fail with DEL and no NETNS and noop callback", func() {
|
||||||
|
err := os.Setenv("CNI_COMMAND", "DEL")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
err = os.Unsetenv("CNI_NETNS")
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
PluginMain(nil, fNoop)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -289,6 +289,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var ipn *net.IPNet
|
var ipn *net.IPNet
|
||||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -152,6 +152,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
return ip.DelLinkByName(args.IfName)
|
return ip.DelLinkByName(args.IfName)
|
||||||
})
|
})
|
||||||
|
@ -170,6 +170,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
return ip.DelLinkByName(args.IfName)
|
return ip.DelLinkByName(args.IfName)
|
||||||
})
|
})
|
||||||
|
@ -199,6 +199,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if args.Netns == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var ipn *net.IPNet
|
var ipn *net.IPNet
|
||||||
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||||
var err error
|
var err error
|
||||||
|
Reference in New Issue
Block a user