1// +build linux 2 3package netns 4 5import ( 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 "syscall" 13) 14 15const ( 16 // These constants belong in the syscall library but have not been 17 // added yet. 18 CLONE_NEWUTS = 0x04000000 /* New utsname group? */ 19 CLONE_NEWIPC = 0x08000000 /* New ipcs */ 20 CLONE_NEWUSER = 0x10000000 /* New user namespace */ 21 CLONE_NEWPID = 0x20000000 /* New pid namespace */ 22 CLONE_NEWNET = 0x40000000 /* New network namespace */ 23 CLONE_IO = 0x80000000 /* Get io context */ 24) 25 26// Setns sets namespace using syscall. Note that this should be a method 27// in syscall but it has not been added. 28func Setns(ns NsHandle, nstype int) (err error) { 29 _, _, e1 := syscall.Syscall(SYS_SETNS, uintptr(ns), uintptr(nstype), 0) 30 if e1 != 0 { 31 err = e1 32 } 33 return 34} 35 36// Set sets the current network namespace to the namespace represented 37// by NsHandle. 38func Set(ns NsHandle) (err error) { 39 return Setns(ns, CLONE_NEWNET) 40} 41 42// New creates a new network namespace and returns a handle to it. 43func New() (ns NsHandle, err error) { 44 if err := syscall.Unshare(CLONE_NEWNET); err != nil { 45 return -1, err 46 } 47 return Get() 48} 49 50// Get gets a handle to the current threads network namespace. 51func Get() (NsHandle, error) { 52 return GetFromThread(os.Getpid(), syscall.Gettid()) 53} 54 55// GetFromPath gets a handle to a network namespace 56// identified by the path 57func GetFromPath(path string) (NsHandle, error) { 58 fd, err := syscall.Open(path, syscall.O_RDONLY, 0) 59 if err != nil { 60 return -1, err 61 } 62 return NsHandle(fd), nil 63} 64 65// GetFromName gets a handle to a named network namespace such as one 66// created by `ip netns add`. 67func GetFromName(name string) (NsHandle, error) { 68 return GetFromPath(fmt.Sprintf("/var/run/netns/%s", name)) 69} 70 71// GetFromPid gets a handle to the network namespace of a given pid. 72func GetFromPid(pid int) (NsHandle, error) { 73 return GetFromPath(fmt.Sprintf("/proc/%d/ns/net", pid)) 74} 75 76// GetFromThread gets a handle to the network namespace of a given pid and tid. 77func GetFromThread(pid, tid int) (NsHandle, error) { 78 return GetFromPath(fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid)) 79} 80 81// GetFromDocker gets a handle to the network namespace of a docker container. 82// Id is prefixed matched against the running docker containers, so a short 83// identifier can be used as long as it isn't ambiguous. 84func GetFromDocker(id string) (NsHandle, error) { 85 pid, err := getPidForContainer(id) 86 if err != nil { 87 return -1, err 88 } 89 return GetFromPid(pid) 90} 91 92// borrowed from docker/utils/utils.go 93func findCgroupMountpoint(cgroupType string) (string, error) { 94 output, err := ioutil.ReadFile("/proc/mounts") 95 if err != nil { 96 return "", err 97 } 98 99 // /proc/mounts has 6 fields per line, one mount per line, e.g. 100 // cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0 101 for _, line := range strings.Split(string(output), "\n") { 102 parts := strings.Split(line, " ") 103 if len(parts) == 6 && parts[2] == "cgroup" { 104 for _, opt := range strings.Split(parts[3], ",") { 105 if opt == cgroupType { 106 return parts[1], nil 107 } 108 } 109 } 110 } 111 112 return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType) 113} 114 115// Returns the relative path to the cgroup docker is running in. 116// borrowed from docker/utils/utils.go 117// modified to get the docker pid instead of using /proc/self 118func getThisCgroup(cgroupType string) (string, error) { 119 dockerpid, err := ioutil.ReadFile("/var/run/docker.pid") 120 if err != nil { 121 return "", err 122 } 123 result := strings.Split(string(dockerpid), "\n") 124 if len(result) == 0 || len(result[0]) == 0 { 125 return "", fmt.Errorf("docker pid not found in /var/run/docker.pid") 126 } 127 pid, err := strconv.Atoi(result[0]) 128 129 output, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid)) 130 if err != nil { 131 return "", err 132 } 133 for _, line := range strings.Split(string(output), "\n") { 134 parts := strings.Split(line, ":") 135 // any type used by docker should work 136 if parts[1] == cgroupType { 137 return parts[2], nil 138 } 139 } 140 return "", fmt.Errorf("cgroup '%s' not found in /proc/%d/cgroup", cgroupType, pid) 141} 142 143// Returns the first pid in a container. 144// borrowed from docker/utils/utils.go 145// modified to only return the first pid 146// modified to glob with id 147// modified to search for newer docker containers 148func getPidForContainer(id string) (int, error) { 149 pid := 0 150 151 // memory is chosen randomly, any cgroup used by docker works 152 cgroupType := "memory" 153 154 cgroupRoot, err := findCgroupMountpoint(cgroupType) 155 if err != nil { 156 return pid, err 157 } 158 159 cgroupThis, err := getThisCgroup(cgroupType) 160 if err != nil { 161 return pid, err 162 } 163 164 id += "*" 165 166 attempts := []string{ 167 filepath.Join(cgroupRoot, cgroupThis, id, "tasks"), 168 // With more recent lxc versions use, cgroup will be in lxc/ 169 filepath.Join(cgroupRoot, cgroupThis, "lxc", id, "tasks"), 170 // With more recent dockee, cgroup will be in docker/ 171 filepath.Join(cgroupRoot, cgroupThis, "docker", id, "tasks"), 172 } 173 174 var filename string 175 for _, attempt := range attempts { 176 filenames, _ := filepath.Glob(attempt) 177 if len(filenames) > 1 { 178 return pid, fmt.Errorf("Ambiguous id supplied: %v", filenames) 179 } else if len(filenames) == 1 { 180 filename = filenames[0] 181 break 182 } 183 } 184 185 if filename == "" { 186 return pid, fmt.Errorf("Unable to find container: %v", id[:len(id)-1]) 187 } 188 189 output, err := ioutil.ReadFile(filename) 190 if err != nil { 191 return pid, err 192 } 193 194 result := strings.Split(string(output), "\n") 195 if len(result) == 0 || len(result[0]) == 0 { 196 return pid, fmt.Errorf("No pid found for container") 197 } 198 199 pid, err = strconv.Atoi(result[0]) 200 if err != nil { 201 return pid, fmt.Errorf("Invalid pid '%s': %s", result[0], err) 202 } 203 204 return pid, nil 205} 206