netns_linux.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. // +build linux,go1.10
  2. package netns
  3. import (
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "syscall"
  12. "golang.org/x/sys/unix"
  13. )
  14. // Deprecated: use syscall pkg instead (go >= 1.5 needed).
  15. const (
  16. CLONE_NEWUTS = 0x04000000 /* New utsname group? */
  17. CLONE_NEWIPC = 0x08000000 /* New ipcs */
  18. CLONE_NEWUSER = 0x10000000 /* New user namespace */
  19. CLONE_NEWPID = 0x20000000 /* New pid namespace */
  20. CLONE_NEWNET = 0x40000000 /* New network namespace */
  21. CLONE_IO = 0x80000000 /* Get io context */
  22. bindMountPath = "/run/netns" /* Bind mount path for named netns */
  23. )
  24. // Setns sets namespace using syscall. Note that this should be a method
  25. // in syscall but it has not been added.
  26. func Setns(ns NsHandle, nstype int) (err error) {
  27. return unix.Setns(int(ns), nstype)
  28. }
  29. // Set sets the current network namespace to the namespace represented
  30. // by NsHandle.
  31. func Set(ns NsHandle) (err error) {
  32. return Setns(ns, CLONE_NEWNET)
  33. }
  34. // New creates a new network namespace, sets it as current and returns
  35. // a handle to it.
  36. func New() (ns NsHandle, err error) {
  37. if err := unix.Unshare(CLONE_NEWNET); err != nil {
  38. return -1, err
  39. }
  40. return Get()
  41. }
  42. // NewNamed creates a new named network namespace and returns a handle to it
  43. func NewNamed(name string) (NsHandle, error) {
  44. if _, err := os.Stat(bindMountPath); os.IsNotExist(err) {
  45. err = os.MkdirAll(bindMountPath, 0755)
  46. if err != nil {
  47. return None(), err
  48. }
  49. }
  50. newNs, err := New()
  51. if err != nil {
  52. return None(), err
  53. }
  54. namedPath := path.Join(bindMountPath, name)
  55. f, err := os.OpenFile(namedPath, os.O_CREATE|os.O_EXCL, 0444)
  56. if err != nil {
  57. return None(), err
  58. }
  59. f.Close()
  60. nsPath := fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), syscall.Gettid())
  61. err = syscall.Mount(nsPath, namedPath, "bind", syscall.MS_BIND, "")
  62. if err != nil {
  63. return None(), err
  64. }
  65. return newNs, nil
  66. }
  67. // DeleteNamed deletes a named network namespace
  68. func DeleteNamed(name string) error {
  69. namedPath := path.Join(bindMountPath, name)
  70. err := syscall.Unmount(namedPath, syscall.MNT_DETACH)
  71. if err != nil {
  72. return err
  73. }
  74. return os.Remove(namedPath)
  75. }
  76. // Get gets a handle to the current threads network namespace.
  77. func Get() (NsHandle, error) {
  78. return GetFromThread(os.Getpid(), unix.Gettid())
  79. }
  80. // GetFromPath gets a handle to a network namespace
  81. // identified by the path
  82. func GetFromPath(path string) (NsHandle, error) {
  83. fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC, 0)
  84. if err != nil {
  85. return -1, err
  86. }
  87. return NsHandle(fd), nil
  88. }
  89. // GetFromName gets a handle to a named network namespace such as one
  90. // created by `ip netns add`.
  91. func GetFromName(name string) (NsHandle, error) {
  92. return GetFromPath(fmt.Sprintf("/var/run/netns/%s", name))
  93. }
  94. // GetFromPid gets a handle to the network namespace of a given pid.
  95. func GetFromPid(pid int) (NsHandle, error) {
  96. return GetFromPath(fmt.Sprintf("/proc/%d/ns/net", pid))
  97. }
  98. // GetFromThread gets a handle to the network namespace of a given pid and tid.
  99. func GetFromThread(pid, tid int) (NsHandle, error) {
  100. return GetFromPath(fmt.Sprintf("/proc/%d/task/%d/ns/net", pid, tid))
  101. }
  102. // GetFromDocker gets a handle to the network namespace of a docker container.
  103. // Id is prefixed matched against the running docker containers, so a short
  104. // identifier can be used as long as it isn't ambiguous.
  105. func GetFromDocker(id string) (NsHandle, error) {
  106. pid, err := getPidForContainer(id)
  107. if err != nil {
  108. return -1, err
  109. }
  110. return GetFromPid(pid)
  111. }
  112. // borrowed from docker/utils/utils.go
  113. func findCgroupMountpoint(cgroupType string) (string, error) {
  114. output, err := ioutil.ReadFile("/proc/mounts")
  115. if err != nil {
  116. return "", err
  117. }
  118. // /proc/mounts has 6 fields per line, one mount per line, e.g.
  119. // cgroup /sys/fs/cgroup/devices cgroup rw,relatime,devices 0 0
  120. for _, line := range strings.Split(string(output), "\n") {
  121. parts := strings.Split(line, " ")
  122. if len(parts) == 6 && parts[2] == "cgroup" {
  123. for _, opt := range strings.Split(parts[3], ",") {
  124. if opt == cgroupType {
  125. return parts[1], nil
  126. }
  127. }
  128. }
  129. }
  130. return "", fmt.Errorf("cgroup mountpoint not found for %s", cgroupType)
  131. }
  132. // Returns the relative path to the cgroup docker is running in.
  133. // borrowed from docker/utils/utils.go
  134. // modified to get the docker pid instead of using /proc/self
  135. func getThisCgroup(cgroupType string) (string, error) {
  136. dockerpid, err := ioutil.ReadFile("/var/run/docker.pid")
  137. if err != nil {
  138. return "", err
  139. }
  140. result := strings.Split(string(dockerpid), "\n")
  141. if len(result) == 0 || len(result[0]) == 0 {
  142. return "", fmt.Errorf("docker pid not found in /var/run/docker.pid")
  143. }
  144. pid, err := strconv.Atoi(result[0])
  145. if err != nil {
  146. return "", err
  147. }
  148. output, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pid))
  149. if err != nil {
  150. return "", err
  151. }
  152. for _, line := range strings.Split(string(output), "\n") {
  153. parts := strings.Split(line, ":")
  154. // any type used by docker should work
  155. if parts[1] == cgroupType {
  156. return parts[2], nil
  157. }
  158. }
  159. return "", fmt.Errorf("cgroup '%s' not found in /proc/%d/cgroup", cgroupType, pid)
  160. }
  161. // Returns the first pid in a container.
  162. // borrowed from docker/utils/utils.go
  163. // modified to only return the first pid
  164. // modified to glob with id
  165. // modified to search for newer docker containers
  166. func getPidForContainer(id string) (int, error) {
  167. pid := 0
  168. // memory is chosen randomly, any cgroup used by docker works
  169. cgroupType := "memory"
  170. cgroupRoot, err := findCgroupMountpoint(cgroupType)
  171. if err != nil {
  172. return pid, err
  173. }
  174. cgroupThis, err := getThisCgroup(cgroupType)
  175. if err != nil {
  176. return pid, err
  177. }
  178. id += "*"
  179. attempts := []string{
  180. filepath.Join(cgroupRoot, cgroupThis, id, "tasks"),
  181. // With more recent lxc versions use, cgroup will be in lxc/
  182. filepath.Join(cgroupRoot, cgroupThis, "lxc", id, "tasks"),
  183. // With more recent docker, cgroup will be in docker/
  184. filepath.Join(cgroupRoot, cgroupThis, "docker", id, "tasks"),
  185. // Even more recent docker versions under systemd use docker-<id>.scope/
  186. filepath.Join(cgroupRoot, "system.slice", "docker-"+id+".scope", "tasks"),
  187. // Even more recent docker versions under cgroup/systemd/docker/<id>/
  188. filepath.Join(cgroupRoot, "..", "systemd", "docker", id, "tasks"),
  189. // Kubernetes with docker and CNI is even more different. Works for BestEffort and Burstable QoS
  190. filepath.Join(cgroupRoot, "..", "systemd", "kubepods", "*", "pod*", id, "tasks"),
  191. // Same as above but for Guaranteed QoS
  192. filepath.Join(cgroupRoot, "..", "systemd", "kubepods", "pod*", id, "tasks"),
  193. // Another flavor of containers location in recent kubernetes 1.11+. Works for BestEffort and Burstable QoS
  194. filepath.Join(cgroupRoot, cgroupThis, "kubepods.slice", "*.slice", "*", "docker-"+id+".scope", "tasks"),
  195. // Same as above but for Guaranteed QoS
  196. filepath.Join(cgroupRoot, cgroupThis, "kubepods.slice", "*", "docker-"+id+".scope", "tasks"),
  197. // When runs inside of a container with recent kubernetes 1.11+. Works for BestEffort and Burstable QoS
  198. filepath.Join(cgroupRoot, "kubepods.slice", "*.slice", "*", "docker-"+id+".scope", "tasks"),
  199. // Same as above but for Guaranteed QoS
  200. filepath.Join(cgroupRoot, "kubepods.slice", "*", "docker-"+id+".scope", "tasks"),
  201. }
  202. var filename string
  203. for _, attempt := range attempts {
  204. filenames, _ := filepath.Glob(attempt)
  205. if len(filenames) > 1 {
  206. return pid, fmt.Errorf("Ambiguous id supplied: %v", filenames)
  207. } else if len(filenames) == 1 {
  208. filename = filenames[0]
  209. break
  210. }
  211. }
  212. if filename == "" {
  213. return pid, fmt.Errorf("Unable to find container: %v", id[:len(id)-1])
  214. }
  215. output, err := ioutil.ReadFile(filename)
  216. if err != nil {
  217. return pid, err
  218. }
  219. result := strings.Split(string(output), "\n")
  220. if len(result) == 0 || len(result[0]) == 0 {
  221. return pid, fmt.Errorf("No pid found for container")
  222. }
  223. pid, err = strconv.Atoi(result[0])
  224. if err != nil {
  225. return pid, fmt.Errorf("Invalid pid '%s': %s", result[0], err)
  226. }
  227. return pid, nil
  228. }