Skip to content

Commit

Permalink
Add network namespace ID management.
Browse files Browse the repository at this point in the history
Adds the ability to set and retrieve network namespace IDs. This is
useful, for example, for determining the "other side" of a veth pair.
  • Loading branch information
Casey Callendrello authored and vishvananda committed Jul 19, 2018
1 parent d68dce4 commit 3ac69fd
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 0 deletions.
141 changes: 141 additions & 0 deletions netns_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package netlink

// Network namespace ID functions
//
// The kernel has a weird concept called the network namespace ID.
// This is different from the file reference in proc (and any bind-mounted
// namespaces, etc.)
//
// Instead, namespaces can be assigned a numeric ID at any time. Once set,
// the ID is fixed. The ID can either be set manually by the user, or
// automatically, triggered by certain kernel actions. The most common kernel
// action that triggers namespace ID creation is moving one end of a veth pair
// in to that namespace.

import (
"fmt"

"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)

// These can be replaced by the values from sys/unix when it is next released.
const (
_ = iota
NETNSA_NSID
NETNSA_PID
NETNSA_FD
)

// GetNetNsIdByPid looks up the network namespace ID for a given pid (really thread id).
// Returns -1 if the namespace does not have an ID set.
func (h *Handle) GetNetNsIdByPid(pid int) (int, error) {
return h.getNetNsId(NETNSA_PID, uint32(pid))
}

// GetNetNsIdByPid looks up the network namespace ID for a given pid (really thread id).
// Returns -1 if the namespace does not have an ID set.
func GetNetNsIdByPid(pid int) (int, error) {
return pkgHandle.GetNetNsIdByPid(pid)
}

// SetNetNSIdByPid sets the ID of the network namespace for a given pid (really thread id).
// The ID can only be set for namespaces without an ID already set.
func (h *Handle) SetNetNsIdByPid(pid, nsid int) error {
return h.setNetNsId(NETNSA_PID, uint32(pid), uint32(nsid))
}

// SetNetNSIdByPid sets the ID of the network namespace for a given pid (really thread id).
// The ID can only be set for namespaces without an ID already set.
func SetNetNsIdByPid(pid, nsid int) error {
return pkgHandle.SetNetNsIdByPid(pid, nsid)
}

// GetNetNsIdByPid looks up the network namespace ID for a given fd.
// fd must be an open file descriptor to a namespace file.
// Returns -1 if the namespace does not have an ID set.
func (h *Handle) GetNetNsIdByFd(fd int) (int, error) {
return h.getNetNsId(NETNSA_FD, uint32(fd))
}

// GetNetNsIdByPid looks up the network namespace ID for a given fd.
// fd must be an open file descriptor to a namespace file.
// Returns -1 if the namespace does not have an ID set.
func GetNetNsIdByFd(fd int) (int, error) {
return pkgHandle.GetNetNsIdByFd(fd)
}

// SetNetNSIdByFd sets the ID of the network namespace for a given fd.
// fd must be an open file descriptor to a namespace file.
// The ID can only be set for namespaces without an ID already set.
func (h *Handle) SetNetNsIdByFd(fd, nsid int) error {
return h.setNetNsId(NETNSA_FD, uint32(fd), uint32(nsid))
}

// SetNetNSIdByFd sets the ID of the network namespace for a given fd.
// fd must be an open file descriptor to a namespace file.
// The ID can only be set for namespaces without an ID already set.
func SetNetNsIdByFd(fd, nsid int) error {
return pkgHandle.SetNetNsIdByFd(fd, nsid)
}

// getNetNsId requests the netnsid for a given type-val pair
// type should be either NETNSA_PID or NETNSA_FD
func (h *Handle) getNetNsId(attrType int, val uint32) (int, error) {
req := h.newNetlinkRequest(unix.RTM_GETNSID, unix.NLM_F_REQUEST)

rtgen := nl.NewRtGenMsg()
req.AddData(rtgen)

b := make([]byte, 4, 4)
native.PutUint32(b, val)
attr := nl.NewRtAttr(attrType, b)
req.AddData(attr)

msgs, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNSID)

if err != nil {
return 0, err
}

for _, m := range msgs {
msg := nl.DeserializeRtGenMsg(m)

attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return 0, err
}

for _, attr := range attrs {
switch attr.Attr.Type {
case NETNSA_NSID:
return int(int32(native.Uint32(attr.Value))), nil
}
}
}

return 0, fmt.Errorf("unexpected empty result")
}

