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:
David Anderson
2021-02-22 20:43:35 -08:00
committed by Dave Anderson
parent b46e337cdc
commit f647e3daaf
3 changed files with 153 additions and 37 deletions

View File

@ -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
}
}