in network.go [386:480]
func (cniConf CNIConfiguration) initializeNetNS() (error, []func() error) {
var cleanupFuncs []func() error
err := ns.IsNSorErr(cniConf.netNSPath)
switch err.(type) {
case nil:
// if the path already exists and is a netns, just use it as is and return early
return nil, cleanupFuncs
case ns.NSPathNotNSErr:
// if the path exists but isn't a netns, return an error
return errors.Wrapf(err, "path %q does not appear to be a mounted netns", cniConf.netNSPath), cleanupFuncs
case ns.NSPathNotExistErr:
// if the path doesn't exist, continue on to creating a new netns at the path
default:
// if something else bad happened return the error
return errors.Wrapf(err, "failure checking if %q is a mounted netns", cniConf.netNSPath), cleanupFuncs
}
// the path doesn't exist, so we need to create a new netns and mount it at the path
// make sure the parent directory for the path exists
parentDir := filepath.Dir(cniConf.netNSPath)
_, err = os.Stat(parentDir)
if os.IsNotExist(err) {
err = os.MkdirAll(parentDir, 0600)
if err != nil {
return errors.Wrapf(err, "failed to create netns parent dir at %q", parentDir), cleanupFuncs
}
cleanupFuncs = append(cleanupFuncs, func() error {
// Use Remove, not RemoveAll, so we don't clear the directory in the
// case where something else ended up creating files in the directory
// concurrently with us.
err := os.Remove(parentDir)
if err != nil {
return errors.Wrapf(err, "failed to remove netns parent dir %q", parentDir)
}
return nil
})
} else if err != nil {
return errors.Wrapf(err, "failed to check if netns parent dir exists at %q", parentDir), cleanupFuncs
}
// We need a file to exist at the path in order for the bind mount to succeed.
fd, err := os.OpenFile(cniConf.netNSPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
if err != nil {
return errors.Wrapf(err,
"failed to open new netns path at %q", cniConf.netNSPath), cleanupFuncs
}
fd.Close()
cleanupFuncs = append(cleanupFuncs, func() error {
err := os.Remove(cniConf.netNSPath)
if err != nil {
return errors.Wrapf(err, "failed to remove netns path %q", cniConf.netNSPath)
}
return nil
})
// Create a new netns and mount it at the destination path. Doing this in a
// separate OS thread that's discarded at the end of the call is the
// simplest way to prevent the namespace from leaking to other goroutines.
doneCh := make(chan error)
go func() {
defer close(doneCh)
// Lock the goroutine to the OS thread but don't ever unlock it. When
// this func finishes execution the OS thread will just be discarded.
runtime.LockOSThread()
// create a new net ns
err := unix.Unshare(unix.CLONE_NEWNET)
if err != nil {
doneCh <- errors.Wrap(err, "failed to unshare netns")
return
}
// mount the new netns at the destination path
err = unix.Mount("/proc/thread-self/ns/net", cniConf.netNSPath, "none", unix.MS_BIND, "none")
if err != nil {
doneCh <- errors.Wrapf(err, "failed to mount netns at path %q", cniConf.netNSPath)
return
}
cleanupFuncs = append(cleanupFuncs, func() error {
err := unix.Unmount(cniConf.netNSPath, unix.MNT_DETACH)
if err != nil {
return errors.Wrapf(err, "failed to unmount netns at %q", cniConf.netNSPath)
}
return nil
})
}()
err = <-doneCh
return err, cleanupFuncs
}