Add Shizuku-powered rootless USB tethering and WiFi Hotspot - #5903
Add Shizuku-powered rootless USB tethering and WiFi Hotspot#5903eliotcougar wants to merge 45 commits into
Conversation
|
@2dust Do you want me to try trimming down and simplifying this PR more over the weekend? Rootless tethering is a really cool and useful feature to have. I've tested it, it works well on my Pixel 9. |
9c1f7a0 to
9d3c593
Compare
|
I've spent way too much time chasing this random issue where enabling tethering can occasionally choose a physical interface instead of testtun, and pass traffic from tethered device around v2rayNG straight to clearnet... Random bugs are the worst...
|
|
感谢你的 PR
请问是否总结的到位? |
It no longer requires API36+, it now works all the way to Android Correction: Apparently, setPreferTestNetworks doesn't exist in Pixel 5 API 30 framework... AI lied to me... The earliest is API33 Android 13... Just confirmed it running on Pixel 6a API33 image...
Installing Shizuku isn't difficult, and it updates very infrequently. It's basically a slightly elevated UID with adb-level access the apps can use.
We can look into it if IPv6 is something people need. I didn't prioritize it, and I can't really test it on my hardware.
Not really. Only if an OEM makes effort to clean up hidden APIs when making their firmware. They are lazy. I don't think they do. Maybe you know more about the state of the industry?
I think it is isolated enough to be easily removable if Android for some reason removes the necessary API. However, since it is probably something they use internally for debugging, I doubt it'll happen in the near future. I haven't worked on streamlining the main |
I think we'll have to put Shizuku Tethering behind API33+ gate after all… There was a promising workaround for API32, but it is quite invasive and absolutely not guaranteed to work on anything other than AOSP - creating a tap interface and a Virtual Carrier Network, and then routing tethering traffic through it. |
Use legacy tethering state and stop APIs below Android 16 while retaining the callback-based API 36 path.
Consolidate routing session state and simplify the UI/service synchronization flow to make the feature easier to review and maintain. Isolate the Android 11-15 and API 36+ tethering calls, as well as the required shell-attribution context bridge, behind focused compatibility helpers. Remove the broad lint suppression, add explicit platform gating, and keep only narrowly documented exceptions for hidden framework APIs.
Move the pure Compose rendering and UI-state mapping into TetheringScreen so ShizukuActivity is limited to lifecycle, permission, binding, and service orchestration responsibilities. This reduces the activity's import surface and makes the feature easier to review while preserving the existing layout and behavior. Consolidate repeated service-call, operation-state, and hotspot-polling plumbing as part of the split.
Pause active tethering before replacing or removing the Android test network. Reuse the same stop-rebuild-restore ordering for manual recovery and synchronized restarts, and refuse UserService exit when tethering cannot be stopped so clients cannot fall back to an ordinary upstream.
Publish a new synchronization session only after the privileged route starts successfully, restore the previous app token on failure, and reject blank or duplicate starts. Clear a persisted token only when the UserService explicitly reports that the matching session is stale.
Remove the duplicate Wi-Fi status Binder call and use the active tethering-type mask as the single source of truth for status, button state, and polling. Bump the UserService protocol version and add a focused mask-derivation regression test.
Move Android 11-15 reflection, interface classification, and stop polling out of the privileged state machine. Share the Android 11+ start callback with API 36, centralize type masks, and avoid redundant active-state queries during bulk stop and restore operations.
Replace mutually exclusive Xray and HEV strings with a single validated engine payload. Separate initial process-environment setup from restart synchronization so core lifecycle updates no longer compute or carry unused launch data, and cover both config builders with focused tests.
Group the TUN descriptor, framework interface, published network, callback, manager, and Binder lifetime token under one resource owner. Creation now publishes the handle before callbacks can arrive, while cleanup tears down the same captured resource set atomically.
Move Shizuku session lookup, running-core snapshots, and ordered lifecycle broadcasts behind a dedicated coordinator. CoreServiceManager now exposes only start, failure, stop, and snapshot-query integration points, keeping the central core lifecycle easier to review.
Use one positional timeout parser and one escaped YAML builder for the regular VPN and Shizuku tethering engines. Snapshot capture now records the same effective HEV settings used by the primary tunnel, with focused tests for defaults, malformed values, IPv6, and credentials.
Track operation generations so completion from a canceled job cannot overwrite a newer action. Always refresh after the current action finishes or fails, while rethrowing coroutine cancellation instead of converting it into a routing error.
Keep ShizukuActivity focused on lifecycle, permission, binding, and operation orchestration while deriving presentation state in TetheringScreen and consolidating privileged platform behavior behind the service helpers. Remove dead routing flags and cleanup parameters, avoid duplicate snapshot and broadcast plumbing, and use the shared HEV and engine configuration paths. Reassert the protected test-network preference immediately before tethering starts, refuse unprotected starts, and report Android's real tethering upstream in the UI so an eth0 handover failure is visible instead of being mistaken for the requested test TUN.
Reject core-resynchronization calls below Android 11 before entering the test-network rebuild path, and attach the same platform contract to the private helper. This removes the feature-owned NewApi lint error without suppressing it and makes accidental calls on unsupported releases fail explicitly.
Keep the production refactor commits focused on behavior and structure, then collect their verification coverage in one reviewable follow-up commit. Cover shared HEV configuration and escaping, native/HEV engine snapshots, legacy tethering type detection, real-upstream status parsing, and Compose control-state behavior including fail-closed waiting states.
Treat Android tethering startup as provisional until the downstream is active and its actual upstream consists only of the owned test TUN. Track in-flight starts so shutdown and failed-start cleanup still stop requests that have not yet appeared in the active type list. Restart pre-existing downstreams so the test-network preference is applied during upstream selection, reject physical or mixed upstreams immediately, and serialize UI toggles while an operation is pending. Document the Android handover race and teardown invariant for reviewers.
Android 11 and 12 can create the shell-owned test TUN, but the platform does not expose TetheringManager.setPreferTestNetworks in that API range. The VCN substitute we explored depends on carrier privileges and test subscription overrides unavailable to ordinary Shizuku users on production hardware. Avoid presenting an unreliable, OEM-dependent path. Hide and disable the Tethering UI, receiver, and Shizuku provider below API 33, reject privileged start requests defensively, and remove the now-unreachable unsupported UI state. API 33-35 continue to use the compatibility path while API 36+ keeps the existing implementation.
81114f3 to
8e29c74
Compare
Restructure the privileged service around smaller lifecycle helpers and document the hidden Android APIs and fail-closed invariants so the implementation is easier to review. Advertise the app-configured VPN DNS servers on the test network instead of hardcoded resolvers. If Android initially selects a physical upstream, stop the downstream, wait for the framework to settle, and retry once for every tethering type. A persistent mismatch remains disabled and is reported with a localized warning rather than a generic error.
Remove duplicate hotspot and legacy stop polling so the privileged service owns one bounded postcondition check across Android versions. Reuse the canonical Shizuku manager ID and existing running-profile helper, avoid repeated permission decisions and loading text, and bump the UserService version so stale shell processes are replaced.
Report IPv6 from the real Wi-Fi or USB downstream instead of assuming that enabling IPv6 on the test TUN was sufficient. Map active tethering types to their interface names on Android 13+ and show IPv4-only or IPv4 + IPv6 independently for each active downstream. Keep probe failures distinct from IPv4-only, restart stale Shizuku UserServices after the AIDL change, and localize the diagnostic across every supported app locale.
Replace the full-width enable and disable actions with compact switches in each section header, matching the app's established settings rows. Keep status, diagnostics, and explanatory text in place while preserving the existing guarded toggle behavior for protected tethering and the Wi-Fi hotspot.
|
The real-world test succeeded through the whole day of work through USB tethering. All traffic was routed correctly through v2rayNG while using a policyGroup connection. IPv6 support was added. It seems to be working in the emulator, but I cannot test it because I have no real IPv6 connectivity here. As a final stroke, I changed the two large buttons into toggle switches like everywhere else in the app. The feature looks more or less complete at this point. I think it is ready. |
Delete the localized enable and disable action strings that became unused when the full-width tethering controls were replaced with switches. Preserve the enabled and disabled result messages still used by operation feedback.
Present protected-upstream handover failures as a temporary Android connection switch, with brief wait-and-retry guidance across every supported locale. Align each section icon with its title row and trailing switch while leaving diagnostics and control behavior unchanged.
Present the transient protected-upstream handover notice through the supported info category instead of the retired warning category. Keep the existing localized message and branch base unchanged.
Delete the unreferenced warning toast helpers and enum branch after the transient tethering notice moved to the info category. This also removes the stale snackbar reference to the warning color dropped by the recent UI update.
|
Just discovered that starting v2rayNG tethering when there was already an active system tethering shows v2rayNG tethering active, but doesn't actually switch to |
Adopt every downstream already active when protected routing starts by sending it through the existing upstream verification, restart, and single-retry path. A downstream that still cannot select the test TUN is left stopped instead of remaining on its physical upstream. Reject an unknown initial tethering state and bump the Shizuku UserService version so APK updates replace a long-lived shell process with the corrected implementation.
A control-process failure must never silently route tethered clients outside v2rayNG. Treat loss of either Shizuku or the main core as fail-closed: preserve the dead protected TUN or take the downstream offline instead of allowing Android to select a physical upstream and leak traffic. Share ownership of the test-network request and TUN descriptor between the normal core and the Shizuku UserService. Track the main-core Binder lifetime so abrupt app death stops the secondary engine while retaining the dead TUN. When Shizuku returns, receive its replacement Binder in the core process, rebind a fresh UserService, retry once past any stale proxy, and recreate the protected route from the current core snapshot before restoring active downstreams.
The core snapshot response carries the Binder that owns the protected test network and receives its TUN descriptor. Register this receiver as not exported so only another v2rayNG process can provide core state and the lifetime lease, while preserving the existing cross-process synchronization flow.
Android 13-15 expose active downstreams as interface names rather than typed objects. Recognize the standard Wi-Fi P2P, NCM, and Ethernet names, and reject any remaining unknown interface instead of silently dropping it from the safety mask. This keeps the protected test network alive whenever every active downstream cannot be stopped or verified.
The protected-upstream check runs while Android tethering is already active and while the UserService state lock is held. Drain dumpsys output concurrently and enforce a short deadline so a stalled OEM service cannot block leak cleanup, disable requests, or lifecycle recovery indefinitely.
Large custom Xray configurations can exceed Android's shared Binder transaction buffer, especially during profile synchronization. Keep the exact running config in the core process and expose its prepared tethering form through a reliable pipe owned by the existing core lease. Broadcasts and UserService calls now carry only metadata and the Binder handle, while the shell process reads the configuration from the returned file descriptor. Bump the UserService contract version so stale shell processes are replaced.
Reduce review noise in the Shizuku tethering change by narrowing helper visibility, consolidating duplicate core-state resets, and keeping common tethering starts in the existing compatibility path. Make the Android 16+ helper stop-only, remove unused parameters and package lookup branching, align the drawer list with upstream style, and add precise API guards without lint suppressions. Bump the UserService version so APK updates cannot retain the pre-cleanup shell implementation.
Android 14 and newer can queue Shizuku's cross-process Binder relay while the provider process is cached. The retained test network keeps tethered clients fail-closed, but the shell UserService cannot be recreated until the app becomes active again. Signal foreground activity resumes to the running core on API 34+, request the replacement Binder once per Shizuku death, and reuse the existing core snapshot synchronization to restore the UserService, secondary engine, and active tethering. Avoid polling and leave Android 13 behavior unchanged.
|
Patched a couple of potential (rare, unlikely) traffic-leak scenarios where Android might silently decide to start routing tethered clients traffic directly. Fail-closed enforcement is very important: after the user enables protected routing in the app, until it is disabled, it should either route through the protected |
|
I think this can be a seperated app "Shizuku tether" so it can work with another VPN app not just V2RayNG |
|
I suggest request this feature to be added into https://github.com/Mygod/VPNHotspot/issues if possible |
The way Android does tethering pass-through to a VPN app (via testtun) makes arbitrary routing unviable. Basically, without root there is not much control over where the testtun binds itself. The current implementation keeps main connection and tethering connection synchronized at all times and allows for strict "fail-closed" operation. Without access to v2rayNG internal states that would be difficult to promise. |
Bring the feature branch onto upstream 2.3.1 while retaining Shizuku initialization across the Compose package move and the new launcher and message-helper structure. Integrate upstream's network-handover core reload with tethering synchronization so the secondary engine pauses before the main core reloads, resumes from the rebuilt configuration, and remains fail-closed if recovery fails. Advance AndroidLibXrayLite and HEV to the revisions selected by upstream.
Sync the feature branch through upstream 2.3.2 and adopt the typed Compose menu actions and Material 3 drawer implementation from 2dust#6016. Represent Tethering as a MainDestination instead of retaining the legacy string route, preserve its existing API-level resource gate when filtering drawer items, and use the same NavigationDrawerItem path as every other destination. This keeps the feature aligned with upstream without a fork-specific drawer abstraction.
Bring the feature branch through the latest server-editor, UI, and internal asset-storage changes. Upstream now keeps geodata and user assets in app-private storage, which the shell-owned Shizuku UserService cannot traverse. Expose those files as read-only descriptors through the existing core lifetime lease and stage unchanged assets only once in the shell runtime directory. This preserves native-Xray geoip, geosite, and custom asset routing without weakening the new private-storage boundary. Bump the UserService contract version so stale shell processes are replaced after the AIDL change.
Bring the feature branch through the centralized per-app locale migration and the latest translation refinements. Preserve Shizuku AIDL support, provider multiprocess recovery, and foreground fail-closed recovery while adopting AppCompatActivity, AppLocaleManager, generated locale configuration, and localized service contexts. Keep shell and platform failure diagnostics in logs instead of exposing English exception text through the UI. Live routing still reports the real interface and profile, while error states and toasts use existing localized resources. Bump the UserService version so an older shell process cannot retain the previous presentation behavior.
Summary
This PR adds a rootless tethering implementation that routes devices connected through the Android Wi-Fi hotspot or USB tethering through the active v2rayNG connection. Fixes #5898
Privileged Android networking operations are performed through a user-approved Shizuku UserService running as the shell UID. No root access, iptables manipulation, or custom DHCP/NAT implementation is required.
The feature supports both existing v2rayNG TUN implementations:
hev-socks5-tunnelIt also follows profile changes and service restarts. When the main v2rayNG connection changes, the tethering datapath is synchronized with the new running configuration and active Wi-Fi/USB tethering modes are preserved where Android permits this.
Motivation
Android’s regular
VpnServiceprotects traffic originating from the Android device itself. Traffic forwarded by Android’s tethering stack does not normally enter that VPN, so clients connected through a hotspot or USB tethering can bypass v2rayNG entirely.Root-based solutions can redirect forwarded traffic with firewall and policy-routing rules, but this is not available on locked, non-rooted devices.
This implementation uses Android’s test-network infrastructure through Shizuku to provide the tethering stack with a protected upstream network without requiring root.
User-facing changes
A new Tethering page is available in the navigation drawer below Backup & Restore.
The page provides:
Starting the Wi-Fi hotspot from this page automatically establishes the protected v2rayNG tethering route first.
USB tethering may be enabled from Android settings. If v2rayNG tethering routing is active, Android’s USB tethering traffic uses the same protected upstream.
All new user-facing strings are localized for every locale currently present in the project.
Working principle
1. Shizuku provides the privileged execution context
After the user grants permission, v2rayNG starts a daemon-style Shizuku UserService. The service executes under Android’s shell UID and owns the privileged tethering datapath.
The regular app process does not receive permanent system networking permissions. Shizuku remains the authorization boundary, and the user can revoke access through Shizuku at any time.
2. The exact running configuration is captured
The normal v2rayNG core records a snapshot of the configuration that was actually used to start the current connection.
This is intentionally different from rebuilding a configuration from preferences: settings or the selected profile may have changed after the running core was started.
The snapshot includes:
The feature requires v2rayNG to be running in VPN mode before protected tethering can be enabled.
3. Android creates the tethering TUN
Through the shell context, the UserService accesses Android’s hidden
TestNetworkManagerservice and:createTunInterface.192.0.2.2/24.setupTestNetwork.ConnectivityManagerto report that network.TetheringManager.setPreferTestNetworks(true)API.Android’s existing tethering stack then selects the test network as its upstream. Android continues to provide the downstream Wi-Fi/USB interface, DHCP, forwarding, NAT, and tethering lifecycle management; v2rayNG only consumes the upstream TUN traffic.
4. The selected TUN engine consumes the traffic
HEV mode
A second HEV instance runs inside the dedicated Shizuku process. Native HEV state is process-global, so using a separate process allows this instance to coexist with the HEV instance serving the normal Android VPN.
The tethering HEV instance consumes the test-network TUN and sends its traffic to the SOCKS inbound of the normal running v2rayNG core.
This means the tethered traffic ultimately uses the active core’s outbounds, DNS behavior, balancing, and applicable routing rules.
Native Xray TUN mode
A second Xray controller is created inside the Shizuku process.
Its configuration is derived from the exact running Xray configuration. Non-TUN inbounds are removed to avoid conflicting with ports already owned by the main core. The following sections remain available to the tethering core:
The second controller consumes the test TUN file descriptor directly.
5. Android tethering is started or attached
The UserService monitors active tethering types through
TetheringManager.TetheringEventCallback.The page can start or stop Wi-Fi tethering directly. USB tethering started from Android settings is detected and uses the protected test-network upstream while routing is active.
6. Profile changes and service restarts are synchronized
The main core sends authenticated lifecycle events to the Shizuku service:
Events are ordered through a single dispatcher and protected by a random per-session token.
When the user changes profiles or restarts v2rayNG, the service:
The preferred path switches the routing engine in place while retaining the existing TUN. If that fails, the implementation falls back to rebuilding the test network. The fallback stops tethering before removing the protected route and restores it afterward.
A short traffic interruption is still possible during a profile or implementation change. Android or an OEM tethering stack may also force hotspot clients to reconnect.
7. Fail-closed cleanup
The implementation deliberately avoids silently falling back to an unprotected upstream.
In particular:
setPreferTestNetworks(false), unregisters callbacks, tears down the test network, closes the TUN, stops the secondary engine, and removes temporary HEV configuration.Routing semantics
Most normal network-based routing behavior remains available, including rules based on:
However, per-app and process-based routing cannot apply to tethered devices.
Android and Shizuku requirements
The current implementation should be treated as requiring Android 16 / API level 36 or newer.Although the application itself retains its existing
minSdkof 24, this feature uses API-level-36 tethering types and signatures if they are available, including:TetheringManager.StopTetheringCallbackstopTethering(TetheringRequest, Executor, StopTetheringCallback)TetheringEventCallback.onTetheredInterfacesChanged(Set<TetheringInterface>)TetheringInterfaceThe fallback method has been added to cover Android 11-15.
See the Android
TetheringManagerreference andTetheringEventCallbackreference.Additional requirements are:
test_networksystem serviceTestNetworkManagermethodsTetheringManager.setPreferTestNetworks(boolean)MANAGE_TEST_NETWORKSandNETWORK_SETTINGSTestNetworkManagerandsetPreferTestNetworksare System/hidden APIs rather than stable third-party application APIs. Their AOSP permission and API definitions can be seen in AOSPTestNetworkManagerand AOSPTetheringManager.Device compatibility warning
Passing the Android version check does not guarantee compatibility.
This implementation depends on behavior below the ordinary Android application API surface. OEMs may:
AOSP itself permits tethering to be disabled by system properties, device configuration, device policy, and carrier entitlement. Carrier configuration may also reject cellular tethering even if Wi-Fi hotspot hardware is present. See the AOSP tethering implementation and entitlement manager.
Known Shizuku setup friction exists on MIUI/HyperOS, ColorOS, Flyme, EMUI, Sony firmware, and some hardened Android distributions. These are not necessarily missing networking features, but they may require additional developer-option changes or background-execution allowances. See the Shizuku setup guide.
Initial testing has covered a Pixel 9 Pro running Android 16 and a Google/AOSP emulator running Android 15 and 17. This should not be interpreted as a complete OEM compatibility matrix.
Potential conflicts
Users should avoid running this feature simultaneously with:
Android normally allows only one active
VpnService, so replacing v2rayNG with another VPN also invalidates the protected tethering route.The feature uses Android’s existing persistent hotspot configuration. It does not manage the hotspot SSID, password, band, channel, or client allowlist.
Security and trust considerations
The native Xray mode passes the exact running Xray configuration to the Shizuku UserService. This configuration may contain server credentials or other sensitive connection parameters.
The privileged UserService is therefore inside the Shizuku trust boundary. Users must trust both Shizuku and the installed v2rayNG build before granting permission.
To limit internal spoofing and stale updates:
Implementation overview
This PR introduces:
Validation
:app:assembleDebugsucceeds for both F-Droid and Play Store variants.Known limitations and future work
Android 15 and earlier are not supported by the current implementation.The protected test-network upstream is currently IPv4-only.Compatibility fallbacks would be required to target earlier Android releases.