ip_forward.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. "os/exec"
  11. "path/filepath"
  12. "runtime"
  13. "strconv"
  14. "strings"
  15. "tailscale.com/net/interfaces"
  16. )
  17. // protocolsRequiredForForwarding reports whether IPv4 and/or IPv6 protocols are
  18. // required to forward the specified routes.
  19. // The state param must be specified.
  20. func protocolsRequiredForForwarding(routes []netip.Prefix, state *interfaces.State) (v4, v6 bool) {
  21. if len(routes) == 0 {
  22. // Nothing to route, so no need to warn.
  23. return false, false
  24. }
  25. localIPs := make(map[netip.Addr]bool)
  26. for _, addrs := range state.InterfaceIPs {
  27. for _, pfx := range addrs {
  28. localIPs[pfx.Addr()] = true
  29. }
  30. }
  31. for _, r := range routes {
  32. // It's possible to advertise a route to one of the local
  33. // machine's local IPs. IP forwarding isn't required for this
  34. // to work, so we shouldn't warn for such exports.
  35. if r.IsSingleIP() && localIPs[r.Addr()] {
  36. continue
  37. }
  38. if r.Addr().Is4() {
  39. v4 = true
  40. } else {
  41. v6 = true
  42. }
  43. }
  44. return v4, v6
  45. }
  46. // CheckIPForwarding reports whether IP forwarding is enabled correctly
  47. // for subnet routing and exit node functionality on any interface.
  48. // The state param must not be nil.
  49. // The routes should only be advertised routes, and should not contain the
  50. // nodes Tailscale IPs.
  51. // It returns an error if it is unable to determine if IP forwarding is enabled.
  52. // It returns a warning describing configuration issues if IP forwarding is
  53. // non-functional or partly functional.
  54. func CheckIPForwarding(routes []netip.Prefix, state *interfaces.State) (warn, err error) {
  55. if runtime.GOOS != "linux" {
  56. switch runtime.GOOS {
  57. case "dragonfly", "freebsd", "netbsd", "openbsd":
  58. 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
  59. }
  60. return nil, nil
  61. }
  62. if state == nil {
  63. return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration; no link state")
  64. }
  65. const kbLink = "\nSee https://tailscale.com/s/ip-forwarding"
  66. wantV4, wantV6 := protocolsRequiredForForwarding(routes, state)
  67. if !wantV4 && !wantV6 {
  68. return nil, nil
  69. }
  70. v4e, err := ipForwardingEnabledLinux(ipv4, "")
  71. if err != nil {
  72. return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
  73. }
  74. v6e, err := ipForwardingEnabledLinux(ipv6, "")
  75. if err != nil {
  76. return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
  77. }
  78. if v4e && v6e {
  79. // IP forwarding is enabled systemwide, all is well.
  80. return nil, nil
  81. }
  82. if !wantV4 {
  83. if !v6e {
  84. return nil, fmt.Errorf("IPv6 forwarding is disabled, subnet routing/exit nodes may not work.%s", kbLink)
  85. }
  86. return nil, nil
  87. }
  88. // IP forwarding isn't enabled globally, but it might be enabled
  89. // on a per-interface basis. Check if it's on for all interfaces,
  90. // and warn appropriately if it's not.
  91. // Note: you might be wondering why we check only the state of
  92. // ipv6.conf.all.forwarding, rather than per-interface forwarding
  93. // configuration. According to kernel documentation, it seems
  94. // that to actually forward packets, you need to enable
  95. // forwarding globally, and the per-interface forwarding
  96. // setting only alters other things such as how router
  97. // advertisements are handled. The kernel itself warns that
  98. // enabling forwarding per-interface and not globally will
  99. // probably not work, so I feel okay calling those configs
  100. // broken until we have proof otherwise.
  101. var (
  102. anyEnabled bool
  103. warnings []string
  104. )
  105. if wantV6 && !v6e {
  106. warnings = append(warnings, "IPv6 forwarding is disabled.")
  107. }
  108. for _, iface := range state.Interface {
  109. if iface.Name == "lo" {
  110. continue
  111. }
  112. v4e, err := ipForwardingEnabledLinux(ipv4, iface.Name)
  113. if err != nil {
  114. return nil, fmt.Errorf("Couldn't check system's IP forwarding configuration, subnet routing/exit nodes may not work: %w%s", err, kbLink)
  115. } else if !v4e {
  116. warnings = append(warnings, fmt.Sprintf("Traffic received on %s won't be forwarded (%s disabled)", iface.Name, ipForwardSysctlKey(dotFormat, ipv4, iface.Name)))
  117. } else {
  118. anyEnabled = true
  119. }
  120. }
  121. if !anyEnabled {
  122. // IP forwarding is completely disabled, just say that rather
  123. // than enumerate all the interfaces on the system.
  124. return fmt.Errorf("IP forwarding is disabled, subnet routing/exit nodes will not work.%s", kbLink), nil
  125. }
  126. if len(warnings) > 0 {
  127. // If partially enabled, enumerate the bits that won't work.
  128. return fmt.Errorf("%s\nSubnet routes and exit nodes may not work correctly.%s", strings.Join(warnings, "\n"), kbLink), nil
  129. }
  130. return nil, nil
  131. }
  132. // CheckReversePathFiltering reports whether reverse path filtering is either
  133. // disabled or set to 'loose' mode for exit node functionality on any
  134. // interface.
  135. //
  136. // The state param can be nil, in which case interfaces.GetState is used.
  137. //
  138. // The routes should only be advertised routes, and should not contain the
  139. // node's Tailscale IPs.
  140. //
  141. // This function returns an error if it is unable to determine whether reverse
  142. // path filtering is enabled, or a warning describing configuration issues if
  143. // reverse path fitering is non-functional or partly functional.
  144. func CheckReversePathFiltering(routes []netip.Prefix, state *interfaces.State) (warn []string, err error) {
  145. if runtime.GOOS != "linux" {
  146. return nil, nil
  147. }
  148. if state == nil {
  149. var err error
  150. state, err = interfaces.GetState()
  151. if err != nil {
  152. return nil, err
  153. }
  154. }
  155. // Reverse path filtering as a syscall is only implemented on Linux for IPv4.
  156. wantV4, _ := protocolsRequiredForForwarding(routes, state)
  157. if !wantV4 {
  158. return nil, nil
  159. }
  160. // The kernel uses the maximum value for rp_filter between the 'all'
  161. // setting and each per-interface config, so we need to fetch both.
  162. allSetting, err := reversePathFilterValueLinux("all")
  163. if err != nil {
  164. return nil, fmt.Errorf("reading global rp_filter value: %w", err)
  165. }
  166. const (
  167. filtOff = 0
  168. filtStrict = 1
  169. filtLoose = 2
  170. )
  171. // Because the kernel use the max rp_filter value, each interface will use 'loose', so we
  172. // can abort early.
  173. if allSetting == filtLoose {
  174. return nil, nil
  175. }
  176. for _, iface := range state.Interface {
  177. if iface.IsLoopback() {
  178. continue
  179. }
  180. iSetting, err := reversePathFilterValueLinux(iface.Name)
  181. if err != nil {
  182. return nil, fmt.Errorf("reading interface rp_filter value for %q: %w", iface.Name, err)
  183. }
  184. // Perform the same max() that the kernel does
  185. if allSetting > iSetting {
  186. iSetting = allSetting
  187. }
  188. if iSetting == filtStrict {
  189. warn = append(warn, fmt.Sprintf("Interface %q has strict reverse-path filtering enabled", iface.Name))
  190. }
  191. }
  192. return warn, nil
  193. }
  194. // ipForwardSysctlKey returns the sysctl key for the given protocol and iface.
  195. // When the dotFormat parameter is true the output is formatted as `net.ipv4.ip_forward`,
  196. // else it is `net/ipv4/ip_forward`
  197. func ipForwardSysctlKey(format sysctlFormat, p protocol, iface string) string {
  198. if iface == "" {
  199. if format == dotFormat {
  200. if p == ipv4 {
  201. return "net.ipv4.ip_forward"
  202. }
  203. return "net.ipv6.conf.all.forwarding"
  204. }
  205. if p == ipv4 {
  206. return "net/ipv4/ip_forward"
  207. }
  208. return "net/ipv6/conf/all/forwarding"
  209. }
  210. var k string
  211. if p == ipv4 {
  212. k = "net/ipv4/conf/%s/forwarding"
  213. } else {
  214. k = "net/ipv6/conf/%s/forwarding"
  215. }
  216. if format == dotFormat {
  217. // Swap the delimiters.
  218. iface = strings.ReplaceAll(iface, ".", "/")
  219. k = strings.ReplaceAll(k, "/", ".")
  220. }
  221. return fmt.Sprintf(k, iface)
  222. }
  223. // rpFilterSysctlKey returns the sysctl key for the given iface.
  224. //
  225. // Format controls whether the output is formatted as
  226. // `net.ipv4.conf.iface.rp_filter` or `net/ipv4/conf/iface/rp_filter`.
  227. func rpFilterSysctlKey(format sysctlFormat, iface string) string {
  228. // No iface means all interfaces
  229. if iface == "" {
  230. iface = "all"
  231. }
  232. k := "net/ipv4/conf/%s/rp_filter"
  233. if format == dotFormat {
  234. // Swap the delimiters.
  235. iface = strings.ReplaceAll(iface, ".", "/")
  236. k = strings.ReplaceAll(k, "/", ".")
  237. }
  238. return fmt.Sprintf(k, iface)
  239. }
  240. type sysctlFormat int
  241. const (
  242. dotFormat sysctlFormat = iota
  243. slashFormat
  244. )
  245. type protocol int
  246. const (
  247. ipv4 protocol = iota
  248. ipv6
  249. )
  250. // ipForwardingEnabledLinux reports whether the IP Forwarding is enabled for the
  251. // given interface.
  252. // The iface param determines which interface to check against, "" means to check
  253. // global config.
  254. // This is Linux-specific: it only reads from /proc/sys and doesn't shell out to
  255. // sysctl (which on Linux just reads from /proc/sys anyway).
  256. func ipForwardingEnabledLinux(p protocol, iface string) (bool, error) {
  257. k := ipForwardSysctlKey(slashFormat, p, iface)
  258. bs, err := os.ReadFile(filepath.Join("/proc/sys", k))
  259. if err != nil {
  260. if os.IsNotExist(err) {
  261. // If IPv6 is disabled, sysctl keys like "net.ipv6.conf.all.forwarding" just don't
  262. // exist on disk. But first diagnose whether procfs is even mounted before assuming
  263. // absence means false.
  264. if fi, err := os.Stat("/proc/sys"); err != nil {
  265. return false, fmt.Errorf("failed to check sysctl %v; no procfs? %w", k, err)
  266. } else if !fi.IsDir() {
  267. return false, fmt.Errorf("failed to check sysctl %v; /proc/sys isn't a directory, is %v", k, fi.Mode())
  268. }
  269. return false, nil
  270. }
  271. return false, err
  272. }
  273. val, err := strconv.ParseInt(string(bytes.TrimSpace(bs)), 10, 32)
  274. if err != nil {
  275. return false, fmt.Errorf("couldn't parse %s: %w", k, err)
  276. }
  277. // 0 = disabled, 1 = enabled, 2 = enabled (but uncommon)
  278. // https://github.com/tailscale/tailscale/issues/8375
  279. if val < 0 || val > 2 {
  280. return false, fmt.Errorf("unexpected value %d for %s", val, k)
  281. }
  282. on := val == 1 || val == 2
  283. return on, nil
  284. }
  285. // reversePathFilterValueLinux reports the reverse path filter setting on Linux
  286. // for the given interface.
  287. //
  288. // The iface param determines which interface to check against; the empty
  289. // string means to check the global config.
  290. //
  291. // This function tries to look up the value directly from `/proc/sys`, and
  292. // falls back to using the `sysctl` command on failure.
  293. func reversePathFilterValueLinux(iface string) (int, error) {
  294. k := rpFilterSysctlKey(slashFormat, iface)
  295. bs, err := os.ReadFile(filepath.Join("/proc/sys", k))
  296. if err != nil {
  297. // Fall back to the sysctl command
  298. k := rpFilterSysctlKey(dotFormat, iface)
  299. bs, err = exec.Command("sysctl", "-n", k).Output()
  300. if err != nil {
  301. return -1, fmt.Errorf("couldn't check %s (%v)", k, err)
  302. }
  303. }
  304. v, err := strconv.Atoi(string(bytes.TrimSpace(bs)))
  305. if err != nil {
  306. return -1, fmt.Errorf("couldn't parse %s (%v)", k, err)
  307. }
  308. return v, nil
  309. }