2024-09-19 16:58:26 +00:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build linux
package main
import (
"context"
"fmt"
"log"
"net"
"net/netip"
"os"
"path/filepath"
"strings"
"tailscale.com/util/linuxfw"
)
// ensureIPForwarding enables IPv4/IPv6 forwarding for the container.
func ensureIPForwarding ( root , clusterProxyTargetIP , tailnetTargetIP , tailnetTargetFQDN string , routes * string ) error {
var (
v4Forwarding , v6Forwarding bool
)
if clusterProxyTargetIP != "" {
proxyIP , err := netip . ParseAddr ( clusterProxyTargetIP )
if err != nil {
return fmt . Errorf ( "invalid cluster destination IP: %v" , err )
}
if proxyIP . Is4 ( ) {
v4Forwarding = true
} else {
v6Forwarding = true
}
}
if tailnetTargetIP != "" {
proxyIP , err := netip . ParseAddr ( tailnetTargetIP )
if err != nil {
return fmt . Errorf ( "invalid tailnet destination IP: %v" , err )
}
if proxyIP . Is4 ( ) {
v4Forwarding = true
} else {
v6Forwarding = true
}
}
// Currently we only proxy traffic to the IPv4 address of the tailnet
// target.
if tailnetTargetFQDN != "" {
v4Forwarding = true
}
if routes != nil && * routes != "" {
for _ , route := range strings . Split ( * routes , "," ) {
cidr , err := netip . ParsePrefix ( route )
if err != nil {
return fmt . Errorf ( "invalid subnet route: %v" , err )
}
if cidr . Addr ( ) . Is4 ( ) {
v4Forwarding = true
} else {
v6Forwarding = true
}
}
}
return enableIPForwarding ( v4Forwarding , v6Forwarding , root )
}
func enableIPForwarding ( v4Forwarding , v6Forwarding bool , root string ) error {
var paths [ ] string
if v4Forwarding {
paths = append ( paths , filepath . Join ( root , "proc/sys/net/ipv4/ip_forward" ) )
}
if v6Forwarding {
paths = append ( paths , filepath . Join ( root , "proc/sys/net/ipv6/conf/all/forwarding" ) )
}
// In some common configurations (e.g. default docker,
// kubernetes), the container environment denies write access to
// most sysctls, including IP forwarding controls. Check the
// sysctl values before trying to change them, so that we
// gracefully do nothing if the container's already been set up
// properly by e.g. a k8s initContainer.
for _ , path := range paths {
bs , err := os . ReadFile ( path )
if err != nil {
return fmt . Errorf ( "reading %q: %w" , path , err )
}
if v := strings . TrimSpace ( string ( bs ) ) ; v != "1" {
if err := os . WriteFile ( path , [ ] byte ( "1" ) , 0644 ) ; err != nil {
return fmt . Errorf ( "enabling %q: %w" , path , err )
}
}
}
return nil
}
func installEgressForwardingRule ( _ context . Context , dstStr string , tsIPs [ ] netip . Prefix , nfr linuxfw . NetfilterRunner ) error {
dst , err := netip . ParseAddr ( dstStr )
if err != nil {
return err
}
var local netip . Addr
for _ , pfx := range tsIPs {
if ! pfx . IsSingleIP ( ) {
continue
}
if pfx . Addr ( ) . Is4 ( ) != dst . Is4 ( ) {
continue
}
local = pfx . Addr ( )
break
}
if ! local . IsValid ( ) {
return fmt . Errorf ( "no tailscale IP matching family of %s found in %v" , dstStr , tsIPs )
}
if err := nfr . DNATNonTailscaleTraffic ( "tailscale0" , dst ) ; err != nil {
return fmt . Errorf ( "installing egress proxy rules: %w" , err )
}
2024-10-03 19:15:00 +00:00
if err := nfr . EnsureSNATForDst ( local , dst ) ; err != nil {
2024-09-19 16:58:26 +00:00
return fmt . Errorf ( "installing egress proxy rules: %w" , err )
}
if err := nfr . ClampMSSToPMTU ( "tailscale0" , dst ) ; err != nil {
return fmt . Errorf ( "installing egress proxy rules: %w" , err )
}
return nil
}
// installTSForwardingRuleForDestination accepts a destination address and a
// list of node's tailnet addresses, sets up rules to forward traffic for
// destination to the tailnet IP matching the destination IP family.
// Destination can be Pod IP of this node.
func installTSForwardingRuleForDestination ( _ context . Context , dstFilter string , tsIPs [ ] netip . Prefix , nfr linuxfw . NetfilterRunner ) error {
dst , err := netip . ParseAddr ( dstFilter )
if err != nil {
return err
}
var local netip . Addr
for _ , pfx := range tsIPs {
if ! pfx . IsSingleIP ( ) {
continue
}
if pfx . Addr ( ) . Is4 ( ) != dst . Is4 ( ) {
continue
}
local = pfx . Addr ( )
break
}
if ! local . IsValid ( ) {
return fmt . Errorf ( "no tailscale IP matching family of %s found in %v" , dstFilter , tsIPs )
}
if err := nfr . AddDNATRule ( dst , local ) ; err != nil {
return fmt . Errorf ( "installing rule for forwarding traffic to tailnet IP: %w" , err )
}
return nil
}
func installIngressForwardingRule ( _ context . Context , dstStr string , tsIPs [ ] netip . Prefix , nfr linuxfw . NetfilterRunner ) error {
dst , err := netip . ParseAddr ( dstStr )
if err != nil {
return err
}
var local netip . Addr
proxyHasIPv4Address := false
for _ , pfx := range tsIPs {
if ! pfx . IsSingleIP ( ) {
continue
}
if pfx . Addr ( ) . Is4 ( ) {
proxyHasIPv4Address = true
}
if pfx . Addr ( ) . Is4 ( ) != dst . Is4 ( ) {
continue
}
local = pfx . Addr ( )
break
}
if proxyHasIPv4Address && dst . Is6 ( ) {
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" )
}
if ! local . IsValid ( ) {
return fmt . Errorf ( "no tailscale IP matching family of %s found in %v" , dstStr , tsIPs )
}
if err := nfr . AddDNATRule ( local , dst ) ; err != nil {
return fmt . Errorf ( "installing ingress proxy rules: %w" , err )
}
if err := nfr . ClampMSSToPMTU ( "tailscale0" , dst ) ; err != nil {
return fmt . Errorf ( "installing ingress proxy rules: %w" , err )
}
return nil
}
func installIngressForwardingRuleForDNSTarget ( _ context . Context , backendAddrs [ ] net . IP , tsIPs [ ] netip . Prefix , nfr linuxfw . NetfilterRunner ) error {
var (
tsv4 netip . Addr
tsv6 netip . Addr
v4Backends [ ] netip . Addr
v6Backends [ ] netip . Addr
)
for _ , pfx := range tsIPs {
if pfx . IsSingleIP ( ) && pfx . Addr ( ) . Is4 ( ) {
tsv4 = pfx . Addr ( )
continue
}
if pfx . IsSingleIP ( ) && pfx . Addr ( ) . Is6 ( ) {
tsv6 = pfx . Addr ( )
continue
}
}
// TODO: log if more than one backend address is found and firewall is
// in nftables mode that only the first IP will be used.
for _ , ip := range backendAddrs {
if ip . To4 ( ) != nil {
v4Backends = append ( v4Backends , netip . AddrFrom4 ( [ 4 ] byte ( ip . To4 ( ) ) ) )
}
if ip . To16 ( ) != nil {
v6Backends = append ( v6Backends , netip . AddrFrom16 ( [ 16 ] byte ( ip . To16 ( ) ) ) )
}
}
// Enable IP forwarding here as opposed to at the start of containerboot
// as the IPv4/IPv6 requirements might have changed.
// For Kubernetes operator proxies, forwarding for both IPv4 and IPv6 is
// enabled by an init container, so in practice enabling forwarding here
// is only needed if this proxy has been configured by manually setting
// TS_EXPERIMENTAL_DEST_DNS_NAME env var for a containerboot instance.
if err := enableIPForwarding ( len ( v4Backends ) != 0 , len ( v6Backends ) != 0 , "" ) ; err != nil {
log . Printf ( "[unexpected] failed to ensure IP forwarding: %v" , err )
}
updateFirewall := func ( dst netip . Addr , backendTargets [ ] netip . Addr ) error {
if err := nfr . DNATWithLoadBalancer ( dst , backendTargets ) ; err != nil {
return fmt . Errorf ( "installing DNAT rules for ingress backends %+#v: %w" , backendTargets , err )
}
// The backend might advertize MSS higher than that of the
// tailscale interfaces. Clamp MSS of packets going out via
// tailscale0 interface to its MTU to prevent broken connections
// in environments where path MTU discovery is not working.
if err := nfr . ClampMSSToPMTU ( "tailscale0" , dst ) ; err != nil {
return fmt . Errorf ( "adding rule to clamp traffic via tailscale0: %v" , err )
}
return nil
}
if len ( v4Backends ) != 0 {
if ! tsv4 . IsValid ( ) {
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 )
} else if err := updateFirewall ( tsv4 , v4Backends ) ; err != nil {
return fmt . Errorf ( "Installing IPv4 firewall rules: %w" , err )
}
}
if len ( v6Backends ) != 0 && ! tsv6 . IsValid ( ) {
if ! tsv6 . IsValid ( ) {
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 )
} else if ! nfr . HasIPV6NAT ( ) {
log . Printf ( "backend targets %v contain at least one IPv6 address, but the chosen firewall mode does not support IPv6 NAT" , backendAddrs )
} else if err := updateFirewall ( tsv6 , v6Backends ) ; err != nil {
return fmt . Errorf ( "Installing IPv6 firewall rules: %w" , err )
}
}
return nil
}