Repository navigation
Windows virtual adapters and macOS VPN tunnels are advertised as LAN routes #17246
Description
What happened
A remote environment's route list fills with "LAN · found automatically" addresses that only exist on the server host itself:
- Windows host:
http://192.168.56.1:3773/(VirtualBox Host-Only adapter) andhttp://192.168.137.1:3773/(the Wi-Fi Direct virtual adapter Windows uses for Mobile hotspot / Internet Connection Sharing), alongside the real Wi-Fi address. - macOS host:
http://198.19.2.2:3773/, the address of autunpoint-to-point interface created by a third-party VPN/security client (not Tailscale).
No client can reach these addresses, but they are saved as routes, and two of them rank above the route actually in use.
Diagnosis
resolveBoundEndpoints in apps/server/src/environment/DirectEndpoints.ts advertises every private IPv4 address on a wildcard-bound server. It skips container and VM networks only by interface-name prefix:
const VIRTUAL_INTERFACE =
/^(docker|br-|veth|virbr|vmnet|vboxnet|vEthernet|podman|cni|flannel|cali|lxcbr|lxdbr|bridge1\d\d)/;Two gaps:
- Windows interface names never match these prefixes. On Windows, the keys of
os.networkInterfaces()are connection names, not driver names. VirtualBox's adapter isVirtualBox Host-Only Network(vboxnetis the Linux/macOS name), VMware's areVMware Network Adapter VMnet1/8, and the hotspot adapter isLocal Area Connection* 2. Only Hyper-V/WSL (vEthernet) is caught. - 198.18.0.0/15 counts as a LAN.
isPrivateNetworkHost(packages/shared/src/hostClassification.ts) treats 198.18.0.0/15 as private, soisAdvertisableAddressaccepts it. That RFC 2544 benchmarking range is not used for real LANs, but VPN, proxy and security clients use it to address their tunnel interfaces. On macOS those interfaces are namedutunN, and a blanketutunfilter would also drop Tailscale, so the address range is the better signal.
Impact is limited. Before connecting, ConnectionDriver.checkRoute fetches the unauthenticated descriptor and requires a matching environmentId, so a stray address on another network answers "silent" rather than receiving the credential. But each unreachable route still costs a probe on every connect attempt, and with a 60 s BETTER_ROUTE_CHECK_INTERVAL, every minute while a lower-ranked route is in use. The list also shows routes the user cannot act on.
Steps to reproduce
- On Windows, install VirtualBox (which creates the Host-Only adapter), or turn on Settings → Network & Internet → Mobile hotspot.
- Run T3 Code with Network access enabled, so the server binds a wildcard host.
- Pair it from another client, then open Settings → Connections on that client and expand the environment's routes.
- The routes include
192.168.56.1and/or192.168.137.1marked "LAN · found automatically".
For the macOS case: run the server on a Mac with any client that creates a utun interface in 198.18.0.0/15 (ifconfig | grep -A1 utun).
Version
Main at 9a3070bcf0 (0.0.45). DirectEndpoints.ts and hostClassification.ts are identical to main.
Environment
- Server A: Windows 10 Pro 22H2 (10.0.19045), VirtualBox 6.1.34, Mobile hotspot on.
- Server B: macOS 26.7.1 (arm64), with a third-party network client's
utunat 198.19.2.2. - Client: T3 Code desktop on macOS.
Evidence
Server A, Get-NetIPAddress joined with Get-NetAdapter (link-local and disconnected adapters omitted):
192.168.56.1 VirtualBox Host-Only Network VirtualBox Host-Only Ethernet Adapter Up
192.168.137.1 Local Area Connection* 2 Microsoft Wi-Fi Direct Virtual Adapter #2 Up
100.80.x.x Tailscale Tailscale Tunnel Up
192.168.50.x Wi-Fi Killer(R) Wi-Fi 6 AX1650w ... Up
Route list on the client: 192.168.56.1, 192.168.50.x, 192.168.137.1 (all "found automatically"), then the user-added route "In use", then Tailscale.
Server B:
utun40: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 16384
inet 198.19.2.2 --> 198.19.2.1 netmask 0xffffff00
The client lists http://198.19.2.2:3773/ as "LAN · found automatically" next to the host's two real LAN addresses.
Related issues
- [Bug]: Proton VPN address is advertised as Tailscale IP and breaks phone pairing #15750: a Proton VPN interface address advertised as the Tailscale IP. Same family of problem (a VPN or virtual interface address reported as a reachable route), different classification path.
- feat(clients): learn an environment's LAN and tailnet addresses #15468 introduced learned LAN and tailnet routes.
Fix applied or workaround
None applied. Possible fixes:
- Extend
VIRTUAL_INTERFACEwith the Windows connection names:VirtualBox Host-Only Network,VMware Network Adapter, andLocal Area Connection\* \d+. The asterisked form is the name Windows gives Wi-Fi Direct/hosted-network virtual adapters; real Ethernet connections are namedEtherneton Windows 10+. - Exclude 198.18.0.0/15 in
isAdvertisableAddress, while keeping it private forisPrivateNetworkHost's other callers.
Workaround: disable the adapters on the server (turn off Mobile hotspot; disable the VirtualBox Host-Only adapter when no VM needs it).
Filed by
@astarktc, with an AI agent (Pi in T3 Code), after inspecting the adapters on both hosts and reading the route code on main.
Activity
juliusmarminge commentedon Oct 8, 2026
Note
Grok responding on behalf of Julius.
Thanks for the detailed report. I checked it against main at
a6ec88f7a, and both gaps are real.1. Windows adapter names slip past the filter
VIRTUAL_INTERFACE(apps/server/src/environment/DirectEndpoints.ts:66-67) is a case-sensitive prefix regex.resolveBoundEndpointsdrops a whole interface only when that regex matches its name (DirectEndpoints.ts:82).VirtualBox Host-Only Network,VMware Network Adapter VMnet1/VMnet8,Local Area Connection* 2andLocal Area Connection* 10all pass through. OnlyvEthernet (WSL),vboxnet0andvmnet8get filtered. The tests only use Linux/macOS names plusvEthernet (WSL)(DirectEndpoints.test.ts:97-112).os.networkInterfaces()) might be a sturdier signal, but I haven't verified that.192.168.137.1does work for devices joined to that PC's hotspot, and the host-only address works from VMs on that host. Filtering them gives up those (rare) clients.2. 198.18.0.0/15 counts as LAN
isPrivateIpv4Addressincludes198.18/15(packages/shared/src/hostClassification.ts:64).isPrivateNetworkHostuses it (:111-112), andisAdvertisableAddressaccepts anythingisPrivateNetworkHostaccepts (DirectEndpoints.ts:54-58).isPrivateNetworkHostfor advertising, and no route test or comment mentions 198.18.isPrivateNetworkHostalso feedsisPublicFaviconHost, the client's route-kind label (packages/client-runtime/src/connection/routes.ts:100) and the web browser target resolver. So excluding the range only inisAdvertisableAddress, as you suggest, seems like the right scope.What a stray route costs (
packages/client-runtime/src/connection/)driver.ts:42,:91-93,:144-154). A dead route ranked above the working one can delay connecting by up to about 2.5 s in total, not 2.5 s per route.supervisor.ts:46,:605), each route ranked above the one in use is preflighted (supervisor.ts:357-361).supervisor.ts:47-49,:749) only applies to a route that answered and then failed to connect. A silent route gets checked again every tick.routes.ts:105-125), which would explain why they ended up above your route in use.Workaround today: there's no way to hide or disable a learned route. The web and mobile route lists don't allow removing one, likely because it would just be learned again (
apps/web/src/components/settings/EnvironmentRoutesList.tsx:124,apps/mobile/src/features/settings/EnvironmentRoutesSection.tsx:187). You can drag the stray routes below the route you use. That should stop the 60 s re-checks, but the per-connect check stays. Turning off the adapter on the server, as you mentioned, should remove them completely.Related
isAdvertisableAddressandresolveBoundEndpointslines but doesn't touchVIRTUAL_INTERFACEor 198.18. So it's complementary, though a fix here would probably need a rebase on top of it.diyaavirmani commentedon Oct 8, 2026
Hi @juliusmarminge , I’d like to work on this issue. Could you assign it to me if nobody else is already working on it?
My proposed scope is to exclude
198.18.0.0/15from direct-endpoint advertising without changing the sharedisPrivateNetworkHostbehavior, and extend the existing virtual-interface filter for the Windows adapter names reported here. I’ll add focused regression tests that also verify normal LAN and Tailscale routes remain available.Before implementing the Windows part, could you confirm whether hotspot and host-only interfaces should be excluded from automatic discovery, given that hotspot clients and local VMs can legitimately reach them? I understand that connection-name filtering also won’t cover every renamed or localized adapter.
I’ll keep the patch focused on this discovery issue and account for the overlapping changes in #17158.