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>
pull/796/head
David Anderson 4 years ago committed by 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"))
}

Loading…
Cancel
Save