Skip to content

Commit 8a5b951

Browse files
committedSep 20, 2024
pkg/netns: add NewNSFrom()
The new API should replace the logic from Podman's setupNetNS() which did several things incorrect. First and foremost it never bind mounted the netns dir causing problems when the netns dir was later bind mounted duplicating the netns mounts on diffeent parents which then caused all unmount attempts to fail[1]. Also the Podman code did never check for conlicts so we way end up overwriting an existing netns (super unlikely as we use 16 random bytes but still). At the very least it duplicates the name logic for no reason there so having this all here makes much more sense. Of course we still have to port podman over to this new API. [1] https://issues.redhat.com/browse/RHEL-59620 Backport for https://issues.redhat.com/browse/RHEL-59703 Signed-off-by: Paul Holzinger <[email protected]>
1 parent 50870e9 commit 8a5b951

File tree

1 file changed

+69
-11
lines changed

1 file changed

+69
-11
lines changed
 

‎pkg/netns/netns_linux.go

+69-11
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) {
@@ -61,12 +63,10 @@ func NewNSAtPath(nsPath string) (ns.NetNS, error) {
6163
// an object representing that namespace, without switching to it.
6264
func NewNS() (ns.NetNS, error) {
6365
for i := 0; i < 10000; i++ {
64-
b := make([]byte, 16)
65-
_, err := rand.Reader.Read(b)
66+
nsName, err := getRandomNetnsName()
6667
if err != nil {
67-
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
68+
return nil, err
6869
}
69-
nsName := fmt.Sprintf("netns-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
7070
ns, err := NewNSWithName(nsName)
7171
if err == nil {
7272
return ns, nil
@@ -77,7 +77,7 @@ func NewNS() (ns.NetNS, error) {
7777
}
7878
return nil, err
7979
}
80-
return nil, errors.New("failed to find free netns path name")
80+
return nil, errNoFreeName
8181
}
8282

8383
// NewNSWithName creates a new persistent (bind-mounted) network namespace and returns
@@ -100,6 +100,58 @@ func NewNSWithName(name string) (ns.NetNS, error) {
100100
return newNSPath(nsPath)
101101
}
102102

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+
103155
func makeNetnsDir(nsRunDir string) error {
104156
err := os.MkdirAll(nsRunDir, 0o755)
105157
if err != nil {
@@ -151,16 +203,22 @@ func makeNetnsDir(nsRunDir string) error {
151203
return nil
152204
}
153205

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)
157210
if err != nil {
158-
return nil, err
211+
return err
159212
}
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 {
161220
return nil, err
162221
}
163-
164222
// Ensure the mount point is cleaned up on errors; if the namespace
165223
// was successfully mounted this will have no effect because the file
166224
// is in-use

0 commit comments

Comments
 (0)
Please sign in to comment.