Files
v2rayNG/compile-hevtun.sh
T
Alizaand2dust e83dba94a0 feat: add a root, system-wide run mode without VpnService (#5812)
* feat: add a root, system-wide run mode without VpnService

Adds an optional Root mode for rooted devices that routes the whole device's
traffic through the existing in-process core without Android's VpnService, plus
an opt-in LAN/tethering sharing feature. Non-root devices are unaffected and keep
VPN / Proxy-only (VPN stays the default).

- ERunMode (VPN, PROXY_ONLY, TUN2SOCKS) persisted in the existing PREF_MODE;
  RootManager gates root modes (greyed-out for non-root, service refuses to start).
- CoreRootService + core/root/RootProxyManager run hev-socks5-tunnel as a
  standalone root process into the core's SOCKS inbound, steered by an iptables
  mangle MARK chain + a dedicated route table. Full TCP + UDP. hev-socks5-tunnel
  is the same engine already bundled for the VPN hev path, so no new third-party
  dependency is added.
- Capture parity with VpnService incl. per-app proxy/bypass; DNS funneled into the
  core (netd-aware, no uid filter) so names resolve through the configured
  resolver with no LAN-resolver leak.
- IPv6 parity: routed into the tun when enabled, otherwise native v6 is blackholed
  for the captured apps (REJECT) so they fall back to v4-through-proxy, like a
  v4-only VpnService.
- MTU taken from the existing VPN MTU setting; hev tun multi-queue + SOCKS
  tcp-fastopen enabled.
- CI fetches the hev-socks5-tunnel binary per-ABI from heiher/hev-socks5-tunnel
  releases (the same upstream the VPN hev path uses).

* build: compile libhevsockstun.so from source instead of downloading

Build the standalone hev-socks5-tunnel binary used by Root mode from the
pinned hev-socks5-tunnel submodule in compile-hevtun.sh, alongside the
existing JNI shared library, and drop the prebuilt release download from
the build workflow.

Both hev artifacts now come from the same in-tree source, so the binary
is fully auditable and version-locked to the submodule rather than a
fetched release asset. The executable is built without -DENABLE_LIBRARY
(so hev-main.c's main() is included) via BUILD_EXECUTABLE, reusing the
NDK toolchain already used for the JNI library.

* refactor(root): address review feedback

- LAN-sharing guard now checks the cheap, usually-false PREF_ROOT_LAN_SHARING
  preference before RootManager.cachedRoot(), so the common path short-circuits
  without touching root state.
- Move RootManager into the core.root package next to RootProxyManager and
  RootShell (CoreRootService stays under service/).
- Probe su only when the user opts into a root feature — selecting a root mode
  or enabling LAN sharing — instead of automatically on every Settings open.
  If root is denied the selection is reverted with a toast. This avoids an
  unsolicited root-grant prompt for the common non-root case; root mode for a
  persisted selection is still re-verified when the service starts.

* refactor(root): use coroutines instead of Thread for su probing

Replace raw Thread usage in the root path with kotlinx coroutines, as
requested in review. RootManager.refreshAsync (a callback + daemon Thread)
becomes a suspending refresh() that runs the blocking su probe on
Dispatchers.IO and returns the result.

Callers updated accordingly:
- SettingsActivity probes on demand via lifecycleScope.launch and updates
  the UI directly on resume (no manual runOnUiThread).
- CoreVpnService starts the LAN-sharing client over CoroutineScope(IO)
  instead of a daemon Thread.

* fix(root): don't capture all apps when per-app proxy resolves no uids

In allow (proxy-only) mode the mangle/v6 builders fell through to the
catch-all "mark/reject everything" branch whenever selectedUids was empty.
That is a fail-open: if the selected packages momentarily fail to resolve to
uids (e.g. at early boot, before PackageManager is ready), every unselected
app gets tunneled instead of none — a privacy leak and the cause of per-app
"proxying everything" after a reboot.

Gate the catch-all on the mode itself (all-apps or bypass) rather than on
"selected list happened to be non-empty". In allow mode mark only the
resolved uids; if none resolved, mark nothing (fail closed). Mirror the same
fix in the IPv6 blackhole chain.

* fix(root): wait for async rule setup before teardown on stop

CoreRootService/CoreVpnService post the foreground notification as soon as the
core starts but install the root routing rules in a launched coroutine, which
can take seconds (the setup script waits for the tun device to appear). If the
user stops the service during that window, onDestroy/stopAllService ran the
synchronous teardown first and the still-running setup then re-installed the
rules and tun afterwards — leaving orphan routing rules and a tun forwarding
into a now-dead core, which blackholes all traffic until the next start/stop
cycle clears it (the "disconnect from the notification kills the internet,
reconnect+disconnect to fix it" bug).

Track the setup job and cancelAndJoin it before tearing down so teardown always
runs last and removes everything the setup installed.

* Adjust root package and add RootLanSharing object

* Remove  ERunMode , add PREF_ROOT_MODE_ENABLE

* fix(root): handle tethered clients' IPv6 in LAN sharing to stop leaks

LAN/tethering sharing only set up IPv4 forwarding for clients, so a
hotspot/USB-tethered client with a native (RA-assigned) global IPv6
egressed the upstream interface directly, bypassing the proxy — an
IPv6 leak.

buildLanShareSetup now handles forwarded clients' v6:
- IPv6 enabled: route it through the tun. A mangle PREROUTING chain
  marks non-LOCAL-sourced (forwarded) v6 into the tun route table,
  keeps loopback/link-local/ULA/multicast direct, and hijacks client
  DNS; FORWARD accepts traffic to/from the tun. A trailing REJECT
  fails closed so anything not marked into the tun (e.g. addrtype
  match unavailable) is dropped instead of leaked.
- IPv6 disabled: REJECT all forwarded v6 (the device's own v6 is
  already blackholed in OUTPUT).

Teardown drops the two new ip6tables chains; the v6 route/rule into
the tun table were already cleaned.

Ported from vincentng295/Magic_V2Ray cae4f7f.

* Update build.gradle.kts

* Adjust settings

---------

Co-authored-by: 2dust <31833384+2dust@users.noreply.github.com>
2026-06-28 11:03:36 +08:00

104 lines
3.3 KiB
Bash

#!/bin/bash
set -o errexit
set -o pipefail
set -o nounset
# Set magic variables for current file & dir
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
__base="$(basename ${__file} .sh)"
if [[ ! -d $NDK_HOME ]]; then
echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME"
exit 1
fi
TMPDIR=$(mktemp -d)
clear_tmp () {
rm -rf $TMPDIR
}
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp; exit 1' ERR INT
ABIS="armeabi-v7a arm64-v8a x86 x86_64"
mkdir -p "$TMPDIR/jni"
pushd "$TMPDIR"
ln -s "$__dir/hev-socks5-tunnel" jni/hev-socks5-tunnel
# 1) JNI shared library (libhev-socks5-tunnel.so) — loaded in-process by
# com.v2ray.ang.service.TProxyService for the VpnService hev tun mode.
echo 'include $(call all-subdir-makefiles)' > jni/Android.mk
"$NDK_HOME/ndk-build" \
NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=jni/Android.mk \
"APP_ABI=$ABIS" \
APP_PLATFORM=android-24 \
NDK_LIBS_OUT="$TMPDIR/libs" \
NDK_OUT="$TMPDIR/obj" \
"APP_CFLAGS=-O3 -DPKGNAME=com/v2ray/ang/service" \
"APP_LDFLAGS=-Wl,--build-id=none -Wl,--hash-style=gnu" \
# 2) Standalone executable (libhevsockstun.so) — run as a separate root
# process by com.v2ray.ang.core.root for the Root run mode. Same hev source,
# no -DENABLE_LIBRARY so hev-main.c's main() is built, and BUILD_EXECUTABLE
# instead of a shared library. It creates its own tun and reads a YAML config.
cat > jni/exec.mk <<'EXECMK'
TOP_PATH := $(call my-dir)/hev-socks5-tunnel
ifeq ($(filter $(modules-get-list),yaml),)
include $(TOP_PATH)/third-part/yaml/Android.mk
endif
ifeq ($(filter $(modules-get-list),lwip),)
include $(TOP_PATH)/third-part/lwip/Android.mk
endif
ifeq ($(filter $(modules-get-list),hev-task-system),)
include $(TOP_PATH)/third-part/hev-task-system/Android.mk
endif
LOCAL_PATH := $(TOP_PATH)
SRCDIR := $(LOCAL_PATH)/src
include $(CLEAR_VARS)
include $(LOCAL_PATH)/build.mk
LOCAL_MODULE := hevsockstun
LOCAL_SRC_FILES := $(patsubst $(SRCDIR)/%,src/%,$(SRCFILES))
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/src \
$(LOCAL_PATH)/src/misc \
$(LOCAL_PATH)/src/core/include \
$(LOCAL_PATH)/third-part/yaml/include \
$(LOCAL_PATH)/third-part/lwip/src/include \
$(LOCAL_PATH)/third-part/lwip/src/ports/include \
$(LOCAL_PATH)/third-part/hev-task-system/include
LOCAL_CFLAGS += -DFD_SET_DEFINED -DSOCKLEN_T_DEFINED
LOCAL_CFLAGS += $(VERSION_CFLAGS)
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_CFLAGS += -mfpu=neon
endif
LOCAL_STATIC_LIBRARIES := yaml lwip hev-task-system
LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384
LOCAL_LDFLAGS += -Wl,-z,common-page-size=16384
include $(BUILD_EXECUTABLE)
EXECMK
"$NDK_HOME/ndk-build" \
NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=jni/exec.mk \
"APP_ABI=$ABIS" \
APP_PLATFORM=android-24 \
NDK_LIBS_OUT="$TMPDIR/libs-exec" \
NDK_OUT="$TMPDIR/obj-exec" \
"APP_CFLAGS=-O3" \
"APP_LDFLAGS=-Wl,--build-id=none -Wl,--hash-style=gnu" \
# Stage both artifacts under libs/<abi>/. The executable is renamed to
# lib*.so so the APK installer extracts it into nativeLibraryDir as an
# executable file (filename distinct from the JNI library above).
mkdir -p "$__dir/libs"
cp -r "$TMPDIR/libs/"* "$__dir/libs/"
for abi in $ABIS; do
cp "$TMPDIR/libs-exec/$abi/hevsockstun" "$__dir/libs/$abi/libhevsockstun.so"
done
popd
rm -rf $TMPDIR