mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-20 21:51:42 +00:00
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>
This commit is contained in:
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")
|
||||||
@ -87,6 +88,7 @@ var upArgs struct {
|
|||||||
acceptDNS bool
|
acceptDNS bool
|
||||||
singleRoutes bool
|
singleRoutes bool
|
||||||
exitNodeIP string
|
exitNodeIP string
|
||||||
|
exitNodeAllowLANAccess bool
|
||||||
shieldsUp bool
|
shieldsUp bool
|
||||||
forceReauth bool
|
forceReauth bool
|
||||||
advertiseRoutes string
|
advertiseRoutes string
|
||||||
@ -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{
|
||||||
|
10
ipn/prefs.go
10
ipn/prefs.go
@ -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
|
||||||
@ -157,6 +161,7 @@ type MaskedPrefs struct {
|
|||||||
AllowSingleHostsSet bool `json:",omitempty"`
|
AllowSingleHostsSet bool `json:",omitempty"`
|
||||||
ExitNodeIDSet bool `json:",omitempty"`
|
ExitNodeIDSet bool `json:",omitempty"`
|
||||||
ExitNodeIPSet bool `json:",omitempty"`
|
ExitNodeIPSet bool `json:",omitempty"`
|
||||||
|
ExitNodeAllowLANAccessSet bool `json:",omitempty"`
|
||||||
CorpDNSSet bool `json:",omitempty"`
|
CorpDNSSet bool `json:",omitempty"`
|
||||||
WantRunningSet bool `json:",omitempty"`
|
WantRunningSet bool `json:",omitempty"`
|
||||||
ShieldsUpSet bool `json:",omitempty"`
|
ShieldsUpSet bool `json:",omitempty"`
|
||||||
@ -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 &&
|
||||||
|
@ -38,6 +38,7 @@ var _PrefsNeedsRegeneration = Prefs(struct {
|
|||||||
AllowSingleHosts bool
|
AllowSingleHosts bool
|
||||||
ExitNodeID tailcfg.StableNodeID
|
ExitNodeID tailcfg.StableNodeID
|
||||||
ExitNodeIP netaddr.IP
|
ExitNodeIP netaddr.IP
|
||||||
|
ExitNodeAllowLANAccess bool
|
||||||
CorpDNS bool
|
CorpDNS bool
|
||||||
WantRunning bool
|
WantRunning bool
|
||||||
ShieldsUp bool
|
ShieldsUp bool
|
||||||
|
@ -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 {
|
||||||
|
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 r.addRouteDef([]string{"throw", normalizeCIDR(cidr)}, cidr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *linuxRouter) addRouteDef(routeDef []string, cidr netaddr.IPPrefix) error {
|
||||||
if !r.v6Available && cidr.IP.Is6() {
|
if !r.v6Available && cidr.IP.Is6() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
args := []string{
|
args := append([]string{"ip", "route", "add"}, routeDef...)
|
||||||
"ip", "route", "add",
|
|
||||||
normalizeCIDR(cidr),
|
|
||||||
"dev", r.tunname,
|
|
||||||
}
|
|
||||||
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 {
|
||||||
|
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 r.delRouteDef([]string{"throw", normalizeCIDR(cidr)}, cidr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *linuxRouter) delRouteDef(routeDef []string, cidr netaddr.IPPrefix) error {
|
||||||
if !r.v6Available && cidr.IP.Is6() {
|
if !r.v6Available && cidr.IP.Is6() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
args := []string{
|
args := append([]string{"ip", "route", "del"}, routeDef...)
|
||||||
"ip", "route", "del",
|
|
||||||
normalizeCIDR(cidr),
|
|
||||||
"dev", r.tunname,
|
|
||||||
}
|
|
||||||
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…
x
Reference in New Issue
Block a user