mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 03:31:39 +00:00
ipn/ipnlocal: transform default routes into "all but LAN" routes.
Fixes #1177. Signed-off-by: David Anderson <danderson@tailscale.com>
This commit is contained in:
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…
x
Reference in New Issue
Block a user