ns

package
v1.6.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2024 License: Apache-2.0 Imports: 6 Imported by: 848

README

Namespaces, Threads, and Go

On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code.

Namespace Switching

Switching namespaces with the ns.Set() method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.

Go provides the runtime.LockOSThread() function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of LockOSThread() and goroutines can provide good control over which network namespace a given goroutine executes in.

For example, you cannot rely on the ns.Set() namespace being the current namespace after the Set() call unless you do two things. First, the goroutine calling Set() must have previously called LockOSThread(). Second, you must ensure runtime.UnlockOSThread() is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called LockOSThread() in that goroutine. Note that LockOSThread() prevents the Go scheduler from optimally scheduling goroutines for best performance, so LockOSThread() should only be used in small, isolated goroutines that release the lock quickly.

The ns.Do() method provides partial control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace (including the root namespace) should be wrapped in the ns.Do() method to ensure the correct namespace is selected for the duration of your code. For example:

err = targetNs.Do(func(hostNs ns.NetNS) error {
	linkAttrs := netlink.NewLinkAttrs()
	linkAttrs.Name = "dummy0"
	dummy := &netlink.Dummy{
		LinkAttrs: linkAttrs,
	}
	return netlink.LinkAdd(dummy)
})

Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call ns.Do(). All goroutines spawned from within the ns.Do will not inherit the new namespace. The CNI plugins all exit very soon after calling ns.Do() which helps to minimize the problem.

When a new thread is spawned in Linux, it inherits the namespace of its parent. In versions of go prior to 1.10, if the runtime spawns a new OS thread, it picks the parent randomly. If the chosen parent thread has been moved to a new namespace (even temporarily), the new OS thread will be permanently "stuck in the wrong namespace", and goroutines will non-deterministically switch namespaces as they are rescheduled.

In short, there was no safe way to change network namespaces, even temporarily, from within a long-lived, multithreaded Go process. If you wish to do this, you must use go 1.10 or greater.

Creating network namespaces

Earlier versions of this library managed namespace creation, but as CNI does not actually utilize this feature (and it was essentially unmaintained), it was removed. If you're writing a container runtime, you should implement namespace management yourself. However, there are some gotchas when doing so, especially around handling /var/run/netns. A reasonably correct reference implementation, borrowed from rkt, can be found in pkg/testutils/netns_linux.go if you're in need of a source of inspiration.

Further Reading

Documentation

Index

Constants

Variables

This section is empty.

Functions

func IsNSorErr added in v0.3.0

func IsNSorErr(nspath string) error

func WithNetNSPath

func WithNetNSPath(nspath string, toRun func(NetNS) error) error

WithNetNSPath executes the passed closure under the given network namespace, restoring the original namespace afterwards.

Types

type NSPathNotExistErr added in v0.3.0

type NSPathNotExistErr struct {
	// contains filtered or unexported fields
}

func (NSPathNotExistErr) Error added in v0.3.0

func (e NSPathNotExistErr) Error() string

type NSPathNotNSErr added in v0.3.0

type NSPathNotNSErr struct {
	// contains filtered or unexported fields
}

func (NSPathNotNSErr) Error added in v0.3.0

func (e NSPathNotNSErr) Error() string

type NetNS added in v0.3.0

type NetNS interface {
	// Executes the passed closure in this object's network namespace,
	// attempting to restore the original namespace before returning.
	// However, since each OS thread can have a different network namespace,
	// and Go's thread scheduling is highly variable, callers cannot
	// guarantee any specific namespace is set unless operations that
	// require that namespace are wrapped with Do().  Also, no code called
	// from Do() should call runtime.UnlockOSThread(), or the risk
	// of executing code in an incorrect namespace will be greater.  See
	// https://github.com/golang/go/wiki/LockOSThread for further details.
	Do(toRun func(NetNS) error) error

	// Sets the current network namespace to this object's network namespace.
	// Note that since Go's thread scheduling is highly variable, callers
	// cannot guarantee the requested namespace will be the current namespace
	// after this function is called; to ensure this wrap operations that
	// require the namespace with Do() instead.
	Set() error

	// Returns the filesystem path representing this object's network namespace
	Path() string

	// Returns a file descriptor representing this object's network namespace
	Fd() uintptr

	// Cleans up this instance of the network namespace; if this instance
	// is the last user the namespace will be destroyed
	Close() error
}

func GetCurrentNS added in v0.3.0

func GetCurrentNS() (NetNS, error)

Returns an object representing the current OS thread's network namespace

func GetNS added in v0.3.0

func GetNS(nspath string) (NetNS, error)

Returns an object representing the namespace referred to by @path

func TempNetNS added in v1.6.0

func TempNetNS() (NetNS, error)

Returns a new empty NetNS. Calling Close() let the kernel garbage collect the network namespace.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL