netns_linux.go 5.5 KB

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