ipn/ipnlocal,wgengine/router,cmd/tailscale: add flag to allow local lan access when routing traffic via an exit node.

For #1527

Signed-off-by: Maisem Ali <maisem@tailscale.com>
pull/1696/head
Maisem Ali 4 years ago committed by Maisem Ali
parent 854d5d36a1
commit 1b9d8771dc

@ -60,6 +60,7 @@ var upFlagSet = (func() *flag.FlagSet {
upf.BoolVar(&upArgs.acceptDNS, "accept-dns", true, "accept DNS configuration from the admin panel") upf.BoolVar(&upArgs.acceptDNS, "accept-dns", true, "accept DNS configuration from the admin panel")
upf.BoolVar(&upArgs.singleRoutes, "host-routes", true, "install host routes to other Tailscale nodes") upf.BoolVar(&upArgs.singleRoutes, "host-routes", true, "install host routes to other Tailscale nodes")
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale IP of the exit node for internet traffic") upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale IP of the exit node for internet traffic")
upf.BoolVar(&upArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node")
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections") upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "comma-separated ACL tags to request; each must start with \"tag:\" (e.g. \"tag:eng,tag:montreal,tag:ssh\")") upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "comma-separated ACL tags to request; each must start with \"tag:\" (e.g. \"tag:eng,tag:montreal,tag:ssh\")")
upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key") upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key")
@ -81,21 +82,22 @@ func defaultNetfilterMode() string {
} }
var upArgs struct { var upArgs struct {
reset bool reset bool
server string server string
acceptRoutes bool acceptRoutes bool
acceptDNS bool acceptDNS bool
singleRoutes bool singleRoutes bool
exitNodeIP string exitNodeIP string
shieldsUp bool exitNodeAllowLANAccess bool
forceReauth bool shieldsUp bool
advertiseRoutes string forceReauth bool
advertiseDefaultRoute bool advertiseRoutes string
advertiseTags string advertiseDefaultRoute bool
snat bool advertiseTags string
netfilterMode string snat bool
authKey string netfilterMode string
hostname string authKey string
hostname string
} }
func warnf(format string, args ...interface{}) { func warnf(format string, args ...interface{}) {
@ -182,6 +184,8 @@ func runUp(ctx context.Context, args []string) error {
if err != nil { if err != nil {
fatalf("invalid IP address %q for --exit-node: %v", upArgs.exitNodeIP, err) fatalf("invalid IP address %q for --exit-node: %v", upArgs.exitNodeIP, err)
} }
} else if upArgs.exitNodeAllowLANAccess {
fatalf("--exit-node-allow-lan-access can only be used with --exit-node")
} }
if !exitNodeIP.IsZero() { if !exitNodeIP.IsZero() {
@ -212,6 +216,7 @@ func runUp(ctx context.Context, args []string) error {
prefs.WantRunning = true prefs.WantRunning = true
prefs.RouteAll = upArgs.acceptRoutes prefs.RouteAll = upArgs.acceptRoutes
prefs.ExitNodeIP = exitNodeIP prefs.ExitNodeIP = exitNodeIP
prefs.ExitNodeAllowLANAccess = upArgs.exitNodeAllowLANAccess
prefs.CorpDNS = upArgs.acceptDNS prefs.CorpDNS = upArgs.acceptDNS
prefs.AllowSingleHosts = upArgs.singleRoutes prefs.AllowSingleHosts = upArgs.singleRoutes
prefs.ShieldsUp = upArgs.shieldsUp prefs.ShieldsUp = upArgs.shieldsUp
@ -413,6 +418,7 @@ func init() {
addPrefFlagMapping("shields-up", "ShieldsUp") addPrefFlagMapping("shields-up", "ShieldsUp")
addPrefFlagMapping("snat-subnet-routes", "NoSNAT") addPrefFlagMapping("snat-subnet-routes", "NoSNAT")
addPrefFlagMapping("exit-node", "ExitNodeIP", "ExitNodeIP") addPrefFlagMapping("exit-node", "ExitNodeIP", "ExitNodeIP")
addPrefFlagMapping("exit-node-allow-lan-access", "ExitNodeAllowLANAccess")
} }
func addPrefFlagMapping(flagName string, prefNames ...string) { func addPrefFlagMapping(flagName string, prefNames ...string) {

@ -821,13 +821,9 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
tsaddr.TailscaleULARange(), tsaddr.TailscaleULARange(),
} }
// shrinkDefaultRoute returns an IPSet representing the IPs in route, func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
// minus those in removeFromDefaultRoute and local interface subnets.
func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
var b netaddr.IPSetBuilder var b netaddr.IPSetBuilder
b.AddPrefix(route) if err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
var hostIPs []netaddr.IP
err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP) { if tsaddr.IsTailscaleIP(pfx.IP) {
return return
} }
@ -835,11 +831,26 @@ func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
return return
} }
hostIPs = append(hostIPs, pfx.IP) hostIPs = append(hostIPs, pfx.IP)
b.RemovePrefix(pfx) b.AddPrefix(pfx)
}) }); err != nil {
return nil, nil, err
}
return b.IPSet(), hostIPs, nil
}
// shrinkDefaultRoute returns an IPSet representing the IPs in route,
// minus those in removeFromDefaultRoute and local interface subnets.
func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
interfaceRoutes, hostIPs, err := interfaceRoutes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var b netaddr.IPSetBuilder
// Add the default route.
b.AddPrefix(route)
// Remove the local interface routes.
b.RemoveSet(interfaceRoutes)
// Having removed all the LAN subnets, re-add the hosts's own // Having removed all the LAN subnets, re-add the hosts's own
// IPs. It's fine for clients to connect to an exit node's public // IPs. It's fine for clients to connect to an exit node's public
@ -1542,7 +1553,7 @@ func (b *LocalBackend) authReconfig() {
return return
} }
rcfg := routerConfig(cfg, uc) rcfg := b.routerConfig(cfg, uc)
var dcfg dns.Config var dcfg dns.Config
@ -1794,7 +1805,7 @@ func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netaddr.IPPref
} }
// routerConfig produces a router.Config from a wireguard config and IPN prefs. // routerConfig produces a router.Config from a wireguard config and IPN prefs.
func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config { func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config {
rs := &router.Config{ rs := &router.Config{
LocalAddrs: unmapIPPrefixes(cfg.Addresses), LocalAddrs: unmapIPPrefixes(cfg.Addresses),
SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes), SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes),
@ -1812,9 +1823,10 @@ func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config {
if prefs.ExitNodeID != "" || !prefs.ExitNodeIP.IsZero() { if prefs.ExitNodeID != "" || !prefs.ExitNodeIP.IsZero() {
var default4, default6 bool var default4, default6 bool
for _, route := range rs.Routes { for _, route := range rs.Routes {
if route == ipv4Default { switch route {
case ipv4Default:
default4 = true default4 = true
} else if route == ipv6Default { case ipv6Default:
default6 = true default6 = true
} }
if default4 && default6 { if default4 && default6 {
@ -1827,6 +1839,17 @@ func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config {
if !default6 { if !default6 {
rs.Routes = append(rs.Routes, ipv6Default) rs.Routes = append(rs.Routes, ipv6Default)
} }
ips, _, err := interfaceRoutes()
if err != nil {
b.logf("failed to discover interface ips: %v", err)
}
if prefs.ExitNodeAllowLANAccess {
rs.LocalRoutes = ips.Prefixes()
} else {
// Explicitly add routes to the local network so that we do not
// leak any traffic.
rs.Routes = append(rs.Routes, ips.Prefixes()...)
}
} }
rs.Routes = append(rs.Routes, netaddr.IPPrefix{ rs.Routes = append(rs.Routes, netaddr.IPPrefix{

@ -66,6 +66,10 @@ type Prefs struct {
ExitNodeID tailcfg.StableNodeID ExitNodeID tailcfg.StableNodeID
ExitNodeIP netaddr.IP ExitNodeIP netaddr.IP
// ExitNodeAllowLANAccess indicates whether locally accessible subnets should be
// routed directly or via the exit node.
ExitNodeAllowLANAccess bool
// CorpDNS specifies whether to install the Tailscale network's // CorpDNS specifies whether to install the Tailscale network's
// DNS configuration, if it exists. // DNS configuration, if it exists.
CorpDNS bool CorpDNS bool
@ -152,23 +156,24 @@ type Prefs struct {
type MaskedPrefs struct { type MaskedPrefs struct {
Prefs Prefs
ControlURLSet bool `json:",omitempty"` ControlURLSet bool `json:",omitempty"`
RouteAllSet bool `json:",omitempty"` RouteAllSet bool `json:",omitempty"`
AllowSingleHostsSet bool `json:",omitempty"` AllowSingleHostsSet bool `json:",omitempty"`
ExitNodeIDSet bool `json:",omitempty"` ExitNodeIDSet bool `json:",omitempty"`
ExitNodeIPSet bool `json:",omitempty"` ExitNodeIPSet bool `json:",omitempty"`
CorpDNSSet bool `json:",omitempty"` ExitNodeAllowLANAccessSet bool `json:",omitempty"`
WantRunningSet bool `json:",omitempty"` CorpDNSSet bool `json:",omitempty"`
ShieldsUpSet bool `json:",omitempty"` WantRunningSet bool `json:",omitempty"`
AdvertiseTagsSet bool `json:",omitempty"` ShieldsUpSet bool `json:",omitempty"`
HostnameSet bool `json:",omitempty"` AdvertiseTagsSet bool `json:",omitempty"`
OSVersionSet bool `json:",omitempty"` HostnameSet bool `json:",omitempty"`
DeviceModelSet bool `json:",omitempty"` OSVersionSet bool `json:",omitempty"`
NotepadURLsSet bool `json:",omitempty"` DeviceModelSet bool `json:",omitempty"`
ForceDaemonSet bool `json:",omitempty"` NotepadURLsSet bool `json:",omitempty"`
AdvertiseRoutesSet bool `json:",omitempty"` ForceDaemonSet bool `json:",omitempty"`
NoSNATSet bool `json:",omitempty"` AdvertiseRoutesSet bool `json:",omitempty"`
NetfilterModeSet bool `json:",omitempty"` NoSNATSet bool `json:",omitempty"`
NetfilterModeSet bool `json:",omitempty"`
} }
// ApplyEdits mutates p, assigning fields from m.Prefs for each MaskedPrefs // ApplyEdits mutates p, assigning fields from m.Prefs for each MaskedPrefs
@ -237,9 +242,9 @@ func (p *Prefs) pretty(goos string) string {
sb.WriteString("shields=true ") sb.WriteString("shields=true ")
} }
if !p.ExitNodeIP.IsZero() { if !p.ExitNodeIP.IsZero() {
fmt.Fprintf(&sb, "exit=%v ", p.ExitNodeIP) fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeIP, p.ExitNodeAllowLANAccess)
} else if !p.ExitNodeID.IsZero() { } else if !p.ExitNodeID.IsZero() {
fmt.Fprintf(&sb, "exit=%v ", p.ExitNodeID) fmt.Fprintf(&sb, "exit=%v lan=%t ", p.ExitNodeID, p.ExitNodeAllowLANAccess)
} }
if len(p.AdvertiseRoutes) > 0 || goos == "linux" { if len(p.AdvertiseRoutes) > 0 || goos == "linux" {
fmt.Fprintf(&sb, "routes=%v ", p.AdvertiseRoutes) fmt.Fprintf(&sb, "routes=%v ", p.AdvertiseRoutes)
@ -290,6 +295,7 @@ func (p *Prefs) Equals(p2 *Prefs) bool {
p.AllowSingleHosts == p2.AllowSingleHosts && p.AllowSingleHosts == p2.AllowSingleHosts &&
p.ExitNodeID == p2.ExitNodeID && p.ExitNodeID == p2.ExitNodeID &&
p.ExitNodeIP == p2.ExitNodeIP && p.ExitNodeIP == p2.ExitNodeIP &&
p.ExitNodeAllowLANAccess == p2.ExitNodeAllowLANAccess &&
p.CorpDNS == p2.CorpDNS && p.CorpDNS == p2.CorpDNS &&
p.WantRunning == p2.WantRunning && p.WantRunning == p2.WantRunning &&
p.NotepadURLs == p2.NotepadURLs && p.NotepadURLs == p2.NotepadURLs &&

@ -33,22 +33,23 @@ func (src *Prefs) Clone() *Prefs {
// A compilation failure here means this code must be regenerated, with command: // A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Prefs // tailscale.com/cmd/cloner -type Prefs
var _PrefsNeedsRegeneration = Prefs(struct { var _PrefsNeedsRegeneration = Prefs(struct {
ControlURL string ControlURL string
RouteAll bool RouteAll bool
AllowSingleHosts bool AllowSingleHosts bool
ExitNodeID tailcfg.StableNodeID ExitNodeID tailcfg.StableNodeID
ExitNodeIP netaddr.IP ExitNodeIP netaddr.IP
CorpDNS bool ExitNodeAllowLANAccess bool
WantRunning bool CorpDNS bool
ShieldsUp bool WantRunning bool
AdvertiseTags []string ShieldsUp bool
Hostname string AdvertiseTags []string
OSVersion string Hostname string
DeviceModel string OSVersion string
NotepadURLs bool DeviceModel string
ForceDaemon bool NotepadURLs bool
AdvertiseRoutes []netaddr.IPPrefix ForceDaemon bool
NoSNAT bool AdvertiseRoutes []netaddr.IPPrefix
NetfilterMode preftype.NetfilterMode NoSNAT bool
Persist *persist.Persist NetfilterMode preftype.NetfilterMode
Persist *persist.Persist
}{}) }{})

@ -33,7 +33,7 @@ func fieldsOf(t reflect.Type) (fields []string) {
func TestPrefsEqual(t *testing.T) { func TestPrefsEqual(t *testing.T) {
tstest.PanicOnLog() tstest.PanicOnLog()
prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "ExitNodeID", "ExitNodeIP", "CorpDNS", "WantRunning", "ShieldsUp", "AdvertiseTags", "Hostname", "OSVersion", "DeviceModel", "NotepadURLs", "ForceDaemon", "AdvertiseRoutes", "NoSNAT", "NetfilterMode", "Persist"} prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "ExitNodeID", "ExitNodeIP", "ExitNodeAllowLANAccess", "CorpDNS", "WantRunning", "ShieldsUp", "AdvertiseTags", "Hostname", "OSVersion", "DeviceModel", "NotepadURLs", "ForceDaemon", "AdvertiseRoutes", "NoSNAT", "NetfilterMode", "Persist"}
if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) { if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) {
t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n", t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
have, prefsHandles) have, prefsHandles)
@ -124,6 +124,17 @@ func TestPrefsEqual(t *testing.T) {
true, true,
}, },
{
&Prefs{},
&Prefs{ExitNodeAllowLANAccess: true},
false,
},
{
&Prefs{ExitNodeAllowLANAccess: true},
&Prefs{ExitNodeAllowLANAccess: true},
true,
},
{ {
&Prefs{CorpDNS: true}, &Prefs{CorpDNS: true},
&Prefs{CorpDNS: false}, &Prefs{CorpDNS: false},
@ -384,14 +395,29 @@ func TestPrefsPretty(t *testing.T) {
ExitNodeIP: netaddr.MustParseIP("1.2.3.4"), ExitNodeIP: netaddr.MustParseIP("1.2.3.4"),
}, },
"linux", "linux",
`Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 routes=[] nf=off Persist=nil}`, `Prefs{ra=false mesh=false dns=false want=false exit=1.2.3.4 lan=false routes=[] nf=off Persist=nil}`,
}, },
{ {
Prefs{ Prefs{
ExitNodeID: tailcfg.StableNodeID("myNodeABC"), ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
}, },
"linux", "linux",
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC routes=[] nf=off Persist=nil}`, `Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=false routes=[] nf=off Persist=nil}`,
},
{
Prefs{
ExitNodeID: tailcfg.StableNodeID("myNodeABC"),
ExitNodeAllowLANAccess: true,
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false exit=myNodeABC lan=true routes=[] nf=off Persist=nil}`,
},
{
Prefs{
ExitNodeAllowLANAccess: true,
},
"linux",
`Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist=nil}`,
}, },
{ {
Prefs{ Prefs{

@ -51,12 +51,17 @@ type Config struct {
// IPv6/128 (Tailscale ULA). // IPv6/128 (Tailscale ULA).
LocalAddrs []netaddr.IPPrefix LocalAddrs []netaddr.IPPrefix
// Routes are the routes that point in to the Tailscale // Routes are the routes that point into the Tailscale
// interface. These are the /32 and /128 routes to peers, as // interface. These are the /32 and /128 routes to peers, as
// well as any other subnets that peers are advertising and // well as any other subnets that peers are advertising and
// this node has chosen to use. // this node has chosen to use.
Routes []netaddr.IPPrefix Routes []netaddr.IPPrefix
// LocalRoutes are the routes that should not be routed through Tailscale.
// There are no priorities set in how these routes are added, normal
// routing rules apply.
LocalRoutes []netaddr.IPPrefix
// Linux-only things below, ignored on other platforms. // Linux-only things below, ignored on other platforms.
SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes
SNATSubnetRoutes bool // SNAT traffic to local subnets SNATSubnetRoutes bool // SNAT traffic to local subnets

@ -59,21 +59,26 @@ const (
tailscaleBypassMark = "0x80000" tailscaleBypassMark = "0x80000"
) )
// tailscaleRouteTable is the routing table number for Tailscale const (
// network routes. See addIPRules for the detailed policy routing defaultRouteTable = "default"
// logic that ends up doing lookups within that table. mainRouteTable = "main"
//
// NOTE(danderson): We chose 52 because those are the digits above the // tailscaleRouteTable is the routing table number for Tailscale
// letters "TS" on a qwerty keyboard, and 52 is sufficiently unlikely // network routes. See addIPRules for the detailed policy routing
// to be picked by other software. // logic that ends up doing lookups within that table.
// //
// NOTE(danderson): You might wonder why we didn't pick some high // NOTE(danderson): We chose 52 because those are the digits above the
// table number like 5252, to further avoid the potential for // letters "TS" on a qwerty keyboard, and 52 is sufficiently unlikely
// collisions with other software. Unfortunately, Busybox's `ip` // to be picked by other software.
// implementation believes that table numbers are 8-bit integers, so //
// for maximum compatibility we have to stay in the 0-255 range even // NOTE(danderson): You might wonder why we didn't pick some high
// though linux itself supports larger numbers. // table number like 5252, to further avoid the potential for
const tailscaleRouteTable = "52" // collisions with other software. Unfortunately, Busybox's `ip`
// implementation believes that table numbers are 8-bit integers, so
// for maximum compatibility we have to stay in the 0-255 range even
// though linux itself supports larger numbers.
tailscaleRouteTable = "52"
)
// netfilterRunner abstracts helpers to run netfilter commands. It // netfilterRunner abstracts helpers to run netfilter commands. It
// exists purely to swap out go-iptables for a fake implementation in // exists purely to swap out go-iptables for a fake implementation in
@ -93,6 +98,7 @@ type linuxRouter struct {
tunname string tunname string
addrs map[netaddr.IPPrefix]bool addrs map[netaddr.IPPrefix]bool
routes map[netaddr.IPPrefix]bool routes map[netaddr.IPPrefix]bool
localRoutes map[netaddr.IPPrefix]bool
snatSubnetRoutes bool snatSubnetRoutes bool
netfilterMode preftype.NetfilterMode netfilterMode preftype.NetfilterMode
@ -185,9 +191,13 @@ func (r *linuxRouter) Close() error {
if err := r.setNetfilterMode(netfilterOff); err != nil { if err := r.setNetfilterMode(netfilterOff); err != nil {
return err return err
} }
if err := r.delRoutes(); err != nil {
return err
}
r.addrs = nil r.addrs = nil
r.routes = nil r.routes = nil
r.localRoutes = nil
return nil return nil
} }
@ -203,6 +213,12 @@ func (r *linuxRouter) Set(cfg *Config) error {
errs = append(errs, err) errs = append(errs, err)
} }
newLocalRoutes, err := cidrDiff("localRoute", r.localRoutes, cfg.LocalRoutes, r.addThrowRoute, r.delThrowRoute, r.logf)
if err != nil {
errs = append(errs, err)
}
r.localRoutes = newLocalRoutes
newRoutes, err := cidrDiff("route", r.routes, cfg.Routes, r.addRoute, r.delRoute, r.logf) newRoutes, err := cidrDiff("route", r.routes, cfg.Routes, r.addRoute, r.delRoute, r.logf)
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)
@ -432,14 +448,25 @@ func (r *linuxRouter) delLoopbackRule(addr netaddr.IP) error {
// interface. Fails if the route already exists, or if adding the // interface. Fails if the route already exists, or if adding the
// route fails. // route fails.
func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error { func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() { return r.addRouteDef([]string{normalizeCIDR(cidr), "dev", r.tunname}, cidr)
}
// addThrowRoute adds a throw route for the provided cidr.
// This has the effect that lookup in the routing table is terminated
// pretending that no route was found. Fails if the route already exists,
// or if adding the route fails.
func (r *linuxRouter) addThrowRoute(cidr netaddr.IPPrefix) error {
if !r.ipRuleAvailable {
return nil return nil
} }
args := []string{ return r.addRouteDef([]string{"throw", normalizeCIDR(cidr)}, cidr)
"ip", "route", "add", }
normalizeCIDR(cidr),
"dev", r.tunname, func (r *linuxRouter) addRouteDef(routeDef []string, cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() {
return nil
} }
args := append([]string{"ip", "route", "add"}, routeDef...)
if r.ipRuleAvailable { if r.ipRuleAvailable {
args = append(args, "table", tailscaleRouteTable) args = append(args, "table", tailscaleRouteTable)
} }
@ -450,20 +477,29 @@ func (r *linuxRouter) addRoute(cidr netaddr.IPPrefix) error {
// interface. Fails if the route doesn't exist, or if removing the // interface. Fails if the route doesn't exist, or if removing the
// route fails. // route fails.
func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error { func (r *linuxRouter) delRoute(cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() { return r.delRouteDef([]string{normalizeCIDR(cidr), "dev", r.tunname}, cidr)
}
// delThrowRoute removes the throw route for the cidr. Fails if the route
// doesn't exist, or if removing the route fails.
func (r *linuxRouter) delThrowRoute(cidr netaddr.IPPrefix) error {
if !r.ipRuleAvailable {
return nil return nil
} }
args := []string{ return r.delRouteDef([]string{"throw", normalizeCIDR(cidr)}, cidr)
"ip", "route", "del", }
normalizeCIDR(cidr),
"dev", r.tunname, func (r *linuxRouter) delRouteDef(routeDef []string, cidr netaddr.IPPrefix) error {
if !r.v6Available && cidr.IP.Is6() {
return nil
} }
args := append([]string{"ip", "route", "del"}, routeDef...)
if r.ipRuleAvailable { if r.ipRuleAvailable {
args = append(args, "table", tailscaleRouteTable) args = append(args, "table", tailscaleRouteTable)
} }
err := r.cmd.run(args...) err := r.cmd.run(args...)
if err != nil { if err != nil {
ok, err := r.hasRoute(cidr) ok, err := r.hasRoute(routeDef, cidr)
if err != nil { if err != nil {
r.logf("warning: error checking whether %v even exists after error deleting it: %v", err) r.logf("warning: error checking whether %v even exists after error deleting it: %v", err)
} else { } else {
@ -483,12 +519,8 @@ func dashFam(ip netaddr.IP) string {
return "-4" return "-4"
} }
func (r *linuxRouter) hasRoute(cidr netaddr.IPPrefix) (bool, error) { func (r *linuxRouter) hasRoute(routeDef []string, cidr netaddr.IPPrefix) (bool, error) {
args := []string{ args := append([]string{"ip", dashFam(cidr.IP), "route", "show"}, routeDef...)
"ip", dashFam(cidr.IP), "route", "show",
normalizeCIDR(cidr),
"dev", r.tunname,
}
if r.ipRuleAvailable { if r.ipRuleAvailable {
args = append(args, "table", tailscaleRouteTable) args = append(args, "table", tailscaleRouteTable)
} }
@ -551,7 +583,7 @@ func (r *linuxRouter) addIPRules() error {
"ip", family, "rule", "add", "ip", family, "rule", "add",
"pref", tailscaleRouteTable+"10", "pref", tailscaleRouteTable+"10",
"fwmark", tailscaleBypassMark, "fwmark", tailscaleBypassMark,
"table", "main", "table", mainRouteTable,
) )
// ...and then we try the 'default' table, for correctness, // ...and then we try the 'default' table, for correctness,
// even though it's been empty on every Linux system I've ever seen. // even though it's been empty on every Linux system I've ever seen.
@ -559,7 +591,7 @@ func (r *linuxRouter) addIPRules() error {
"ip", family, "rule", "add", "ip", family, "rule", "add",
"pref", tailscaleRouteTable+"30", "pref", tailscaleRouteTable+"30",
"fwmark", tailscaleBypassMark, "fwmark", tailscaleBypassMark,
"table", "default", "table", defaultRouteTable,
) )
// If neither of those matched (no default route on this system?) // If neither of those matched (no default route on this system?)
// then packets from us should be aborted rather than falling through // then packets from us should be aborted rather than falling through
@ -590,7 +622,18 @@ func (r *linuxRouter) addIPRules() error {
return rg.ErrAcc return rg.ErrAcc
} }
// delBypassrule removes the policy routing rules that avoid // delRoutes removes any local routes that we added that would not be
// cleaned up on interface down.
func (r *linuxRouter) delRoutes() error {
for rt := range r.localRoutes {
if err := r.delThrowRoute(rt); err != nil {
r.logf("failed to delete throw route(%q): %v", rt, err)
}
}
return nil
}
// delIPRules removes the policy routing rules that avoid
// tailscaled routing loops, if it exists. // tailscaled routing loops, if it exists.
func (r *linuxRouter) delIPRules() error { func (r *linuxRouter) delIPRules() error {
if !r.ipRuleAvailable { if !r.ipRuleAvailable {

@ -280,6 +280,54 @@ v6/filter/ts-forward -o tailscale0 -j ACCEPT
v6/nat/POSTROUTING -j ts-postrouting v6/nat/POSTROUTING -j ts-postrouting
`, `,
}, },
{
name: "addr, routes, and local routes with netfilter",
in: &Config{
LocalAddrs: mustCIDRs("100.101.102.104/10"),
Routes: mustCIDRs("100.100.100.100/32", "0.0.0.0/0"),
LocalRoutes: mustCIDRs("10.0.0.0/8"),
NetfilterMode: netfilterOn,
},
want: `
up
ip addr add 100.101.102.104/10 dev tailscale0
ip route add 0.0.0.0/0 dev tailscale0 table 52
ip route add 100.100.100.100/32 dev tailscale0 table 52
ip route add throw 10.0.0.0/8 table 52` + basic +
`v4/filter/FORWARD -j ts-forward
v4/filter/INPUT -j ts-input
v4/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
v4/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
v4/filter/ts-forward -o tailscale0 -s 100.64.0.0/10 -j DROP
v4/filter/ts-forward -o tailscale0 -j ACCEPT
v4/filter/ts-input -i lo -s 100.101.102.104 -j ACCEPT
v4/filter/ts-input ! -i tailscale0 -s 100.115.92.0/23 -j RETURN
v4/filter/ts-input ! -i tailscale0 -s 100.64.0.0/10 -j DROP
v4/nat/POSTROUTING -j ts-postrouting
v6/filter/FORWARD -j ts-forward
v6/filter/INPUT -j ts-input
v6/filter/ts-forward -i tailscale0 -j MARK --set-mark 0x40000
v6/filter/ts-forward -m mark --mark 0x40000 -j ACCEPT
v6/filter/ts-forward -o tailscale0 -j ACCEPT
v6/nat/POSTROUTING -j ts-postrouting
`,
},
{
name: "addr, routes, and local routes with no netfilter",
in: &Config{
LocalAddrs: mustCIDRs("100.101.102.104/10"),
Routes: mustCIDRs("100.100.100.100/32", "0.0.0.0/0"),
LocalRoutes: mustCIDRs("10.0.0.0/8", "192.168.0.0/24"),
NetfilterMode: netfilterOff,
},
want: `
up
ip addr add 100.101.102.104/10 dev tailscale0
ip route add 0.0.0.0/0 dev tailscale0 table 52
ip route add 100.100.100.100/32 dev tailscale0 table 52
ip route add throw 10.0.0.0/8 table 52
ip route add throw 192.168.0.0/24 table 52` + basic,
},
} }
fake := NewFakeOS(t) fake := NewFakeOS(t)

Loading…
Cancel
Save