ipn/ipnlocal: transform default routes into "all but LAN" routes.

Fixes #1177.

Signed-off-by: David Anderson <danderson@tailscale.com>
naman/netstack-incoming
David Anderson 4 years ago committed by Dave Anderson
parent b46e337cdc
commit f647e3daaf

@ -170,6 +170,10 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
go b.authReconfig() go b.authReconfig()
} }
} }
// If the local network configuration has changed, our filter may
// need updating to tweak default routes.
b.updateFilter(b.netMap, b.prefs)
} }
// Shutdown halts the backend and all its sub-components. The backend // Shutdown halts the backend and all its sub-components. The backend
@ -606,9 +610,22 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
} }
if prefs != nil { if prefs != nil {
for _, r := range prefs.AdvertiseRoutes { for _, r := range prefs.AdvertiseRoutes {
// TODO: when advertising default routes, trim out local if r.Bits == 0 {
// nets. // When offering a default route to the world, we
localNetsB.AddPrefix(r) // filter out locally reachable LANs, so that the
// default route effectively appears to be a "guest
// wifi": you get internet access, but to additionally
// get LAN access the LAN(s) need to be offered
// explicitly as well.
s, err := shrinkDefaultRoute(r)
if err != nil {
b.logf("computing default route filter: %v", err)
continue
}
localNetsB.AddSet(s)
} else {
localNetsB.AddPrefix(r)
}
} }
} }
localNets := localNetsB.IPSet() localNets := localNetsB.IPSet()
@ -634,6 +651,42 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
} }
} }
var removeFromDefaultRoute = []netaddr.IPPrefix{
// RFC1918 LAN ranges
netaddr.MustParseIPPrefix("192.168.0.0/16"),
netaddr.MustParseIPPrefix("172.16.0.0/12"),
netaddr.MustParseIPPrefix("10.0.0.0/8"),
// Tailscale IPv4 range
tsaddr.CGNATRange(),
// IPv6 Link-local addresses
netaddr.MustParseIPPrefix("fe80::/10"),
// Tailscale IPv6 range
tsaddr.TailscaleULARange(),
}
// 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) {
var b netaddr.IPSetBuilder
b.AddPrefix(route)
err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP) {
return
}
if pfx.IsSingleIP() {
return
}
b.RemovePrefix(pfx)
})
if err != nil {
return nil, err
}
for _, pfx := range removeFromDefaultRoute {
b.RemovePrefix(pfx)
}
return b.IPSet(), nil
}
// dnsCIDRsEqual determines whether two CIDR lists are equal // dnsCIDRsEqual determines whether two CIDR lists are equal
// for DNS map construction purposes (that is, only the first entry counts). // for DNS map construction purposes (that is, only the first entry counts).
func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool { func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool {

@ -8,6 +8,7 @@ import (
"testing" "testing"
"inet.af/netaddr" "inet.af/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/netmap" "tailscale.com/types/netmap"
) )
@ -118,3 +119,55 @@ func TestNetworkMapCompare(t *testing.T) {
} }
} }
} }
func TestShrinkDefaultRoute(t *testing.T) {
tests := []struct {
route string
in []string
out []string
}{
{
route: "0.0.0.0/0",
in: []string{"1.2.3.4", "25.0.0.1"},
out: []string{
"10.0.0.1",
"10.255.255.255",
"192.168.0.1",
"192.168.255.255",
"172.16.0.1",
"172.31.255.255",
"100.101.102.103",
// Some random IPv6 stuff that shouldn't be in a v4
// default route.
"fe80::",
"2601::1",
},
},
{
route: "::/0",
in: []string{"::1", "2601::1"},
out: []string{
"fe80::1",
tsaddr.TailscaleULARange().IP.String(),
},
},
}
for _, test := range tests {
def := netaddr.MustParseIPPrefix(test.route)
got, err := shrinkDefaultRoute(def)
if err != nil {
t.Fatalf("shrinkDefaultRoute(%q): %v", test.route, err)
}
for _, ip := range test.in {
if !got.Contains(netaddr.MustParseIP(ip)) {
t.Errorf("shrink(%q).Contains(%v) = false, want true", test.route, ip)
}
}
for _, ip := range test.out {
if got.Contains(netaddr.MustParseIP(ip)) {
t.Errorf("shrink(%q).Contains(%v) = true, want false", test.route, ip)
}
}
}
}