// setNetNsId sets the netnsid for a given type-val pair
// type should be either NETNSA_PID or NETNSA_FD
// The ID can only be set for namespaces without an ID already set
func (h *Handle) setNetNsId(attrType int, val uint32, newnsid uint32) error {
req := h.newNetlinkRequest(unix.RTM_NEWNSID, unix.NLM_F_REQUEST|unix.NLM_F_ACK)

rtgen := nl.NewRtGenMsg()
req.AddData(rtgen)

b := make([]byte, 4, 4)
native.PutUint32(b, val)
attr := nl.NewRtAttr(attrType, b)
req.AddData(attr)

b1 := make([]byte, 4, 4)
native.PutUint32(b1, newnsid)
attr1 := nl.NewRtAttr(NETNSA_NSID, b1)
req.AddData(attr1)

_, err := req.Execute(unix.NETLINK_ROUTE, unix.RTM_NEWNSID)
return err
}
77 changes: 77 additions & 0 deletions netns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// +build linux

package netlink

import (
"os"
"runtime"
"syscall"
"testing"

"github.com/vishvananda/netns"
)

// TestNetNsIdByFd tests setting and getting the network namespace ID
// by file descriptor. It opens a namespace fd, sets it to a random id,
// then retrieves the ID.
// This does not do any namespace switching.
func TestNetNsIdByFd(t *testing.T) {
// create a network namespace
ns, err := netns.New()
CheckErrorFail(t, err)

// set its ID
// In an attempt to avoid namespace id collisions, set this to something
// insanely high. When the kernel assigns IDs, it does so starting from 0
// So, just use our pid shifted up 16 bits
wantID := os.Getpid() << 16

h, err := NewHandle()
CheckErrorFail(t, err)
err = h.SetNetNsIdByFd(int(ns), wantID)
CheckErrorFail(t, err)

// Get the ID back, make sure it matches
haveID, err := h.GetNetNsIdByFd(int(ns))
if haveID != wantID {
t.Errorf("GetNetNsIdByFd returned %d, want %d", haveID, wantID)
}

ns.Close()
}

// TestNetNsIdByPid tests manipulating namespace IDs by pid (really, task / thread id)
// Does the same as TestNetNsIdByFd, but we need to change namespaces so we
// actually have a pid in that namespace
func TestNetNsIdByPid(t *testing.T) {
runtime.LockOSThread() // we need a constant OS thread
origNs, _ := netns.Get()

// create and enter a new netns
ns, err := netns.New()
CheckErrorFail(t, err)
err = netns.Set(ns)
CheckErrorFail(t, err)
// make sure we go back to the original namespace when done
defer func() {
err := netns.Set(origNs)
if err != nil {
panic("failed to restore network ns, bailing!")
}
runtime.UnlockOSThread()
}()

// As above, we'll pick a crazy large netnsid to avoid collisions
wantID := syscall.Gettid() << 16

h, err := NewHandle()
CheckErrorFail(t, err)
err = h.SetNetNsIdByPid(syscall.Gettid(), wantID)
CheckErrorFail(t, err)

//Get the ID and see if it worked
haveID, err := h.GetNetNsIdByPid(syscall.Gettid())
if haveID != wantID {
t.Errorf("GetNetNsIdByPid returned %d, want %d", haveID, wantID)
}
}
19 changes: 19 additions & 0 deletions netns_unspecified.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// +build !linux

package netlink

func GetNetNsIdByPid(pid int) (int, error) {
return 0, ErrNotImplemented
}

func SetNetNsIdByPid(pid, nsid int) error {
return ErrNotImplemented
}

func GetNetNsIdByFd(fd int) (int, error) {
return 0, ErrNotImplemented
}

func SetNetNsIdByFd(fd, nsid int) error {
return ErrNotImplemented
}
26 changes: 26 additions & 0 deletions nl/route_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,29 @@ func (msg *RtNexthop) Serialize() []byte {
}
return buf
}

type RtGenMsg struct {
unix.RtGenmsg
}

func NewRtGenMsg() *RtGenMsg {
return &RtGenMsg{
RtGenmsg: unix.RtGenmsg{
Family: unix.AF_UNSPEC,
},
}
}

func (msg *RtGenMsg) Len() int {
return rtaAlignOf(unix.SizeofRtGenmsg)
}

func DeserializeRtGenMsg(b []byte) *RtGenMsg {
return &RtGenMsg{RtGenmsg: unix.RtGenmsg{Family: b[0]}}
}

func (msg *RtGenMsg) Serialize() []byte {
out := make([]byte, msg.Len())
out[0] = msg.Family
return out
}

0 comments on commit 3ac69fd

Please sign in to comment.