ip_forward.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package netutil contains misc shared networking code & types.
  4. package netutil
  5. import (
  6. "bytes"
  7. "fmt"
  8. "net/netip"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strconv"
  13. "strings"
  14. "tailscale.com/net/interfaces"
  15. )
  16. // protocolsRequiredForForwarding reports whether IPv4 and/or IPv6 protocols are
  17. // required to forward the specified routes.
  18. // The state param must be specified.
  19. func protocolsRequiredForForwarding(routes []netip.Prefix, state *interfaces.State) (v4, v6 bool) {
  20. if len(routes) == 0 {
  21. // Nothing to route, so no need to warn.
  22. return false, false
  23. }
  24. localIPs := make(map[netip.Addr]bool)
  25. for _, addrs := range state.InterfaceIPs {
  26. for _, pfx := range addrs {
  27. localIPs[pfx.Addr()] = true
  28. }
  29. }
  30. for _, r := range routes {
  31. // It's possible to advertise a route to one of the local
  32. // machine's local IPs. IP forwarding isn't required for this
  33. // to work, so we shouldn't warn for such exports.
  34. if r.IsSingleIP() && localIPs[r.Addr()] {
  35. continue
  36. }
  37. if r.Addr().Is4() {
  38. v4 = true
  39. } else {
  40. v6 = true
  41. }
  42. }
  43. return v4, v6
  44. }
  45. // CheckIPForwarding reports whether IP forwarding is enabled correctly
  46. // for subnet routing and exit node functionality on any interface.
  47. // The state param can be nil, in which case interfaces.GetState is used.
  48. // The routes should only be advertised routes, and should not contain the
  49. // nodes Tailscale IPs.
  50. // It returns an error if it is unable to determine if IP forwarding is enabled.
  51. // It returns a warning describing configuration issues if IP forwarding is
  52. // non-functional or partly functional.
  53. func CheckIPForwarding(routes []netip.Prefix, state *interfaces.State) (warn, err error) {
  54. if runtime.GOOS != "linux" {
  55. switch runtime.GOOS {
  56. case "dragonfly", "freebsd", "netbsd", "openbsd":
  57. return fmt.Errorf("Subnet routing and exit nodes only work with additional manual configuration on %v, and is not currently officially supported.", runtime.GOOS), nil
  58. }
  59. return nil, nil
  60. }
  61. const kbLink = "\nSee https://tailscale.com/s/ip-forwarding"
  62. if state == nil {
  63. var err error
  64. state, err = interfaces.GetState()
  65. if err != nil {
  66. return nil, err
  67. }
  68. }
  69. wantV4, wantV6 := protocolsRequiredForForwarding(routes, state)
  70. if !wantV4 && !wantV6 {
  71. return nil, nil
  72. }
  73. v4e, err := ipForwardingEnabledLinux(ipv4, "")
  74. if err != nil {
  75. return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
  76. }
  77. v6e, err := ipForwardingEnabledLinux(ipv6, "")
  78. if err != nil {
  79. return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
  80. }
  81. if v4e && v6e {
  82. // IP forwarding is enabled systemwide, all is well.
  83. return nil, nil
  84. }
  85. if !wantV4 {
  86. if !v6e {
  87. return nil, fmt.Errorf("IPv6 forwarding is disabled, subnet routing/exit nodes may not work.%s", kbLink)
  88. }
  89. return nil, nil
  90. }
  91. // IP forwarding isn't enabled globally, but it might be enabled
  92. // on a per-interface basis. Check if it's on for all interfaces,
  93. // and warn appropriately if it's not.
  94. // Note: you might be wondering why we check only the state of
  95. // ipv6.conf.all.forwarding, rather than per-interface forwarding
  96. // configuration. According to kernel documentation, it seems
  97. // that to actually forward packets, you need to enable
  98. // forwarding globally, and the per-interface forwarding
  99. // setting only alters other things such as how router
  100. // advertisements are handled. The kernel itself warns that
  101. // enabling forwarding per-interface and not globally will
  102. // probably not work, so I feel okay calling those configs
  103. // broken until we have proof otherwise.
  104. var (
  105. anyEnabled bool
  106. warnings []string
  107. )
  108. if wantV6 && !v6e {
  109. warnings = append(warnings, "IPv6 forwarding is disabled.")
  110. }
  111. for _, iface := range state.Interface {
  112. if iface.Name == "lo" {
  113. continue
  114. }
  115. v4e, err := ipForwardingEnabledLinux(ipv4, iface.Name)
  116. if err != nil {
  117. return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
  118. } else if !v4e {
  119. warnings = append(warnings, fmt.Sprintf("Traffic received on %s won't be forwarded (%s disabled)", iface.Name, ipForwardSysctlKey(dotFormat, ipv4, iface.Name)))
  120. } else {
  121. anyEnabled = true
  122. }
  123. }
  124. if !anyEnabled {
  125. // IP forwarding is completely disabled, just say that rather
  126. // than enumerate all the interfaces on the system.
  127. return fmt.Errorf("IP forwarding is disabled, subnet routing/exit nodes will not work.%s", kbLink), nil
  128. }
  129. if len(warnings) > 0 {
  130. // If partially enabled, enumerate the bits that won't work.
  131. return fmt.Errorf("%s\nSubnet routes and exit nodes may not work correctly.%s", strings.Join(warnings, "\n"), kbLink), nil
  132. }
  133. return nil, nil
  134. }
  135. // ipForwardSysctlKey returns the sysctl key for the given protocol and iface.
  136. // When the dotFormat parameter is true the output is formatted as `net.ipv4.ip_forward`,
  137. // else it is `net/ipv4/ip_forward`
  138. func ipForwardSysctlKey(format sysctlFormat, p protocol, iface string) string {
  139. if iface == "" {
  140. if format == dotFormat {
  141. if p == ipv4 {
  142. return "net.ipv4.ip_forward"
  143. }
  144. return "net.ipv6.conf.all.forwarding"
  145. }
  146. if p == ipv4 {
  147. return "net/ipv4/ip_forward"
  148. }
  149. return "net/ipv6/conf/all/forwarding"
  150. }
  151. var k string
  152. if p == ipv4 {
  153. k = "net/ipv4/conf/%s/forwarding"
  154. } else {
  155. k = "net/ipv6/conf/%s/forwarding"
  156. }
  157. if format == dotFormat {
  158. // Swap the delimiters.
  159. iface = strings.ReplaceAll(iface, ".", "/")
  160. k = strings.ReplaceAll(k, "/", ".")
  161. }
  162. return fmt.Sprintf(k, iface)
  163. }
  164. type sysctlFormat int
  165. const (
  166. dotFormat sysctlFormat = iota
  167. slashFormat
  168. )
  169. type protocol int
  170. const (
  171. ipv4 protocol = iota
  172. ipv6
  173. )
  174. // ipForwardingEnabledLinux reports whether the IP Forwarding is enabled for the
  175. // given interface.
  176. // The iface param determines which interface to check against, "" means to check
  177. // global config.
  178. // This is Linux-specific: it only reads from /proc/sys and doesn't shell out to
  179. // sysctl (which on Linux just reads from /proc/sys anyway).
  180. func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
  181. k := ipForwardSysctlKey(slashFormat, p, iface)
  182. bs, err := os.ReadFile(filepath.Join("/proc/sys", k))
  183. if err != nil {
  184. if os.IsNotExist(err) {
  185. // If IPv6 is disabled, sysctl keys like "net.ipv6.conf.all.forwarding" just don't
  186. // exist on disk. But first diagnose whether procfs is even mounted before assuming
  187. // absence means false.
  188. if fi, err := os.Stat("/proc/sys"); err != nil {
  189. return false, fmt.Errorf("failed to check sysctl %v; no procfs? %w", k, err)
  190. } else if !fi.IsDir() {
  191. return false, fmt.Errorf("failed to check sysctl %v; /proc/sys isn't a directory, is %v", k, fi.Mode())
  192. }
  193. return false, nil
  194. }
  195. return false, err
  196. }
  197. val, err := strconv.ParseInt(string(bytes.TrimSpace(bs)), 10, 32)
  198. if err != nil {
  199. return false, fmt.Errorf("couldn't parse %s: %w", k, err)
  200. }
  201. // 0 = disabled, 1 = enabled, 2 = enabled (but uncommon)
  202. // https://github.com/tailscale/tailscale/issues/8375
  203. if val < 0 || val > 2 {
  204. return false, fmt.Errorf("unexpected value %d for %s", val, k)
  205. }
  206. on := val == 1 || val == 2
  207. return on, nil
  208. }