Files
tailscale/net/netns/netns_android.go
kari-ts 4c7c1091ba netns: add Android callback to bind socket to network (#18915)
After switching from cellular to wifi without ipv6, ForeachInterface still sees rmnet prefixes, so HaveV6 stays true, and magicsock keeps attempting ipv6 connections that either route through cellular or time out for users on wifi without ipv6

This:
-Adds SetAndroidBindToNetworkFunc, a callback to bind the socket to the selected Android Network object

Updates tailscale/tailscale#6152

Signed-off-by: kari-ts <kari@tailscale.com>
2026-03-11 12:28:28 -07:00

109 lines
3.0 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:build android
package netns
import (
"fmt"
"sync"
"syscall"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
var (
androidProtectFuncMu sync.Mutex
androidProtectFunc func(fd int) error
androidBindToNetworkFuncMu sync.Mutex
androidBindToNetworkFunc func(fd int) error
)
// UseSocketMark reports whether SO_MARK is in use. Android does not use SO_MARK.
func UseSocketMark() bool {
return false
}
// SetAndroidProtectFunc register a func that Android provides that JNI calls into
// https://developer.android.com/reference/android/net/VpnService#protect(int)
// which is documented as:
//
// "Protect a socket from VPN connections. After protecting, data sent
// through this socket will go directly to the underlying network, so
// its traffic will not be forwarded through the VPN. This method is
// useful if some connections need to be kept outside of VPN. For
// example, a VPN tunnel should protect itself if its destination is
// covered by VPN routes. Otherwise its outgoing packets will be sent
// back to the VPN interface and cause an infinite loop. This method
// will fail if the application is not prepared or is revoked."
//
// A nil func disables the use the hook.
//
// This indirection is necessary because this is the supported, stable
// interface to use on Android, and doing the sockopts to set the
// fwmark return errors on Android. The actual implementation of
// VpnService.protect ends up doing an IPC to another process on
// Android, asking for the fwmark to be set.
func SetAndroidProtectFunc(f func(fd int) error) {
androidProtectFuncMu.Lock()
defer androidProtectFuncMu.Unlock()
androidProtectFunc = f
}
// SetAndroidBindToNetworkFunc registers a func provided by Android that binds
// the socket FD to the currently selected underlying network.
func SetAndroidBindToNetworkFunc(f func(fd int) error) {
androidBindToNetworkFuncMu.Lock()
defer androidBindToNetworkFuncMu.Unlock()
androidBindToNetworkFunc = f
}
func control(logger.Logf, *netmon.Monitor) func(network, address string, c syscall.RawConn) error {
return controlC
}
// controlC marks c as necessary to dial in a separate network namespace.
//
// It's intentionally the same signature as net.Dialer.Control
// and net.ListenConfig.Control.
func controlC(network, address string, c syscall.RawConn) error {
var sockErr error
err := c.Control(func(fd uintptr) {
fdInt := int(fd)
// Protect from VPN loops
androidProtectFuncMu.Lock()
pf := androidProtectFunc
androidProtectFuncMu.Unlock()
if pf != nil {
if err := pf(fdInt); err != nil {
sockErr = err
return
}
}
if disableAndroidBindToActiveNetwork.Load() {
return
}
androidBindToNetworkFuncMu.Lock()
bf := androidBindToNetworkFunc
androidBindToNetworkFuncMu.Unlock()
if bf != nil {
if err := bf(fdInt); err != nil {
sockErr = err
return
}
}
})
if err != nil {
return fmt.Errorf("RawConn.Control on %T: %w", c, err)
}
return sockErr
}