forwarding.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build linux
  4. package main
  5. import (
  6. "context"
  7. "fmt"
  8. "log"
  9. "net"
  10. "net/netip"
  11. "os"
  12. "path/filepath"
  13. "strings"
  14. "tailscale.com/util/linuxfw"
  15. )
  16. // ensureIPForwarding enables IPv4/IPv6 forwarding for the container.
  17. func ensureIPForwarding(root, clusterProxyTargetIP, tailnetTargetIP, tailnetTargetFQDN string, routes *string) error {
  18. var (
  19. v4Forwarding, v6Forwarding bool
  20. )
  21. if clusterProxyTargetIP != "" {
  22. proxyIP, err := netip.ParseAddr(clusterProxyTargetIP)
  23. if err != nil {
  24. return fmt.Errorf("invalid cluster destination IP: %v", err)
  25. }
  26. if proxyIP.Is4() {
  27. v4Forwarding = true
  28. } else {
  29. v6Forwarding = true
  30. }
  31. }
  32. if tailnetTargetIP != "" {
  33. proxyIP, err := netip.ParseAddr(tailnetTargetIP)
  34. if err != nil {
  35. return fmt.Errorf("invalid tailnet destination IP: %v", err)
  36. }
  37. if proxyIP.Is4() {
  38. v4Forwarding = true
  39. } else {
  40. v6Forwarding = true
  41. }
  42. }
  43. // Currently we only proxy traffic to the IPv4 address of the tailnet
  44. // target.
  45. if tailnetTargetFQDN != "" {
  46. v4Forwarding = true
  47. }
  48. if routes != nil && *routes != "" {
  49. for _, route := range strings.Split(*routes, ",") {
  50. cidr, err := netip.ParsePrefix(route)
  51. if err != nil {
  52. return fmt.Errorf("invalid subnet route: %v", err)
  53. }
  54. if cidr.Addr().Is4() {
  55. v4Forwarding = true
  56. } else {
  57. v6Forwarding = true
  58. }
  59. }
  60. }
  61. return enableIPForwarding(v4Forwarding, v6Forwarding, root)
  62. }
  63. func enableIPForwarding(v4Forwarding, v6Forwarding bool, root string) error {
  64. var paths []string
  65. if v4Forwarding {
  66. paths = append(paths, filepath.Join(root, "proc/sys/net/ipv4/ip_forward"))
  67. }
  68. if v6Forwarding {
  69. paths = append(paths, filepath.Join(root, "proc/sys/net/ipv6/conf/all/forwarding"))
  70. }
  71. // In some common configurations (e.g. default docker,
  72. // kubernetes), the container environment denies write access to
  73. // most sysctls, including IP forwarding controls. Check the
  74. // sysctl values before trying to change them, so that we
  75. // gracefully do nothing if the container's already been set up
  76. // properly by e.g. a k8s initContainer.
  77. for _, path := range paths {
  78. bs, err := os.ReadFile(path)
  79. if err != nil {
  80. return fmt.Errorf("reading %q: %w", path, err)
  81. }
  82. if v := strings.TrimSpace(string(bs)); v != "1" {
  83. if err := os.WriteFile(path, []byte("1"), 0644); err != nil {
  84. return fmt.Errorf("enabling %q: %w", path, err)
  85. }
  86. }
  87. }
  88. return nil
  89. }
  90. func installEgressForwardingRule(_ context.Context, dstStr string, tsIPs []netip.Prefix, nfr linuxfw.NetfilterRunner) error {
  91. dst, err := netip.ParseAddr(dstStr)
  92. if err != nil {
  93. return err
  94. }
  95. var local netip.Addr
  96. for _, pfx := range tsIPs {
  97. if !pfx.IsSingleIP() {
  98. continue
  99. }
  100. if pfx.Addr().Is4() != dst.Is4() {
  101. continue
  102. }
  103. local = pfx.Addr()
  104. break
  105. }
  106. if !local.IsValid() {
  107. return fmt.Errorf("no tailscale IP matching family of %s found in %v", dstStr, tsIPs)
  108. }
  109. if err := nfr.DNATNonTailscaleTraffic("tailscale0", dst); err != nil {
  110. return fmt.Errorf("installing egress proxy rules: %w", err)
  111. }
  112. if err := nfr.EnsureSNATForDst(local, dst); err != nil {
  113. return fmt.Errorf("installing egress proxy rules: %w", err)
  114. }
  115. if err := nfr.ClampMSSToPMTU("tailscale0", dst); err != nil {
  116. return fmt.Errorf("installing egress proxy rules: %w", err)
  117. }
  118. return nil
  119. }
  120. // installTSForwardingRuleForDestination accepts a destination address and a
  121. // list of node's tailnet addresses, sets up rules to forward traffic for
  122. // destination to the tailnet IP matching the destination IP family.
  123. // Destination can be Pod IP of this node.
  124. func installTSForwardingRuleForDestination(_ context.Context, dstFilter string, tsIPs []netip.Prefix, nfr linuxfw.NetfilterRunner) error {
  125. dst, err := netip.ParseAddr(dstFilter)
  126. if err != nil {
  127. return err
  128. }
  129. var local netip.Addr
  130. for _, pfx := range tsIPs {
  131. if !pfx.IsSingleIP() {
  132. continue
  133. }
  134. if pfx.Addr().Is4() != dst.Is4() {
  135. continue
  136. }
  137. local = pfx.Addr()
  138. break
  139. }
  140. if !local.IsValid() {
  141. return fmt.Errorf("no tailscale IP matching family of %s found in %v", dstFilter, tsIPs)
  142. }
  143. if err := nfr.AddDNATRule(dst, local); err != nil {
  144. return fmt.Errorf("installing rule for forwarding traffic to tailnet IP: %w", err)
  145. }
  146. return nil
  147. }
  148. func installIngressForwardingRule(_ context.Context, dstStr string, tsIPs []netip.Prefix, nfr linuxfw.NetfilterRunner) error {
  149. dst, err := netip.ParseAddr(dstStr)
  150. if err != nil {
  151. return err
  152. }
  153. var local netip.Addr
  154. proxyHasIPv4Address := false
  155. for _, pfx := range tsIPs {
  156. if !pfx.IsSingleIP() {
  157. continue
  158. }
  159. if pfx.Addr().Is4() {
  160. proxyHasIPv4Address = true
  161. }
  162. if pfx.Addr().Is4() != dst.Is4() {
  163. continue
  164. }
  165. local = pfx.Addr()
  166. break
  167. }
  168. if proxyHasIPv4Address && dst.Is6() {
  169. log.Printf("Warning: proxy backend ClusterIP is an IPv6 address and the proxy has a IPv4 tailnet address. You might need to disable IPv4 address allocation for the proxy for forwarding to work. See https://github.com/tailscale/tailscale/issues/12156")
  170. }
  171. if !local.IsValid() {
  172. return fmt.Errorf("no tailscale IP matching family of %s found in %v", dstStr, tsIPs)
  173. }
  174. if err := nfr.AddDNATRule(local, dst); err != nil {
  175. return fmt.Errorf("installing ingress proxy rules: %w", err)
  176. }
  177. if err := nfr.ClampMSSToPMTU("tailscale0", dst); err != nil {
  178. return fmt.Errorf("installing ingress proxy rules: %w", err)
  179. }
  180. return nil
  181. }
  182. func installIngressForwardingRuleForDNSTarget(_ context.Context, backendAddrs []net.IP, tsIPs []netip.Prefix, nfr linuxfw.NetfilterRunner) error {
  183. var (
  184. tsv4 netip.Addr
  185. tsv6 netip.Addr
  186. v4Backends []netip.Addr
  187. v6Backends []netip.Addr
  188. )
  189. for _, pfx := range tsIPs {
  190. if pfx.IsSingleIP() && pfx.Addr().Is4() {
  191. tsv4 = pfx.Addr()
  192. continue
  193. }
  194. if pfx.IsSingleIP() && pfx.Addr().Is6() {
  195. tsv6 = pfx.Addr()
  196. continue
  197. }
  198. }
  199. // TODO: log if more than one backend address is found and firewall is
  200. // in nftables mode that only the first IP will be used.
  201. for _, ip := range backendAddrs {
  202. if ip.To4() != nil {
  203. v4Backends = append(v4Backends, netip.AddrFrom4([4]byte(ip.To4())))
  204. }
  205. if ip.To16() != nil {
  206. v6Backends = append(v6Backends, netip.AddrFrom16([16]byte(ip.To16())))
  207. }
  208. }
  209. // Enable IP forwarding here as opposed to at the start of containerboot
  210. // as the IPv4/IPv6 requirements might have changed.
  211. // For Kubernetes operator proxies, forwarding for both IPv4 and IPv6 is
  212. // enabled by an init container, so in practice enabling forwarding here
  213. // is only needed if this proxy has been configured by manually setting
  214. // TS_EXPERIMENTAL_DEST_DNS_NAME env var for a containerboot instance.
  215. if err := enableIPForwarding(len(v4Backends) != 0, len(v6Backends) != 0, ""); err != nil {
  216. log.Printf("[unexpected] failed to ensure IP forwarding: %v", err)
  217. }
  218. updateFirewall := func(dst netip.Addr, backendTargets []netip.Addr) error {
  219. if err := nfr.DNATWithLoadBalancer(dst, backendTargets); err != nil {
  220. return fmt.Errorf("installing DNAT rules for ingress backends %+#v: %w", backendTargets, err)
  221. }
  222. // The backend might advertize MSS higher than that of the
  223. // tailscale interfaces. Clamp MSS of packets going out via
  224. // tailscale0 interface to its MTU to prevent broken connections
  225. // in environments where path MTU discovery is not working.
  226. if err := nfr.ClampMSSToPMTU("tailscale0", dst); err != nil {
  227. return fmt.Errorf("adding rule to clamp traffic via tailscale0: %v", err)
  228. }
  229. return nil
  230. }
  231. if len(v4Backends) != 0 {
  232. if !tsv4.IsValid() {
  233. log.Printf("backend targets %v contain at least one IPv4 address, but this node's Tailscale IPs do not contain a valid IPv4 address: %v", backendAddrs, tsIPs)
  234. } else if err := updateFirewall(tsv4, v4Backends); err != nil {
  235. return fmt.Errorf("Installing IPv4 firewall rules: %w", err)
  236. }
  237. }
  238. if len(v6Backends) != 0 && !tsv6.IsValid() {
  239. if !tsv6.IsValid() {
  240. log.Printf("backend targets %v contain at least one IPv6 address, but this node's Tailscale IPs do not contain a valid IPv6 address: %v", backendAddrs, tsIPs)
  241. } else if !nfr.HasIPV6NAT() {
  242. log.Printf("backend targets %v contain at least one IPv6 address, but the chosen firewall mode does not support IPv6 NAT", backendAddrs)
  243. } else if err := updateFirewall(tsv6, v6Backends); err != nil {
  244. return fmt.Errorf("Installing IPv6 firewall rules: %w", err)
  245. }
  246. }
  247. return nil
  248. }