netns_linux.go 6.7 KB

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