//go:build android package tailcore /* Give the Tailscale core a way to keep its own sockets out of its own tunnel. WHY THIS EXISTS A VPN has a bootstrap problem: the core's WireGuard sockets must reach the internet, but the tunnel they create captures the internet. Left alone, the encrypted packets are routed back into the tunnel that produced them and loop forever. Android's answer is `VpnService.protect(fd)`, which marks ONE socket as belonging outside the tunnel. This app has been using a much blunter substitute — `addDisallowedApplication(packageName)`, which excludes the ENTIRE app from its own VPN. That does stop the loop, and it also means nothing this app does can ever travel through the tunnel it is holding open: not an in-app browser, not Officer's calls to the platform, not the core reaching a service that only exists on the tailnet. `SetProtector` narrows it to what actually needs excluding. The core protects the sockets it dials out; everything else the app does is free to use the tunnel like any other app's traffic. WHAT THIS DOES NOT DO It does not decide whether an address inside the tailnet is reachable — that is tsdial's business and goes over WireGuard rather than any OS socket. Do not expect this to make an in-tailnet DNS resolver start answering; that was a separate claim and it was never verified. Android only. iOS Network Extensions are excluded from their own tunnel by the OS, with no equivalent call and no equivalent problem — hence the build tag. */ import ( "errors" "tailscale.com/net/netns" ) // Protector is implemented on the Java side by the VpnService, which is the only object that can call // protect(). gomobile generates a Java interface from this; int32 maps to Java's int, matching // VpnService.protect(int). type Protector interface { // Protect marks a socket as belonging outside the tunnel. False means the call failed, which // Android reports as a plain boolean with no reason attached. Protect(fd int32) bool } // SetProtector installs the callback, or removes it when p is nil. // // Must be called BEFORE Start: the core dials as soon as it comes up, and a socket created before the // hook is registered is an unprotected one that will loop. func SetProtector(p Protector) { if p == nil { netns.SetAndroidProtectFunc(nil) return } netns.SetAndroidProtectFunc(func(fd int) error { if p.Protect(int32(fd)) { return nil } // Returning an error here fails the dial, which is correct: an unprotected socket would loop // back into the tunnel, and a hung connection is harder to diagnose than a refused one. return errors.New("VpnService.protect failed") }) }