@ -84,10 +84,19 @@ import (
"golang.org/x/sys/unix"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/types/logger"
"tailscale.com/types/ptr"
"tailscale.com/util/deephash"
"tailscale.com/util/linuxfw"
)
func newNetfilterRunner ( logf logger . Logf ) ( linuxfw . NetfilterRunner , error ) {
if defaultBool ( "TS_TEST_FAKE_NETFILTER" , false ) {
return linuxfw . NewFakeIPTablesRunner ( ) , nil
}
return linuxfw . New ( logf )
}
func main ( ) {
log . SetPrefix ( "boot: " )
tailscale . I_Acknowledge_This_API_Is_Unstable = true
@ -295,6 +304,13 @@ authLoop:
if cfg . ServeConfigPath != "" {
go watchServeConfigChanges ( ctx , cfg . ServeConfigPath , certDomainChanged , certDomain , client )
}
var nfr linuxfw . NetfilterRunner
if wantProxy {
nfr , err = newNetfilterRunner ( log . Printf )
if err != nil {
log . Fatalf ( "error creating new netfilter runner: %v" , err )
}
}
for {
n , err := w . Next ( )
if err != nil {
@ -315,7 +331,7 @@ authLoop:
ipsHaveChanged := newCurrentIPs != currentIPs
if cfg . ProxyTo != "" && len ( addrs ) > 0 && ipsHaveChanged {
log . Printf ( "Installing proxy rules" )
if err := installIngressForwardingRule ( ctx , cfg . ProxyTo , addrs ); err != nil {
if err := installIngressForwardingRule ( ctx , cfg . ProxyTo , addrs , nfr ); err != nil {
log . Fatalf ( "installing ingress proxy rules: %v" , err )
}
}
@ -330,7 +346,7 @@ authLoop:
}
}
if cfg . TailnetTargetIP != "" && ipsHaveChanged && len ( addrs ) > 0 {
if err := installEgressForwardingRule ( ctx , cfg . TailnetTargetIP , addrs ); err != nil {
if err := installEgressForwardingRule ( ctx , cfg . TailnetTargetIP , addrs , nfr ); err != nil {
log . Fatalf ( "installing egress proxy rules: %v" , err )
}
}
@ -662,16 +678,12 @@ func ensureIPForwarding(root, clusterProxyTarget, tailnetTargetiP, routes string
return nil
}
func installEgressForwardingRule ( ctx context . Context , dstStr string , tsIPs [ ] netip . Prefix ) error {
func installEgressForwardingRule ( ctx context . Context , dstStr string , tsIPs [ ] netip . Prefix , nfr linuxfw . NetfilterRunner ) error {
dst , err := netip . ParseAddr ( dstStr )
if err != nil {
return err
}
argv0 := "iptables"
if dst . Is6 ( ) {
argv0 = "ip6tables"
}
var local string
var local netip . Addr
for _ , pfx := range tsIPs {
if ! pfx . IsSingleIP ( ) {
continue
@ -679,52 +691,30 @@ func installEgressForwardingRule(ctx context.Context, dstStr string, tsIPs []net
if pfx . Addr ( ) . Is4 ( ) != dst . Is4 ( ) {
continue
}
local = pfx . Addr ( ) . String ( )
local = pfx . Addr ( )
break
}
if local == "" {
if ! local . IsValid ( ) {
return fmt . Errorf ( "no tailscale IP matching family of %s found in %v" , dstStr , tsIPs )
}
// Technically, if the control server ever changes the IPs assigned to this
// node, we'll slowly accumulate iptables rules. This shouldn't happen, so
// for now we'll live with it.
// Set up a rule that ensures that all packets
// except for those received on tailscale0 interface is forwarded to
// destination address
cmdDNAT := exec . CommandContext ( ctx , argv0 , "-t" , "nat" , "-I" , "PREROUTING" , "1" , "!" , "-i" , "tailscale0" , "-j" , "DNAT" , "--to-destination" , dstStr )
cmdDNAT . Stdout = os . Stdout
cmdDNAT . Stderr = os . Stderr
if err := cmdDNAT . Run ( ) ; err != nil {
return fmt . Errorf ( "executing iptables failed: %w" , err )
}
// Set up a rule that ensures that all packets sent to the destination
// address will have the proxy's IP set as source IP
cmdSNAT := exec . CommandContext ( ctx , argv0 , "-t" , "nat" , "-I" , "POSTROUTING" , "1" , "--destination" , dstStr , "-j" , "SNAT" , "--to-source" , local )
cmdSNAT . Stdout = os . Stdout
cmdSNAT . Stderr = os . Stderr
if err := cmdSNAT . Run ( ) ; err != nil {
return fmt . Errorf ( "setting up SNAT via iptables failed: %w" , err )
}
cmdClamp := exec . CommandContext ( ctx , argv0 , "-t" , "mangle" , "-A" , "FORWARD" , "-o" , "tailscale0" , "-p" , "tcp" , "-m" , "tcp" , "--tcp-flags" , "SYN,RST" , "SYN" , "-j" , "TCPMSS" , "--clamp-mss-to-pmtu" )
cmdClamp . Stdout = os . Stdout
cmdClamp . Stderr = os . Stderr
if err := cmdClamp . Run ( ) ; err != nil {
return fmt . Errorf ( "executing iptables failed: %w" , err )
if err := nfr . DNATNonTailscaleTraffic ( "tailscale0" , dst ) ; err != nil {
return fmt . Errorf ( "installing egress proxy rules: %w" , err )
}
if err := nfr . AddSNATRuleForDst ( local , dst ) ; err != nil {
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
}
func installIngressForwardingRule ( ctx context . Context , dstStr string , tsIPs [ ] netip . Prefix ) error {
func installIngressForwardingRule ( ctx context . Context , dstStr string , tsIPs [ ] netip . Prefix , nfr linuxfw . NetfilterRunner ) error {
dst , err := netip . ParseAddr ( dstStr )
if err != nil {
return err
}
argv0 := "iptables"
if dst . Is6 ( ) {
argv0 = "ip6tables"
}
var local string
var local netip . Addr
for _ , pfx := range tsIPs {
if ! pfx . IsSingleIP ( ) {
continue
@ -732,26 +722,17 @@ func installIngressForwardingRule(ctx context.Context, dstStr string, tsIPs []ne
if pfx . Addr ( ) . Is4 ( ) != dst . Is4 ( ) {
continue
}
local = pfx . Addr ( ) . String ( )
local = pfx . Addr ( )
break
}
if local == "" {
if ! local . IsValid ( ) {
return fmt . Errorf ( "no tailscale IP matching family of %s found in %v" , dstStr , tsIPs )
}
// Technically, if the control server ever changes the IPs assigned to this
// node, we'll slowly accumulate iptables rules. This shouldn't happen, so
// for now we'll live with it.
cmd := exec . CommandContext ( ctx , argv0 , "-t" , "nat" , "-I" , "PREROUTING" , "1" , "-d" , local , "-j" , "DNAT" , "--to-destination" , dstStr )
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
if err := cmd . Run ( ) ; err != nil {
return fmt . Errorf ( "executing iptables failed: %w" , err )
if err := nfr . AddDNATRule ( local , dst ) ; err != nil {
return fmt . Errorf ( "installing ingress proxy rules: %w" , err )
}
cmdClamp := exec . CommandContext ( ctx , argv0 , "-t" , "mangle" , "-A" , "FORWARD" , "-o" , "tailscale0" , "-p" , "tcp" , "-m" , "tcp" , "--tcp-flags" , "SYN,RST" , "SYN" , "-j" , "TCPMSS" , "--clamp-mss-to-pmtu" )
cmdClamp . Stdout = os . Stdout
cmdClamp . Stderr = os . Stderr
if err := cmdClamp . Run ( ) ; err != nil {
return fmt . Errorf ( "executing iptables failed: %w" , err )
if err := nfr . ClampMSSToPMTU ( "tailscale0" , dst ) ; err != nil {
return fmt . Errorf ( "installing ingress proxy rules: %w" , err )
}
return nil
}