//go:build linux package namespaces import ( "fmt" "net" "runtime" "sync" "syscall" "github.com/vishvananda/netlink" "github.com/vishvananda/netns" ) // NetworkNamespace represents a network namespace. type NetworkNamespace struct { nethandler NetlinkHandler nshandler NamespaceHandler handle netns.NsHandle mutex sync.Mutex origins map[int]netns.NsHandle name string cfg Configuration } // Close closes and deletes the network namespace. func (n *NetworkNamespace) Close() error { if err := n.handle.Close(); err != nil { return fmt.Errorf("error closing namespace: %w", err) } if err := n.nshandler.DeleteNamed(n.name); err != nil { return fmt.Errorf("error deleting namespace: %w", err) } return nil } // AttachInterface attaches the the provided interface into the namespace. This // function does not bring the interface up. func (n *NetworkNamespace) AttachInterface(ifname string) error { n.cfg.Logf("attaching interface %q to namespace %q", ifname, n.name) iface, err := n.nethandler.LinkByName(ifname) if err != nil { return fmt.Errorf("error finding interface: %w", err) } // put the `in` interface into the namespace. if err := n.nethandler.LinkSetNsFd(iface, int(n.handle)); err != nil { return fmt.Errorf("error moving peer into namespace: %w", err) } return nil } // Leave makes the thread leave the namespace. This function returns the thread // to the previous namespace. Leaves() can't be called without Joining first. // This function unlocks the current OS thread so it can be used again by // multiple goroutines. func (n *NetworkNamespace) Leave() error { n.mutex.Lock() defer n.mutex.Unlock() var origin netns.NsHandle var ok bool threadID := syscall.Gettid() if origin, ok = n.origins[threadID]; !ok { return fmt.Errorf("error leaving namespace: namespace not joined") } if err := n.nshandler.Set(origin); err != nil { return fmt.Errorf("error switching to original namespace: %w", err) } if err := origin.Close(); err != nil { return fmt.Errorf("error closing original namespace: %w", err) } delete(n.origins, threadID) runtime.UnlockOSThread() return nil } // Join makes the thread join the namespace. The current thread is saved in the // origin field. Callers are responsible for calling Leave() once they are // done. This namespace can only be joined once and this is by design. You need // to Leave() before Joining again. The current OS thread will be locked to the // namespace. func (n *NetworkNamespace) Join() (err error) { n.mutex.Lock() defer n.mutex.Unlock() runtime.LockOSThread() defer func() { if err != nil { runtime.UnlockOSThread() } }() threadID := syscall.Gettid() if _, ok := n.origins[threadID]; ok { return fmt.Errorf("error joining namespace: namespace already joined") } origin, err := n.nshandler.Get() if err != nil { return fmt.Errorf("error getting current namespace: %w", err) } if err := n.nshandler.Set(n.handle); err != nil { return fmt.Errorf("error switching to the namespace: %w", err) } n.origins[threadID] = origin return nil } // SetInterfaceIP sets the ip address for the provided interface. func (n *NetworkNamespace) SetInterfaceIP(ifname, ipaddr string) error { addr, err := n.nethandler.ParseAddr(ipaddr) if err != nil { return fmt.Errorf("error parsing ip: %w", err) } // this function will be executed inside the namespace. fn := func() error { iface, err := n.nethandler.LinkByName(ifname) if err != nil { return err } return n.nethandler.AddrAdd(iface, addr) } if err := n.Run(fn); err != nil { return fmt.Errorf("error setting interface ip: %w", err) } return nil } // BringInterfaceUp brings the provided interface up inside the namespace. func (n *NetworkNamespace) BringInterfaceUp(ifname string) error { fn := func() error { iface, err := n.nethandler.LinkByName(ifname) if err != nil { return err } return n.nethandler.LinkSetUp(iface) } if err := n.Run(fn); err != nil { return fmt.Errorf("error bringing interface up: %w", err) } return nil } // SetDefaultGateway sets the default gateway for the namespace. func (n *NetworkNamespace) SetDefaultGateway(addr string) error { n.cfg.Logf("setting default gateway %q for namespace %q", addr, n.name) gw := net.ParseIP(addr) if gw == nil { return fmt.Errorf("error parsing invalid gateway: %s", addr) } if err := n.Run( func() error { route := netlink.Route{Gw: gw} return n.nethandler.RouteAdd(&route) }, ); err != nil { return fmt.Errorf("error setting default gateway: %w", err) } return nil } // Run runs the provided function inside the namespace. Restores the original // namespace once the function has finished. func (n *NetworkNamespace) Run(f func() error) (err error) { runtime.LockOSThread() defer runtime.UnlockOSThread() var origin netns.NsHandle if origin, err = n.nshandler.Get(); err != nil { return fmt.Errorf("error getting current namespace: %w", err) } defer func() { err = WrapIfFail("error closing namespace", err, origin.Close) }() if err := n.nshandler.Set(n.handle); err != nil { return fmt.Errorf("error switching to namespace: %w", err) } defer func() { setter := func() error { return n.nshandler.Set(origin) } err = WrapIfFail("error exiting namespace", err, setter) }() return f() } func (n *NetworkNamespace) Setup() (err error) { runtime.LockOSThread() defer runtime.UnlockOSThread() var origin netns.NsHandle if origin, err = n.nshandler.Get(); err != nil { return fmt.Errorf("error getting current namespace: %w", err) } defer func() { err = WrapIfFail("error closing original namespace", err, origin.Close) }() var handle netns.NsHandle if handle, err = n.nshandler.NewNamed(n.name); err != nil { return fmt.Errorf("error creating network namespace: %w", err) } defer func() { setter := func() error { return n.nshandler.Set(origin) } err = WrapIfFail("error exiting namespace", err, setter) }() n.handle = handle return nil } // NewNetworkNamespace creates a new network namespace. once the namespace is // created this function restores the thread to the original namespace. func NewNetworkNamespace(name string, options ...Option) *NetworkNamespace { return &NetworkNamespace{ nethandler: NetlinkHandle{}, nshandler: NamespaceHandle{}, name: name, cfg: NewConfiguration(options...), origins: map[int]netns.NsHandle{}, } }