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