netns_linux.go 8.3 KB

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