mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-05 14:57:49 +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:
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…
x
Reference in New Issue
Block a user