2015-07-02 01:00:48 -04:00
|
|
|
package osl
|
2015-02-19 20:21:42 -05:00
|
|
|
|
2015-03-11 19:37:43 -04:00
|
|
|
import (
|
|
|
|
"fmt"
|
2015-04-13 21:36:58 -04:00
|
|
|
"net"
|
2015-03-11 19:37:43 -04:00
|
|
|
"os"
|
2015-05-26 17:14:01 -04:00
|
|
|
"os/exec"
|
2015-03-11 19:37:43 -04:00
|
|
|
"runtime"
|
2015-04-28 01:57:36 -04:00
|
|
|
"sync"
|
2015-03-11 19:37:43 -04:00
|
|
|
"syscall"
|
2015-05-26 17:14:01 -04:00
|
|
|
"time"
|
2015-03-11 19:37:43 -04:00
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
log "github.com/Sirupsen/logrus"
|
|
|
|
"github.com/docker/docker/pkg/reexec"
|
2015-05-19 20:08:56 -04:00
|
|
|
"github.com/docker/libnetwork/types"
|
2015-03-11 19:37:43 -04:00
|
|
|
"github.com/vishvananda/netlink"
|
|
|
|
"github.com/vishvananda/netns"
|
|
|
|
)
|
2015-02-24 14:19:00 -05:00
|
|
|
|
2015-05-18 19:05:10 -04:00
|
|
|
const prefix = "/var/run/docker/netns"
|
2015-04-28 01:57:36 -04:00
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
var (
|
|
|
|
once sync.Once
|
|
|
|
garbagePathMap = make(map[string]bool)
|
|
|
|
gpmLock sync.Mutex
|
|
|
|
gpmWg sync.WaitGroup
|
2015-05-27 16:20:24 -04:00
|
|
|
gpmCleanupPeriod = 60 * time.Second
|
2015-06-05 14:46:33 -04:00
|
|
|
gpmChan = make(chan chan struct{})
|
2015-08-31 14:55:58 -04:00
|
|
|
nsOnce sync.Once
|
|
|
|
initNs netns.NsHandle
|
2015-05-26 17:14:01 -04:00
|
|
|
)
|
2015-04-28 01:57:36 -04:00
|
|
|
|
2015-04-13 14:40:42 -04:00
|
|
|
// The networkNamespace type is the linux implementation of the Sandbox
|
|
|
|
// interface. It represents a linux network namespace, and moves an interface
|
|
|
|
// into it when called on method AddInterface or sets the gateway etc.
|
2015-02-19 20:21:42 -05:00
|
|
|
type networkNamespace struct {
|
2015-06-04 23:21:23 -04:00
|
|
|
path string
|
|
|
|
iFaces []*nwIface
|
|
|
|
gw net.IP
|
|
|
|
gwv6 net.IP
|
|
|
|
staticRoutes []*types.StaticRoute
|
2015-06-15 14:35:13 -04:00
|
|
|
neighbors []*neigh
|
2015-06-04 23:21:23 -04:00
|
|
|
nextIfIndex int
|
2015-05-21 14:04:49 -04:00
|
|
|
sync.Mutex
|
2015-02-19 20:21:42 -05:00
|
|
|
}
|
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
func init() {
|
|
|
|
reexec.Register("netns-create", reexecCreateNamespace)
|
|
|
|
}
|
|
|
|
|
2015-04-30 01:58:12 -04:00
|
|
|
func createBasePath() {
|
2015-04-28 01:57:36 -04:00
|
|
|
err := os.MkdirAll(prefix, 0644)
|
Simplify and fix os.MkdirAll() usage
TL;DR: check for IsExist(err) after a failed MkdirAll() is both
redundant and wrong -- so two reasons to remove it.
Quoting MkdirAll documentation:
> MkdirAll creates a directory named path, along with any necessary
> parents, and returns nil, or else returns an error. If path
> is already a directory, MkdirAll does nothing and returns nil.
This means two things:
1. If a directory to be created already exists, no error is
returned.
2. If the error returned is IsExist (EEXIST), it means there exists
a non-directory with the same name as MkdirAll need to use for
directory. Example: we want to MkdirAll("a/b"), but file "a"
(or "a/b") already exists, so MkdirAll fails.
The above is a theory, based on quoted documentation and my UNIX
knowledge.
3. In practice, though, current MkdirAll implementation [1] returns
ENOTDIR in most of cases described in #2, with the exception when
there is a race between MkdirAll and someone else creating the
last component of MkdirAll argument as a file. In this very case
MkdirAll() will indeed return EEXIST.
Because of #1, IsExist check after MkdirAll is not needed.
Because of #2 and #3, ignoring IsExist error is just plain wrong,
as directory we require is not created. It's cleaner to report
the error now.
[1] https://github.com/golang/go/blob/f9ed2f75/src/os/path.go
Signed-off-by: Kir Kolyshkin <kir@openvz.org>
2015-07-29 21:08:29 -04:00
|
|
|
if err != nil {
|
2015-04-28 01:57:36 -04:00
|
|
|
panic("Could not create net namespace path directory")
|
|
|
|
}
|
2015-05-26 17:14:01 -04:00
|
|
|
|
|
|
|
// Start the garbage collection go routine
|
|
|
|
go removeUnusedPaths()
|
|
|
|
}
|
|
|
|
|
|
|
|
func removeUnusedPaths() {
|
2015-05-28 19:29:21 -04:00
|
|
|
gpmLock.Lock()
|
|
|
|
period := gpmCleanupPeriod
|
|
|
|
gpmLock.Unlock()
|
|
|
|
|
2015-06-05 14:46:33 -04:00
|
|
|
ticker := time.NewTicker(period)
|
|
|
|
for {
|
|
|
|
var (
|
|
|
|
gc chan struct{}
|
|
|
|
gcOk bool
|
|
|
|
)
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-ticker.C:
|
|
|
|
case gc, gcOk = <-gpmChan:
|
|
|
|
}
|
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
gpmLock.Lock()
|
|
|
|
pathList := make([]string, 0, len(garbagePathMap))
|
|
|
|
for path := range garbagePathMap {
|
|
|
|
pathList = append(pathList, path)
|
|
|
|
}
|
|
|
|
garbagePathMap = make(map[string]bool)
|
|
|
|
gpmWg.Add(1)
|
|
|
|
gpmLock.Unlock()
|
|
|
|
|
|
|
|
for _, path := range pathList {
|
|
|
|
os.Remove(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
gpmWg.Done()
|
2015-06-05 14:46:33 -04:00
|
|
|
if gcOk {
|
|
|
|
close(gc)
|
|
|
|
}
|
2015-05-26 17:14:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func addToGarbagePaths(path string) {
|
|
|
|
gpmLock.Lock()
|
|
|
|
garbagePathMap[path] = true
|
2015-05-27 17:40:02 -04:00
|
|
|
gpmLock.Unlock()
|
2015-05-26 17:14:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func removeFromGarbagePaths(path string) {
|
|
|
|
gpmLock.Lock()
|
|
|
|
delete(garbagePathMap, path)
|
2015-05-27 17:40:02 -04:00
|
|
|
gpmLock.Unlock()
|
2015-04-28 01:57:36 -04:00
|
|
|
}
|
|
|
|
|
2015-06-05 14:46:33 -04:00
|
|
|
// GC triggers garbage collection of namespace path right away
|
|
|
|
// and waits for it.
|
|
|
|
func GC() {
|
2015-06-10 14:55:14 -04:00
|
|
|
gpmLock.Lock()
|
|
|
|
if len(garbagePathMap) == 0 {
|
|
|
|
// No need for GC if map is empty
|
|
|
|
gpmLock.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
gpmLock.Unlock()
|
2015-06-05 14:46:33 -04:00
|
|
|
|
2015-06-10 14:55:14 -04:00
|
|
|
// if content exists in the garbage paths
|
|
|
|
// we can trigger GC to run, providing a
|
|
|
|
// channel to be notified on completion
|
|
|
|
waitGC := make(chan struct{})
|
2015-06-05 14:46:33 -04:00
|
|
|
gpmChan <- waitGC
|
2015-06-10 14:55:14 -04:00
|
|
|
// wait for GC completion
|
2015-06-05 14:46:33 -04:00
|
|
|
<-waitGC
|
|
|
|
}
|
|
|
|
|
2015-04-28 01:57:36 -04:00
|
|
|
// GenerateKey generates a sandbox key based on the passed
|
|
|
|
// container id.
|
|
|
|
func GenerateKey(containerID string) string {
|
|
|
|
maxLen := 12
|
|
|
|
if len(containerID) < maxLen {
|
|
|
|
maxLen = len(containerID)
|
|
|
|
}
|
|
|
|
|
|
|
|
return prefix + "/" + containerID[:maxLen]
|
|
|
|
}
|
|
|
|
|
2015-04-13 14:40:42 -04:00
|
|
|
// NewSandbox provides a new sandbox instance created in an os specific way
|
|
|
|
// provided a key which uniquely identifies the sandbox
|
2015-05-05 00:20:45 -04:00
|
|
|
func NewSandbox(key string, osCreate bool) (Sandbox, error) {
|
2015-06-04 23:21:23 -04:00
|
|
|
err := createNetworkNamespace(key, osCreate)
|
2015-05-03 16:29:43 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
return &networkNamespace{path: key}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *networkNamespace) InterfaceOptions() IfaceOptionSetter {
|
|
|
|
return n
|
2015-04-13 14:40:42 -04:00
|
|
|
}
|
|
|
|
|
2015-06-15 14:35:13 -04:00
|
|
|
func (n *networkNamespace) NeighborOptions() NeighborOptionSetter {
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
func reexecCreateNamespace() {
|
|
|
|
if len(os.Args) < 2 {
|
|
|
|
log.Fatal("no namespace path provided")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := syscall.Mount("/proc/self/ns/net", os.Args[1], "bind", syscall.MS_BIND, ""); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2015-05-27 16:20:24 -04:00
|
|
|
|
|
|
|
if err := loopbackUp(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2015-05-26 17:14:01 -04:00
|
|
|
}
|
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
func createNetworkNamespace(path string, osCreate bool) error {
|
2015-03-11 19:37:43 -04:00
|
|
|
runtime.LockOSThread()
|
|
|
|
defer runtime.UnlockOSThread()
|
|
|
|
|
|
|
|
origns, err := netns.Get()
|
|
|
|
if err != nil {
|
2015-06-04 23:21:23 -04:00
|
|
|
return err
|
2015-03-11 19:37:43 -04:00
|
|
|
}
|
|
|
|
defer origns.Close()
|
|
|
|
|
|
|
|
if err := createNamespaceFile(path); err != nil {
|
2015-06-04 23:21:23 -04:00
|
|
|
return err
|
2015-03-11 19:37:43 -04:00
|
|
|
}
|
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
cmd := &exec.Cmd{
|
|
|
|
Path: reexec.Self(),
|
|
|
|
Args: append([]string{"netns-create"}, path),
|
|
|
|
Stdout: os.Stdout,
|
|
|
|
Stderr: os.Stderr,
|
|
|
|
}
|
2015-05-05 00:20:45 -04:00
|
|
|
if osCreate {
|
2015-05-26 17:14:01 -04:00
|
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
|
|
|
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNET
|
2015-02-19 20:21:42 -05:00
|
|
|
}
|
2015-05-26 17:14:01 -04:00
|
|
|
if err := cmd.Run(); err != nil {
|
2015-06-04 23:21:23 -04:00
|
|
|
return fmt.Errorf("namespace creation reexec command failed: %v", err)
|
2015-03-11 19:37:43 -04:00
|
|
|
}
|
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
return nil
|
2015-02-19 20:21:42 -05:00
|
|
|
}
|
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
func unmountNamespaceFile(path string) {
|
2015-05-19 18:08:58 -04:00
|
|
|
if _, err := os.Stat(path); err == nil {
|
2015-05-26 17:14:01 -04:00
|
|
|
syscall.Unmount(path, syscall.MNT_DETACH)
|
2015-05-19 18:08:58 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-11 19:37:43 -04:00
|
|
|
func createNamespaceFile(path string) (err error) {
|
|
|
|
var f *os.File
|
2015-04-28 01:57:36 -04:00
|
|
|
|
2015-04-30 01:58:12 -04:00
|
|
|
once.Do(createBasePath)
|
2015-05-26 17:14:01 -04:00
|
|
|
// Remove it from garbage collection list if present
|
|
|
|
removeFromGarbagePaths(path)
|
|
|
|
|
|
|
|
// If the path is there unmount it first
|
|
|
|
unmountNamespaceFile(path)
|
|
|
|
|
|
|
|
// wait for garbage collection to complete if it is in progress
|
|
|
|
// before trying to create the file.
|
|
|
|
gpmWg.Wait()
|
|
|
|
|
2015-03-11 19:37:43 -04:00
|
|
|
if f, err = os.Create(path); err == nil {
|
|
|
|
f.Close()
|
|
|
|
}
|
2015-05-26 17:14:01 -04:00
|
|
|
|
2015-03-11 19:37:43 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func loopbackUp() error {
|
|
|
|
iface, err := netlink.LinkByName("lo")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return netlink.LinkSetUp(iface)
|
|
|
|
}
|
|
|
|
|
2015-06-15 14:35:13 -04:00
|
|
|
func (n *networkNamespace) InvokeFunc(f func()) error {
|
|
|
|
return nsInvoke(n.nsPath(), func(nsFD int) error { return nil }, func(callerFD int) error {
|
|
|
|
f()
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2015-08-31 14:55:58 -04:00
|
|
|
func getLink() string {
|
|
|
|
l, err := os.Readlink(fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), syscall.Gettid()))
|
2015-05-05 20:32:38 -04:00
|
|
|
if err != nil {
|
2015-08-31 14:55:58 -04:00
|
|
|
return fmt.Sprintf("(nil: %v)", err)
|
2015-05-05 20:32:38 -04:00
|
|
|
}
|
2015-08-31 14:55:58 -04:00
|
|
|
|
|
|
|
return l
|
|
|
|
}
|
|
|
|
|
|
|
|
func nsInit() {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if initNs, err = netns.Get(); err != nil {
|
|
|
|
log.Errorf("could not get initial namespace: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// InitOSContext initializes OS context while configuring network resources
|
|
|
|
func InitOSContext() func() {
|
|
|
|
runtime.LockOSThread()
|
|
|
|
nsOnce.Do(nsInit)
|
|
|
|
if err := netns.Set(initNs); err != nil {
|
|
|
|
log.Errorf("failed to set to initial namespace, link %s, initns fd %d: %v",
|
|
|
|
getLink(), initNs, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return runtime.UnlockOSThread
|
|
|
|
}
|
|
|
|
|
|
|
|
func nsInvoke(path string, prefunc func(nsFD int) error, postfunc func(callerFD int) error) error {
|
|
|
|
defer InitOSContext()()
|
2015-05-05 20:32:38 -04:00
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
f, err := os.OpenFile(path, os.O_RDONLY, 0)
|
2015-05-05 20:32:38 -04:00
|
|
|
if err != nil {
|
2015-06-04 23:21:23 -04:00
|
|
|
return fmt.Errorf("failed get network namespace %q: %v", path, err)
|
2015-05-05 20:32:38 -04:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
nsFD := f.Fd()
|
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
// Invoked before the namespace switch happens but after the namespace file
|
|
|
|
// handle is obtained.
|
|
|
|
if err := prefunc(int(nsFD)); err != nil {
|
|
|
|
return fmt.Errorf("failed in prefunc: %v", err)
|
2015-02-19 20:21:42 -05:00
|
|
|
}
|
2015-03-11 19:37:43 -04:00
|
|
|
|
|
|
|
if err = netns.Set(netns.NsHandle(nsFD)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-08-31 14:55:58 -04:00
|
|
|
defer netns.Set(initNs)
|
2015-03-11 19:37:43 -04:00
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
// Invoked after the namespace switch.
|
2015-08-31 14:55:58 -04:00
|
|
|
return postfunc(int(initNs))
|
2015-02-19 20:21:42 -05:00
|
|
|
}
|
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
func (n *networkNamespace) nsPath() string {
|
2015-06-02 16:20:15 -04:00
|
|
|
n.Lock()
|
2015-06-04 23:21:23 -04:00
|
|
|
defer n.Unlock()
|
2015-05-19 20:08:56 -04:00
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
return n.path
|
2015-05-19 20:08:56 -04:00
|
|
|
}
|
|
|
|
|
2015-06-04 23:21:23 -04:00
|
|
|
func (n *networkNamespace) Info() Info {
|
|
|
|
return n
|
2015-02-19 20:21:42 -05:00
|
|
|
}
|
|
|
|
|
2015-04-13 14:40:42 -04:00
|
|
|
func (n *networkNamespace) Key() string {
|
2015-02-19 20:21:42 -05:00
|
|
|
return n.path
|
|
|
|
}
|
2015-02-24 14:19:00 -05:00
|
|
|
|
|
|
|
func (n *networkNamespace) Destroy() error {
|
|
|
|
// Assuming no running process is executing in this network namespace,
|
|
|
|
// unmounting is sufficient to destroy it.
|
2015-04-28 01:57:36 -04:00
|
|
|
if err := syscall.Unmount(n.path, syscall.MNT_DETACH); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-05-26 17:14:01 -04:00
|
|
|
// Stash it into the garbage collection list
|
|
|
|
addToGarbagePaths(n.path)
|
|
|
|
return nil
|
2015-02-24 14:19:00 -05:00
|
|
|
}
|