gonso

package module
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2023 License: MIT Imports: 14 Imported by: 0

README

Gonso (Go-ns-o)

Gonso is a library to do safe Linux namespace manipulation in Go.

Why?

Linux namespaces are per-thread, but Go programs don't have direct access to threads. Doing namespace manipulation in Go requires careful use of runtime.LockOSThread() and runtime.UnlockOSThread(). Linux namespaces can have special rules for how they interact with each other and when/how you can join them. Gonso tries to handle these things for you.

While you can definitely use gonso to help learn Linux namespaces with Go since it helps take care of Go's quirks due to the threading model, you should take great care when using gonso in real code.

Usage

Gonso has a concept called a Set which is a collection of namespaces. When you create a Set you specify which namespaces are part of that set. You can then Unshare namespaces from that Set to create a new Set with the new namespaces. When you have the set setup with the namespaces you want you call Do and pass a function in, this will run the function in the Set's namespaces.

current, _ := gonso.Current()
newSet, _ := current.Unshare(unix.CLONE_NEWNS)
newSet.Do(false, func() bool {
    // do stuff in the new mount namespace
})

When calling Do you have some control over if the thread that was used to run the function should be restored to the original namespaces or dropped. You can completely disable thread restoration by passing false to Do. You can also do the same by returning false from the function you pass to Do. In either case, false will cause Go to exit the thread after the goroutine is done and the thread cannot be re-used. When true is returned from the function and true is passed to Do gonso will make a best effort to restore the thread to the original namespaces. If there are any errors restoring the thread the thread will be dropped. In some cases, as with CLONE_NEWNS, the thread cannot be restored to the original namespaces and will be dropped.

The reason you may want to restore the thread is that creating new threads is not free, which is why Go manages a pool of threads to schedule your goroutines on. Of course restoring the thread is also not free, and once you have a certain number of namespaces that need to be restored the cost of restoring the thread can become greater. Gonso leaves this up to you to decide.

Some actions cannot be undone in a thread so you will still need to be careful to not try to restore a thread that has had such changes applied. Gonso also can't track if you have done other things to the thread that would make it unsafe to restore. For the most part this shouldn't be a problem as the Linux kernel is pretty good about return an error in these cases, but it is something to be aware of.

Gonso never changes the current thread or goroutine, it always runs the function in a new goroutine.

Documentation

Index

Constants

View Source
const (
	NS_CGROUP = unix.CLONE_NEWCGROUP
	NS_IPC    = unix.CLONE_NEWIPC
	NS_MNT    = unix.CLONE_NEWNS
	NS_NET    = unix.CLONE_NEWNET
	NS_PID    = unix.CLONE_NEWPID
	NS_TIME   = unix.CLONE_NEWTIME
	NS_USER   = unix.CLONE_NEWUSER
	NS_UTS    = unix.CLONE_NEWUTS
)

These are the flags that can be passed to `Unshare` and `Current`. They are the same as the flags for `unshare(2)` and `clone(2)`.

Pretty much these values are here to (hopefully) make the code easier to understand since `CLONE_NEW*` is werid when being used to filter existing namespaces (as with `Current`) rather than creating a new one.

Variables

This section is empty.

Functions

This section is empty.

Types

type FdSet

type FdSet map[int]*os.File

FdSet is a map of namespace flags to file descriptors. It is used by a Set to store raw file descriptors.

func (FdSet) Close

func (f FdSet) Close()

Close closes all the fds in the set.

func (FdSet) Get

func (f FdSet) Get(flag int) *os.File

Get returns the fd for the given flag Only one fd is returned. Only one flag should be provided. If the flag is not in the set, nil is returned.

type IDMap

type IDMap = syscall.SysProcIDMap

type Pool

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

Pool manages a pool of Sets. It is safe to use a Pool from multiple goroutines. Create one using `NewPool` with the flags you want to use for sets managed by this pool.

func NewPool

func NewPool(flags int, afterCreate func() error) *Pool

NewPool creates a new pool with the given flags. Call `pool.Run` start filling the pool.

`afterCreate` is called after a set is created for the pool. This is useful to set up the set before it is needed.

func (*Pool) Get

func (p *Pool) Get() (Set, error)

Get returns a set from the pool. If there are no sets available, Get will create a new one.

func (*Pool) Len

func (p *Pool) Len() int

Len shows how many sets are currently in the pool.

func (*Pool) Put

func (p *Pool) Put(s Set)

Put returns a set to the pool. It is up to the caller to ensure the set is in a re-usable state. For instance if the set was created with CLONE_NEWNET and there are changes to the network namespace, the caller is responsible for resetting that namespace.

In most cases it is probably best to never call `Put` and instead close the set and throw it away.

func (*Pool) Run

func (p *Pool) Run(ctx context.Context, n int) (_ context.Context, cancel func())

Run makes sure that the pool has at least `n` sets available. Run spins up a new goroutine to maintain the pool. The goroutine will exit when the context is cancelled.

The returned context will have an error set if the pool fails to create a set or is otherwise cancelled.

type Set

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

Set represents a set of Linux namespaces. It can be used to perform operations in the context of those namespaces.

See `Current` and `Unshare` for creating a new set.

