github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/client/lib/nsutil/netns_linux.go (about) 1 // Copyright 2018 CNI authors 2 // Copyright 2019 HashiCorp 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 // The functions in this file are derived from: 17 // https://github.com/containernetworking/plugins/blob/0950a3607bf5e8a57c6a655c7e573e6aab0dc650/pkg/testutils/netns_linux.go 18 19 package nsutil 20 21 import ( 22 "fmt" 23 "os" 24 "path" 25 "runtime" 26 "strings" 27 "sync" 28 29 "github.com/containernetworking/plugins/pkg/ns" 30 "golang.org/x/sys/unix" 31 ) 32 33 // NetNSRunDir is the directory which new network namespaces will be bind mounted 34 const NetNSRunDir = "/var/run/netns" 35 36 // NewNS creates a new persistent (bind-mounted) network namespace and returns 37 // an object representing that namespace, without switching to it. 38 func NewNS(nsName string) (ns.NetNS, error) { 39 40 // Create the directory for mounting network namespaces 41 // This needs to be a shared mountpoint in case it is mounted in to 42 // other namespaces (containers) 43 err := os.MkdirAll(NetNSRunDir, 0755) 44 if err != nil { 45 return nil, err 46 } 47 48 // Remount the namespace directory shared. This will fail if it is not 49 // already a mountpoint, so bind-mount it on to itself to "upgrade" it 50 // to a mountpoint. 51 err = unix.Mount("", NetNSRunDir, "none", unix.MS_SHARED|unix.MS_REC, "") 52 if err != nil { 53 if err != unix.EINVAL { 54 return nil, fmt.Errorf("mount --make-rshared %s failed: %q", NetNSRunDir, err) 55 } 56 57 // Recursively remount /var/run/netns on itself. The recursive flag is 58 // so that any existing netns bindmounts are carried over. 59 err = unix.Mount(NetNSRunDir, NetNSRunDir, "none", unix.MS_BIND|unix.MS_REC, "") 60 if err != nil { 61 return nil, fmt.Errorf("mount --rbind %s %s failed: %q", NetNSRunDir, NetNSRunDir, err) 62 } 63 64 // Now we can make it shared 65 err = unix.Mount("", NetNSRunDir, "none", unix.MS_SHARED|unix.MS_REC, "") 66 if err != nil { 67 return nil, fmt.Errorf("mount --make-rshared %s failed: %q", NetNSRunDir, err) 68 } 69 70 } 71 72 // create an empty file at the mount point 73 nsPath := path.Join(NetNSRunDir, nsName) 74 mountPointFd, err := os.Create(nsPath) 75 if err != nil { 76 return nil, err 77 } 78 mountPointFd.Close() 79 80 // Ensure the mount point is cleaned up on errors; if the namespace 81 // was successfully mounted this will have no effect because the file 82 // is in-use 83 defer os.RemoveAll(nsPath) 84 85 var wg sync.WaitGroup 86 wg.Add(1) 87 88 // do namespace work in a dedicated goroutine, so that we can safely 89 // Lock/Unlock OSThread without upsetting the lock/unlock state of 90 // the caller of this function 91 go (func() { 92 defer wg.Done() 93 runtime.LockOSThread() 94 // Don't unlock. By not unlocking, golang will kill the OS thread when the 95 // goroutine is done (for go1.10+) 96 97 var origNS ns.NetNS 98 origNS, err = ns.GetNS(getCurrentThreadNetNSPath()) 99 if err != nil { 100 err = fmt.Errorf("failed to get the current netns: %v", err) 101 return 102 } 103 defer origNS.Close() 104 105 // create a new netns on the current thread 106 err = unix.Unshare(unix.CLONE_NEWNET) 107 if err != nil { 108 err = fmt.Errorf("error from unshare: %v", err) 109 return 110 } 111 112 // Put this thread back to the orig ns, since it might get reused (pre go1.10) 113 defer origNS.Set() 114 115 // bind mount the netns from the current thread (from /proc) onto the 116 // mount point. This causes the namespace to persist, even when there 117 // are no threads in the ns. 118 err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "") 119 if err != nil { 120 err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err) 121 } 122 })() 123 wg.Wait() 124 125 if err != nil { 126 return nil, fmt.Errorf("failed to create namespace: %v", err) 127 } 128 129 return ns.GetNS(nsPath) 130 } 131 132 // UnmountNS unmounts the NS held by the netns object 133 func UnmountNS(nsPath string) error { 134 // Only unmount if it's been bind-mounted (don't touch namespaces in /proc...) 135 if strings.HasPrefix(nsPath, NetNSRunDir) { 136 if err := unix.Unmount(nsPath, 0); err != nil { 137 return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err) 138 } 139 140 if err := os.Remove(nsPath); err != nil { 141 return fmt.Errorf("failed to remove ns path %s: %v", nsPath, err) 142 } 143 } 144 145 return nil 146 } 147 148 // getCurrentThreadNetNSPath copied from pkg/ns 149 func getCurrentThreadNetNSPath() string { 150 // /proc/self/ns/net returns the namespace of the main thread, not 151 // of whatever thread this goroutine is running on. Make sure we 152 // use the thread's net namespace since the thread is switching around 153 return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid()) 154 }