github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/netns/netns.go (about)

     1  // Copyright (c) 2016 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  // Package netns provides a utility function that allows a user to
     6  // perform actions in a different network namespace
     7  package netns
     8  
     9  import (
    10  	"fmt"
    11  	"os"
    12  	"runtime"
    13  )
    14  
    15  const (
    16  	netNsRunDir = "/var/run/netns/"
    17  	selfNsFile  = "/proc/self/ns/net"
    18  )
    19  
    20  // Callback is a function that gets called in a given network namespace.
    21  // The user needs to check any errors from any calls inside this function.
    22  type Callback func() error
    23  
    24  // File descriptor interface so we can mock for testing
    25  type handle interface {
    26  	close() error
    27  	fd() int
    28  }
    29  
    30  // The file descriptor associated with a network namespace
    31  type nsHandle int
    32  
    33  // setNsByName wraps setNs, allowing specification of the network namespace by name.
    34  // It returns the file descriptor mapped to the given network namespace.
    35  func setNsByName(nsName string) error {
    36  	netPath := netNsRunDir + nsName
    37  	handle, err := getNs(netPath)
    38  	if err != nil {
    39  		return fmt.Errorf("Failed to getNs: %s", err)
    40  	}
    41  	err = setNs(handle)
    42  	handle.close()
    43  	if err != nil {
    44  		return fmt.Errorf("Failed to setNs: %s", err)
    45  	}
    46  	return nil
    47  }
    48  
    49  // Do takes a function which it will call in the network namespace specified by nsName.
    50  // The goroutine that calls this will lock itself to its current OS thread, hop
    51  // namespaces, call the given function, hop back to its original namespace, and then
    52  // unlock itself from its current OS thread.
    53  // Do returns an error if an error occurs at any point besides in the invocation of
    54  // the given function, or if the given function itself returns an error.
    55  //
    56  // The callback function is expected to do something simple such as just
    57  // creating a socket / opening a connection, as it's not desirable to start
    58  // complex logic in a goroutine that is pinned to the current OS thread.
    59  // Also any goroutine started from the callback function may or may not
    60  // execute in the desired namespace.
    61  func Do(nsName string, cb Callback) error {
    62  	// If destNS is empty, the function is called in the caller's namespace
    63  	if nsName == "" {
    64  		return cb()
    65  	}
    66  
    67  	// Get the file descriptor to the current namespace
    68  	currNsFd, err := getNs(selfNsFile)
    69  	if os.IsNotExist(err) {
    70  		return fmt.Errorf("File descriptor to current namespace does not exist: %s", err)
    71  	} else if err != nil {
    72  		return fmt.Errorf("Failed to open %s: %s", selfNsFile, err)
    73  	}
    74  	defer currNsFd.close()
    75  
    76  	runtime.LockOSThread()
    77  	defer runtime.UnlockOSThread()
    78  
    79  	// Jump to the new network namespace
    80  	if err := setNsByName(nsName); err != nil {
    81  		return fmt.Errorf("Failed to set the namespace to %s: %s", nsName, err)
    82  	}
    83  
    84  	// Call the given function
    85  	cbErr := cb()
    86  
    87  	// Come back to the original namespace
    88  	if err = setNs(currNsFd); err != nil {
    89  		return fmt.Errorf("Failed to return to the original namespace: %s (callback returned %v)",
    90  			err, cbErr)
    91  	}
    92  
    93  	return cbErr
    94  }