func Current

func Current(flags int) (Set, error)

Current returns the set of namespaces for the current thread.

If `flags` is 0, all namespaces are returned.

func FromDir

func FromDir(dir string, flags int) (_ Set, retErr error)

FromDir creates a set of namespaces from the specified directory. As an example, you could use the `Set.Mount` function and then use this to create a new set from those mounts. Or you can even point directly at /proc/<pid>/ns.

func FromPid

func FromPid(pid int, flags int) (Set, error)

FromPid returns a `Set` for the given pid and namespace flags.

func Unshare

func Unshare(flags int, opts ...UnshareOpt) (Set, error)

Unshare returns a new `Set` with the namespaces specified in `flags` unshared (i.e. new namespaces are created). The returned set only contains the namespaces specified in `flags`. This is the same as calling `Current(flags).Unshare(flags)`.

func (Set) Close

func (s Set) Close() error

Close closes all the file descriptors associated with the set.

If this is the last reference to the file descriptors, the namespaces will be destroyed.

func (Set) Do

func (s Set) Do(f func()) error

Do does the same as DoRaw(f, false)

func (Set) DoRaw

func (s Set) DoRaw(f func() bool, restore bool) error

DoRaw performs the given function in the context of the set of namespaces. This does not affect the state of the current thread or goroutine.

The bool on the return function should be used to indicate if the thread should be restored to the old state. In some cases even true is returned the thread may still not be restored and will subsequently be thrown away. When in doubt, return false. You can also just outright skip restoration by passing `false` to `Do`. In some cases, particularly when more than a couple of namespaces are set, this will perform better.

Keep in mind it is *always* safer to not restore the thread, which causes go to throw away the thread and create a new one.

The passed in function should not create any new goroutinues or those goroutines will not be in the correct namespace. If you need to create a goroutine and want it to be in the correct namespace, call `set.Do` again from that goroutine.

If the stored namespaces includes a mount namespace, then CLONE_FS will also be implicitly unshared since it is impossible to setns to a mount namespace without also unsharing CLONE_FS.

If the stored namespaces includes a user namespace, then Do is expected to fail.

func (Set) Dup

func (s Set) Dup(flags int) (newS Set, retErr error)

Dup creates a duplicate of the current set by duplicating the namespace file descriptors in the set and returning a new set. Specifying `flags` will only duplicate the namespaces specified in `flags`. If flags is 0, all namespaces in the set will be duplicated.

The caller is responsible for closing both the current and the new Set.

func (Set) Fds

func (s Set) Fds(flags int) (_ FdSet, retErr error)

Fds returns an FdSet, which is a dup of all the fds in the set. The caller is responsible for closing the returned FdSet. Additionally the caller is responsible for closing the original set.

On error, any new fd that was created during this function call is closed.

func (Set) ID

func (s Set) ID(flag int) (string, error)

ID gets the id of the namespace for the given flag. Only one flag should ever be provided.

func (Set) Mount

func (s Set) Mount(target string) error

Mount the set's namespaces to the specified target directory with each namespace being mounted to a file named after the namespace type as seen in procfs.

The target directory must already exist. It is up to the caller to clean up mounts.

If the set contains a mount namespace it is the caller's responsibility to make sure that the mounts performed here are propagated to caller's desired mount namespace.

Mounting a mount namespace is also tricky see the mount(2) documentation for details. In particular, mounting a mount namespace magic link may cause EINVAL if the parent uses MS_SHARED.

func (Set) MountNS added in v0.2.0

func (s Set) MountNS(ns int, target string) error

MountNSX mounts a single, specific namespace from the set to the specified target. This differs from `Mount` because it treats the target as a file to mount to rather than the directory.

You must only pass one namespace type to this function. If the set only contains 1 namespace, you can pass 0 to mount that namespace.

func (Set) Unshare

func (s Set) Unshare(flags int, opts ...UnshareOpt) (Set, error)

Unshare creates a new set with the namespaces specified in `flags` unshared (i.e. new namespaces are created).

This does not change the current set of namespaces, it only creates a new set of namespaces that can be used later with the returned `Set`, e.g. `newSet.Do(func() { ... })`.

If CLONE_NEWUSER is specified, the Set will be unable to be used with `Do`. This is because the user namespace can only be created (which is done using `clone(2)`) and not joined from a multi-threaded process. The forked process is used to create the user namespace and any other namespaces specified in `flags`. You can use `Do` by calling `Dup` on the set and dropping CLONE_NEWUSER from the flags.

type UnshareConfig

type UnshareConfig struct {
	// UidMappings is a list of uid mappings to use for the user namespace.
	UidMaps []IDMap
	// GidMappings is a list of gid mappings to use for the user namespace.
	GidMaps []IDMap
}

UnshareConfig holds configuration options for the Unshare function.

type UnshareOpt

type UnshareOpt func(*UnshareConfig)

UnshareOpt is used to configure the Unshare function.

func WithIDMaps

func WithIDMaps(uidMaps, gidMaps []IDMap) UnshareOpt

WithIDMaps sets the uid and gid mappings to use for the user namespace. It can be used as an UnshareOpt to configure the Unshare function.

Jump to

Keyboard shortcuts

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