github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/netns/netns_linux.go (about) 1 // Copyright 2018 CNI authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // This file was originally a part of the containernetworking/plugins 16 // repository. 17 // It was copied here and modified for local use by the libpod maintainers. 18 19 package netns 20 21 import ( 22 "crypto/rand" 23 "fmt" 24 "os" 25 "path" 26 "path/filepath" 27 "runtime" 28 "strings" 29 "sync" 30 31 "github.com/containernetworking/plugins/pkg/ns" 32 "github.com/containers/libpod/pkg/rootless" 33 "github.com/containers/libpod/pkg/util" 34 "github.com/sirupsen/logrus" 35 "golang.org/x/sys/unix" 36 ) 37 38 // get NSRunDir returns the dir of where to create the netNS. When running 39 // rootless, it needs to be at a location writable by user. 40 func getNSRunDir() (string, error) { 41 if rootless.IsRootless() { 42 rootlessDir, err := util.GetRuntimeDir() 43 if err != nil { 44 return "", err 45 } 46 return filepath.Join(rootlessDir, "netns"), nil 47 } 48 return "/var/run/netns", nil 49 } 50 51 // NewNS creates a new persistent (bind-mounted) network namespace and returns 52 // an object representing that namespace, without switching to it. 53 func NewNS() (ns.NetNS, error) { 54 55 nsRunDir, err := getNSRunDir() 56 if err != nil { 57 return nil, err 58 } 59 60 b := make([]byte, 16) 61 _, err = rand.Reader.Read(b) 62 if err != nil { 63 return nil, fmt.Errorf("failed to generate random netns name: %v", err) 64 } 65 66 // Create the directory for mounting network namespaces 67 // This needs to be a shared mountpoint in case it is mounted in to 68 // other namespaces (containers) 69 err = os.MkdirAll(nsRunDir, 0755) 70 if err != nil { 71 return nil, err 72 } 73 74 // Remount the namespace directory shared. This will fail if it is not 75 // already a mountpoint, so bind-mount it on to itself to "upgrade" it 76 // to a mountpoint. 77 err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "") 78 if err != nil { 79 if err != unix.EINVAL { 80 return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err) 81 } 82 83 // Recursively remount /var/run/netns on itself. The recursive flag is 84 // so that any existing netns bindmounts are carried over. 85 err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "") 86 if err != nil { 87 return nil, fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err) 88 } 89 90 // Now we can make it shared 91 err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "") 92 if err != nil { 93 return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err) 94 } 95 96 } 97 98 nsName := fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) 99 100 // create an empty file at the mount point 101 nsPath := path.Join(nsRunDir, nsName) 102 mountPointFd, err := os.Create(nsPath) 103 if err != nil { 104 return nil, err 105 } 106 if err := mountPointFd.Close(); err != nil { 107 return nil, err 108 } 109 110 // Ensure the mount point is cleaned up on errors; if the namespace 111 // was successfully mounted this will have no effect because the file 112 // is in-use 113 defer func() { 114 _ = os.RemoveAll(nsPath) 115 }() 116 117 var wg sync.WaitGroup 118 wg.Add(1) 119 120 // do namespace work in a dedicated goroutine, so that we can safely 121 // Lock/Unlock OSThread without upsetting the lock/unlock state of 122 // the caller of this function 123 go (func() { 124 defer wg.Done() 125 runtime.LockOSThread() 126 // Don't unlock. By not unlocking, golang will kill the OS thread when the 127 // goroutine is done (for go1.10+) 128 129 threadNsPath := getCurrentThreadNetNSPath() 130 131 var origNS ns.NetNS 132 origNS, err = ns.GetNS(threadNsPath) 133 if err != nil { 134 logrus.Warnf("cannot open current network namespace %s: %q", threadNsPath, err) 135 return 136 } 137 defer func() { 138 if err := origNS.Close(); err != nil { 139 logrus.Errorf("unable to close namespace: %q", err) 140 } 141 }() 142 143 // create a new netns on the current thread 144 err = unix.Unshare(unix.CLONE_NEWNET) 145 if err != nil { 146 logrus.Warnf("cannot create a new network namespace: %q", err) 147 return 148 } 149 150 // Put this thread back to the orig ns, since it might get reused (pre go1.10) 151 defer func() { 152 if err := origNS.Set(); err != nil { 153 if rootless.IsRootless() && strings.Contains(err.Error(), "operation not permitted") { 154 // When running in rootless mode it will fail to re-join 155 // the network namespace owned by root on the host. 156 return 157 } 158 logrus.Warnf("unable to reset namespace: %q", err) 159 } 160 }() 161 162 // bind mount the netns from the current thread (from /proc) onto the 163 // mount point. This causes the namespace to persist, even when there 164 // are no threads in the ns. Make this a shared mount; it needs to be 165 // back-propogated to the host 166 err = unix.Mount(threadNsPath, nsPath, "none", unix.MS_BIND|unix.MS_SHARED|unix.MS_REC, "") 167 if err != nil { 168 err = fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err) 169 } 170 })() 171 wg.Wait() 172 173 if err != nil { 174 return nil, fmt.Errorf("failed to create namespace: %v", err) 175 } 176 177 return ns.GetNS(nsPath) 178 } 179 180 // UnmountNS unmounts the NS held by the netns object 181 func UnmountNS(ns ns.NetNS) error { 182 nsRunDir, err := getNSRunDir() 183 if err != nil { 184 return err 185 } 186 187 nsPath := ns.Path() 188 // Only unmount if it's been bind-mounted (don't touch namespaces in /proc...) 189 if strings.HasPrefix(nsPath, nsRunDir) { 190 if err := unix.Unmount(nsPath, unix.MNT_DETACH); err != nil { 191 return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err) 192 } 193 194 if err := os.Remove(nsPath); err != nil { 195 return fmt.Errorf("failed to remove ns path %s: %v", nsPath, err) 196 } 197 } 198 199 return nil 200 } 201 202 // getCurrentThreadNetNSPath copied from pkg/ns 203 func getCurrentThreadNetNSPath() string { 204 // /proc/self/ns/net returns the namespace of the main thread, not 205 // of whatever thread this goroutine is running on. Make sure we 206 // use the thread's net namespace since the thread is switching around 207 return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid()) 208 }