ipnlocal: allow access to guest VMs/containers while using an exit node
@@ -984,6 +984,44 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
tsaddr.TailscaleULARange(),
}
+// internalAndExternalInterfaces splits interface routes into "internal"
+// and "external" sets. Internal routes are those of virtual ethernet
+// network interfaces used by guest VMs and containers, such as WSL and
+// Docker.
+//
+// Given that "internal" routes don't leave the device, we choose to
+// trust them more, allowing access to them when an Exit Node is enabled.
+func internalAndExternalInterfaces() (internal, external []netaddr.IPPrefix, err error) {
+ if err := interfaces.ForeachInterfaceAddress(func(iface interfaces.Interface, pfx netaddr.IPPrefix) {
+ if tsaddr.IsTailscaleIP(pfx.IP()) {
+ return
+ }
+ if pfx.IsSingleIP() {
+ return
+ }
+ if runtime.GOOS == "windows" {
+ // Windows Hyper-V prefixes all MAC addresses with 00:15:5d.
+ // https://docs.microsoft.com/en-us/troubleshoot/windows-server/virtualization/default-limit-256-dynamic-mac-addresses
+ //
+ // This includes WSL2 vEthernet.
+ // Importantly: by default WSL2 /etc/resolv.conf points to
+ // a stub resolver running on the host vEthernet IP.
+ // So enabling exit nodes with the default tailnet
+ // configuration breaks WSL2 DNS without this.
+ mac := iface.Interface.HardwareAddr
+ if len(mac) == 6 && mac[0] == 0x00 && mac[1] == 0x15 && mac[2] == 0x5d {
+ internal = append(internal, pfx)
+ return
+ }
+ }
+ external = append(external, pfx)
+ }); err != nil {
+ return nil, nil, err
+ }
+
+ return internal, external, nil
+}
+
func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
var b netaddr.IPSetBuilder
if err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
@@ -2119,18 +2157,21 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router
if !default6 {
rs.Routes = append(rs.Routes, ipv6Default)
}
- if runtime.GOOS == "linux" || runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
- // Only allow local lan access on linux machines for now.
- ips, _, err := interfaceRoutes()
+ internalIPs, externalIPs, err := internalAndExternalInterfaces()
if err != nil {
b.logf("failed to discover interface ips: %v", err)
}
+ if runtime.GOOS == "linux" || runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
+ rs.LocalRoutes = internalIPs // unconditionally allow access to guest VM networks
if prefs.ExitNodeAllowLANAccess {
- rs.LocalRoutes = ips.Prefixes()
+ rs.LocalRoutes = append(rs.LocalRoutes, externalIPs...)
+ if len(externalIPs) != 0 {
+ b.logf("allowing exit node access to internal IPs: %v", internalIPs)
+ }
} 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, externalIPs...)
}
}
}
