7fc29c1435
Aside from unconditionally unlocking the OS thread even if restoring the thread's network namespace fails, func (*networkNamespace).InvokeFunc() correctly implements invoking a function inside a network namespace. This is far from obvious, however. func InitOSContext() does much of the heavy lifting but in a bizarre fashion: it restores the initial network namespace before it is changed in the first place, and the cleanup function it returns does not restore the network namespace at all! The InvokeFunc() implementation has to restore the network namespace explicitly by deferring a call to ns.SetNamespace(). func InitOSContext() is a leaky abstraction taped to a footgun. On the one hand, it defensively resets the current thread's network namespace, which has the potential to fix up the thread state if other buggy code had failed to maintain the invariant that an OS thread must be locked to a goroutine unless it is interchangeable with a "clean" thread as spawned by the Go runtime. On the other hand, it _facilitates_ writing buggy code which fails to maintain the aforementioned invariant because the cleanup function it returns unlocks the thread from the goroutine unconditionally while neglecting to restore the thread's network namespace! It is quite scary to need a function which fixes up threads' network namespaces after the fact as an arbitrary number of goroutines could have been scheduled onto a "dirty" thread and run non-libnetwork code before the thread's namespace is fixed up. Any number of (not-so-)subtle misbehaviours could result if an unfortunate goroutine is scheduled onto a "dirty" thread. The whole repository has been audited to ensure that the aforementioned invariant is never violated, making after-the-fact fixing up of thread network namespaces redundant. Make InitOSContext() a no-op on Linux and inline the thread-locking into the function (singular) which previously relied on it to do so. func ns.SetNamespace() is of similarly dubious utility. It intermixes capturing the initial network namespace and restoring the thread's network namespace, which could result in threads getting put into the wrong network namespace if the wrong thread is the first to call it. Delete it entirely; functions which need to manipulate a thread's network namespace are better served by being explicit about capturing and restoring the thread's namespace. Rewrite InvokeFunc() to invoke the closure inside a goroutine to enable a graceful and safe recovery if the thread's network namespace could not be restored. Avoid any potential race conditions due to changing the main thread's network namespace by preventing the aforementioned goroutines from being eligible to be scheduled onto the main thread. Signed-off-by: Cory Snider <csnider@mirantis.com>
108 lines
2.8 KiB
Go
108 lines
2.8 KiB
Go
package ns
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netns"
|
|
)
|
|
|
|
var (
|
|
initNs netns.NsHandle
|
|
initNl *netlink.Handle
|
|
initOnce sync.Once
|
|
// NetlinkSocketsTimeout represents the default timeout duration for the sockets
|
|
NetlinkSocketsTimeout = 3 * time.Second
|
|
)
|
|
|
|
// Init initializes a new network namespace
|
|
func Init() {
|
|
var err error
|
|
initNs, err = netns.Get()
|
|
if err != nil {
|
|
logrus.Errorf("could not get initial namespace: %v", err)
|
|
}
|
|
initNl, err = netlink.NewHandle(getSupportedNlFamilies()...)
|
|
if err != nil {
|
|
logrus.Errorf("could not create netlink handle on initial namespace: %v", err)
|
|
}
|
|
err = initNl.SetSocketTimeout(NetlinkSocketsTimeout)
|
|
if err != nil {
|
|
logrus.Warnf("Failed to set the timeout on the default netlink handle sockets: %v", err)
|
|
}
|
|
}
|
|
|
|
// ParseHandlerInt transforms the namespace handler into an integer
|
|
func ParseHandlerInt() int {
|
|
return int(getHandler())
|
|
}
|
|
|
|
// GetHandler returns the namespace handler
|
|
func getHandler() netns.NsHandle {
|
|
initOnce.Do(Init)
|
|
return initNs
|
|
}
|
|
|
|
// NlHandle returns the netlink handler
|
|
func NlHandle() *netlink.Handle {
|
|
initOnce.Do(Init)
|
|
return initNl
|
|
}
|
|
|
|
func getSupportedNlFamilies() []int {
|
|
fams := []int{syscall.NETLINK_ROUTE}
|
|
// NETLINK_XFRM test
|
|
if err := checkXfrmSocket(); err != nil {
|
|
logrus.Warnf("Could not load necessary modules for IPSEC rules: %v", err)
|
|
} else {
|
|
fams = append(fams, syscall.NETLINK_XFRM)
|
|
}
|
|
// NETLINK_NETFILTER test
|
|
if err := loadNfConntrackModules(); err != nil {
|
|
if checkNfSocket() != nil {
|
|
logrus.Warnf("Could not load necessary modules for Conntrack: %v", err)
|
|
} else {
|
|
fams = append(fams, syscall.NETLINK_NETFILTER)
|
|
}
|
|
} else {
|
|
fams = append(fams, syscall.NETLINK_NETFILTER)
|
|
}
|
|
|
|
return fams
|
|
}
|
|
|
|
// API check on required xfrm modules (xfrm_user, xfrm_algo)
|
|
func checkXfrmSocket() error {
|
|
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_XFRM)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
syscall.Close(fd)
|
|
return nil
|
|
}
|
|
|
|
func loadNfConntrackModules() error {
|
|
if out, err := exec.Command("modprobe", "-va", "nf_conntrack").CombinedOutput(); err != nil {
|
|
return fmt.Errorf("Running modprobe nf_conntrack failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
|
|
}
|
|
if out, err := exec.Command("modprobe", "-va", "nf_conntrack_netlink").CombinedOutput(); err != nil {
|
|
return fmt.Errorf("Running modprobe nf_conntrack_netlink failed with message: `%s`, error: %v", strings.TrimSpace(string(out)), err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// API check on required nf_conntrack* modules (nf_conntrack, nf_conntrack_netlink)
|
|
func checkNfSocket() error {
|
|
fd, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_NETFILTER)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
syscall.Close(fd)
|
|
return nil
|
|
}
|