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