// Copyright 2018 CNI authors // Copyright 2019 HashiCorp // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // The functions in this file are derived from: // https://github.com/containernetworking/plugins/blob/0950a3607bf5e8a57c6a655c7e573e6aab0dc650/pkg/testutils/netns_linux.go package nsutil import ( "fmt" "os" "path" "runtime" "strings" "sync" "github.com/containernetworking/plugins/pkg/ns" "golang.org/x/sys/unix" ) // NetNSRunDir is the directory which new network namespaces will be bind mounted const NetNSRunDir = "/var/run/netns" // NewNS creates a new persistent (bind-mounted) network namespace and returns // an object representing that namespace, without switching to it. func NewNS(nsName string) (ns.NetNS, error) { // Create the directory for mounting network namespaces // This needs to be a shared mountpoint in case it is mounted in to // other namespaces (containers) err := os.MkdirAll(NetNSRunDir, 0755) if err != nil { return nil, err } // Remount the namespace directory shared. This will fail if it is not // already a mountpoint, so bind-mount it on to itself to "upgrade" it // to a mountpoint. err = unix.Mount("", NetNSRunDir, "none", unix.MS_SHARED|unix.MS_REC, "") if err != nil { if err != unix.EINVAL { return nil, fmt.Errorf("mount --make-rshared %s failed: %q", NetNSRunDir, err) } // Recursively remount /var/run/netns on itself. The recursive flag is // so that any existing netns bindmounts are carried over. err = unix.Mount(NetNSRunDir, NetNSRunDir, "none", unix.MS_BIND|unix.MS_REC, "") if err != nil { return nil, fmt.Errorf("mount --rbind %s %s failed: %q", NetNSRunDir, NetNSRunDir, err) } // Now we can make it shared err = unix.Mount("", NetNSRunDir, "none", unix.MS_SHARED|unix.MS_REC, "") if err != nil { return nil, fmt.Errorf("mount --make-rshared %s failed: %q", NetNSRunDir, err) } } // create an empty file at the mount point nsPath := path.Join(NetNSRunDir, nsName) mountPointFd, err := os.Create(nsPath) if err != nil { return nil, err } mountPointFd.Close() // Ensure the mount point is cleaned up on errors; if the namespace // was successfully mounted this will have no effect because the file // is in-use defer os.RemoveAll(nsPath) var wg sync.WaitGroup wg.Add(1) // do namespace work in a dedicated goroutine, so that we can safely // Lock/Unlock OSThread without upsetting the lock/unlock state of // the caller of this function go (func() { defer wg.Done() runtime.LockOSThread() // Don't unlock. By not unlocking, golang will kill the OS thread when the // goroutine is done (for go1.10+) var origNS ns.NetNS origNS, err = ns.GetNS(getCurrentThreadNetNSPath()) if err != nil { err = fmt.Errorf("failed to get the current netns: %v", err) return } defer origNS.Close() // create a new netns on the current thread err = unix.Unshare(unix.CLONE_NEWNET) if err != nil { err = fmt.Errorf("error from unshare: %v", err) return } // Put this thread back to the orig ns, since it might get reused (pre go1.10) defer origNS.Set() // bind mount the netns from the current thread (from /proc) onto the // mount point. This causes the namespace to persist, even when there // are no threads in the ns. err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "") if err != nil { err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err) } })() wg.Wait() if err != nil { return nil, fmt.Errorf("failed to create namespace: %v", err) } return ns.GetNS(nsPath) } // UnmountNS unmounts the NS held by the netns object func UnmountNS(nsPath string) error { // Only unmount if it's been bind-mounted (don't touch namespaces in /proc...) if strings.HasPrefix(nsPath, NetNSRunDir) { if err := unix.Unmount(nsPath, 0); err != nil { return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err) } if err := os.Remove(nsPath); err != nil { return fmt.Errorf("failed to remove ns path %s: %v", nsPath, err) } } return nil } // getCurrentThreadNetNSPath copied from pkg/ns func getCurrentThreadNetNSPath() string { // /proc/self/ns/net returns the namespace of the main thread, not // of whatever thread this goroutine is running on. Make sure we // use the thread's net namespace since the thread is switching around return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid()) }