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 }