Skip to content

Commit 91f5148

Browse files
Merge pull request #2170 from Luap99/v0.60
[v0.60] Backport + version 0.60.3
2 parents 52c82b1 + 8264002 commit 91f5148

File tree

2 files changed

+118
-38
lines changed

2 files changed

+118
-38
lines changed

pkg/netns/netns_linux.go

+117-37
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import (
4040
// threadNsPath is the /proc path to the current netns handle for the current thread
4141
const threadNsPath = "/proc/thread-self/ns/net"
4242

43+
var errNoFreeName = errors.New("failed to find free netns path name")
44+
4345
// GetNSRunDir returns the dir of where to create the netNS. When running
4446
// rootless, it needs to be at a location writable by user.
4547
func GetNSRunDir() (string, error) {
@@ -60,14 +62,26 @@ func NewNSAtPath(nsPath string) (ns.NetNS, error) {
6062
// NewNS creates a new persistent (bind-mounted) network namespace and returns
6163
// an object representing that namespace, without switching to it.
6264
func NewNS() (ns.NetNS, error) {
65+
nsRunDir, err := GetNSRunDir()
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
// Create the directory for mounting network namespaces
71+
// This needs to be a shared mountpoint in case it is mounted in to
72+
// other namespaces (containers)
73+
err = makeNetnsDir(nsRunDir)
74+
if err != nil {
75+
return nil, err
76+
}
77+
6378
for i := 0; i < 10000; i++ {
64-
b := make([]byte, 16)
65-
_, err := rand.Reader.Read(b)
79+
nsName, err := getRandomNetnsName()
6680
if err != nil {
67-
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
81+
return nil, err
6882
}
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-
ns, err := NewNSWithName(nsName)
83+
nsPath := path.Join(nsRunDir, nsName)
84+
ns, err := newNSPath(nsPath)
7185
if err == nil {
7286
return ns, nil
7387
}
@@ -77,62 +91,128 @@ func NewNS() (ns.NetNS, error) {
7791
}
7892
return nil, err
7993
}
80-
return nil, errors.New("failed to find free netns path name")
94+
return nil, errNoFreeName
8195
}
8296

83-
// NewNSWithName creates a new persistent (bind-mounted) network namespace and returns
84-
// an object representing that namespace, without switching to it.
85-
func NewNSWithName(name string) (ns.NetNS, error) {
97+
// NewNSFrom creates a persistent (bind-mounted) network namespace from the
98+
// given netns path, i.e. /proc/<pid>/ns/net, and returns the new full path to
99+
// the bind mounted file in the netns run dir.
100+
func NewNSFrom(fromNetns string) (string, error) {
86101
nsRunDir, err := GetNSRunDir()
87102
if err != nil {
88-
return nil, err
103+
return "", err
89104
}
90105

91-
// Create the directory for mounting network namespaces
92-
// This needs to be a shared mountpoint in case it is mounted in to
93-
// other namespaces (containers)
94-
err = os.MkdirAll(nsRunDir, 0o755)
106+
err = makeNetnsDir(nsRunDir)
95107
if err != nil {
96-
return nil, err
108+
return "", err
97109
}
98110

99-
// Remount the namespace directory shared. This will fail if it is not
100-
// already a mountpoint, so bind-mount it on to itself to "upgrade" it
101-
// to a mountpoint.
102-
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
103-
if err != nil {
104-
if err != unix.EINVAL {
105-
return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
111+
for i := 0; i < 10000; i++ {
112+
nsName, err := getRandomNetnsName()
113+
if err != nil {
114+
return "", err
106115
}
116+
nsPath := filepath.Join(nsRunDir, nsName)
107117

108-
// Recursively remount /run/netns on itself. The recursive flag is
109-
// so that any existing netns bindmounts are carried over.
110-
err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "")
118+
// create an empty file to use as at the mount point
119+
err = createNetnsFile(nsPath)
111120
if err != nil {
112-
return nil, fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err)
121+
// retry when the name already exists
122+
if errors.Is(err, os.ErrExist) {
123+
continue
124+
}
125+
return "", err
113126
}
114127

115-
// Now we can make it shared
116-
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
128+
err = unix.Mount(fromNetns, nsPath, "none", unix.MS_BIND|unix.MS_SHARED|unix.MS_REC, "")
117129
if err != nil {
118-
return nil, fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
130+
// Do not leak the ns on errors
131+
_ = os.RemoveAll(nsPath)
132+
return "", fmt.Errorf("failed to bind mount ns at %s: %v", nsPath, err)
119133
}
134+
return nsPath, nil
120135
}
121136

122-
nsPath := path.Join(nsRunDir, name)
123-
return newNSPath(nsPath)
137+
return "", errNoFreeName
124138
}
125139

126-
func newNSPath(nsPath string) (ns.NetNS, error) {
127-
// create an empty file at the mount point
128-
mountPointFd, err := os.OpenFile(nsPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
140+
func getRandomNetnsName() (string, error) {
141+
b := make([]byte, 16)
142+
_, err := rand.Reader.Read(b)
129143
if err != nil {
130-
return nil, err
144+
return "", fmt.Errorf("failed to generate random netns name: %v", err)
131145
}
132-
if err := mountPointFd.Close(); err != nil {
133-
return nil, err
146+
return fmt.Sprintf("netns-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil
147+
}
148+
149+
func makeNetnsDir(nsRunDir string) error {
150+
err := os.MkdirAll(nsRunDir, 0o755)
151+
if err != nil {
152+
return err
153+
}
154+
// Important, the bind mount setup is racy if two process try to set it up in parallel.
155+
// This can have very bad consequences because we end up with two duplicated mounts
156+
// for the netns file that then might have a different parent mounts.
157+
// Also because as root netns dir is also created by ip netns we should not race against them.
158+
// Use a lock on the netns dir like they do, compare the iproute2 ip netns add code.
159+
// https://github.com/iproute2/iproute2/blob/8b9d9ea42759c91d950356ca43930a975d0c352b/ip/ipnetns.c#L806-L815
160+
161+
dirFD, err := unix.Open(nsRunDir, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
162+
if err != nil {
163+
return &os.PathError{Op: "open", Path: nsRunDir, Err: err}
164+
}
165+
// closing the fd will also unlock so we do not have to call flock(fd,LOCK_UN)
166+
defer unix.Close(dirFD)
167+
168+
err = unix.Flock(dirFD, unix.LOCK_EX)
169+
if err != nil {
170+
return fmt.Errorf("failed to lock %s dir: %w", nsRunDir, err)
171+
}
172+
173+
// Remount the namespace directory shared. This will fail with EINVAL
174+
// if it is not already a mountpoint, so bind-mount it on to itself
175+
// to "upgrade" it to a mountpoint.
176+
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
177+
if err == nil {
178+
return nil
179+
}
180+
if err != unix.EINVAL {
181+
return fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
182+
}
183+
184+
// Recursively remount /run/netns on itself. The recursive flag is
185+
// so that any existing netns bindmounts are carried over.
186+
err = unix.Mount(nsRunDir, nsRunDir, "none", unix.MS_BIND|unix.MS_REC, "")
187+
if err != nil {
188+
return fmt.Errorf("mount --rbind %s %s failed: %q", nsRunDir, nsRunDir, err)
134189
}
135190

191+
// Now we can make it shared
192+
err = unix.Mount("", nsRunDir, "none", unix.MS_SHARED|unix.MS_REC, "")
193+
if err != nil {
194+
return fmt.Errorf("mount --make-rshared %s failed: %q", nsRunDir, err)
195+
}
196+
197+
return nil
198+
}
199+
200+
// createNetnsFile created the file with O_EXCL to ensure there are no conflicts with others
201+
// Callers should check for ErrExist and loop over it to find a free file.
202+
func createNetnsFile(path string) error {
203+
mountPointFd, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600)
204+
if err != nil {
205+
return err
206+
}
207+
return mountPointFd.Close()
208+
}
209+
210+
func newNSPath(nsPath string) (ns.NetNS, error) {
211+
// create an empty file to use as at the mount point
212+
err := createNetnsFile(nsPath)
213+
if err != nil {
214+
return nil, err
215+
}
136216
// Ensure the mount point is cleaned up on errors; if the namespace
137217
// was successfully mounted this will have no effect because the file
138218
// is in-use

version/version.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
package version
22

33
// Version is the version of the build.
4-
const Version = "0.60.2"
4+
const Version = "0.60.3"

0 commit comments

Comments
 (0)