-
Notifications
You must be signed in to change notification settings - Fork 12
/
netns_linux.go
150 lines (125 loc) · 3.93 KB
/
netns_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//go:build linux
// +build linux
package socket
import (
"errors"
"fmt"
"os"
"runtime"
"golang.org/x/sync/errgroup"
"golang.org/x/sys/unix"
)
// errNetNSDisabled is returned when network namespaces are unavailable on
// a given system.
var errNetNSDisabled = errors.New("socket: Linux network namespaces are not enabled on this system")
// withNetNS invokes fn within the context of the network namespace specified by
// fd, while also managing the logic required to safely do so by manipulating
// thread-local state.
func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) {
var (
eg errgroup.Group
conn *Conn
)
eg.Go(func() error {
// Retrieve and store the calling OS thread's network namespace so the
// thread can be reassigned to it after creating a socket in another network
// namespace.
runtime.LockOSThread()
ns, err := threadNetNS()
if err != nil {
// No thread-local manipulation, unlock.
runtime.UnlockOSThread()
return err
}
defer ns.Close()
// Beyond this point, the thread's network namespace is poisoned. Do not
// unlock the OS thread until all network namespace manipulation completes
// to avoid returning to the caller with altered thread-local state.
// Assign the current OS thread the goroutine is locked to to the given
// network namespace.
if err := ns.Set(fd); err != nil {
return err
}
// Attempt Conn creation and unconditionally restore the original namespace.
c, err := fn()
if nerr := ns.Restore(); nerr != nil {
// Failed to restore original namespace. Return an error and allow the
// runtime to terminate the thread.
if err == nil {
_ = c.Close()
}
return nerr
}
// No more thread-local state manipulation; return the new Conn.
runtime.UnlockOSThread()
conn = c
return nil
})
if err := eg.Wait(); err != nil {
return nil, err
}
return conn, nil
}
// A netNS is a handle that can manipulate network namespaces.
//
// Operations performed on a netNS must use runtime.LockOSThread before
// manipulating any network namespaces.
type netNS struct {
// The handle to a network namespace.
f *os.File
// Indicates if network namespaces are disabled on this system, and thus
// operations should become a no-op or return errors.
disabled bool
}
// threadNetNS constructs a netNS using the network namespace of the calling
// thread. If the namespace is not the default namespace, runtime.LockOSThread
// should be invoked first.
func threadNetNS() (*netNS, error) {
return fileNetNS(fmt.Sprintf("/proc/self/task/%d/ns/net", unix.Gettid()))
}
// fileNetNS opens file and creates a netNS. fileNetNS should only be called
// directly in tests.
func fileNetNS(file string) (*netNS, error) {
f, err := os.Open(file)
switch {
case err == nil:
return &netNS{f: f}, nil
case os.IsNotExist(err):
// Network namespaces are not enabled on this system. Use this signal
// to return errors elsewhere if the caller explicitly asks for a
// network namespace to be set.
return &netNS{disabled: true}, nil
default:
return nil, err
}
}
// Close releases the handle to a network namespace.
func (n *netNS) Close() error {
return n.do(func() error { return n.f.Close() })
}
// FD returns a file descriptor which represents the network namespace.
func (n *netNS) FD() int {
if n.disabled {
// No reasonable file descriptor value in this case, so specify a
// non-existent one.
return -1
}
return int(n.f.Fd())
}
// Restore restores the original network namespace for the calling thread.
func (n *netNS) Restore() error {
return n.do(func() error { return n.Set(n.FD()) })
}
// Set sets a new network namespace for the current thread using fd.
func (n *netNS) Set(fd int) error {
return n.do(func() error {
return os.NewSyscallError("setns", unix.Setns(fd, unix.CLONE_NEWNET))
})
}
// do runs fn if network namespaces are enabled on this system.
func (n *netNS) do(fn func() error) error {
if n.disabled {
return errNetNSDisabled
}
return fn()
}