@ -135,8 +135,10 @@ type Interface struct {
func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) } func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) }
func (i Interface) IsUp() bool { return isUp(i.Interface) } func (i Interface) IsUp() bool { return isUp(i.Interface) }
// ForeachInterfaceAddress calls fn for each interface's address on the machine. // ForeachInterfaceAddress calls fn for each interface's address on
func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error { // the machine. The IPPrefix's IP is the IP address assigned to the
// interface, and Bits are the subnet mask.
func ForeachInterfaceAddress(fn func(Interface, netaddr.IPPrefix)) error {
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
return err return err
@ -150,8 +152,8 @@ func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error {
for _, a := range addrs { for _, a := range addrs {
switch v := a.(type) { switch v := a.(type) {
case *net.IPNet: case *net.IPNet:
if ip, ok := netaddr.FromStdIP(v.IP); ok { if pfx, ok := netaddr.FromStdIPNet(v); ok {
fn(Interface{iface}, ip) fn(Interface{iface}, pfx)
} }
} }
} }
@ -159,8 +161,10 @@ func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error {
return nil return nil
} }
// ForeachInterface calls fn for each interface on the machine, with all its addresses. // ForeachInterface calls fn for each interface on the machine, with
func ForeachInterface(fn func(Interface, []netaddr.IP)) error { // all its addresses. The IPPrefix's IP is the IP address assigned to
// the interface, and Bits are the subnet mask.
func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
ifaces, err := net.Interfaces() ifaces, err := net.Interfaces()
if err != nil { if err != nil {
return err return err
@ -171,16 +175,16 @@ func ForeachInterface(fn func(Interface, []netaddr.IP)) error {
if err != nil { if err != nil {
return err return err
} }
var ips []netaddr.IP var pfxs []netaddr.IPPrefix
for _, a := range addrs { for _, a := range addrs {
switch v := a.(type) { switch v := a.(type) {
case *net.IPNet: case *net.IPNet:
if ip, ok := netaddr.FromStdIP(v.IP); ok { if pfx, ok := netaddr.FromStdIPNet(v); ok {
ips = append(ips, ip) pfxs = append(pfxs, pfx)
} }
} }
} }
fn(Interface{iface}, ips) fn(Interface{iface}, pfxs)
} }
return nil return nil
} }
@ -189,7 +193,11 @@ func ForeachInterface(fn func(Interface, []netaddr.IP)) error {
// routing table, and other network configuration. // routing table, and other network configuration.
// For now it's pretty basic. // For now it's pretty basic.
type State struct { type State struct {
InterfaceIPs map[string][]netaddr.IP // InterfaceIPs maps from an interface name to the IP addresses
// configured on that interface. Each address is represented as an
// IPPrefix, where the IP is the interface IP address and Bits is
// the subnet mask.
InterfaceIPs map[string][]netaddr.IPPrefix
InterfaceUp map[string]bool InterfaceUp map[string]bool
// HaveV6Global is whether this machine has an IPv6 global address // HaveV6Global is whether this machine has an IPv6 global address
@ -242,14 +250,14 @@ func (s *State) String() string {
if s.InterfaceUp[ifName] { if s.InterfaceUp[ifName] {
fmt.Fprintf(&sb, "%s:[", ifName) fmt.Fprintf(&sb, "%s:[", ifName)
needSpace := false needSpace := false
for _, ip := range s.InterfaceIPs[ifName] { for _, pfx := range s.InterfaceIPs[ifName] {
if !isInterestingIP(ip) { if !isInterestingIP(pfx.IP) {
continue continue
} }
if needSpace { if needSpace {
sb.WriteString(" ") sb.WriteString(" ")
} }
fmt.Fprintf(&sb, "%s", ip) fmt.Fprintf(&sb, "%s", pfx)
needSpace = true needSpace = true
} }
sb.WriteString("]") sb.WriteString("]")
@ -287,24 +295,24 @@ func (s *State) AnyInterfaceUp() bool {
// are owned by this process. (TODO: make this true; currently it // are owned by this process. (TODO: make this true; currently it
// uses some heuristics) // uses some heuristics)
func (s *State) RemoveTailscaleInterfaces() { func (s *State) RemoveTailscaleInterfaces() {
for name, ips := range s.InterfaceIPs { for name, pfxs := range s.InterfaceIPs {
if isTailscaleInterface(name, ips) { if isTailscaleInterface(name, pfxs) {
delete(s.InterfaceIPs, name) delete(s.InterfaceIPs, name)
delete(s.InterfaceUp, name) delete(s.InterfaceUp, name)
} }
} }
} }
func hasTailscaleIP(ips []netaddr.IP) bool { func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
for _, ip := range ips { for _, pfx := range pfxs {
if tsaddr.IsTailscaleIP(ip) { if tsaddr.IsTailscaleIP(pfx.IP) {
return true return true
} }
} }
return false return false
} }
func isTailscaleInterface(name string, ips []netaddr.IP) bool { func isTailscaleInterface(name string, ips []netaddr.IPPrefix) bool {
if runtime.GOOS == "darwin" && strings.HasPrefix(name, "utun") && hasTailscaleIP(ips) { if runtime.GOOS == "darwin" && strings.HasPrefix(name, "utun") && hasTailscaleIP(ips) {
// On macOS in the sandboxed app (at least as of // On macOS in the sandboxed app (at least as of
// 2021-02-25), we often see two utun devices // 2021-02-25), we often see two utun devices
@ -326,22 +334,22 @@ var getPAC func() string
// It does not set the returned State.IsExpensive. The caller can populate that. // It does not set the returned State.IsExpensive. The caller can populate that.
func GetState() (*State, error) { func GetState() (*State, error) {
s := &State{ s := &State{
InterfaceIPs: make(map[string][]netaddr.IP), InterfaceIPs: make(map[string][]netaddr.IPPrefix),
InterfaceUp: make(map[string]bool), InterfaceUp: make(map[string]bool),
} }
if err := ForeachInterface(func(ni Interface, ips []netaddr.IP) { if err := ForeachInterface(func(ni Interface, pfxs []netaddr.IPPrefix) {
ifUp := ni.IsUp() ifUp := ni.IsUp()
s.InterfaceUp[ni.Name] = ifUp s.InterfaceUp[ni.Name] = ifUp
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], ips...) s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
if !ifUp || isTailscaleInterface(ni.Name, ips) { if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
return return
} }
for _, ip := range ips { for _, pfx := range pfxs {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() { if pfx.IP.IsLoopback() || pfx.IP.IsLinkLocalUnicast() {
continue continue
} }
s.HaveV6Global = s.HaveV6Global || isGlobalV6(ip) s.HaveV6Global = s.HaveV6Global || isGlobalV6(pfx.IP)
s.HaveV4 = s.HaveV4 || ip.Is4() s.HaveV4 = s.HaveV4 || pfx.IP.Is4()
} }
}); err != nil { }); err != nil {
return nil, err return nil, err
@ -375,7 +383,8 @@ func HTTPOfListener(ln net.Listener) string {
var goodIP string var goodIP string
var privateIP string var privateIP string
ForeachInterfaceAddress(func(i Interface, ip netaddr.IP) { ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
ip := pfx.IP
if isPrivateIP(ip) { if isPrivateIP(ip) {
if privateIP == "" { if privateIP == "" {
privateIP = ip.String() privateIP = ip.String()
@ -411,7 +420,8 @@ func LikelyHomeRouterIP() (gateway, myIP netaddr.IP, ok bool) {
if !ok { if !ok {
return return
} }
ForeachInterfaceAddress(func(i Interface, ip netaddr.IP) { ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
ip := pfx.IP
if !i.IsUp() || ip.IsZero() || !myIP.IsZero() { if !i.IsUp() || ip.IsZero() || !myIP.IsZero() {
return return
} }
@ -451,11 +461,11 @@ var (
v6Global1 = mustCIDR("2000::/3") v6Global1 = mustCIDR("2000::/3")
) )
// anyInterestingIP reports ips contains any IP that matches // anyInterestingIP reports whether pfxs contains any IP that matches
// isInterestingIP. // isInterestingIP.
func anyInterestingIP(ips []netaddr.IP) bool { func anyInterestingIP(pfxs []netaddr.IPPrefix) bool {
for _, ip := range ips { for _, pfx := range pfxs {
if isInterestingIP(ip) { if isInterestingIP(pfx.IP) {
return true return true
} }
} }

Loading…
Cancel
Save