diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index f82f19273..3fd31375b 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -170,6 +170,10 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) { 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 @@ -606,9 +610,22 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs) } if prefs != nil { for _, r := range prefs.AdvertiseRoutes { - // TODO: when advertising default routes, trim out local - // nets. - localNetsB.AddPrefix(r) + if r.Bits == 0 { + // When offering a default route to the world, we + // 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() @@ -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 // for DNS map construction purposes (that is, only the first entry counts). func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool { diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 9f19a08f1..f8c1d88df 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -8,6 +8,7 @@ import ( "testing" "inet.af/netaddr" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "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) + } + } + } +} diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index a0843ab99..02afd44ba 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -135,8 +135,10 @@ type Interface struct { func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) } func (i Interface) IsUp() bool { return isUp(i.Interface) } -// ForeachInterfaceAddress calls fn for each interface's address on the machine. -func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error { +// ForeachInterfaceAddress calls fn for each interface's address on +// 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() if err != nil { return err @@ -150,8 +152,8 @@ func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error { for _, a := range addrs { switch v := a.(type) { case *net.IPNet: - if ip, ok := netaddr.FromStdIP(v.IP); ok { - fn(Interface{iface}, ip) + if pfx, ok := netaddr.FromStdIPNet(v); ok { + fn(Interface{iface}, pfx) } } } @@ -159,8 +161,10 @@ func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error { return nil } -// ForeachInterface calls fn for each interface on the machine, with all its addresses. -func ForeachInterface(fn func(Interface, []netaddr.IP)) error { +// ForeachInterface calls fn for each interface on the machine, with +// 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() if err != nil { return err @@ -171,16 +175,16 @@ func ForeachInterface(fn func(Interface, []netaddr.IP)) error { if err != nil { return err } - var ips []netaddr.IP + var pfxs []netaddr.IPPrefix for _, a := range addrs { switch v := a.(type) { case *net.IPNet: - if ip, ok := netaddr.FromStdIP(v.IP); ok { - ips = append(ips, ip) + if pfx, ok := netaddr.FromStdIPNet(v); ok { + pfxs = append(pfxs, pfx) } } } - fn(Interface{iface}, ips) + fn(Interface{iface}, pfxs) } return nil } @@ -189,7 +193,11 @@ func ForeachInterface(fn func(Interface, []netaddr.IP)) error { // routing table, and other network configuration. // For now it's pretty basic. 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 // HaveV6Global is whether this machine has an IPv6 global address @@ -242,14 +250,14 @@ func (s *State) String() string { if s.InterfaceUp[ifName] { fmt.Fprintf(&sb, "%s:[", ifName) needSpace := false - for _, ip := range s.InterfaceIPs[ifName] { - if !isInterestingIP(ip) { + for _, pfx := range s.InterfaceIPs[ifName] { + if !isInterestingIP(pfx.IP) { continue } if needSpace { sb.WriteString(" ") } - fmt.Fprintf(&sb, "%s", ip) + fmt.Fprintf(&sb, "%s", pfx) needSpace = true } sb.WriteString("]") @@ -287,24 +295,24 @@ func (s *State) AnyInterfaceUp() bool { // are owned by this process. (TODO: make this true; currently it // uses some heuristics) func (s *State) RemoveTailscaleInterfaces() { - for name, ips := range s.InterfaceIPs { - if isTailscaleInterface(name, ips) { + for name, pfxs := range s.InterfaceIPs { + if isTailscaleInterface(name, pfxs) { delete(s.InterfaceIPs, name) delete(s.InterfaceUp, name) } } } -func hasTailscaleIP(ips []netaddr.IP) bool { - for _, ip := range ips { - if tsaddr.IsTailscaleIP(ip) { +func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool { + for _, pfx := range pfxs { + if tsaddr.IsTailscaleIP(pfx.IP) { return true } } 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) { // On macOS in the sandboxed app (at least as of // 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. func GetState() (*State, error) { s := &State{ - InterfaceIPs: make(map[string][]netaddr.IP), + InterfaceIPs: make(map[string][]netaddr.IPPrefix), 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() s.InterfaceUp[ni.Name] = ifUp - s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], ips...) - if !ifUp || isTailscaleInterface(ni.Name, ips) { + s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...) + if !ifUp || isTailscaleInterface(ni.Name, pfxs) { return } - for _, ip := range ips { - if ip.IsLoopback() || ip.IsLinkLocalUnicast() { + for _, pfx := range pfxs { + if pfx.IP.IsLoopback() || pfx.IP.IsLinkLocalUnicast() { continue } - s.HaveV6Global = s.HaveV6Global || isGlobalV6(ip) - s.HaveV4 = s.HaveV4 || ip.Is4() + s.HaveV6Global = s.HaveV6Global || isGlobalV6(pfx.IP) + s.HaveV4 = s.HaveV4 || pfx.IP.Is4() } }); err != nil { return nil, err @@ -375,7 +383,8 @@ func HTTPOfListener(ln net.Listener) string { var goodIP 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 privateIP == "" { privateIP = ip.String() @@ -411,7 +420,8 @@ func LikelyHomeRouterIP() (gateway, myIP netaddr.IP, ok bool) { if !ok { 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() { return } @@ -451,11 +461,11 @@ var ( v6Global1 = mustCIDR("2000::/3") ) -// anyInterestingIP reports ips contains any IP that matches +// anyInterestingIP reports whether pfxs contains any IP that matches // isInterestingIP. -func anyInterestingIP(ips []netaddr.IP) bool { - for _, ip := range ips { - if isInterestingIP(ip) { +func anyInterestingIP(pfxs []netaddr.IPPrefix) bool { + for _, pfx := range pfxs { + if isInterestingIP(pfx.IP) { return true } }