mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-14 23:11:39 -04:00
Android denies app UIDs both NETLINK_ROUTE (golang/go#40569, #2293) and /proc/net, so net.Interfaces always fails and netmon.New errors out before a standalone binary can do anything. The Android app solves this from Java via netmon.RegisterInterfaceGetter, but raw binaries run under Termux or a rooted shell have no Java to lean on. This is the second half of #21129, following the androiddns feature. I added a netmon fallback hook, consulted only when no interface getter was registered and net.Interfaces failed, and a new androidbin feature (ts_omit_androidbin) that implements it: report a single synthetic interface whose v4 and v6 addresses come from asking the kernel to route an outbound UDP socket, which sends no packets and is permitted in the app sandbox. That's enough for magicsock to discover local endpoints. The fallback also requires runtime evidence of Android (GOOS=android, or /dev/__properties__ existing for GOOS=linux binaries running under an Android kernel), so it's inert on regular Linux. The androidbin feature also blank imports androiddns, and fixes a third gap I found while testing: GOOS=linux binaries on Android have an empty CA root pool, because Go's unix root loader doesn't know Android's /system/etc/security/cacerts (the GOOS=android loader does), so all TLS verification fails. On Android it points SSL_CERT_DIR there unless the user already set it, as Termux's ca-certificates package does. The feature is on by default in tailscaled builds on Linux and Android via condregister, and deliberately not linked into tsnet by default; tsnet apps and other programs opt in with a blank import of tailscale.com/feature/androidbin. I verified on an Android 13 emulator with SELinux enforcing, running GOOS=linux static binaries under the app UID (run-as), where net.Interfaces fails with the exact netlinkrib permission denial from the issue: netmon.New succeeds with the synthetic interface, and a tailcat binary importing this feature does DNS via dnsproxyd, fetches its DERP map over TLS using the Android cert store, completes STUN, selects a DERP region, and prints its address. Updates #21129 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: If7dbcbb825ecd24bcf6d9d64b1e334dd00700d51
34 lines
1.0 KiB
Go
34 lines
1.0 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux && !android
|
|
|
|
package androidbin
|
|
|
|
import "os"
|
|
|
|
// Android keeps its CA roots in /system/etc/security/cacerts as PEM
|
|
// files, a path Go's crypto/x509 knows about in GOOS=android builds
|
|
// but not GOOS=linux ones, so a linux binary on Android has an empty
|
|
// system cert pool and all TLS verification fails. Point Go's unix
|
|
// root loader there via SSL_CERT_DIR, which it honors on linux. This
|
|
// runs at init, before crypto/x509 lazily loads roots on first use.
|
|
//
|
|
// If the user already configured SSL_CERT_DIR or SSL_CERT_FILE (as
|
|
// Termux does when its ca-certificates package is installed), leave
|
|
// their configuration alone.
|
|
func init() {
|
|
if !onAndroid() {
|
|
return
|
|
}
|
|
if os.Getenv("SSL_CERT_DIR") != "" || os.Getenv("SSL_CERT_FILE") != "" {
|
|
return
|
|
}
|
|
if fi, err := os.Stat(androidCACertDir); err != nil || !fi.IsDir() {
|
|
return
|
|
}
|
|
os.Setenv("SSL_CERT_DIR", androidCACertDir)
|
|
}
|
|
|
|
const androidCACertDir = "/system/etc/security/cacerts"
|