mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 13:05:22 +00:00 
			
		
		
		
	wgengine/router: support various degrees of broken IPv6.
Gracefully skips touching the v6 NAT table on systems that don't have it, and doesn't configure IPv6 at all if IPv6 is globally disabled. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
		 David Anderson
					David Anderson
				
			
				
					committed by
					
						 Dave Anderson
						Dave Anderson
					
				
			
			
				
	
			
			
			 Dave Anderson
						Dave Anderson
					
				
			
						parent
						
							0d80904fc2
						
					
				
				
					commit
					fddbcb0c7b
				
			| @@ -5,7 +5,12 @@ | ||||
| package router | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/coreos/go-iptables/iptables" | ||||
| @@ -79,13 +84,17 @@ type netfilterRunner interface { | ||||
|  | ||||
| type linuxRouter struct { | ||||
| 	logf             func(fmt string, args ...interface{}) | ||||
| 	ipRuleAvailable  bool | ||||
| 	tunname          string | ||||
| 	addrs            map[netaddr.IPPrefix]bool | ||||
| 	routes           map[netaddr.IPPrefix]bool | ||||
| 	snatSubnetRoutes bool | ||||
| 	netfilterMode    NetfilterMode | ||||
|  | ||||
| 	// Various feature checks for the network stack. | ||||
| 	ipRuleAvailable bool | ||||
| 	v6Available     bool | ||||
| 	v6NATAvailable  bool | ||||
|  | ||||
| 	dns *dns.Manager | ||||
|  | ||||
| 	ipt4 netfilterRunner | ||||
| @@ -104,9 +113,14 @@ func newUserspaceRouter(logf logger.Logf, _ *device.Device, tunDev tun.Device) ( | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	ipt6, err := iptables.NewWithProtocol(iptables.ProtocolIPv6) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	var ipt6 netfilterRunner | ||||
| 	if supportsV6() { | ||||
| 		// The iptables package probes for `ip6tables` and errors out | ||||
| 		// if unavailable. We want that to be a non-fatal error. | ||||
| 		ipt6, err = iptables.NewWithProtocol(iptables.ProtocolIPv6) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return newUserspaceRouterAdvanced(logf, tunname, ipt4, ipt6, osCommandRunner{}) | ||||
| @@ -120,15 +134,21 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter4, ne | ||||
| 		InterfaceName: tunname, | ||||
| 	} | ||||
|  | ||||
| 	supportsV6 := supportsV6() | ||||
|  | ||||
| 	return &linuxRouter{ | ||||
| 		logf:            logf, | ||||
| 		logf:          logf, | ||||
| 		tunname:       tunname, | ||||
| 		netfilterMode: NetfilterOff, | ||||
|  | ||||
| 		ipRuleAvailable: ipRuleAvailable, | ||||
| 		tunname:         tunname, | ||||
| 		netfilterMode:   NetfilterOff, | ||||
| 		ipt4:            netfilter4, | ||||
| 		ipt6:            netfilter6, | ||||
| 		cmd:             cmd, | ||||
| 		dns:             dns.NewManager(mconfig), | ||||
| 		v6Available:     supportsV6, | ||||
| 		v6NATAvailable:  supportsV6 && supportsV6NAT(), | ||||
|  | ||||
| 		ipt4: netfilter4, | ||||
| 		ipt6: netfilter6, | ||||
| 		cmd:  cmd, | ||||
| 		dns:  dns.NewManager(mconfig), | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| @@ -423,6 +443,13 @@ func (r *linuxRouter) downInterface() error { | ||||
| 	return r.cmd.run("ip", "link", "set", "dev", r.tunname, "down") | ||||
| } | ||||
|  | ||||
| func (r *linuxRouter) iprouteFamilies() []string { | ||||
| 	if r.v6Available { | ||||
| 		return []string{"-4", "-6"} | ||||
| 	} | ||||
| 	return []string{"-4"} | ||||
| } | ||||
|  | ||||
| // addIPRules adds the policy routing rule that avoids tailscaled | ||||
| // routing loops. If the rule exists and appears to be a | ||||
| // tailscale-managed rule, it is gracefully replaced. | ||||
| @@ -439,7 +466,7 @@ func (r *linuxRouter) addIPRules() error { | ||||
|  | ||||
| 	rg := newRunGroup(nil, r.cmd) | ||||
|  | ||||
| 	for _, family := range []string{"-4", "-6"} { | ||||
| 	for _, family := range r.iprouteFamilies() { | ||||
| 		// NOTE(apenwarr): We leave spaces between each pref number. | ||||
| 		// This is so the sysadmin can override by inserting rules in | ||||
| 		// between if they want. | ||||
| @@ -512,7 +539,7 @@ func (r *linuxRouter) delIPRules() error { | ||||
| 	// unknown rules during deletion. | ||||
| 	rg := newRunGroup([]int{2, 254}, r.cmd) | ||||
|  | ||||
| 	for _, family := range []string{"-4", "-6"} { | ||||
| 	for _, family := range r.iprouteFamilies() { | ||||
| 		// When deleting rules, we want to be a bit specific (mention which | ||||
| 		// table we were routing to) but not *too* specific (fwmarks, etc). | ||||
| 		// That leaves us some flexibility to change these values in later | ||||
| @@ -554,6 +581,13 @@ func (r *linuxRouter) delIPRules() error { | ||||
| 	return rg.ErrAcc | ||||
| } | ||||
|  | ||||
| func (r *linuxRouter) netfilterFamilies() []netfilterRunner { | ||||
| 	if r.v6Available { | ||||
| 		return []netfilterRunner{r.ipt4, r.ipt6} | ||||
| 	} | ||||
| 	return []netfilterRunner{r.ipt4} | ||||
| } | ||||
|  | ||||
| // addNetfilterChains creates custom Tailscale chains in netfilter. | ||||
| func (r *linuxRouter) addNetfilterChains() error { | ||||
| 	create := func(ipt netfilterRunner, table, chain string) error { | ||||
| @@ -568,14 +602,19 @@ func (r *linuxRouter) addNetfilterChains() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { | ||||
| 	for _, ipt := range r.netfilterFamilies() { | ||||
| 		if err := create(ipt, "filter", "ts-input"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := create(ipt, "filter", "ts-forward"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := create(ipt, "nat", "ts-postrouting"); err != nil { | ||||
| 	} | ||||
| 	if err := create(r.ipt4, "nat", "ts-postrouting"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if r.v6NATAvailable { | ||||
| 		if err := create(r.ipt6, "nat", "ts-postrouting"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| @@ -588,8 +627,10 @@ func (r *linuxRouter) addNetfilterBase() error { | ||||
| 	if err := r.addNetfilterBase4(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := r.addNetfilterBase6(); err != nil { | ||||
| 		return err | ||||
| 	if r.v6Available { | ||||
| 		if err := r.addNetfilterBase6(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -686,14 +727,19 @@ func (r *linuxRouter) delNetfilterChains() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { | ||||
| 	for _, ipt := range r.netfilterFamilies() { | ||||
| 		if err := del(ipt, "filter", "ts-input"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := del(ipt, "filter", "ts-forward"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := del(ipt, "nat", "ts-postrouting"); err != nil { | ||||
| 	} | ||||
| 	if err := del(r.ipt4, "nat", "ts-postrouting"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if r.v6NATAvailable { | ||||
| 		if err := del(r.ipt6, "nat", "ts-postrouting"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| @@ -716,14 +762,19 @@ func (r *linuxRouter) delNetfilterBase() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { | ||||
| 	for _, ipt := range r.netfilterFamilies() { | ||||
| 		if err := del(ipt, "filter", "ts-input"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := del(ipt, "filter", "ts-forward"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := del(ipt, "nat", "ts-postrouting"); err != nil { | ||||
| 	} | ||||
| 	if err := del(r.ipt4, "nat", "ts-postrouting"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if r.v6NATAvailable { | ||||
| 		if err := del(r.ipt6, "nat", "ts-postrouting"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| @@ -752,14 +803,19 @@ func (r *linuxRouter) addNetfilterHooks() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { | ||||
| 	for _, ipt := range r.netfilterFamilies() { | ||||
| 		if err := divert(ipt, "filter", "INPUT"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := divert(ipt, "filter", "FORWARD"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := divert(ipt, "nat", "POSTROUTING"); err != nil { | ||||
| 	} | ||||
| 	if err := divert(r.ipt4, "nat", "POSTROUTING"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if r.v6NATAvailable { | ||||
| 		if err := divert(r.ipt6, "nat", "POSTROUTING"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| @@ -784,14 +840,19 @@ func (r *linuxRouter) delNetfilterHooks() error { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	for _, ipt := range []netfilterRunner{r.ipt4, r.ipt6} { | ||||
| 	for _, ipt := range r.netfilterFamilies() { | ||||
| 		if err := del(ipt, "filter", "INPUT"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := del(ipt, "filter", "FORWARD"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if err := del(ipt, "nat", "POSTROUTING"); err != nil { | ||||
| 	} | ||||
| 	if err := del(r.ipt4, "nat", "POSTROUTING"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if r.v6NATAvailable { | ||||
| 		if err := del(r.ipt6, "nat", "POSTROUTING"); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| @@ -809,8 +870,10 @@ func (r *linuxRouter) addSNATRule() error { | ||||
| 	if err := r.ipt4.Append("nat", "ts-postrouting", args...); err != nil { | ||||
| 		return fmt.Errorf("adding %v in v4/nat/ts-postrouting: %w", args, err) | ||||
| 	} | ||||
| 	if err := r.ipt6.Append("nat", "ts-postrouting", args...); err != nil { | ||||
| 		return fmt.Errorf("adding %v in v6/nat/ts-postrouting: %w", args, err) | ||||
| 	if r.v6NATAvailable { | ||||
| 		if err := r.ipt6.Append("nat", "ts-postrouting", args...); err != nil { | ||||
| 			return fmt.Errorf("adding %v in v6/nat/ts-postrouting: %w", args, err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -826,8 +889,10 @@ func (r *linuxRouter) delSNATRule() error { | ||||
| 	if err := r.ipt4.Delete("nat", "ts-postrouting", args...); err != nil { | ||||
| 		return fmt.Errorf("deleting %v in v4/nat/ts-postrouting: %w", args, err) | ||||
| 	} | ||||
| 	if err := r.ipt6.Delete("nat", "ts-postrouting", args...); err != nil { | ||||
| 		return fmt.Errorf("deleting %v in v6/nat/ts-postrouting: %w", args, err) | ||||
| 	if r.v6NATAvailable { | ||||
| 		if err := r.ipt6.Delete("nat", "ts-postrouting", args...); err != nil { | ||||
| 			return fmt.Errorf("deleting %v in v6/nat/ts-postrouting: %w", args, err) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -915,3 +980,47 @@ func normalizeCIDR(cidr netaddr.IPPrefix) string { | ||||
| func cleanup(logf logger.Logf, interfaceName string) { | ||||
| 	// TODO(dmytro): clean up iptables. | ||||
| } | ||||
|  | ||||
| // supportsV6 returns whether the system appears to have a working | ||||
| // IPv6 network stack. | ||||
| func supportsV6() bool { | ||||
| 	_, err := os.Stat("/proc/sys/net/ipv6") | ||||
| 	if os.IsNotExist(err) { | ||||
| 		return false | ||||
| 	} | ||||
| 	bs, err := ioutil.ReadFile("/proc/sys/net/ipv6/conf/all/disable_ipv6") | ||||
| 	if err != nil { | ||||
| 		// Be conservative if we can't find the ipv6 configuration knob. | ||||
| 		return false | ||||
| 	} | ||||
| 	disabled, err := strconv.ParseBool(strings.TrimSpace(string(bs))) | ||||
| 	if err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	if disabled { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// Some distros ship ip6tables separately from iptables. | ||||
| 	if _, err := exec.LookPath("ip6tables"); err != nil { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // supportsV6NAT returns whether the system has a "nat" table in the | ||||
| // IPv6 netfilter stack. | ||||
| // | ||||
| // The nat table was added after the initial release of ipv6 | ||||
| // netfilter, so some older distros ship a kernel that can't NAT IPv6 | ||||
| // traffic. | ||||
| func supportsV6NAT() bool { | ||||
| 	bs, err := ioutil.ReadFile("/proc/net/ip6_tables_names") | ||||
| 	if err != nil { | ||||
| 		// Can't read the file. Assume SNAT works. | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return bytes.Contains(bs, []byte("nat\n")) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user