...

Source file src/github.com/vishvananda/netns/netns_linux.go

Documentation: github.com/vishvananda/netns

     1  package netns
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"golang.org/x/sys/unix"
    12  )
    13  
    14  // Deprecated: use golang.org/x/sys/unix pkg instead.
    15  const (
    16  	CLONE_NEWUTS  = unix.CLONE_NEWUTS  /* New utsname group? */
    17  	CLONE_NEWIPC  = unix.CLONE_NEWIPC  /* New ipcs */
    18  	CLONE_NEWUSER = unix.CLONE_NEWUSER /* New user namespace */
    19  	CLONE_NEWPID  = unix.CLONE_NEWPID  /* New pid namespace */
    20  	CLONE_NEWNET  = unix.CLONE_NEWNET  /* New network namespace */
    21  	CLONE_IO      = unix.CLONE_IO      /* Get io context */
    22  )
    23  
    24  const bindMountPath = "/run/netns" /* Bind mount path for named netns */
    25  
    26  // Setns sets namespace using golang.org/x/sys/unix.Setns.
    27  //
    28  // Deprecated: Use golang.org/x/sys/unix.Setns instead.
    29  func Setns(ns NsHandle, nstype int) (err error) {
    30  	return unix.Setns(int(ns), nstype)
    31  }
    32  
    33  // Set sets the current network namespace to the namespace represented
    34  // by NsHandle.
    35  func Set(ns NsHandle) (err error) {
    36  	return unix.Setns(int(ns), unix.CLONE_NEWNET)
    37  }
    38  
    39  // New creates a new network namespace, sets it as current and returns
    40  // a handle to it.
    41  func New() (ns NsHandle, err error) {
    42  	if err := unix.Unshare(unix.CLONE_NEWNET); err != nil {
    43  		return -1, err
    44  	}
    45  	return Get()
    46  }
    47  
    48  // NewNamed creates a new named network namespace, sets it as current,
    49  // and returns a handle to it
    50  func NewNamed(name string) (NsHandle, error) {
    51  	if _, err := os.Stat(bindMountPath); os.IsNotExist(err) {
    52  		err = os.MkdirAll(bindMountPath, 0755)
    53  		if err != nil {
    54  			return None(), err
    55  		}
    56  	}
    57  
    58  	newNs, err := New()
    59  	if err != nil {
    60  		return None(), err
    61  	}
    62  
    63  	namedPath := path.Join(bindMountPath, name)
    64  
    65  	f, err := os.OpenFile(namedPath, os.O_CREATE|os.O_EXCL, 0444)
    66  	if err != nil {
    67  		newNs.Close()
    68  		return None(), err
    69  	}
    70  	f.Close()
    71  
    72  	nsPath := fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
    73  	err = unix.Mount(nsPath, namedPath, "bind", unix.MS_BIND, "")
    74  	if err != nil {
    75  		newNs.Close()
    76  		return None(), err
    77  	}
    78  
    79  	return newNs, nil
    80  }
    81  
    82  // DeleteNamed deletes a named network namespace
    83  func DeleteNamed(name string) error {
    84  	namedPath := path.Join(bindMountPath, name)
    85  
    86  	err := unix.Unmount(namedPath, unix.MNT_DETACH)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	return os.Remove(namedPath)
    92  }
    93  
    94  // Get gets a handle to the current threads network namespace.
    95  func Get() (NsHandle, error) {
    96  	return GetFromThread(os.Getpid(), unix.Gettid())
    97  }
    98  
    99  // GetFromPath gets a handle to a network namespace
   100  // identified by the path
   101  func GetFromPath(path string) (NsHandle, error) {
   102  	fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
   103  	if err != nil {
   104  		return -1, err
   105  	}
   106  	return NsHandle(fd), nil
   107  }
   108  
   109  // GetFromName gets a handle to a named network namespace such as one
   110  // created by `ip netns add`.
   111  func GetFromName(name string) (NsHandle, error) {
   112  	return GetFromPath(filepath.Join(bindMountPath, name))
   113  }
   114  
   115  // GetFromPid gets a handle to the network namespace of a given pid.
   116  func GetFromPid(pid int) (NsHandle, error) {
   117  	return GetFromPath(fmt.Sprintf("/proc/%d/ns/net", pid))
   118  }
   119  
   120  // GetFromThread gets a handle to the network namespace of a given pid and tid.
   121  func GetFromThread(pid, tid int) (NsHandle, error) {
   122  	return GetFromPath(fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid))
   123  }
   124  
   125  // GetFromDocker gets a handle to the network namespace of a docker container.
   126  // Id is prefixed matched against the running docker containers, so a short
   127  // identifier can be used as long as it isn't ambiguous.
   128  func GetFromDocker(id string) (NsHandle, error) {
   129  	pid, err := getPidForContainer(id)
   130  	if err != nil {
   131  		return -1, err
   132  	}
   133  	return GetFromPid(pid)
   134  }
   135  
   136  // borrowed from docker/utils/utils.go
   137  func findCgroupMountpoint(cgroupType string) (int, string, error) {
   138  	output, err := os.ReadFile("/proc/mounts")
   139  	if err != nil {
   140  		return -1, "", err
   141  	}
   142  
   143  	// /proc/mounts has 6 fields per line, one mount per line, e.g.
   144  	// cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
   145  	for _, line := range strings.Split(string(output), "\n") {
   146  		parts := strings.Split(line, " ")
   147  		if len(parts) == 6 {
   148  			switch parts[2] {
   149  			case "cgroup2":
   150  				return 2, parts[1], nil
   151  			case "cgroup":
   152  				for _, opt := range strings.Split(parts[3], ",") {
   153  					if opt == cgroupType {
   154  						return 1, parts[1], nil
   155  					}
   156  				}
   157  			}
   158  		}
   159  	}
   160  
   161  	return -1, "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
   162  }
   163  
   164  // Returns the relative path to the cgroup docker is running in.
   165  // borrowed from docker/utils/utils.go
   166  // modified to get the docker pid instead of using /proc/self
   167  func getDockerCgroup(cgroupVer int, cgroupType string) (string, error) {
   168  	dockerpid, err := os.ReadFile("/var/run/docker.pid")
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  	result := strings.Split(string(dockerpid), "\n")
   173  	if len(result) == 0 || len(result[0]) == 0 {
   174  		return "", fmt.Errorf("docker pid not found in /var/run/docker.pid")
   175  	}
   176  	pid, err := strconv.Atoi(result[0])
   177  	if err != nil {
   178  		return "", err
   179  	}
   180  	output, err := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid))
   181  	if err != nil {
   182  		return "", err
   183  	}
   184  	for _, line := range strings.Split(string(output), "\n") {
   185  		parts := strings.Split(line, ":")
   186  		// any type used by docker should work
   187  		if (cgroupVer == 1 && parts[1] == cgroupType) ||
   188  			(cgroupVer == 2 && parts[1] == "") {
   189  			return parts[2], nil
   190  		}
   191  	}
   192  	return "", fmt.Errorf("cgroup '%s' not found in /proc/%d/cgroup", cgroupType, pid)
   193  }
   194  
   195  // Returns the first pid in a container.
   196  // borrowed from docker/utils/utils.go
   197  // modified to only return the first pid
   198  // modified to glob with id
   199  // modified to search for newer docker containers
   200  // modified to look for cgroups v2
   201  func getPidForContainer(id string) (int, error) {
   202  	pid := 0
   203  
   204  	// memory is chosen randomly, any cgroup used by docker works
   205  	cgroupType := "memory"
   206  
   207  	cgroupVer, cgroupRoot, err := findCgroupMountpoint(cgroupType)
   208  	if err != nil {
   209  		return pid, err
   210  	}
   211  
   212  	cgroupDocker, err := getDockerCgroup(cgroupVer, cgroupType)
   213  	if err != nil {
   214  		return pid, err
   215  	}
   216  
   217  	id += "*"
   218  
   219  	var pidFile string
   220  	if cgroupVer == 1 {
   221  		pidFile = "tasks"
   222  	} else if cgroupVer == 2 {
   223  		pidFile = "cgroup.procs"
   224  	} else {
   225  		return -1, fmt.Errorf("Invalid cgroup version '%d'", cgroupVer)
   226  	}
   227  
   228  	attempts := []string{
   229  		filepath.Join(cgroupRoot, cgroupDocker, id, pidFile),
   230  		// With more recent lxc versions use, cgroup will be in lxc/
   231  		filepath.Join(cgroupRoot, cgroupDocker, "lxc", id, pidFile),
   232  		// With more recent docker, cgroup will be in docker/
   233  		filepath.Join(cgroupRoot, cgroupDocker, "docker", id, pidFile),
   234  		// Even more recent docker versions under systemd use docker-<id>.scope/
   235  		filepath.Join(cgroupRoot, "system.slice", "docker-"+id+".scope", pidFile),
   236  		// Even more recent docker versions under cgroup/systemd/docker/<id>/
   237  		filepath.Join(cgroupRoot, "..", "systemd", "docker", id, pidFile),
   238  		// Kubernetes with docker and CNI is even more different. Works for BestEffort and Burstable QoS
   239  		filepath.Join(cgroupRoot, "..", "systemd", "kubepods", "*", "pod*", id, pidFile),
   240  		// Same as above but for Guaranteed QoS
   241  		filepath.Join(cgroupRoot, "..", "systemd", "kubepods", "pod*", id, pidFile),
   242  		// Another flavor of containers location in recent kubernetes 1.11+. Works for BestEffort and Burstable QoS
   243  		filepath.Join(cgroupRoot, cgroupDocker, "kubepods.slice", "*.slice", "*", "docker-"+id+".scope", pidFile),
   244  		// Same as above but for Guaranteed QoS
   245  		filepath.Join(cgroupRoot, cgroupDocker, "kubepods.slice", "*", "docker-"+id+".scope", pidFile),
   246  		// When runs inside of a container with recent kubernetes 1.11+. Works for BestEffort and Burstable QoS
   247  		filepath.Join(cgroupRoot, "kubepods.slice", "*.slice", "*", "docker-"+id+".scope", pidFile),
   248  		// Same as above but for Guaranteed QoS
   249  		filepath.Join(cgroupRoot, "kubepods.slice", "*", "docker-"+id+".scope", pidFile),
   250  	}
   251  
   252  	var filename string
   253  	for _, attempt := range attempts {
   254  		filenames, _ := filepath.Glob(attempt)
   255  		if len(filenames) > 1 {
   256  			return pid, fmt.Errorf("Ambiguous id supplied: %v", filenames)
   257  		} else if len(filenames) == 1 {
   258  			filename = filenames[0]
   259  			break
   260  		}
   261  	}
   262  
   263  	if filename == "" {
   264  		return pid, fmt.Errorf("Unable to find container: %v", id[:len(id)-1])
   265  	}
   266  
   267  	output, err := os.ReadFile(filename)
   268  	if err != nil {
   269  		return pid, err
   270  	}
   271  
   272  	result := strings.Split(string(output), "\n")
   273  	if len(result) == 0 || len(result[0]) == 0 {
   274  		return pid, fmt.Errorf("No pid found for container")
   275  	}
   276  
   277  	pid, err = strconv.Atoi(result[0])
   278  	if err != nil {
   279  		return pid, fmt.Errorf("Invalid pid '%s': %s", result[0], err)
   280  	}
   281  
   282  	return pid, nil
   283  }
   284  

View as plain text