podman

Форк
0
283 строки · 8.3 Кб
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

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.