netns_linux.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux && !android
  4. package netns
  5. import (
  6. "fmt"
  7. "net"
  8. "os"
  9. "sync"
  10. "syscall"
  11. "golang.org/x/sys/unix"
  12. "tailscale.com/envknob"
  13. "tailscale.com/net/interfaces"
  14. "tailscale.com/net/netmon"
  15. "tailscale.com/types/logger"
  16. )
  17. // tailscaleBypassMark is the mark indicating that packets originating
  18. // from a socket should bypass Tailscale-managed routes during routing
  19. // table lookups.
  20. //
  21. // Keep this in sync with tailscaleBypassMark in
  22. // wgengine/router/router_linux.go.
  23. const tailscaleBypassMark = 0x80000
  24. // socketMarkWorksOnce is the sync.Once & cached value for useSocketMark.
  25. var socketMarkWorksOnce struct {
  26. sync.Once
  27. v bool
  28. }
  29. // socketMarkWorks returns whether SO_MARK works.
  30. func socketMarkWorks() bool {
  31. addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:1")
  32. if err != nil {
  33. return true // unsure, returning true does the least harm.
  34. }
  35. sConn, err := net.DialUDP("udp", nil, addr)
  36. if err != nil {
  37. return true // unsure, return true
  38. }
  39. defer sConn.Close()
  40. rConn, err := sConn.SyscallConn()
  41. if err != nil {
  42. return true // unsure, return true
  43. }
  44. var sockErr error
  45. err = rConn.Control(func(fd uintptr) {
  46. sockErr = setBypassMark(fd)
  47. })
  48. if err != nil || sockErr != nil {
  49. return false
  50. }
  51. return true
  52. }
  53. var forceBindToDevice = envknob.RegisterBool("TS_FORCE_LINUX_BIND_TO_DEVICE")
  54. // UseSocketMark reports whether SO_MARK is in use.
  55. // If it doesn't, we have to use SO_BINDTODEVICE on our sockets instead.
  56. func UseSocketMark() bool {
  57. if forceBindToDevice() {
  58. return false
  59. }
  60. socketMarkWorksOnce.Do(func() {
  61. socketMarkWorksOnce.v = socketMarkWorks()
  62. })
  63. return socketMarkWorksOnce.v
  64. }
  65. // ignoreErrors returns true if we should ignore setsocketopt errors in
  66. // this instance.
  67. func ignoreErrors() bool {
  68. if os.Getuid() != 0 {
  69. // only root can manipulate these socket flags
  70. return true
  71. }
  72. return false
  73. }
  74. func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
  75. return controlC
  76. }
  77. // controlC marks c as necessary to dial in a separate network namespace.
  78. //
  79. // It's intentionally the same signature as net.Dialer.Control
  80. // and net.ListenConfig.Control.
  81. func controlC(network, address string, c syscall.RawConn) error {
  82. if isLocalhost(address) {
  83. // Don't bind to an interface for localhost connections.
  84. return nil
  85. }
  86. var sockErr error
  87. err := c.Control(func(fd uintptr) {
  88. if UseSocketMark() {
  89. sockErr = setBypassMark(fd)
  90. } else {
  91. sockErr = bindToDevice(fd)
  92. }
  93. })
  94. if err != nil {
  95. return fmt.Errorf("RawConn.Control on %T: %w", c, err)
  96. }
  97. if sockErr != nil && ignoreErrors() {
  98. // TODO(bradfitz): maybe log once? probably too spammy for e.g. CLI tools like tailscale netcheck.
  99. return nil
  100. }
  101. return sockErr
  102. }
  103. func setBypassMark(fd uintptr) error {
  104. if err := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, tailscaleBypassMark); err != nil {
  105. return fmt.Errorf("setting SO_MARK bypass: %w", err)
  106. }
  107. return nil
  108. }
  109. func bindToDevice(fd uintptr) error {
  110. ifc, err := interfaces.DefaultRouteInterface()
  111. if err != nil {
  112. // Make sure we bind to *some* interface,
  113. // or we could get a routing loop.
  114. // "lo" is always wrong, but if we don't have
  115. // a default route anyway, it doesn't matter.
  116. ifc = "lo"
  117. }
  118. if err := unix.SetsockoptString(int(fd), unix.SOL_SOCKET, unix.SO_BINDTODEVICE, ifc); err != nil {
  119. return fmt.Errorf("setting SO_BINDTODEVICE: %w", err)
  120. }
  121. return nil
  122. }