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
15 const (
16 CLONE_NEWUTS = unix.CLONE_NEWUTS
17 CLONE_NEWIPC = unix.CLONE_NEWIPC
18 CLONE_NEWUSER = unix.CLONE_NEWUSER
19 CLONE_NEWPID = unix.CLONE_NEWPID
20 CLONE_NEWNET = unix.CLONE_NEWNET
21 CLONE_IO = unix.CLONE_IO
22 )
23
24 const bindMountPath = "/run/netns"
25
26
27
28
29 func Setns(ns NsHandle, nstype int) (err error) {
30 return unix.Setns(int(ns), nstype)
31 }
32
33
34
35 func Set(ns NsHandle) (err error) {
36 return unix.Setns(int(ns), unix.CLONE_NEWNET)
37 }
38
39
40
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
49
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
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
95 func Get() (NsHandle, error) {
96 return GetFromThread(os.Getpid(), unix.Gettid())
97 }
98
99
100
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
110
111 func GetFromName(name string) (NsHandle, error) {
112 return GetFromPath(filepath.Join(bindMountPath, name))
113 }
114
115
116 func GetFromPid(pid int) (NsHandle, error) {
117 return GetFromPath(fmt.Sprintf("/proc/%d/ns/net", pid))
118 }
119
120
121 func GetFromThread(pid, tid int) (NsHandle, error) {
122 return GetFromPath(fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid))
123 }
124
125
126
127
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
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
144
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
165
166
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
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
196
197
198
199
200
201 func getPidForContainer(id string) (int, error) {
202 pid := 0
203
204
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
231 filepath.Join(cgroupRoot, cgroupDocker, "lxc", id, pidFile),
232
233 filepath.Join(cgroupRoot, cgroupDocker, "docker", id, pidFile),
234
235 filepath.Join(cgroupRoot, "system.slice", "docker-"+id+".scope", pidFile),
236
237 filepath.Join(cgroupRoot, "..", "systemd", "docker", id, pidFile),
238
239 filepath.Join(cgroupRoot, "..", "systemd", "kubepods", "*", "pod*", id, pidFile),
240
241 filepath.Join(cgroupRoot, "..", "systemd", "kubepods", "pod*", id, pidFile),
242
243 filepath.Join(cgroupRoot, cgroupDocker, "kubepods.slice", "*.slice", "*", "docker-"+id+".scope", pidFile),
244
245 filepath.Join(cgroupRoot, cgroupDocker, "kubepods.slice", "*", "docker-"+id+".scope", pidFile),
246
247 filepath.Join(cgroupRoot, "kubepods.slice", "*.slice", "*", "docker-"+id+".scope", pidFile),
248
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