mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-13 14:29:49 -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
73 lines
1.6 KiB
Go
73 lines
1.6 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux
|
|
|
|
package androidbin
|
|
|
|
import (
|
|
"net"
|
|
"testing"
|
|
|
|
"tailscale.com/net/netmon"
|
|
)
|
|
|
|
func TestFallbackInterfacesNotAndroid(t *testing.T) {
|
|
if onAndroid() {
|
|
t.Skip("running on Android")
|
|
}
|
|
if _, err := fallbackInterfaces(); err == nil {
|
|
t.Fatal("fallbackInterfaces succeeded off Android; want error")
|
|
}
|
|
}
|
|
|
|
func TestOutboundIP(t *testing.T) {
|
|
ip, ok := outboundIP("udp4", "8.8.8.8:53")
|
|
if !ok {
|
|
t.Skip("no IPv4 route (offline?)")
|
|
}
|
|
if !ip.Is4() || ip.IsLoopback() || ip.IsUnspecified() {
|
|
t.Errorf("outboundIP = %v; want global unicast IPv4", ip)
|
|
}
|
|
t.Logf("outbound IPv4 source: %v", ip)
|
|
}
|
|
|
|
func TestSyntheticInterface(t *testing.T) {
|
|
ifs, err := syntheticFor(t)
|
|
if err != nil {
|
|
t.Skip("no routes (offline?)")
|
|
}
|
|
if len(ifs) != 1 {
|
|
t.Fatalf("got %d interfaces; want 1", len(ifs))
|
|
}
|
|
nif := ifs[0]
|
|
if !nif.IsUp() {
|
|
t.Error("synthetic interface is not up")
|
|
}
|
|
addrs, err := nif.Addrs()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(addrs) == 0 {
|
|
t.Fatal("no addresses on synthetic interface")
|
|
}
|
|
for _, a := range addrs {
|
|
ipn, ok := a.(*net.IPNet)
|
|
if !ok {
|
|
t.Errorf("address %v is %T; want *net.IPNet", a, a)
|
|
continue
|
|
}
|
|
if ipn.IP.IsLoopback() || ipn.IP.IsUnspecified() {
|
|
t.Errorf("address %v is not a usable source address", ipn)
|
|
}
|
|
}
|
|
}
|
|
|
|
// syntheticFor builds the synthetic interface list regardless of
|
|
// whether the test machine is Android, by skipping the onAndroid
|
|
// check that fallbackInterfaces performs.
|
|
func syntheticFor(t *testing.T) ([]netmon.Interface, error) {
|
|
t.Helper()
|
|
return buildSynthetic()
|
|
}
|