@@ -40,6 +40,8 @@ import (
40
40
// threadNsPath is the /proc path to the current netns handle for the current thread
41
41
const threadNsPath = "/proc/thread-self/ns/net"
42
42
43
+ var errNoFreeName = errors .New ("failed to find free netns path name" )
44
+
43
45
// GetNSRunDir returns the dir of where to create the netNS. When running
44
46
// rootless, it needs to be at a location writable by user.
45
47
func GetNSRunDir () (string , error ) {
@@ -61,12 +63,10 @@ func NewNSAtPath(nsPath string) (ns.NetNS, error) {
61
63
// an object representing that namespace, without switching to it.
62
64
func NewNS () (ns.NetNS , error ) {
63
65
for i := 0 ; i < 10000 ; i ++ {
64
- b := make ([]byte , 16 )
65
- _ , err := rand .Reader .Read (b )
66
+ nsName , err := getRandomNetnsName ()
66
67
if err != nil {
67
- return nil , fmt . Errorf ( "failed to generate random netns name: %v" , err )
68
+ return nil , err
68
69
}
69
- nsName := fmt .Sprintf ("netns-%x-%x-%x-%x-%x" , b [0 :4 ], b [4 :6 ], b [6 :8 ], b [8 :10 ], b [10 :])
70
70
ns , err := NewNSWithName (nsName )
71
71
if err == nil {
72
72
return ns , nil
@@ -77,7 +77,7 @@ func NewNS() (ns.NetNS, error) {
77
77
}
78
78
return nil , err
79
79
}
80
- return nil , errors . New ( "failed to find free netns path name" )
80
+ return nil , errNoFreeName
81
81
}
82
82
83
83
// NewNSWithName creates a new persistent (bind-mounted) network namespace and returns
@@ -100,6 +100,58 @@ func NewNSWithName(name string) (ns.NetNS, error) {
100
100
return newNSPath (nsPath )
101
101
}
102
102
103
+ // NewNSFrom creates a persistent (bind-mounted) network namespace from the
104
+ // given netns path, i.e. /proc/<pid>/ns/net, and returns the new full path to
105
+ // the bind mounted file in the netns run dir.
106
+ func NewNSFrom (fromNetns string ) (string , error ) {
107
+ nsRunDir , err := GetNSRunDir ()
108
+ if err != nil {
109
+ return "" , err
110
+ }
111
+
112
+ err = makeNetnsDir (nsRunDir )
113
+ if err != nil {
114
+ return "" , err
115
+ }
116
+
117
+ for i := 0 ; i < 10000 ; i ++ {
118
+ nsName , err := getRandomNetnsName ()
119
+ if err != nil {
120
+ return "" , err
121
+ }
122
+ nsPath := filepath .Join (nsRunDir , nsName )
123
+
124
+ // create an empty file to use as at the mount point
125
+ err = createNetnsFile (nsPath )
126
+ if err != nil {
127
+ // retry when the name already exists
128
+ if errors .Is (err , os .ErrExist ) {
129
+ continue
130
+ }
131
+ return "" , err
132
+ }
133
+
134
+ err = unix .Mount (fromNetns , nsPath , "none" , unix .MS_BIND | unix .MS_SHARED | unix .MS_REC , "" )
135
+ if err != nil {
136
+ // Do not leak the ns on errors
137
+ _ = os .RemoveAll (nsPath )
138
+ return "" , fmt .Errorf ("failed to bind mount ns at %s: %v" , nsPath , err )
139
+ }
140
+ return nsPath , nil
141
+ }
142
+
143
+ return "" , errNoFreeName
144
+ }
145
+
146
+ func getRandomNetnsName () (string , error ) {
147
+ b := make ([]byte , 16 )
148
+ _ , err := rand .Reader .Read (b )
149
+ if err != nil {
150
+ return "" , fmt .Errorf ("failed to generate random netns name: %v" , err )
151
+ }
152
+ return fmt .Sprintf ("netns-%x-%x-%x-%x-%x" , b [0 :4 ], b [4 :6 ], b [6 :8 ], b [8 :10 ], b [10 :]), nil
153
+ }
154
+
103
155
func makeNetnsDir (nsRunDir string ) error {
104
156
err := os .MkdirAll (nsRunDir , 0o755 )
105
157
if err != nil {
@@ -151,16 +203,22 @@ func makeNetnsDir(nsRunDir string) error {
151
203
return nil
152
204
}
153
205
154
- func newNSPath (nsPath string ) (ns.NetNS , error ) {
155
- // create an empty file at the mount point
156
- mountPointFd , err := os .OpenFile (nsPath , os .O_RDWR | os .O_CREATE | os .O_EXCL , 0o600 )
206
+ // createNetnsFile created the file with O_EXCL to ensure there are no conflicts with others
207
+ // Callers should check for ErrExist and loop over it to find a free file.
208
+ func createNetnsFile (path string ) error {
209
+ mountPointFd , err := os .OpenFile (path , os .O_RDWR | os .O_CREATE | os .O_EXCL , 0o600 )
157
210
if err != nil {
158
- return nil , err
211
+ return err
159
212
}
160
- if err := mountPointFd .Close (); err != nil {
213
+ return mountPointFd .Close ()
214
+ }
215
+
216
+ func newNSPath (nsPath string ) (ns.NetNS , error ) {
217
+ // create an empty file to use as at the mount point
218
+ err := createNetnsFile (nsPath )
219
+ if err != nil {
161
220
return nil , err
162
221
}
163
-
164
222
// Ensure the mount point is cleaned up on errors; if the namespace
165
223
// was successfully mounted this will have no effect because the file
166
224
// is in-use
0 commit comments