Build with a Go toolchain patched by Termux, drop the netmon and DNS patches - #13
Merged
Conversation
…tches
The interface-discovery and DNS problems this project works around are not
tailscale problems, they are Go standard-library problems on Android -- and
Termux already fixes both in the Go it ships, and applies the same patches when
cross-compiling other packages. Doing the same here replaces ~470 lines of our
own code with four vendored diffs.
Measured on an Android 16 phone in a real untrusted_app context, running the
same probe built two ways:
vanilla Go: net.Interfaces(): err=netlinkrib: permission denied count=0
LookupHost: ... on [::1]:53 ... connection refused
patched Go: net.Interfaces(): err=<nil> count=8
LookupHost: err=<nil> ips=[...]
The patched build sees more than our patch did: cellular rmnet interfaces and
a global IPv6 address, which the ifconfig/procfs ladder never reliably got.
Then verified with the real daemon on the same phone -- registered with the
control plane, and `curl --socks5-hostname` through it returned 200, which is
the DNS path that matters.
So patches/fix_android_netmon.go is gone, along with the tsdial resolver
injection and the wlynxg/anet dependency. What is left of that file is the
hostinfo hook, which is a deliberate choice rather than a workaround, moved to
patches/fix_hostinfo_android.go.
Consequences worth stating:
- Every architecture is now GOOS=android with -buildmode=pie, so all four ship
as ET_DYN against /system/bin/linker instead of one PIE and three ET_EXEC.
That also covers Termux builds targeting API 29+, which launch binaries
through the linker and reject ET_EXEC.
- Cross-compiling arm/i686/x86_64 now needs the NDK, because Go refuses
GOOS=android without cgo there. Building on a phone needs nothing extra:
Termux's own Go already carries the patches, so build.sh detects that and
skips the toolchain download entirely.
- GOTOOLCHAIN=local is not optional. Without it Go silently downloads the
toolchain named in tailscale's go.mod and discards every patch above.
- DNS is now whatever $PREFIX/etc/resolv.conf says -- the same resolver as the
rest of Termux, editable by the user -- so TS_DNS_SERVER is gone and nothing
is hardcoded to a public resolver. resolv-conf became a package dependency,
since without that file there is no resolver at all.
- verify_patched checks the artifact for the toolchain's resolv.conf path: a
toolchain patch that stops applying otherwise yields a binary that looks fine
and cannot resolve a name or see an interface.
- Patch application is fatal on failure. These diffs patch Go's own sources and
are sensitive to its version; they applied cleanly to go1.27.1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TS_DNS_SERVER stopped existing in the previous commit. Anyone who had it in .env would otherwise find it silently ignored, with no hint that the address now belongs in $PREFIX/etc/resolv.conf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The two hardest things this project patches — interface discovery and DNS — are not tailscale problems. They are Go standard-library problems on Android, and Termux already fixes both in the Go it ships, and applies the same patches when cross-compiling other packages. This does the same, and deletes our versions.
Evidence
Same probe program, built two ways, run on an Android 16 phone in a real
untrusted_appcontext (not viasu, which runs in a privileged SELinux domain and hides the failure):The patched build sees more than our own patch did: cellular
rmnetinterfaces and a global IPv6 address, which theanet→/proc/net/if_inet6→ifconfig→ UDPv6-probe ladder never reliably got.Then verified with the real daemon on the same phone, on its existing tailnet state: it registered with the control plane (
machineAuthorized=true), andcurl --socks5-hostnamethrough its proxy returned200— the DNS path that actually matters.tailscale-testcame back green after restoring the service.What changed
patches/fix_android_netmon.go(470 lines) is gone, along with thenet/tsdialresolver injection and thewlynxg/anetdependency. What survived is the hostinfo hook — a deliberate choice, not a workaround — now inpatches/fix_hostinfo_android.go.In its place,
patches/go/vendors Termux's four Go patches, applied to the toolchain before building.Consequences worth reading
GOOS=androidwith-buildmode=pie. Previously one was PIE and three wereET_EXEC. That also covers Termux builds targeting API 29+, which launch binaries through/system/bin/linkerand rejectET_EXEC.arm/i686/x86_64now needs the NDK, because Go refusesGOOS=androidwithout cgo there. CI runners have one; the workflow now fails loudly if that changes.aarch64needs nothing.build.shdetects an on-device build and skips the toolchain download entirely — a linux-amd64 toolchain could not run there anyway.GOTOOLCHAIN=localis load-bearing. Without it Go silently downloads the toolchain named in tailscale'sgo.modand discards every patch above it.$PREFIX/etc/resolv.conf— the same resolver as the rest of your Termux, editable by hand.TS_DNS_SERVERis gone and nothing is hardcoded to a public resolver any more.resolv-confbecame a package dependency, since without that file there is no resolver at all.verify_patchednow greps the artifact for the toolchain'sresolv.confpath. A toolchain patch that quietly stops applying otherwise produces a binary that looks fine and cannot resolve a name or see an interface.go1.27.1, andpatches/go/refresh.shre-vendors them.Not done here
Adopting termux-packages'
build-package.shwholesale. This takes the part that carries the benefit — the toolchain treatment — without depending on their Docker image or their repository layout. The recipe intermux-packages/for upstream submission is unaffected.🤖 Generated with Claude Code