go.ligato.io/vpp-agent/v3@v3.5.0/plugins/linux/nsplugin/linuxcalls/namespace_linuxcalls.go (about)

     1  //  Copyright (c) 2018 Cisco and/or its affiliates.
     2  //
     3  //  Licensed under the Apache License, Version 2.0 (the "License");
     4  //  you may not use this file except in compliance with the License.
     5  //  You may obtain a copy of the License at:
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //  Unless required by applicable law or agreed to in writing, software
    10  //  distributed under the License is distributed on an "AS IS" BASIS,
    11  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //  See the License for the specific language governing permissions and
    13  //  limitations under the License.
    14  
    15  //go:build !windows && !darwin
    16  
    17  package linuxcalls
    18  
    19  import (
    20  	"os"
    21  	"path"
    22  	"strconv"
    23  	"syscall"
    24  
    25  	"github.com/pkg/errors"
    26  	"github.com/vishvananda/netns"
    27  
    28  	"go.ligato.io/cn-infra/v2/logging"
    29  )
    30  
    31  const (
    32  	// Network namespace mount directory.
    33  	netNsMountDir = "/var/run/netns"
    34  )
    35  
    36  // CreateNamedNetNs creates a new named Linux network namespace.
    37  // It does exactly the same thing as the command "ip netns add NAMESPACE".
    38  func (nh *namedNetNsHandler) CreateNamedNetNs(ctx NamespaceMgmtCtx, nsName string) (netns.NsHandle, error) {
    39  	// Lock the OS Thread so we don't accidentally switch namespaces.
    40  	ctx.LockOSThread()
    41  	defer ctx.UnlockOSThread()
    42  
    43  	// Save the current network namespace.
    44  	origns, err := nh.sysHandler.GetCurrentNamespace()
    45  	if err != nil {
    46  		return netns.None(), errors.Errorf("failed to get original namespace: %v", err)
    47  	}
    48  	defer origns.Close()
    49  
    50  	// Create directory for namespace mounts.
    51  	err = nh.sysHandler.MkDirAll(netNsMountDir, 0755)
    52  	if err != nil {
    53  		return netns.None(), errors.Errorf("failed to create directory for namespace mounts: %v", err)
    54  	}
    55  
    56  	/* Make it possible for network namespace mounts to propagate between
    57  	   mount namespaces.  This makes it likely that unmounting a network
    58  	   namespace file in one namespace will unmount the network namespace
    59  	   file in all namespaces allowing the network namespace to be freed
    60  	   sooner.
    61  	*/
    62  	mountedNetnsDir := false
    63  	for {
    64  		err = nh.sysHandler.Mount("", netNsMountDir, "none", syscall.MS_SHARED|syscall.MS_REC, "")
    65  		if err == nil {
    66  			break
    67  		}
    68  		if e, ok := err.(syscall.Errno); !ok || e != syscall.EINVAL || mountedNetnsDir {
    69  			return netns.None(), errors.Errorf("%s mount --make-shared failed: %v", nsName, err)
    70  		}
    71  		/* Upgrade netNsMountDir to a mount point */
    72  		err = nh.sysHandler.Mount(netNsMountDir, netNsMountDir, "none", syscall.MS_BIND, "")
    73  		if err != nil {
    74  			return netns.None(), errors.Errorf("%s mount --bind failed: %v", nsName, err)
    75  		}
    76  		mountedNetnsDir = true
    77  	}
    78  
    79  	// Create file path for the mount.
    80  	netnsMountFile := path.Join(netNsMountDir, nsName)
    81  	file, err := nh.sysHandler.OpenFile(netnsMountFile, os.O_RDONLY|os.O_CREATE|os.O_EXCL, 0444)
    82  	if err != nil {
    83  		return netns.None(), errors.Errorf("failed to create destination path for the namespace %s mount: %v",
    84  			nsName, err)
    85  	}
    86  	file.Close()
    87  
    88  	// Create and switch to a new namespace.
    89  	newNsHandle, err := nh.sysHandler.NewNetworkNamespace()
    90  	if err != nil {
    91  		nh.log.WithFields(logging.Fields{"namespace": nsName}).
    92  			Error("failed to create namespace")
    93  		return netns.None(), errors.Errorf("failed to create namespace %s: %v", nsName, err)
    94  	}
    95  	err = nh.sysHandler.SetNamespace(newNsHandle)
    96  	if err != nil {
    97  		newNsHandle.Close()
    98  		nh.log.WithFields(logging.Fields{"namespace": nsName}).
    99  			Error("failed to set namespace")
   100  		return netns.None(), errors.Errorf("failed to set namespace %s: %v", nsName, err)
   101  	}
   102  
   103  	// Create a bind-mount for the namespace.
   104  	tid := syscall.Gettid()
   105  	err = nh.sysHandler.Mount("/proc/self/task/"+strconv.Itoa(tid)+"/ns/net", netnsMountFile, "none", syscall.MS_BIND, "")
   106  	if err != nil {
   107  		newNsHandle.Close()
   108  		return netns.None(), errors.Errorf("failed to create namespace %s bind-mount: %v", nsName, err)
   109  	}
   110  
   111  	// Switch back to the original namespace.
   112  	err = nh.sysHandler.SetNamespace(origns)
   113  	if err != nil {
   114  		newNsHandle.Close()
   115  		nh.log.WithFields(logging.Fields{"namespace": nsName}).
   116  			Error("failed to set namespace")
   117  		return netns.None(), errors.Errorf("failed to set namespace %s: %v", nsName, err)
   118  	}
   119  
   120  	return newNsHandle, nil
   121  }
   122  
   123  // DeleteNamedNetNs deletes an existing named Linux network namespace.
   124  // It does exactly the same thing as the command "ip netns del NAMESPACE".
   125  func (nh *namedNetNsHandler) DeleteNamedNetNs(nsName string) error {
   126  	// Unmount the namespace.
   127  	netnsMountFile := path.Join(netNsMountDir, nsName)
   128  	err := nh.sysHandler.Unmount(netnsMountFile, syscall.MNT_DETACH)
   129  	if err != nil {
   130  		return errors.Errorf("failed to unmount namespace %s: %v", nsName, err)
   131  	}
   132  
   133  	// Remove file path used for the mount.
   134  	err = nh.sysHandler.Remove(netnsMountFile)
   135  	if err != nil {
   136  		return errors.Errorf("failed to remove namespace %s: %v", nsName, err)
   137  	}
   138  
   139  	return err
   140  }
   141  
   142  // NamedNetNsExists checks whether named  namespace exists.
   143  func (nh *namedNetNsHandler) NamedNetNsExists(nsName string) (bool, error) {
   144  	netnsMountFile := path.Join(netNsMountDir, nsName)
   145  	return nh.sysHandler.FileExists(netnsMountFile)
   146  }