293dd2e848
* Add vendor dir so builds dont require dep * Pin specific version go-eth version
99 lines
3.0 KiB
Go
99 lines
3.0 KiB
Go
// Copyright (c) 2016 Arista Networks, Inc.
|
|
// Use of this source code is governed by the Apache License 2.0
|
|
// that can be found in the COPYING file.
|
|
|
|
// Package netns provides a utility function that allows a user to
|
|
// perform actions in a different network namespace
|
|
package netns
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"runtime"
|
|
)
|
|
|
|
const (
|
|
netNsRunDir = "/var/run/netns/"
|
|
selfNsFile = "/proc/self/ns/net"
|
|
)
|
|
|
|
// Callback is a function that gets called in a given network namespace.
|
|
// The user needs to check any errors from any calls inside this function.
|
|
type Callback func() error
|
|
|
|
// File descriptor interface so we can mock for testing
|
|
type handle interface {
|
|
close() error
|
|
fd() int
|
|
}
|
|
|
|
// The file descriptor associated with a network namespace
|
|
type nsHandle int
|
|
|
|
// setNsByName wraps setNs, allowing specification of the network namespace by name.
|
|
// It returns the file descriptor mapped to the given network namespace.
|
|
func setNsByName(nsName string) error {
|
|
netPath := netNsRunDir + nsName
|
|
handle, err := getNs(netPath)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to getNs: %s", err)
|
|
}
|
|
err = setNs(handle)
|
|
handle.close()
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to setNs: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Do takes a function which it will call in the network namespace specified by nsName.
|
|
// The goroutine that calls this will lock itself to its current OS thread, hop
|
|
// namespaces, call the given function, hop back to its original namespace, and then
|
|
// unlock itself from its current OS thread.
|
|
// Do returns an error if an error occurs at any point besides in the invocation of
|
|
// the given function, or if the given function itself returns an error.
|
|
//
|
|
// The callback function is expected to do something simple such as just
|
|
// creating a socket / opening a connection, and you should not kick off any
|
|
// complex logic from the callback or call any complicated code or create any
|
|
// new goroutine from the callback. The callback should not panic or use defer.
|
|
// The behavior of this function is undefined if the callback doesn't conform
|
|
// these demands.
|
|
//go:nosplit
|
|
func Do(nsName string, cb Callback) error {
|
|
// If destNS is empty, the function is called in the caller's namespace
|
|
if nsName == "" {
|
|
return cb()
|
|
}
|
|
|
|
// Get the file descriptor to the current namespace
|
|
currNsFd, err := getNs(selfNsFile)
|
|
if os.IsNotExist(err) {
|
|
return fmt.Errorf("File descriptor to current namespace does not exist: %s", err)
|
|
} else if err != nil {
|
|
return fmt.Errorf("Failed to open %s: %s", selfNsFile, err)
|
|
}
|
|
|
|
runtime.LockOSThread()
|
|
|
|
// Jump to the new network namespace
|
|
if err := setNsByName(nsName); err != nil {
|
|
runtime.UnlockOSThread()
|
|
currNsFd.close()
|
|
return fmt.Errorf("Failed to set the namespace to %s: %s", nsName, err)
|
|
}
|
|
|
|
// Call the given function
|
|
cbErr := cb()
|
|
|
|
// Come back to the original namespace
|
|
if err = setNs(currNsFd); err != nil {
|
|
cbErr = fmt.Errorf("Failed to return to the original namespace: %s (callback returned %v)",
|
|
err, cbErr)
|
|
}
|
|
|
|
runtime.UnlockOSThread()
|
|
currNsFd.close()
|
|
return cbErr
|
|
}
|