ip_forward.go 11 KB

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