├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values-night │ │ │ └── themes.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── jniLibs │ │ ├── arm64-v8a │ │ │ ├── libnfqws.so │ │ │ ├── libtpws.so │ │ │ ├── libdnsmasq.so │ │ │ ├── libhevserver.so │ │ │ ├── libhevtproxy.so │ │ │ └── libusbgadget.so │ │ └── armeabi-v7a │ │ │ ├── libtpws.so │ │ │ ├── libnfqws.so │ │ │ ├── libdnsmasq.so │ │ │ ├── libhevserver.so │ │ │ ├── libhevtproxy.so │ │ │ └── libusbgadget.so │ │ ├── java │ │ └── com │ │ │ └── worstperson │ │ │ └── usbtether │ │ │ ├── BootUpReceiver.java │ │ │ ├── MainActivity.java │ │ │ ├── Script.java │ │ │ └── ForegroundService.java │ │ └── AndroidManifest.xml ├── .directory ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── .directory ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── native ├── usbgadget │ └── lineage16_system_core_usbd │ │ ├── Android.bp │ │ └── usbd.cpp ├── hev │ ├── server.patch │ ├── tproxy.patch │ └── make_hev ├── dnsmasq │ ├── make_dnsmasq │ └── dnsmasq.patch ├── zapret │ ├── libnetfilter_queue-1.0.5.patch │ ├── make_zapret │ └── nfqws.patch └── curl │ └── make_curl ├── gradle.properties ├── gradlew.bat ├── gradlew ├── README.md └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name = "USB Tether" -------------------------------------------------------------------------------- /.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | Timestamp=2020,11,16,22,32,2 3 | Version=4 4 | 5 | [Settings] 6 | HiddenFilesShown=true 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | USB Tether 3 | -------------------------------------------------------------------------------- /app/.directory: -------------------------------------------------------------------------------- 1 | [Dolphin] 2 | Timestamp=2020,11,16,22,31,49 3 | Version=4 4 | 5 | [Settings] 6 | HiddenFilesShown=true 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libnfqws.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/arm64-v8a/libnfqws.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libtpws.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/arm64-v8a/libtpws.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libtpws.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/armeabi-v7a/libtpws.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libdnsmasq.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/arm64-v8a/libdnsmasq.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libnfqws.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/armeabi-v7a/libnfqws.so -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libhevserver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/arm64-v8a/libhevserver.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libhevtproxy.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/arm64-v8a/libhevtproxy.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/arm64-v8a/libusbgadget.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/arm64-v8a/libusbgadget.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libdnsmasq.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/armeabi-v7a/libdnsmasq.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libhevserver.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/armeabi-v7a/libhevserver.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libhevtproxy.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/armeabi-v7a/libhevtproxy.so -------------------------------------------------------------------------------- /app/src/main/jniLibs/armeabi-v7a/libusbgadget.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/jniLibs/armeabi-v7a/libusbgadget.so -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worstperson/USBTether/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /native/usbgadget/lineage16_system_core_usbd/Android.bp: -------------------------------------------------------------------------------- 1 | cc_binary { 2 | name: "usbd", 3 | init_rc: ["usbd.rc"], 4 | srcs: ["usbd.cpp"], 5 | shared_libs: [ 6 | "libbase", 7 | "libhidlbase", 8 | "liblog", 9 | "libutils", 10 | "android.hardware.usb.gadget@1.0", 11 | "libcutils", 12 | ], 13 | static_libs: [ 14 | "libhidltransport", 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /native/hev/server.patch: -------------------------------------------------------------------------------- 1 | diff -ruN org/jni/Android.mk patch/jni/Android.mk 2 | --- org/jni/Android.mk 2024-03-06 22:47:28.148607162 -0600 3 | +++ patch/jni/Android.mk 2024-03-06 22:50:36.561934193 -0600 4 | @@ -39,4 +39,4 @@ 5 | LOCAL_CFLAGS += -mfpu=neon 6 | endif 7 | LOCAL_STATIC_LIBRARIES := yaml hev-task-system 8 | -include $(BUILD_SHARED_LIBRARY) 9 | +include $(BUILD_EXECUTABLE) 10 | diff -ruN org/jni/Application.mk patch/jni/Application.mk 11 | --- org/jni/Application.mk 2024-03-06 23:03:52.741907698 -0600 12 | +++ patch/jni/Application.mk 2024-03-06 23:04:48.011905863 -0600 13 | @@ -14,7 +14,7 @@ 14 | # 15 | 16 | APP_OPTIM := release 17 | -APP_PLATFORM := android-21 18 | +APP_PLATFORM := android-24 19 | APP_ABI := armeabi-v7a arm64-v8a 20 | APP_CFLAGS := -O3 21 | NDK_TOOLCHAIN_VERSION := clang 22 | -------------------------------------------------------------------------------- /native/hev/tproxy.patch: -------------------------------------------------------------------------------- 1 | diff -ruN org/jni/Android.mk patch/jni/Android.mk 2 | --- org/jni/Android.mk 2024-03-06 22:47:28.148607162 -0600 3 | +++ patch/jni/Android.mk 2024-03-06 22:50:36.561934193 -0600 4 | @@ -39,4 +39,4 @@ 5 | LOCAL_CFLAGS += -mfpu=neon 6 | endif 7 | LOCAL_STATIC_LIBRARIES := yaml hev-task-system 8 | -include $(BUILD_SHARED_LIBRARY) 9 | +include $(BUILD_EXECUTABLE) 10 | diff -ruN org/jni/Application.mk patch/jni/Application.mk 11 | --- org/jni/Application.mk 2024-03-06 22:47:28.148607162 -0600 12 | +++ patch/jni/Application.mk 2024-03-06 22:50:02.675268655 -0600 13 | @@ -14,7 +14,7 @@ 14 | # 15 | 16 | APP_OPTIM := release 17 | -APP_PLATFORM := android-21 18 | +APP_PLATFORM := android-24 19 | APP_ABI := armeabi-v7a arm64-v8a 20 | APP_CFLAGS := -O3 21 | NDK_TOOLCHAIN_VERSION := clang 22 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /native/hev/make_hev: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | export ANDROID_NDK_HOME=~/android-tools/android-ndk-r26d 4 | 5 | mkdir hev-socks5-server 6 | cd hev-socks5-server 7 | git clone --recursive --depth 1 --branch 2.8.0 https://github.com/heiher/hev-socks5-server jni 8 | patch -N -p1 < ../server.patch 9 | cd jni 10 | $ANDROID_NDK_HOME/ndk-build 11 | mv ../libs/armeabi-v7a/hev-socks5-server ../../hev-socks5-server_arm7a 12 | mv ../libs/arm64-v8a/hev-socks5-server ../../hev-socks5-server_aarch64 13 | cd ../.. 14 | 15 | mkdir hev-socks5-tproxy 16 | cd hev-socks5-tproxy 17 | git clone --recursive --depth 1 --branch 2.8.0 https://github.com/heiher/hev-socks5-tproxy jni 18 | patch -N -p1 < ../tproxy.patch 19 | cd jni 20 | $ANDROID_NDK_HOME/ndk-build 21 | mv ../libs/armeabi-v7a/hev-socks5-tproxy ../../hev-socks5-tproxy_arm7a 22 | mv ../libs/arm64-v8a/hev-socks5-tproxy ../../hev-socks5-tproxy_aarch64 23 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /native/dnsmasq/make_dnsmasq: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | options=("arm7a" "aarch64") 4 | select opt in "${options[@]}" 5 | do 6 | case $opt in 7 | "arm7a") 8 | export HOST=armv7a-linux-androideabi 9 | break 10 | ;; 11 | "aarch64") 12 | export HOST=aarch64-linux-android 13 | break 14 | ;; 15 | *) echo "invalid option $REPLY";; 16 | esac 17 | done 18 | 19 | export ANDROID_NDK_HOME=~/android-tools/android-ndk-r26d 20 | export HOST_TAG=linux-x86_64 21 | export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$HOST_TAG 22 | export AR=$TOOLCHAIN/bin/llvm-ar 23 | export AS=$TOOLCHAIN/bin/llvm-as 24 | export CC=$TOOLCHAIN/bin/${HOST}24-clang 25 | export CXX=$TOOLCHAIN/bin/${HOST}24-clang++ 26 | export LD=$TOOLCHAIN/bin/ld 27 | export RANLIB=$TOOLCHAIN/bin/llvm-ranlib 28 | export STRIP=$TOOLCHAIN/bin/llvm-strip 29 | 30 | rm -R dnsmasq-2.91 31 | wget -nc https://thekelleys.org.uk/dnsmasq/dnsmasq-2.91.tar.gz 32 | tar -xf dnsmasq-2.91.tar.gz 33 | patch -N -p1 < dnsmasq.patch 34 | cd dnsmasq-2.91 35 | make 36 | $STRIP src/dnsmasq 37 | mv src/dnsmasq ../dnsmasq_$HOST 38 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=false 20 | android.nonTransitiveRClass=false 21 | android.nonFinalResIds=false 22 | android.javaCompile.suppressSourceTargetDeprecationWarning=true -------------------------------------------------------------------------------- /native/dnsmasq/dnsmasq.patch: -------------------------------------------------------------------------------- 1 | diff -ruN org/dnsmasq-2.91/Makefile patch/dnsmasq-2.91/Makefile 2 | --- org/dnsmasq-2.91/Makefile 2025-03-14 10:09:35.000000000 -0500 3 | +++ patch/dnsmasq-2.91/Makefile 2025-03-31 23:52:40.927904166 -0500 4 | @@ -24,8 +24,8 @@ 5 | LOCALEDIR = $(PREFIX)/share/locale 6 | BUILDDIR = $(SRC) 7 | DESTDIR = 8 | -CFLAGS = -Wall -W -O2 9 | -LDFLAGS = 10 | +CFLAGS = -Wall -W -O2 -fPIC 11 | +LDFLAGS = -llog 12 | COPTS = 13 | RPM_OPT_FLAGS = 14 | LIBS = 15 | diff -ruN org/dnsmasq-2.91/src/dhcp.c patch/dnsmasq-2.91/src/dhcp.c 16 | --- org/dnsmasq-2.91/src/dhcp.c 2025-03-14 10:09:35.000000000 -0500 17 | +++ patch/dnsmasq-2.91/src/dhcp.c 2025-03-31 23:53:49.272491755 -0500 18 | @@ -16,6 +16,10 @@ 19 | 20 | #include "dnsmasq.h" 21 | 22 | +#ifdef __ANDROID__ 23 | +#define ETHER_ADDR_LEN 6 24 | +#endif 25 | + 26 | #ifdef HAVE_DHCP 27 | 28 | struct iface_param { 29 | diff -ruN org/dnsmasq-2.91/src/tftp.c patch/dnsmasq-2.91/src/tftp.c 30 | --- org/dnsmasq-2.91/src/tftp.c 2025-03-14 10:09:35.000000000 -0500 31 | +++ patch/dnsmasq-2.91/src/tftp.c 2025-03-31 23:54:36.758208856 -0500 32 | @@ -16,6 +16,10 @@ 33 | 34 | #include "dnsmasq.h" 35 | 36 | +#ifdef __ANDROID__ 37 | +#define ETHER_ADDR_LEN 6 38 | +#endif 39 | + 40 | #ifdef HAVE_TFTP 41 | 42 | static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len); 43 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | 7 | defaultConfig { 8 | applicationId "com.worstperson.usbtether" 9 | minSdkVersion 24 10 | //noinspection EditedTargetSdkVersion,ExpiredTargetSdkVersion 11 | targetSdkVersion 35 12 | compileSdk 35 13 | versionCode 1 14 | versionName "0.1" 15 | } 16 | 17 | buildFeatures { 18 | buildConfig = true 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | } 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | 32 | gradle.projectsEvaluated { 33 | tasks.withType(JavaCompile) { 34 | options.compilerArgs << "-Xlint:-options" 35 | } 36 | } 37 | 38 | 39 | packagingOptions { 40 | jniLibs { 41 | useLegacyPackaging = true 42 | } 43 | } 44 | 45 | namespace 'com.worstperson.usbtether' 46 | } 47 | 48 | repositories { 49 | maven { url 'https://jitpack.io' } 50 | } 51 | 52 | dependencies { 53 | def libsuVersion = '6.0.0' 54 | 55 | implementation "com.github.topjohnwu.libsu:core:${libsuVersion}" 56 | implementation 'androidx.core:core:1.15.0' 57 | implementation 'com.google.android.material:material:1.12.0' 58 | implementation 'androidx.constraintlayout:constraintlayout:2.2.1' 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /native/usbgadget/lineage16_system_core_usbd/usbd.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #define LOG_TAG "usbgadget" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | using android::hardware::usb::gadget::V1_0::IUsbGadget; 27 | using android::hardware::Return; 28 | 29 | int main(int argc, char** argv) { 30 | if (argc > 1) { 31 | android::sp gadget = IUsbGadget::getService(); 32 | Return ret; 33 | 34 | if (gadget != nullptr) { 35 | LOG(INFO) << "Usb HAL found."; 36 | uint64_t function = std::strtoull(argv[1], NULL, 0); 37 | std::string message = "Setting USB mode to "; 38 | LOG(INFO) << message.append(argv[1]); 39 | ret = gadget->setCurrentUsbFunctions(function, nullptr, 0); 40 | if (ret.isOk()) exit(0); 41 | LOG(ERROR) << "Error while invoking usb hal"; 42 | } else { 43 | LOG(INFO) << "Usb HAL not found"; 44 | } 45 | } else { 46 | LOG(INFO) << "Usb config argument missing"; 47 | } 48 | exit(1); 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/worstperson/usbtether/BootUpReceiver.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 worstperson 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.worstperson.usbtether; 18 | 19 | import android.content.BroadcastReceiver; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.SharedPreferences; 23 | import android.os.Build; 24 | import android.util.Log; 25 | 26 | public class BootUpReceiver extends BroadcastReceiver { 27 | 28 | @Override 29 | public void onReceive(Context context, Intent intent) { 30 | String action; 31 | if ((action = intent.getAction()) != null && action.equals(Intent.ACTION_BOOT_COMPLETED)) { 32 | Log.i("USBTether", "bootupreceiver onrecieve called..."); 33 | SharedPreferences sharedPref = context.getSharedPreferences("Settings", Context.MODE_PRIVATE); 34 | boolean serviceEnabled = sharedPref.getBoolean("serviceEnabled", false); 35 | if (serviceEnabled && !ForegroundService.isStarted) { 36 | Intent it = new Intent(context.getApplicationContext(), ForegroundService.class); 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 38 | context.startForegroundService(it); 39 | } else { 40 | context.startService(it); 41 | } 42 | ForegroundService.isStarted = true; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /native/zapret/libnetfilter_queue-1.0.5.patch: -------------------------------------------------------------------------------- 1 | diff -ru org/libnetfilter_queue-1.0.5/src/extra/ipv4.c fix/libnetfilter_queue-1.0.5/src/extra/ipv4.c 2 | --- org/libnetfilter_queue-1.0.5/src/extra/ipv4.c 2020-06-12 04:39:24.759841569 -0500 3 | +++ fix/libnetfilter_queue-1.0.5/src/extra/ipv4.c 2024-02-17 16:38:05.012918055 -0600 4 | @@ -12,7 +12,8 @@ 5 | #include 6 | #include 7 | #include 8 | -#include 9 | +//#include 10 | +#include 11 | 12 | #include 13 | #include 14 | diff -ru org/libnetfilter_queue-1.0.5/src/extra/pktbuff.c fix/libnetfilter_queue-1.0.5/src/extra/pktbuff.c 15 | --- org/libnetfilter_queue-1.0.5/src/extra/pktbuff.c 2020-06-12 04:39:24.759841569 -0500 16 | +++ fix/libnetfilter_queue-1.0.5/src/extra/pktbuff.c 2024-02-17 16:30:11.282933764 -0600 17 | @@ -14,7 +14,8 @@ 18 | #include /* for memcpy */ 19 | #include 20 | 21 | -#include 22 | +//#include 23 | +#include 24 | #include 25 | #include 26 | 27 | diff -ru org/libnetfilter_queue-1.0.5/src/extra/tcp.c fix/libnetfilter_queue-1.0.5/src/extra/tcp.c 28 | --- org/libnetfilter_queue-1.0.5/src/extra/tcp.c 2020-06-12 04:39:24.759841569 -0500 29 | +++ fix/libnetfilter_queue-1.0.5/src/extra/tcp.c 2024-02-17 16:29:38.356268191 -0600 30 | @@ -139,10 +139,10 @@ 31 | * (union is compatible to any of its members) 32 | * This means this part of the code is -fstrict-aliasing safe now. 33 | */ 34 | -union tcp_word_hdr { 35 | +/*union tcp_word_hdr { 36 | struct tcphdr hdr; 37 | uint32_t words[5]; 38 | -}; 39 | +};*/ 40 | 41 | #define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words[3]) 42 | 43 | diff -ru org/libnetfilter_queue-1.0.5/src/nlmsg.c fix/libnetfilter_queue-1.0.5/src/nlmsg.c 44 | --- org/libnetfilter_queue-1.0.5/src/nlmsg.c 2020-06-12 04:39:24.763841571 -0500 45 | +++ fix/libnetfilter_queue-1.0.5/src/nlmsg.c 2024-02-17 16:29:05.749602598 -0600 46 | @@ -21,7 +21,7 @@ 47 | 48 | #include 49 | 50 | -#include 51 | +//#include 52 | 53 | #include "internal.h" 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /native/curl/make_curl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | options=("arm7a" "aarch64") 4 | select opt in "${options[@]}" 5 | do 6 | case $opt in 7 | "arm7a") 8 | export HOST=armv7a-linux-androideabi 9 | export SYSROOT=$PWD/sysroot_arm7a 10 | break 11 | ;; 12 | "aarch64") 13 | export HOST=aarch64-linux-android 14 | export SYSROOT=$PWD/sysroot_aarch64 15 | break 16 | ;; 17 | *) echo "invalid option $REPLY";; 18 | esac 19 | done 20 | 21 | export ANDROID_NDK_HOME=~/android-tools/android-ndk-r26d 22 | export HOST_TAG=linux-x86_64 23 | export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$HOST_TAG 24 | export AR=$TOOLCHAIN/bin/llvm-ar 25 | export AS=$TOOLCHAIN/bin/llvm-as 26 | export CC=$TOOLCHAIN/bin/${HOST}24-clang 27 | export CXX=$TOOLCHAIN/bin/${HOST}24-clang++ 28 | export LD=$TOOLCHAIN/bin/ld 29 | export RANLIB=$TOOLCHAIN/bin/llvm-ranlib 30 | export STRIP=$TOOLCHAIN/bin/llvm-strip 31 | 32 | rm -R $SYSROOT 33 | export CFLAGS+='' 34 | export CPPFLAGS+=' -I'${SYSROOT}'/include/' 35 | export LDFLAGS+=' -L'${SYSROOT}'/lib/' 36 | 37 | rm -R libiconv-1.17 38 | wget -nc https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz 39 | tar -xf libiconv-1.17.tar.gz 40 | cd libiconv-1.17 41 | ./configure --prefix=$SYSROOT --host=$HOST --with-pic --enable-shared=no 42 | make 43 | make install 44 | cd .. 45 | 46 | rm -R libunistring-1.2 47 | wget -nc https://ftp.gnu.org/gnu/libunistring/libunistring-1.2.tar.gz 48 | tar -xf libunistring-1.2.tar.gz 49 | cd libunistring-1.2 50 | ./configure --prefix=$SYSROOT --host=$HOST --with-pic --enable-shared=no 51 | make 52 | make install 53 | cd .. 54 | 55 | rm -R libidn2-2.3.7 56 | wget -nc https://ftp.gnu.org/gnu/libidn/libidn2-2.3.7.tar.gz 57 | tar -xf libidn2-2.3.7.tar.gz 58 | cd libidn2-2.3.7 59 | ./configure --prefix=$SYSROOT --host=$HOST --with-pic --enable-shared=no 60 | make 61 | make install 62 | cd .. 63 | 64 | rm -R libpsl-0.21.5 65 | wget -nc https://github.com/rockdaboot/libpsl/releases/download/0.21.5/libpsl-0.21.5.tar.gz 66 | tar -xf libpsl-0.21.5.tar.gz 67 | cd libpsl-0.21.5 68 | ./configure --prefix=$SYSROOT --host=$HOST --with-pic --enable-shared=no 69 | make 70 | make install 71 | cd .. 72 | 73 | rm -R curl-8.6.0 74 | wget -nc https://curl.se/download/curl-8.6.0.tar.gz 75 | tar -xf curl-8.6.0.tar.gz 76 | cd curl-8.6.0 77 | export LDFLAGS+=' -l:libiconv.a -l:libunistring.a -l:libidn2.a -l:libpsl.a' 78 | make clean 79 | ./configure --host=$HOST --prefix=$SYSROOT --with-pic --disable-shared --without-ssl 80 | make 81 | make install-strip 82 | cd .. 83 | mv ${SYSROOT}/bin/curl curl_${HOST} 84 | -------------------------------------------------------------------------------- /native/zapret/make_zapret: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | options=("arm7a" "aarch64") 4 | select opt in "${options[@]}" 5 | do 6 | case $opt in 7 | "arm7a") 8 | export HOST=armv7a-linux-androideabi 9 | export SYSROOT=$PWD/sysroot_arm7a 10 | break 11 | ;; 12 | "aarch64") 13 | export HOST=aarch64-linux-android 14 | export SYSROOT=$PWD/sysroot_aarch64 15 | break 16 | ;; 17 | *) echo "invalid option $REPLY";; 18 | esac 19 | done 20 | 21 | export ANDROID_NDK_HOME=~/android-tools/android-ndk-r26d 22 | export HOST_TAG=linux-x86_64 23 | export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$HOST_TAG 24 | export AR=$TOOLCHAIN/bin/llvm-ar 25 | export AS=$TOOLCHAIN/bin/llvm-as 26 | export CC=$TOOLCHAIN/bin/${HOST}24-clang 27 | export CXX=$TOOLCHAIN/bin/${HOST}24-clang++ 28 | export LD=$TOOLCHAIN/bin/ld 29 | export RANLIB=$TOOLCHAIN/bin/llvm-ranlib 30 | export STRIP=$TOOLCHAIN/bin/llvm-strip 31 | 32 | rm -R $SYSROOT 33 | export CFLAGS+='' 34 | export CPPFLAGS+=' -I'${SYSROOT}'/include/' 35 | export LDFLAGS+=' -L'${SYSROOT}'/lib/' 36 | 37 | rm -R libmnl-1.0.5 38 | wget -nc https://www.netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 39 | tar -xf libmnl-1.0.5.tar.bz2 40 | cd libmnl-1.0.5 41 | ./configure --prefix=$SYSROOT --host=$HOST --with-pic --enable-shared=no 42 | make 43 | make install 44 | cd .. 45 | 46 | rm -R libnfnetlink-1.0.2 47 | wget -nc https://www.netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.2.tar.bz2 48 | tar -xf libnfnetlink-1.0.2.tar.bz2 49 | cd libnfnetlink-1.0.2 50 | ./configure --prefix=$SYSROOT --host=$HOST --with-pic --enable-shared=no 51 | make 52 | make install 53 | cd .. 54 | 55 | rm -R libnetfilter_queue-1.0.5 56 | wget -nc https://www.netfilter.org/projects/libnetfilter_queue/files/libnetfilter_queue-1.0.5.tar.bz2 57 | tar -xf libnetfilter_queue-1.0.5.tar.bz2 58 | patch -p1 < libnetfilter_queue-1.0.5.patch 59 | cd libnetfilter_queue-1.0.5 60 | export PKG_CONFIG_PATH=${SYSROOT}'/lib/pkgconfig/' 61 | ./configure --prefix=$SYSROOT --host=$HOST --with-pic --enable-shared=no 62 | make 63 | make install 64 | mkdir $SYSROOT/include/src 65 | cp config.h $SYSROOT/include/src/ 66 | cp src/internal.h $SYSROOT/include/src/ 67 | cd .. 68 | 69 | export CHOST=aarch64-linux-android 70 | export CFLAGS+=' -fPIC' 71 | 72 | rm -R zlib-1.3.1 73 | wget -nc https://www.zlib.net/zlib-1.3.1.tar.gz 74 | tar -xf zlib-1.3.1.tar.gz 75 | cd zlib-1.3.1 76 | ./configure --prefix=$SYSROOT --static 77 | make 78 | make install 79 | cd .. 80 | 81 | export LDFLAGS+=' -I'${SYSROOT}'/include/' 82 | 83 | git clone --depth 1 --branch v70.5 https://github.com/bol-van/zapret.git 84 | patch -N -p1 < nfqws.patch 85 | cd zapret/nfq/ 86 | make clean 87 | make 88 | $STRIP nfqws 89 | mv nfqws ../../nfqws_$HOST 90 | cd ../tpws/ 91 | make clean 92 | make android 93 | $STRIP tpws 94 | mv tpws ../../tpws_$HOST 95 | -------------------------------------------------------------------------------- /native/zapret/nfqws.patch: -------------------------------------------------------------------------------- 1 | diff -ruN org/zapret/nfq/desync.c patch/zapret/nfq/desync.c 2 | --- org/zapret/nfq/desync.c 2025-04-01 01:01:51.053813905 -0500 3 | +++ patch/zapret/nfq/desync.c 2025-04-01 01:14:12.840939999 -0500 4 | @@ -2424,6 +2424,18 @@ 5 | if (!!dis.ip != !!dis.ip6) 6 | { 7 | packet_debug(replay, &dis); 8 | + if (params.force_ttl) 9 | + { 10 | + if (dis.ip) 11 | + { 12 | + dis.ip->ip_ttl = params.ttl; 13 | + ip4_fix_checksum(dis.ip); 14 | + } 15 | + else 16 | + { 17 | + dis.ip6->ip6_ctlun.ip6_un1.ip6_un1_hlim = params.ttl; 18 | + } 19 | + } 20 | switch(dis.proto) 21 | { 22 | case IPPROTO_TCP: 23 | @@ -2442,6 +2454,10 @@ 24 | break; 25 | } 26 | *len_pkt = dis.len_pkt; 27 | + if (params.force_ttl && (verdict & VERDICT_MASK) == VERDICT_PASS) 28 | + { 29 | + verdict = VERDICT_MODIFY; 30 | + } 31 | } 32 | return verdict; 33 | } 34 | diff -ruN org/zapret/nfq/nfqws.c patch/zapret/nfq/nfqws.c 35 | --- org/zapret/nfq/nfqws.c 2025-04-01 01:01:51.054813901 -0500 36 | +++ patch/zapret/nfq/nfqws.c 2025-04-01 01:11:59.463203585 -0500 37 | @@ -1300,6 +1300,8 @@ 38 | #ifdef __linux__ 39 | " --bind-fix4\t\t\t\t\t; apply outgoing interface selection fix for generated ipv4 packets\n" 40 | " --bind-fix6\t\t\t\t\t; apply outgoing interface selection fix for generated ipv6 packets\n" 41 | + " --force-ttl\t\t\t\t\t; overwrite the ttl/hl for traffic passing through nfqws\n" 42 | + " --ttl=\t\t\t\t\t; ttl/hl applied by --force-ttl, default is 64\n" 43 | #endif 44 | " --ctrack-timeouts=S:E:F[:U]\t\t\t; internal conntrack timeouts for TCP SYN, ESTABLISHED, FIN stages, UDP timeout. default %u:%u:%u:%u\n" 45 | #ifdef __CYGWIN__ 46 | @@ -1516,6 +1518,7 @@ 47 | params.ctrack_t_est = CTRACK_T_EST; 48 | params.ctrack_t_fin = CTRACK_T_FIN; 49 | params.ctrack_t_udp = CTRACK_T_UDP; 50 | + params.ttl = 64; 51 | 52 | LIST_INIT(¶ms.hostlists); 53 | LIST_INIT(¶ms.ipsets); 54 | @@ -1631,6 +1634,8 @@ 55 | #ifdef __linux__ 56 | {"bind-fix4",no_argument,0,0}, // optidx=70 57 | {"bind-fix6",no_argument,0,0}, // optidx=71 58 | + {"force-ttl",no_argument,0,0}, // optidx=72 59 | + {"ttl",required_argument,0,0}, // optidx=73 60 | #elif defined(__CYGWIN__) 61 | {"wf-iface",required_argument,0,0}, // optidx=70 62 | {"wf-l3",required_argument,0,0}, // optidx=71 63 | @@ -2320,6 +2325,17 @@ 64 | case 71: /* bind-fix6 */ 65 | params.bind_fix6 = true; 66 | break; 67 | + case 72: /* force_ttl */ 68 | + params.force_ttl = true; 69 | + break; 70 | + case 73: /* ttl */ 71 | + params.ttl = (uint8_t)atoi(optarg); 72 | + if (params.ttl < 1 || params.ttl > 255) 73 | + { 74 | + fprintf(stderr, "packet ttl is not valid\n"); 75 | + exit_clean(1); 76 | + } 77 | + break; 78 | #elif defined(__CYGWIN__) 79 | case 70: /* wf-iface */ 80 | if (!sscanf(optarg,"%u.%u",&IfIdx,&SubIfIdx)) 81 | diff -ruN org/zapret/nfq/params.h patch/zapret/nfq/params.h 82 | --- org/zapret/nfq/params.h 2025-04-01 01:01:51.055813897 -0500 83 | +++ patch/zapret/nfq/params.h 2025-04-01 01:12:42.390116542 -0500 84 | @@ -161,6 +161,8 @@ 85 | 86 | unsigned int ctrack_t_syn, ctrack_t_est, ctrack_t_fin, ctrack_t_udp; 87 | t_conntrack conntrack; 88 | + char force_ttl; 89 | + uint8_t ttl; 90 | }; 91 | 92 | extern struct params_s params; 93 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | org.gradle.wrapper.GradleWrapperMain \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USB Tether 2 | 3 | USB Tether is an application to automatically manage and maintain a tethered connection. It is capable of automatically switching networks and keeping a tether operation going despite USB and connectivity events. This has many uses, but was mainly designed for tethering directly to a router to serve a larger network. It is highly recommended to use charge control apps like [Battery Charge Limit](https://play.google.com/store/apps/details?id=com.slash.batterychargelimit&hl=en_US&gl=US) or [Advanced Charging Controller](https://forum.xda-developers.com/t/advanced-charging-controller-acc.3668427/) for dedicated modems. 4 | 5 | - Lets you tether to any interface or lock on to your phone's primary internet source 6 | - Supports split CLAT "v4-" interfaces in Auto mode 7 | - Does not require APN modification to avoid classification 8 | - Built-in dnsmasq with support for DHCP, DHCP6, and SLAAC 9 | - Ability to set the IPv4 address (and /24 subnet) 10 | - IPv6 NAT supporting Masquerading, SNAT, and TPROXY 11 | - IPv6 Prefix selection to set IPv6 priority 12 | - TTL/HL modification to make packets look like they came from your device 13 | - DPI Circumvention for bypassing traffic throttling 14 | - VPN Autostart and watchdog support to ensure your VPN stays connected 15 | - IP-based bandwidth control to help manage larger networks 16 | - Cellular watchdog to detect and fix broken cellular connections 17 | - Watchdog for restarting crashed services 18 | 19 | ## Kernel Prerequisites: 20 | 21 | Some features require your kernel to be compiled with specific options to be usable. This is up to you or your kernel maintainer to enable them at compile time. Distros Like Lineage OS may have none of these enabled by default, requiring you to obtain the sources and recompile the kernel yourself. You can either rebuild and flash the entire kernel or just build the modules and insmod them on startup. 22 | 23 | #### For Modify TTL/HL: 24 | 25 | - CONFIG_NETFILTER_XT_TARGET_HL - modify TTL and HL hoplimit 26 | 27 | #### For Modify TTL/HL via NFQUEUE: 28 | 29 | - CONFIG_NETFILTER_XT_TARGET_NFQUEUE 30 | 31 | #### For IPv6 TPROXY: 32 | 33 | - NETFILTER_XT_TARGET_TPROXY - tproxy target support 34 | 35 | #### For IPv6 SNAT: 36 | 37 | - CONFIG_NF_NAT_IPV6 - IPv6 NAT 38 | - CONFIG_IP6_NF_NAT - IPv6 NAT IPTables 39 | 40 | #### For IPv6 Masquerading: 41 | 42 | - (The options for IPv6 SNAT) 43 | - CONFIG_IP6_NF_TARGET_MASQUERADE - IPv6 Masquerading target 44 | 45 | For kernels 5.2 and later, build this instead: 46 | 47 | - CONFIG_NETFILTER_XT_TARGET_MASQUERADE - IPv6 Masquerading target 48 | 49 | Note: If your target kernel uses CONFIG_MODULE_SIG_FORCE, learn how to disable it [here](https://forum.xda-developers.com/t/guide-kernel-mod-patching-out-config_module_sig_force-on-stock-kernels.4278981/). 50 | 51 | ## Q&A: 52 | 53 | #### Why does USB Tether require root? 54 | 55 | We need to be able to set network configuration, configure the Linux firewall to manage packets, and bind to ports for DNSMasq. USB Tether sets everything up manually and needs escalated privileges to do so. 56 | 57 | #### Can you do the same thing with wireless Hotspotting? 58 | 59 | Yes, but it's not been implemented yet. [VPN Hotspot](https://github.com/Mygod/VPNHotspot) is a great option, though it does not support IPv6 and should always be used with a local or remote VPN. 60 | 61 | #### What if I have a locked down phone? 62 | 63 | There are a bunch of paid apps that tunnel data through adb, but they all have their downsides. Running a socks server through a tether if allowed by your carrier or alternatively tunneled through adb to a router would work well. I recommended running [hev-socks5-server](https://github.com/heiher/hev-socks5-server) on your phone and installing [openwrt-hev-socks5-tproxy](openwrt-hev-socks5-tproxy) on your router. It will take a bit of work to set up, but it can be used even on very locked-down devices. 64 | 65 | #### What if I can't build the kernel modules mentioned? 66 | 67 | USB Tether can still be used to tether IPv4 traffic normally. TTL/HL modification can be applied if your device supports NFQUEUE and some IPv6 support can be enabled if your device supports TPROXY. If NFQUEUE is not available you can use a local VPN like ADGuard, manually set the TTL on your device(s), [patch your kernel/bpf modules](https://xdaforums.com/t/magisk-tethering-ttl-hl-patcher.4623067/), or use these firewall rules to set the TTL/HL on the bridged traffic passing through your router: 68 | 69 | Install required packages 70 | 71 | opkg update 72 | opkg install iptables-mod-ipopt 73 | opkg install iptables-mod-physdev 74 | 75 | System -> Startup -> Local startup 76 | 77 | sysctl -w net.bridge.bridge-nf-call-arptables=1 78 | sysctl -w net.bridge.bridge-nf-call-iptables=1 79 | sysctl -w net.bridge.bridge-nf-call-ip6tables=1 80 | 81 | Network -> Firewall -> Custom Rules 82 | 83 | iptables -t mangle -I POSTROUTING -m physdev --physdev-out usb0 -j TTL --ttl-set 65 84 | ip6tables -t mangle -I POSTROUTING -m physdev --physdev-out usb0 -m hl ! --hl-eq 255 -j HL --hl-set 65 85 | 86 | The ip6tables rule is generally not needed, but included just in case. Can be useful for normal tethering without this app. 87 | 88 | #### Why does IPv6 require NAT support? 89 | 90 | Standard IPv6 tethering has every device addressed individually on the mobile network and is trivially detectable. Using NAT allows us to tether to any interface, modify traffic as it passes, and hide network topology. Proxying traffic through TPROXY is supported on many devices and can offer a NAT-like experience, but it only supports TCP/UDP traffic. 91 | 92 | #### Can carriers detect this? 93 | 94 | Hiding devices behind NATs and setting the TTL/HL goes a long way towards avoiding being easily flagged, but does nothing to hide packet structure or your activity. A remote VPN is highly recommended and has the added benefit avoiding throttling and peering issues, while at the same time, long-running VPN tunnels are a prime target for throttling themselves. 95 | 96 | ## Router Setup: 97 | 98 | These setups typically use the phone as the router bridged to a consumer router configured as a wireless AP to avoid double NAT. Try to avoid any Broadcom MIPS routers as their USB support is very poor. Something like a RPi should be used as a bridge device when tethering with an otherwise unsuitable router. OpenWRT devices are highly recommended, Qualcomm IPQ Linksys routers go for cheap and are well suited to this task. OpenWRT makes it possible to run the DHCP server on the router and offers many other powerful resources for managing your network. 99 | 100 | You must also be prepaired for testing and problem solving software/hardware quirks. As an example, on my EA6350v3 I had to: 101 | 102 | - Lock CPU frequency scaling to workaround a IPQ kernel bug 103 | - Replace wireless calibration data to improve preformance 104 | - Write [a script](https://forum.openwrt.org/t/optimized-build-for-ipq40xx-devices/44125/341) to reset USB when it fails(IPQ kernel bug) 105 | - Mirror configuration on both partitions to servive multiple resets 106 | 107 | #### Example OpenWRT Setup: 108 | 109 | Requires kernel RNDIS support: 110 | 111 | opkg update 112 | opkg install kmod-usb-net-rndis 113 | 114 | Settings as of 19.07 (router handles DHCP and uses ULA prefix): 115 | 116 | Network -> Interfaces -> LAN 117 | -> General Settings 118 | Protocol: Static Address 119 | IPv4 address: 192.168.42.1 120 | IPv4 netmask: 255.255.255.0 121 | IPv4 gateway: 192.168.42.129 122 | IPv6 assignment length: disabled 123 | IPv6 address: fd00::2/64 124 | IPv6 gateway: fd00::1 125 | IPv6 routed prefix: fd00::/64 126 | -> Physical Settings 127 | Interface: add usb0 128 | -> DHCP Server 129 | --> General Setup 130 | Start: 100 131 | Limit: 150 132 | -->Advanced Settings 133 | Force: enabled 134 | --> IPv6 Settings 135 | Always announce default router: enabled 136 | 137 | Settings as of 21.02 (router handles DHCP and uses ULA prefix): 138 | 139 | Network -> Interfaces -> Devices Tab 140 | -> Configure br-lan 141 | Bridge Ports: add usb0 142 | 143 | Network -> Interfaces -> LAN 144 | -> Advanced Settings 145 | IPv6 assignment length: Disabled 146 | -> General Settings 147 | Protocol: Static Address 148 | IPv4 address: 192.168.42.1 149 | IPv4 netmask: 255.255.255.0 150 | IPv4 gateway: 192.168.42.129 151 | IPv6 address: fd00::2/64 152 | IPv6 gateway: fd00::1 153 | -> DHCP Server 154 | --> General Setup 155 | Start: 10 156 | Limit: 99 157 | -->Advanced Settings 158 | Force: enabled 159 | --> IPv6 Settings 160 | RA-Service: server mode 161 | DHCPv6-Service: server mode 162 | Local IPv6 DNS server: enabled 163 | NDP-Proxy: disabled 164 | --> IPv6 RA Settings 165 | Default Router: forced 166 | Enable SLAAC: enabled 167 | 168 | Be sure to set your preferred DNS servers as appropriate: 169 | 170 | DHCP and DNS -> General Settings -> DNS forwardings 171 | 172 | 173 | **For SQM Support** 174 | 175 | Install required packages: 176 | 177 | opkg update 178 | opkg install luci-app-sqm 179 | 180 | Set the interface: 181 | 182 | Network -> SQM QOS -> Basic Settings 183 | -> Interface Name: usb0 184 | 185 | And create a script as /etc/hotplug.d/usb/11-sqm 186 | 187 | cat << "EOF" > /etc/hotplug.d/usb/11-sqm 188 | [ "${ACTION}" = "add" ] && /etc/init.d/sqm restart 189 | EOF 190 | 191 | This hotplug script is required or SQM will not be applied when the device is replugged. Setting Download and Uploaded speed to half the link's capacity has the best results for me in managing bufferbloat on mobile networks, but ymmv. 192 | 193 | ## TODO: 194 | 195 | - **Static Assignments** - It would be nice if we could reserve addresses for specific devices 196 | - **VPN Bypass** - Make part of the private range route outgoing traffic to a secondary interface 197 | 198 | ## DEPENDENCIES: 199 | 200 | - dnsmasq - https://thekelleys.org.uk/dnsmasq/doc.html 201 | - nfqws and tpws - https://github.com/bol-van/zapret 202 | - hev-socks5-server - https://github.com/heiher/hev-socks5-server 203 | - hev-socks5-tproxy - https://github.com/heiher/hev-socks5-tproxy 204 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 24 | 33 | 34 | 35 | 42 | 43 | 47 | 48 | 51 | 52 | 61 | 67 | 72 | 73 | 74 | 84 | 90 | 96 | 97 | 98 | 108 | 114 | 119 | 120 | 121 | 129 | 136 | 146 | 147 | 148 | 158 | 164 | 170 | 171 | 172 | 182 | 188 | 194 | 195 | 196 | 206 | 212 | 217 | 218 | 219 | 229 | 235 | 240 | 241 | 242 | 252 | 258 | 264 | 265 | 266 | 274 | 281 | 291 | 292 | 293 | 301 | 308 | 318 | 319 | 320 | 330 | 336 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | -------------------------------------------------------------------------------- /app/src/main/java/com/worstperson/usbtether/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 worstperson 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.worstperson.usbtether; 18 | 19 | import androidx.appcompat.app.AppCompatActivity; 20 | import androidx.core.os.HandlerCompat; 21 | 22 | import android.Manifest; 23 | import android.annotation.SuppressLint; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.content.SharedPreferences; 27 | import android.content.pm.PackageManager; 28 | import android.net.ConnectivityManager; 29 | import android.net.LinkProperties; 30 | import android.net.Network; 31 | import android.net.Uri; 32 | import android.os.Build; 33 | import android.os.Bundle; 34 | import android.os.Handler; 35 | import android.os.Looper; 36 | import android.os.PowerManager; 37 | import android.provider.Settings; 38 | import android.text.Editable; 39 | import android.text.TextWatcher; 40 | import android.view.KeyEvent; 41 | import android.view.View; 42 | import android.view.inputmethod.EditorInfo; 43 | import android.widget.AdapterView; 44 | import android.widget.ArrayAdapter; 45 | import android.widget.CompoundButton; 46 | import android.widget.EditText; 47 | import android.widget.LinearLayout; 48 | import android.widget.Spinner; 49 | import android.widget.Switch; 50 | import android.widget.TextView; 51 | import com.google.android.material.snackbar.Snackbar; 52 | 53 | import java.net.Inet4Address; 54 | import java.net.InetAddress; 55 | import java.net.NetworkInterface; 56 | import java.net.SocketException; 57 | import java.util.ArrayList; 58 | import java.util.Collections; 59 | import java.util.Enumeration; 60 | import java.util.regex.Pattern; 61 | 62 | public class MainActivity extends AppCompatActivity { 63 | 64 | PowerManager powerManager; 65 | 66 | void setInterfaceSpinner(String upstreamInterface, Spinner interface_spinner) { 67 | ArrayList arraySpinner = new ArrayList<>(); 68 | arraySpinner.add(upstreamInterface); 69 | if (!upstreamInterface.equals("Auto")) { 70 | arraySpinner.add("Auto"); 71 | } 72 | Enumeration nets; 73 | try { 74 | nets = NetworkInterface.getNetworkInterfaces(); 75 | for (NetworkInterface networkInterface : Collections.list(nets)){ 76 | if (networkInterface.isUp() && !networkInterface.isLoopback() && !networkInterface.getName().equals("rndis0")) { 77 | for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())){ 78 | if (inetAddress instanceof Inet4Address && !arraySpinner.contains(networkInterface.getName())) { 79 | arraySpinner.add(networkInterface.getName()); 80 | } 81 | } 82 | } 83 | } 84 | } catch (SocketException e) { 85 | e.printStackTrace(); 86 | } 87 | ArrayAdapter adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, arraySpinner); 88 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 89 | interface_spinner.setAdapter(adapter); 90 | } 91 | 92 | @SuppressLint("SetTextI18n") 93 | void setNetTextview(TextView net_textview) { 94 | String name = "UNKNOWN"; 95 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 96 | Network activeNetwork; 97 | if (connectivityManager != null && (activeNetwork = connectivityManager.getActiveNetwork()) != null) { 98 | name = ""; 99 | // FIXME - uses deprecated function getAllNetworks() 100 | /*NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork); 101 | if (networkCapabilities != null && !networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)) { 102 | name = " (UNKNOWN)"; 103 | boolean usesCellular = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR); 104 | Network[] networks = connectivityManager.getAllNetworks(); 105 | for (Network network : networks) { 106 | NetworkCapabilities upstreamCapabilities = connectivityManager.getNetworkCapabilities(network); 107 | if (upstreamCapabilities != null && upstreamCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) && 108 | upstreamCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && 109 | (upstreamCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == usesCellular)) { 110 | LinkProperties linkProperties = connectivityManager.getLinkProperties(network); 111 | String interfaceName; 112 | if (linkProperties != null && (interfaceName = linkProperties.getInterfaceName()) != null) { 113 | name = " (" + interfaceName + ")"; 114 | break; 115 | } 116 | } 117 | } 118 | }*/ 119 | LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNetwork); 120 | String interfaceName; 121 | if (linkProperties != null && (interfaceName = linkProperties.getInterfaceName()) != null) { 122 | name = interfaceName + name; 123 | } 124 | } 125 | net_textview.setText(name); 126 | } 127 | 128 | final Handler handler = new Handler(Looper.getMainLooper()); 129 | 130 | Runnable save_ipv4_text = new Runnable() { 131 | @Override 132 | public void run() { 133 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 134 | EditText ipv4_text = findViewById(R.id.ipv4_text); 135 | String contents = ipv4_text.getText().toString(); 136 | Pattern sPattern = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); 137 | if (sPattern.matcher(contents).matches()) { 138 | SharedPreferences.Editor edit = sharedPref.edit(); 139 | edit.putString("ipv4Addr", contents); 140 | edit.apply(); 141 | } 142 | } 143 | }; 144 | 145 | Runnable save_wg_text = new Runnable() { 146 | @Override 147 | public void run() { 148 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 149 | EditText wg_text = findViewById(R.id.wg_text); 150 | Spinner interface_spinner = findViewById(R.id.interface_spinner); 151 | int autostartVPN = sharedPref.getInt("autostartVPN", 0); 152 | SharedPreferences.Editor edit = sharedPref.edit(); 153 | edit.putString("wireguardProfile", String.valueOf(wg_text.getText())); 154 | if (autostartVPN == 2) { 155 | String upstreamInterface = String.valueOf(wg_text.getText()); 156 | MainActivity.this.setInterfaceSpinner(upstreamInterface, interface_spinner); 157 | edit.putString("upstreamInterface", upstreamInterface); 158 | } 159 | edit.apply(); 160 | } 161 | }; 162 | 163 | Runnable save_bandwidth_text = new Runnable() { 164 | @Override 165 | public void run() { 166 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 167 | EditText bandwidth_text = findViewById(R.id.bandwidth_text); 168 | Pattern sPattern = Pattern.compile("^\\d+$"); 169 | if (sPattern.matcher(String.valueOf(bandwidth_text.getText())).matches()) { 170 | SharedPreferences.Editor edit = sharedPref.edit(); 171 | edit.putString("clientBandwidth", String.valueOf(bandwidth_text.getText())); 172 | edit.apply(); 173 | } 174 | } 175 | }; 176 | 177 | Runnable start_service = new Runnable() { 178 | @Override 179 | public void run() { 180 | if (HandlerCompat.hasCallbacks(handler, save_ipv4_text) || HandlerCompat.hasCallbacks(handler, save_wg_text) || HandlerCompat.hasCallbacks(handler, save_bandwidth_text)) { 181 | if (HandlerCompat.hasCallbacks(handler, start_service)) { 182 | handler.removeCallbacks(start_service); 183 | } 184 | handler.postDelayed(start_service, 1000); 185 | } else { 186 | Intent it = new Intent(MainActivity.this, ForegroundService.class); 187 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 188 | MainActivity.this.startForegroundService(it); 189 | } else { 190 | MainActivity.this.startService(it); 191 | } 192 | } 193 | } 194 | }; 195 | 196 | @SuppressLint({"UseSwitchCompatOrMaterialCode", "BatteryLife", "WrongConstant"}) 197 | @Override 198 | protected void onCreate(Bundle savedInstanceState) { 199 | super.onCreate(savedInstanceState); 200 | setContentView(R.layout.activity_main); 201 | 202 | View view = findViewById(android.R.id.content); 203 | 204 | if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || 205 | checkSelfPermission("com.wireguard.android.permission.CONTROL_TUNNELS") != PackageManager.PERMISSION_GRANTED) { 206 | String[] PERMISSIONS; 207 | if (Build.VERSION.SDK_INT >= 33) { 208 | PERMISSIONS = new String[]{ 209 | Manifest.permission.READ_EXTERNAL_STORAGE, 210 | "com.wireguard.android.permission.CONTROL_TUNNELS", 211 | Manifest.permission.POST_NOTIFICATIONS 212 | }; 213 | } else { 214 | PERMISSIONS = new String[]{ 215 | Manifest.permission.READ_EXTERNAL_STORAGE, 216 | "com.wireguard.android.permission.CONTROL_TUNNELS" 217 | }; 218 | } 219 | requestPermissions(PERMISSIONS, 1); 220 | } 221 | 222 | powerManager = (PowerManager) getSystemService(POWER_SERVICE); 223 | 224 | if (powerManager != null && !powerManager.isIgnoringBatteryOptimizations(getPackageName())) { 225 | Snackbar.make(view, "IGNORE_BATTERY_OPTIMIZATIONS", Snackbar.LENGTH_INDEFINITE).setAction( 226 | "Grant", new View.OnClickListener() { 227 | @Override 228 | public void onClick(View view1) { 229 | MainActivity.this.startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, 230 | Uri.parse("package:" + MainActivity.this.getPackageName()))); 231 | } 232 | }).show(); 233 | } 234 | 235 | TextView net_textview = findViewById(R.id.net_textview); 236 | Switch service_switch = findViewById(R.id.service_switch); 237 | Switch dnsmasq_switch = findViewById(R.id.dnsmasq_switch); 238 | Switch ttl_switch = findViewById(R.id.ttl_switch); 239 | Switch dpi_switch = findViewById(R.id.dpi_switch); 240 | Switch cell_switch = findViewById(R.id.cell_switch); 241 | Spinner vpn_spinner = findViewById(R.id.vpn_spinner); 242 | Spinner interface_spinner = findViewById(R.id.interface_spinner); 243 | Spinner nat_spinner = findViewById(R.id.nat_spinner); 244 | Spinner prefix_spinner = findViewById(R.id.prefix_spinner); 245 | EditText ipv4_text = findViewById(R.id.ipv4_text); 246 | EditText wg_text = findViewById(R.id.wg_text); 247 | EditText bandwidth_text = findViewById(R.id.bandwidth_text); 248 | LinearLayout prefix_layout = findViewById(R.id.prefix_layout); 249 | LinearLayout wgp_layout = findViewById(R.id.wgp_layout); 250 | 251 | setNetTextview(net_textview); 252 | 253 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 254 | boolean serviceEnabled = sharedPref.getBoolean("serviceEnabled", false); 255 | boolean dnsmasq = sharedPref.getBoolean("dnsmasq", true); 256 | boolean mangleTTL = sharedPref.getBoolean("mangleTTL", true); 257 | String ipv6TYPE = sharedPref.getString("ipv6TYPE", "None"); 258 | boolean ipv6Default = sharedPref.getBoolean("ipv6Default", false); 259 | boolean dpiCircumvention = sharedPref.getBoolean("dpiCircumvention", false); 260 | int autostartVPN = sharedPref.getInt("autostartVPN", 0); 261 | String upstreamInterface = sharedPref.getString("upstreamInterface", "Auto"); 262 | String ipv4Addr = sharedPref.getString("ipv4Addr", "192.168.42.129"); 263 | String wireguardProfile = sharedPref.getString("wireguardProfile", "wgcf-profile"); 264 | String clientBandwidth = sharedPref.getString("clientBandwidth", "0"); 265 | boolean cellularWatchdog = sharedPref.getBoolean("cellularWatchdog", false); 266 | 267 | boolean hasTTL = Script.hasTTL; 268 | boolean hasNFQUEUE = Script.hasNFQUEUE; 269 | boolean hasTPROXY = Script.hasTPROXY; 270 | boolean hasTable = Script.hasTable; 271 | boolean hasSNAT = Script.hasSNAT; 272 | boolean hasMASQUERADE = Script.hasMASQUERADE; 273 | 274 | SharedPreferences.Editor edit = sharedPref.edit(); 275 | if (mangleTTL && !hasTTL && !hasNFQUEUE) { 276 | edit.putBoolean("mangleTTL", false); 277 | mangleTTL = false; 278 | } 279 | if ((ipv6TYPE.equals("TPROXY") && !hasTPROXY) || 280 | (ipv6TYPE.equals("SNAT") && (!hasTable || !hasSNAT)) || 281 | (ipv6TYPE.equals("MASQUERADE") && (!hasTable || !hasMASQUERADE))) { 282 | ipv6TYPE = "None"; 283 | edit.putString("ipv6TYPE", ipv6TYPE); 284 | } 285 | edit.apply(); 286 | 287 | service_switch.setChecked(serviceEnabled); 288 | dnsmasq_switch.setChecked(dnsmasq); 289 | ttl_switch.setChecked(mangleTTL); 290 | dpi_switch.setChecked(dpiCircumvention); 291 | cell_switch.setChecked(cellularWatchdog); 292 | 293 | ipv4_text.setText(ipv4Addr); 294 | wg_text.setText(wireguardProfile); 295 | bandwidth_text.setText(clientBandwidth); 296 | 297 | setInterfaceSpinner(upstreamInterface, interface_spinner); 298 | 299 | ArrayList arraySpinner2 = new ArrayList<>(); 300 | arraySpinner2.add("None"); 301 | if (hasTPROXY) { 302 | arraySpinner2.add("TPROXY"); 303 | } 304 | if (hasTable) { 305 | if (hasSNAT) { 306 | arraySpinner2.add("SNAT"); 307 | } 308 | if (hasMASQUERADE) { 309 | arraySpinner2.add("MASQUERADE"); 310 | } 311 | } 312 | ArrayAdapter adapter2 = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, arraySpinner2); 313 | adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 314 | nat_spinner.setAdapter(adapter2); 315 | int position = arraySpinner2.indexOf(ipv6TYPE); 316 | if (position < 0) { 317 | position = 0; 318 | } 319 | nat_spinner.setSelection(position); 320 | 321 | ArrayList arraySpinner3 = new ArrayList<>(); 322 | arraySpinner3.add("ULA (fd00::)"); // Prefer IPv4 323 | arraySpinner3.add("GUA (2001:db8::)"); // Prefer IPv6 324 | ArrayAdapter adapter3 = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, arraySpinner3); 325 | adapter3.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 326 | prefix_spinner.setAdapter(adapter3); 327 | prefix_spinner.setSelection(ipv6Default ? 1 : 0); 328 | 329 | ArrayList arraySpinner4 = new ArrayList<>(); 330 | arraySpinner4.add("disabled"); 331 | arraySpinner4.add("WireGuard"); 332 | arraySpinner4.add("WireGuard Kernel Mode"); 333 | arraySpinner4.add("Cloudflare 1.1.1.1 Warp"); 334 | ArrayAdapter adapter4 = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, arraySpinner4); 335 | adapter4.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 336 | vpn_spinner.setAdapter(adapter4); 337 | vpn_spinner.setSelection(autostartVPN); 338 | 339 | if (serviceEnabled) { 340 | dnsmasq_switch.setEnabled(false); 341 | ttl_switch.setEnabled(false); 342 | dpi_switch.setEnabled(false); 343 | cell_switch.setEnabled(false); 344 | ipv4_text.setEnabled(false); 345 | wg_text.setEnabled(false); 346 | bandwidth_text.setEnabled(false); 347 | interface_spinner.setEnabled(false); 348 | nat_spinner.setEnabled(false); 349 | prefix_spinner.setEnabled(false); 350 | vpn_spinner.setEnabled(false); 351 | } else { 352 | if (!hasTTL && !hasNFQUEUE) { 353 | ttl_switch.setEnabled(false); 354 | } 355 | if (autostartVPN > 0) { 356 | interface_spinner.setEnabled(false); 357 | } 358 | } 359 | 360 | if (ipv6TYPE.equals("None")) { 361 | prefix_layout.setVisibility(View.GONE); 362 | } 363 | 364 | if (autostartVPN != 1 && autostartVPN != 2) { 365 | wgp_layout.setVisibility(View.GONE); 366 | } 367 | 368 | service_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 369 | @Override 370 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 371 | dnsmasq_switch.setEnabled(!isChecked); 372 | if (hasTTL || hasNFQUEUE) { 373 | ttl_switch.setEnabled(!isChecked); 374 | } 375 | dpi_switch.setEnabled(!isChecked); 376 | cell_switch.setEnabled(!isChecked); 377 | ipv4_text.setEnabled(!isChecked); 378 | wg_text.setEnabled(!isChecked); 379 | bandwidth_text.setEnabled(!isChecked); 380 | if (autostartVPN == 0) { 381 | interface_spinner.setEnabled(!isChecked); 382 | } 383 | nat_spinner.setEnabled(!isChecked); 384 | prefix_spinner.setEnabled(!isChecked); 385 | vpn_spinner.setEnabled(!isChecked); 386 | SharedPreferences.Editor edit = sharedPref.edit(); 387 | edit.putBoolean("serviceEnabled", isChecked); 388 | edit.apply(); 389 | if (isChecked) { 390 | if (HandlerCompat.hasCallbacks(handler, start_service)) { 391 | handler.removeCallbacks(start_service); 392 | } 393 | handler.postDelayed(start_service, 1000); 394 | ForegroundService.isStarted = true; 395 | } else { 396 | Intent it = new Intent(MainActivity.this, ForegroundService.class); 397 | MainActivity.this.stopService(it); 398 | } 399 | } 400 | }); 401 | 402 | dnsmasq_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 403 | @Override 404 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 405 | SharedPreferences.Editor edit = sharedPref.edit(); 406 | edit.putBoolean("dnsmasq", isChecked); 407 | edit.apply(); 408 | } 409 | }); 410 | 411 | ttl_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 412 | @Override 413 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 414 | SharedPreferences.Editor edit = sharedPref.edit(); 415 | edit.putBoolean("mangleTTL", isChecked); 416 | edit.apply(); 417 | } 418 | }); 419 | 420 | dpi_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 421 | @Override 422 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 423 | SharedPreferences.Editor edit = sharedPref.edit(); 424 | edit.putBoolean("dpiCircumvention", isChecked); 425 | edit.apply(); 426 | } 427 | }); 428 | 429 | cell_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 430 | @Override 431 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 432 | SharedPreferences.Editor edit = sharedPref.edit(); 433 | edit.putBoolean("cellularWatchdog", isChecked); 434 | edit.apply(); 435 | } 436 | }); 437 | 438 | nat_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 439 | @Override 440 | public void onItemSelected(AdapterView adapterView, View view, int position, long id) { 441 | Object item = adapterView.getItemAtPosition(position); 442 | SharedPreferences.Editor edit = sharedPref.edit(); 443 | edit.putString("ipv6TYPE", item.toString()); 444 | if (item.equals("None")) { 445 | prefix_layout.setVisibility(View.GONE); 446 | } else { 447 | prefix_layout.setVisibility(View.VISIBLE); 448 | } 449 | edit.apply(); 450 | } 451 | @Override 452 | public void onNothingSelected(AdapterView adapterView) { 453 | } 454 | }); 455 | 456 | prefix_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 457 | @Override 458 | public void onItemSelected(AdapterView adapterView, View view, int position, long id) { 459 | SharedPreferences.Editor edit = sharedPref.edit(); 460 | edit.putBoolean("ipv6Default", position == 1); 461 | edit.apply(); 462 | } 463 | @Override 464 | public void onNothingSelected(AdapterView adapterView) { 465 | } 466 | }); 467 | 468 | vpn_spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 469 | @Override 470 | public void onItemSelected(AdapterView adapterView, View view, int position, long id) { 471 | // Set default value 472 | String upstreamInterface = "Auto"; 473 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 474 | String wireguardProfile = sharedPref.getString("wireguardProfile", "wgcf-profile"); 475 | SharedPreferences.Editor edit = sharedPref.edit(); 476 | edit.putInt("autostartVPN", position); 477 | switch (position) { 478 | case 0: 479 | wgp_layout.setVisibility(View.GONE); 480 | break; 481 | case 1: case 2: 482 | wgp_layout.setVisibility(View.VISIBLE); 483 | if (position == 1) { 484 | upstreamInterface = "tun"; 485 | } else { 486 | upstreamInterface = wireguardProfile; 487 | } 488 | break; 489 | default: 490 | wgp_layout.setVisibility(View.GONE); 491 | upstreamInterface = "tun"; 492 | } 493 | interface_spinner.setEnabled(position == 0); 494 | setInterfaceSpinner(upstreamInterface, interface_spinner); 495 | edit.putString("upstreamInterface", upstreamInterface); 496 | edit.apply(); 497 | } 498 | @Override 499 | public void onNothingSelected(AdapterView adapterView) { 500 | } 501 | }); 502 | 503 | ipv4_text.addTextChangedListener(new TextWatcher() { 504 | @Override 505 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 506 | 507 | @Override 508 | public void onTextChanged(CharSequence s, int start, int before, int count) {} 509 | 510 | @Override 511 | public void afterTextChanged(Editable s) { 512 | if (HandlerCompat.hasCallbacks(handler, save_ipv4_text)) { 513 | handler.removeCallbacks(save_ipv4_text); 514 | } 515 | handler.postDelayed(save_ipv4_text, 5000); 516 | } 517 | }); 518 | 519 | ipv4_text.setOnEditorActionListener(new TextView.OnEditorActionListener() { 520 | @Override 521 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 522 | if (actionId == EditorInfo.IME_ACTION_DONE) { 523 | Pattern sPattern = Pattern.compile("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"); 524 | if (sPattern.matcher(String.valueOf(ipv4_text.getText())).matches()) { 525 | if (HandlerCompat.hasCallbacks(handler, save_ipv4_text)) { 526 | handler.removeCallbacks(save_ipv4_text); 527 | } 528 | SharedPreferences.Editor edit = sharedPref.edit(); 529 | edit.putString("ipv4Addr", String.valueOf(ipv4_text.getText())); 530 | edit.apply(); 531 | return false; 532 | } 533 | } 534 | return true; 535 | } 536 | }); 537 | 538 | wg_text.addTextChangedListener(new TextWatcher() { 539 | @Override 540 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 541 | 542 | @Override 543 | public void onTextChanged(CharSequence s, int start, int before, int count) {} 544 | 545 | @Override 546 | public void afterTextChanged(Editable s) { 547 | if (HandlerCompat.hasCallbacks(handler, save_wg_text)) { 548 | handler.removeCallbacks(save_wg_text); 549 | } 550 | handler.postDelayed(save_wg_text, 5000); 551 | } 552 | }); 553 | 554 | wg_text.setOnEditorActionListener(new TextView.OnEditorActionListener() { 555 | @Override 556 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 557 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 558 | int autostartVPN = sharedPref.getInt("autostartVPN", 0); 559 | if (actionId == EditorInfo.IME_ACTION_DONE) { 560 | if (HandlerCompat.hasCallbacks(handler, save_wg_text)) { 561 | handler.removeCallbacks(save_wg_text); 562 | } 563 | SharedPreferences.Editor edit = sharedPref.edit(); 564 | edit.putString("wireguardProfile", String.valueOf(wg_text.getText())); 565 | if (autostartVPN == 2) { 566 | String upstreamInterface = String.valueOf(wg_text.getText()); 567 | MainActivity.this.setInterfaceSpinner(upstreamInterface, interface_spinner); 568 | edit.putString("upstreamInterface", upstreamInterface); 569 | } 570 | edit.apply(); 571 | return false; 572 | } 573 | return true; 574 | } 575 | }); 576 | 577 | bandwidth_text.addTextChangedListener(new TextWatcher() { 578 | @Override 579 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 580 | 581 | @Override 582 | public void onTextChanged(CharSequence s, int start, int before, int count) {} 583 | 584 | @Override 585 | public void afterTextChanged(Editable s) { 586 | if (HandlerCompat.hasCallbacks(handler, save_bandwidth_text)) { 587 | handler.removeCallbacks(save_bandwidth_text); 588 | } 589 | handler.postDelayed(save_bandwidth_text, 5000); 590 | } 591 | }); 592 | 593 | bandwidth_text.setOnEditorActionListener(new TextView.OnEditorActionListener() { 594 | @Override 595 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 596 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 597 | if (actionId == EditorInfo.IME_ACTION_DONE) { 598 | if (HandlerCompat.hasCallbacks(handler, save_bandwidth_text)) { 599 | handler.removeCallbacks(save_bandwidth_text); 600 | } 601 | Pattern sPattern = Pattern.compile("^\\d+$"); 602 | if (sPattern.matcher(String.valueOf(bandwidth_text.getText())).matches()) { 603 | SharedPreferences.Editor edit = sharedPref.edit(); 604 | edit.putString("clientBandwidth", String.valueOf(bandwidth_text.getText())); 605 | edit.apply(); 606 | return false; 607 | } 608 | } 609 | return true; 610 | } 611 | }); 612 | 613 | if (serviceEnabled && !ForegroundService.isStarted) { 614 | Intent it = new Intent(MainActivity.this, ForegroundService.class); 615 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 616 | startForegroundService(it); 617 | } else { 618 | startService(it); 619 | } 620 | ForegroundService.isStarted = true; 621 | } 622 | } 623 | 624 | @Override 625 | public void onResume(){ 626 | super.onResume(); 627 | 628 | TextView net_textview = findViewById(R.id.net_textview); 629 | Spinner interface_spinner = findViewById(R.id.interface_spinner); 630 | //EditText ipv4_text = findViewById(R.id.ipv4_text); 631 | 632 | setNetTextview(net_textview); 633 | 634 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 635 | String upstreamInterface = sharedPref.getString("upstreamInterface", "Auto"); 636 | //String ipv4Addr = sharedPref.getString("ipv4Addr", "192.168.42.129"); 637 | 638 | setInterfaceSpinner(upstreamInterface, interface_spinner); 639 | //ipv4_text.setText(ipv4Addr); 640 | } 641 | } -------------------------------------------------------------------------------- /app/src/main/java/com/worstperson/usbtether/Script.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 worstperson 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.worstperson.usbtether; 18 | 19 | import android.os.Build; 20 | import android.util.Log; 21 | import com.topjohnwu.superuser.Shell; 22 | 23 | import java.util.List; 24 | 25 | public class Script { 26 | 27 | static { 28 | Shell.enableVerboseLogging = BuildConfig.DEBUG; 29 | Shell.setDefaultBuilder(Shell.Builder.create() 30 | // FIXME - clearing this deprecated flag breaks 'svc usb getGadgetHalVersion' check 31 | .setFlags(Shell.FLAG_REDIRECT_STDERR) 32 | .setTimeout(10)); 33 | } 34 | 35 | private static boolean shellCommand(String command) { 36 | Shell.Result result = Shell.cmd(command).exec(); 37 | for (String message : result.getOut()) { 38 | Log.i("USBTether", message); 39 | } 40 | return result.isSuccess(); 41 | } 42 | 43 | static String testWait() { 44 | String cmd = ""; 45 | if (shellCommand("iptables -w 0 --help > /dev/null")) { 46 | cmd = "-w 2 "; 47 | } else if (shellCommand("iptables -w --help > /dev/null")) { // Some versions do not have the timeout 48 | cmd = "-w "; 49 | } 50 | return cmd; 51 | } 52 | 53 | static String hasWait = testWait(); 54 | static boolean hasTable = shellCommand("ip6tables " + hasWait + "--table nat --list > /dev/null"); 55 | 56 | // iptables can lie, inject test rules to verify the the modules are actually available 57 | static boolean hasCONNBYTES = shellCommand("iptables " + hasWait + "-t mangle -A POSTROUTING -m connbytes --connbytes 0:0 --connbytes-dir=original --connbytes-mode=packets") && shellCommand("iptables " + hasWait + "-t mangle -D POSTROUTING -m connbytes --connbytes 0:0 --connbytes-dir=original --connbytes-mode=packets"); 58 | static boolean hasNFQUEUE = shellCommand("iptables " + hasWait + "-t mangle -A FORWARD -j NFQUEUE --queue-num 0") && shellCommand("iptables " + hasWait + "-t mangle -D FORWARD -j NFQUEUE --queue-num 0"); 59 | static boolean hasTPROXY = shellCommand("ip6tables " + hasWait + "-t mangle -A PREROUTING -p tcp -j TPROXY --on-ip :: --on-port 0 --tproxy-mark 0") && shellCommand("ip6tables " + hasWait + "-t mangle -D PREROUTING -p tcp -j TPROXY --on-ip :: --on-port 0 --tproxy-mark 0"); 60 | static boolean hasTTL = shellCommand("iptables " + hasWait + "-t mangle -A FORWARD -j TTL --ttl-set 1") && shellCommand("iptables " + hasWait + "-t mangle -D FORWARD -j TTL --ttl-set 1"); 61 | static boolean hasSNAT = hasTable && shellCommand("ip6tables " + hasWait + "-t nat -A POSTROUTING -j SNAT --to [::]") && shellCommand("ip6tables " + hasWait + "-t nat -D POSTROUTING -j SNAT --to [::]"); 62 | static boolean hasMASQUERADE = hasTable && shellCommand("ip6tables " + hasWait + "-t nat -A POSTROUTING -j MASQUERADE") && shellCommand("ip6tables " + hasWait + "-t nat -D POSTROUTING -j MASQUERADE"); 63 | 64 | //static boolean hasCURL = shellCommand("command -v curl > /dev/null"); 65 | 66 | static boolean isUSBConfigured() { 67 | return Shell.cmd("[ \"$(cat /sys/class/android_usb/android0/state)\" = \"CONFIGURED\" ]").exec().isSuccess(); 68 | } 69 | 70 | static void killProcess(String pidFile) { 71 | if ( Shell.cmd("[ -f " + pidFile + " -a -d /proc/$(cat " + pidFile + ") ]").exec().isSuccess()) { 72 | shellCommand("kill -s 9 $(cat " + pidFile + ")"); 73 | } 74 | } 75 | 76 | static void iptables(boolean isIPv6, String table, String operation, String rule) { 77 | String command = isIPv6 ? "ip6tables" : "iptables"; 78 | boolean exists = Shell.cmd(command + " " + hasWait + "-t " + table + " -C " + rule).exec().isSuccess(); 79 | if ((!exists && (operation.equals("N") || operation.equals("I") || operation.equals("A"))) || 80 | (exists && (operation.equals("D") || operation.equals("F") || operation.equals("X")))) { 81 | shellCommand(command + " " + hasWait + "-t " + table + " -" + operation + " " + rule); 82 | } 83 | } 84 | 85 | static void createBridge(String tetherInterface) { 86 | shellCommand("ip link add name " + tetherInterface + " type bridge"); 87 | shellCommand("ip link set " + tetherInterface + " up"); 88 | shellCommand("echo 0 > /proc/sys/net/ipv6/conf/" + tetherInterface + "/accept_ra"); 89 | } 90 | 91 | static void bindBridge(String tetherInterface, String usbInterface) { 92 | if (shellCommand("ip link set " + usbInterface + " up")) { 93 | shellCommand("ip link set " + usbInterface + " master " + tetherInterface); 94 | } 95 | } 96 | 97 | static void destroyBridge(String tetherInterface) { 98 | shellCommand("ip link delete " + tetherInterface + " type bridge"); 99 | } 100 | 101 | static boolean[] getTetherConfig() { 102 | boolean adbEnabled = false; 103 | if (Shell.cmd("getprop persist.sys.usb.config").exec().getOut().get(0).contains("adb")) { 104 | adbEnabled = true; 105 | } 106 | 107 | boolean useService = false; 108 | if (Build.VERSION.SDK_INT >= 34 && !shellCommand("service check android.hardware.usb.gadget.IUsbGadget/default | grep \"not found\" > /dev/null")) { 109 | useService = true; 110 | } 111 | 112 | // sys.config.state is left undefined with GadgetHal impl 113 | boolean useGadget = false; 114 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !shellCommand("getprop | grep sys.usb.state > /dev/null")) { 115 | useGadget = true; 116 | } 117 | 118 | // getGadgetHalVersion was added to svc in Android 12 119 | // Need to use root service with reflection to properly support Android 11 120 | boolean hasNCM = false; 121 | if (useGadget && Build.VERSION.SDK_INT >= 31) { 122 | Shell.Result shell = Shell.cmd("svc usb getGadgetHalVersion").exec(); 123 | if (shell.isSuccess()) { 124 | List output = shell.getOut(); 125 | if (!output.isEmpty()) { 126 | String version = output.get(0); 127 | // State should never be "unknown", but we know it's not LegacyHal, so just ignore it 128 | if (!(version.equals("unknown") || version.equals("V1_0") || version.equals("V1_1"))) { 129 | hasNCM = true; 130 | } 131 | } 132 | } 133 | } 134 | 135 | return new boolean[] { adbEnabled, useService, useGadget, hasNCM }; 136 | } 137 | 138 | static String configureRNDIS(String libDIR) { 139 | 140 | boolean[] config = getTetherConfig(); 141 | boolean adbEnabled = config[0]; 142 | boolean useService = config[1]; 143 | boolean useGadget = config[2]; 144 | boolean hasNCM = config[3]; 145 | 146 | int NONE = 0; 147 | int ADB = 1 << 0; 148 | int RNDIS = 1 << 5; 149 | int NCM = 1 << 10; 150 | String gagdetFunctions = Integer.toString((adbEnabled ? ADB : NONE) + (hasNCM ? NCM : RNDIS)); 151 | 152 | String functionName = hasNCM ? "ncm" : "rndis"; 153 | if (useService) { 154 | // This is untested, who knows if it works as expected 155 | Log.i("USBTether", "Configuring " + functionName + " via IUsbGadget"); 156 | shellCommand("service call android.hardware.usb.gadget.IUsbGadget/default 1 i64 " + gagdetFunctions + " null i64 0 i64 1"); 157 | } else if (useGadget) { 158 | Log.i("USBTether", "Configuring " + functionName + " via GadgetHal"); 159 | shellCommand(libDIR + "/libusbgadget.so " + gagdetFunctions); 160 | } else { 161 | // LegacyHal can impl ncm, but we have no way to check 162 | Log.i("USBTether", "Configuring rndis via LegacyHal"); 163 | // Some broken HAL impls need none to be set or the gadget will not reset 164 | shellCommand("setprop sys.usb.config none"); 165 | if (adbEnabled) { 166 | shellCommand("setprop sys.usb.config rndis,adb"); 167 | } else { 168 | shellCommand("setprop sys.usb.config rndis"); 169 | } 170 | } 171 | shellCommand("n=0; while [[ $n -lt 10 ]]; do if [[ -d /sys/class/net/" + functionName + "0 ]]; then break; fi; n=$((n+1)); echo \"waiting for usb... $n\"; sleep 1; done"); 172 | 173 | return functionName + "0"; 174 | } 175 | 176 | static void unconfigureRNDIS(String libDIR) { 177 | boolean[] config = getTetherConfig(); 178 | boolean adbEnabled = config[0]; 179 | boolean useService = config[1]; 180 | boolean useGadget = config[2]; 181 | 182 | int NONE = 0; 183 | int ADB = 1 << 0; 184 | String gagdetFunctions = Integer.toString(adbEnabled ? ADB : NONE); 185 | 186 | if (useService) { 187 | Log.i("USBTether", "Configuring default USB state via IUsbGadget"); 188 | shellCommand("service call android.hardware.usb.gadget.IUsbGadget/default 1 i64 " + gagdetFunctions + " null i64 0 i64 1"); 189 | } else if (useGadget) { 190 | Log.i("USBTether", "Configuring default USB state via GadgetHal"); 191 | shellCommand(libDIR + "/libusbgadget.so " + gagdetFunctions); 192 | } else { 193 | Log.i("USBTether", "Configuring default USB state via LegacyHal"); 194 | shellCommand("setprop sys.usb.config none"); 195 | if (adbEnabled) { 196 | shellCommand("setprop sys.usb.config adb"); 197 | } 198 | } 199 | } 200 | 201 | private static boolean configureAddresses(String tetherInterface, String ipv4Addr, String ipv6Prefix) { 202 | Log.i("USBTether", "Setting IP addresses"); 203 | String ipv4Prefix = ipv4Addr.substring(0, ipv4Addr.lastIndexOf(".")); 204 | return shellCommand("ip link set dev " + tetherInterface + " up") 205 | && shellCommand("ip address add local " + ipv4Addr + "/24 broadcast " + ipv4Prefix + ".255 scope global dev " + tetherInterface) 206 | && shellCommand("ip -6 addr add " + ipv6Prefix + "1/64 dev " + tetherInterface + " scope global"); 207 | } 208 | 209 | private static void unconfigureAddresses(String tetherInterface) { 210 | Log.i("USBTether", "Clearing IP addresses"); 211 | shellCommand("ip address flush dev " + tetherInterface); 212 | shellCommand("ip -6 address flush dev " + tetherInterface); 213 | shellCommand("ip link set " + tetherInterface + " down"); 214 | } 215 | 216 | static boolean configureRoutes(String tetherInterface, String tetherLocalPrefix, String ipv4Addr, String ipv6Prefix) { 217 | Log.i("USBTether", "Setting IP routes"); 218 | String ipv4Prefix = ipv4Addr.substring(0, ipv4Addr.lastIndexOf(".")); 219 | return shellCommand("ip route add " + ipv4Prefix + ".0/24 dev " + tetherInterface + " table local_network proto static scope link") 220 | && shellCommand("ip -6 route add " + ipv6Prefix + "/64 dev " + tetherInterface + " table local_network proto static scope link") 221 | && shellCommand("ip -6 route add " + tetherLocalPrefix + "::/64 dev " + tetherInterface + " table local_network proto static scope link"); 222 | } 223 | 224 | static void unconfigureRoutes(String tetherInterface) { 225 | Log.i("USBTether", "Removing IP routes"); 226 | shellCommand("ip route flush dev " + tetherInterface); 227 | shellCommand("ip -6 route flush dev " + tetherInterface); 228 | } 229 | 230 | static boolean configureRules(String tetherInterface, String ipv4Interface, String ipv6Interface) { 231 | Log.i("USBTether", "Setting IP rules"); 232 | return shellCommand("ip rule add pref 500 from all iif lo oif " + tetherInterface + " uidrange 0-0 lookup local_network") 233 | && shellCommand("ip rule add pref 510 from all iif lo oif " + tetherInterface + " lookup local_network") 234 | && shellCommand("ip rule add pref 540 from all iif " + tetherInterface + " lookup " + ipv4Interface) 235 | && shellCommand("ip -6 rule add pref 500 from all iif lo oif " + tetherInterface + " uidrange 0-0 lookup local_network") 236 | && shellCommand("ip -6 rule add pref 510 from all iif lo oif " + tetherInterface + " lookup local_network") 237 | && shellCommand("ip -6 rule add pref 540 from all iif " + tetherInterface + " lookup " + ipv6Interface); 238 | } 239 | 240 | static boolean checkRules(String ipv4Interface, String ipv6Interface) { 241 | if (!shellCommand("ip rule list pref 540 | grep " + ipv4Interface + " > /dev/null") || !shellCommand("ip -6 rule list pref 540 | grep " + ipv6Interface + " > /dev/null")) { 242 | return true; 243 | } 244 | return false; 245 | } 246 | 247 | static void unconfigureRules() { 248 | Log.i("USBTether", "Removing IP rules"); 249 | shellCommand("ip rule del pref 500"); 250 | shellCommand("ip rule del pref 510"); 251 | shellCommand("ip rule del pref 540"); 252 | shellCommand("ip -6 rule del pref 500"); 253 | shellCommand("ip -6 rule del pref 510"); 254 | shellCommand("ip -6 rule del pref 540"); 255 | } 256 | 257 | static boolean configureInterface(String tetherInterface, String tetherLocalPrefix, String ipv4Interface, String ipv6Interface, String ipv4Addr, String ipv6Prefix) { 258 | Log.i("USBTether", "Configuring interface"); 259 | return shellCommand("ip link set dev " + tetherInterface + " down") 260 | && configureAddresses(tetherInterface, ipv4Addr, ipv6Prefix) 261 | && configureRoutes(tetherInterface, tetherLocalPrefix, ipv4Addr, ipv6Prefix) 262 | && configureRules(tetherInterface, ipv4Interface, ipv6Interface); 263 | } 264 | 265 | static void unconfigureInterface(String tetherInterface) { 266 | Log.i("USBTether", "Unconfiguring interface"); 267 | unconfigureRules(); 268 | unconfigureRoutes(tetherInterface); 269 | unconfigureAddresses(tetherInterface); 270 | } 271 | 272 | static private void configureNAT(boolean isIPv6, String tetherInterface, String upstreamInterface, String upstreamIP, boolean useSNAT) { 273 | // Used to find the names of chains 274 | String prefix = "natctrl"; 275 | String counter = prefix + "_tether"; 276 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { 277 | prefix = "tetherctrl"; 278 | counter = prefix; 279 | } 280 | 281 | // Add conntrack helpers - android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N 282 | iptables(isIPv6, "raw", "A", prefix + "_raw_PREROUTING -i " + tetherInterface + " -p tcp -m tcp --dport 21 -j CT --helper ftp"); 283 | iptables(isIPv6, "raw", "A", prefix + "_raw_PREROUTING -i " + tetherInterface + " -p tcp -m tcp --dport 1723 -j CT --helper pptp"); 284 | 285 | // Enable MSS Clamping 286 | iptables(isIPv6, "mangle", "A", prefix + "_mangle_FORWARD -p tcp -m tcp --tcp-flags SYN SYN -j TCPMSS --clamp-mss-to-pmtu"); 287 | 288 | // Insert forwarding rules 289 | iptables(isIPv6, "filter", "I", prefix + "_FORWARD -i " + tetherInterface + " -o " + upstreamInterface + " -g " + counter + "_counters"); 290 | iptables(isIPv6, "filter", "I", prefix + "_FORWARD -i " + tetherInterface + " -o " + upstreamInterface + " -m state --state INVALID -j DROP"); 291 | iptables(isIPv6, "filter", "I", prefix + "_FORWARD -i " + upstreamInterface + " -o " + tetherInterface + " -m state --state RELATED,ESTABLISHED -g " + counter + "_counters"); 292 | iptables(isIPv6, "filter", "A", prefix + "_FORWARD -j DROP"); 293 | // Remove rule that forwards all packets if it exists 294 | iptables(isIPv6, "filter", "D", prefix + "_FORWARD -g " + counter + "_counters"); 295 | 296 | // Add MASQUERADE/SNAT rule 297 | if (isIPv6) { 298 | // Android will not create the NAT6 chain for us 299 | iptables(true, "nat", "N", prefix + "_nat_POSTROUTING"); 300 | iptables(true, "nat", "A", "POSTROUTING -j " + prefix + "_nat_POSTROUTING"); 301 | } 302 | if (useSNAT) { 303 | iptables(isIPv6, "nat", "A", prefix + "_nat_POSTROUTING -o " + upstreamInterface + " -j SNAT --to " + upstreamIP); 304 | } else { 305 | iptables(isIPv6, "nat", "A", prefix + "_nat_POSTROUTING -o " + upstreamInterface + " -j MASQUERADE"); 306 | } 307 | } 308 | 309 | static private void unconfigureNAT(boolean isIPv6, String tetherInterface, String upstreamInterface, String upstreamIP, boolean useSNAT) { 310 | String prefix = "natctrl"; 311 | String counter = prefix + "_tether"; 312 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { 313 | prefix = "tetherctrl"; 314 | counter = prefix; 315 | } 316 | 317 | iptables(isIPv6, "raw", "D", prefix + "_raw_PREROUTING -i " + tetherInterface + " -p tcp -m tcp --dport 21 -j CT --helper ftp"); 318 | iptables(isIPv6, "raw", "D", prefix + "_raw_PREROUTING -i " + tetherInterface + " -p tcp -m tcp --dport 1723 -j CT --helper pptp"); 319 | 320 | iptables(isIPv6, "filter", "D", prefix + "_FORWARD -i " + tetherInterface + " -o " + upstreamInterface + " -g " + counter + "_counters"); 321 | iptables(isIPv6, "filter", "D", prefix + "_FORWARD -i " + tetherInterface + " -o " + upstreamInterface + " -m state --state INVALID -j DROP"); 322 | iptables(isIPv6, "filter", "D", prefix + "_FORWARD -i " + upstreamInterface + " -o " + tetherInterface + " -m state --state RELATED,ESTABLISHED -g " + counter + "_counters"); 323 | 324 | if (useSNAT) { 325 | iptables(isIPv6, "nat", "D", prefix + "_nat_POSTROUTING -o " + upstreamInterface + " -j SNAT --to " + upstreamIP); 326 | } else { 327 | iptables(isIPv6, "nat", "D", prefix + "_nat_POSTROUTING -o " + upstreamInterface + " -j MASQUERADE"); 328 | } 329 | } 330 | 331 | static private void configureTPROXY() { 332 | iptables(true, "mangle", "N", "TPROXY_ROUTE_PREROUTING"); 333 | iptables(true, "mangle", "A", "PREROUTING -j TPROXY_ROUTE_PREROUTING"); 334 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d :: -j RETURN"); 335 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d ::1 -j RETURN"); 336 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d ::ffff:0:0:0/96 -j RETURN"); 337 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d 64:ff9b::/96 -j RETURN"); 338 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d 100::/64 -j RETURN"); 339 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d 2001::/32 -j RETURN"); 340 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d 2001:20::/28 -j RETURN"); 341 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d 2001:db8::/32 -j RETURN"); 342 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d 2002::/16 -j RETURN"); 343 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d fc00::/7 -j RETURN"); 344 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d fe80::/10 -j RETURN"); 345 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -d ff00::/8 -j RETURN"); 346 | iptables(true, "mangle", "N", "TPROXY_MARK_PREROUTING"); 347 | iptables(true, "mangle", "A", "TPROXY_ROUTE_PREROUTING -j TPROXY_MARK_PREROUTING"); 348 | iptables(true, "mangle", "A", "TPROXY_MARK_PREROUTING -p tcp -j TPROXY --on-ip ::1 --on-port 1088 --tproxy-mark 1088"); 349 | iptables(true, "mangle", "A", "TPROXY_MARK_PREROUTING -p udp -j TPROXY --on-ip ::1 --on-port 1088 --tproxy-mark 1088"); 350 | shellCommand("ip -6 rule add pref 530 fwmark 1088 table 999"); 351 | shellCommand("ip -6 route add local default dev lo table 999"); 352 | } 353 | 354 | static private void unconfigureTPROXY() { 355 | iptables(true, "mangle", "D", "TPROXY_ROUTE_PREROUTING -j TPROXY_MARK_PREROUTING"); 356 | iptables(true, "mangle", "F", "TPROXY_MARK_PREROUTING"); 357 | iptables(true, "mangle", "X", "TPROXY_MARK_PREROUTING"); 358 | iptables(true, "mangle", "D", "PREROUTING -j TPROXY_ROUTE_PREROUTING"); 359 | iptables(true, "mangle", "F", "TPROXY_ROUTE_PREROUTING"); 360 | iptables(true, "mangle", "X", "TPROXY_ROUTE_PREROUTING"); 361 | shellCommand("ip -6 rule delete pref 530 fwmark 1088 table 999"); 362 | shellCommand("ip -6 route delete local default dev lo table 999"); 363 | } 364 | 365 | static void setTPROXYRoute(String prefix) { 366 | iptables(true, "mangle", "I", "TPROXY_ROUTE_PREROUTING -d " + prefix + "::/64 -j RETURN"); 367 | } 368 | 369 | static boolean configureTether(String tetherInterface, String tetherLocalPrefix, String ipv4Interface, String ipv6Interface, String ipv4Addr, String ipv6Prefix, String ipv6TYPE,/* upstreamIPv4,*/ String upstreamIPv6, boolean mangleTTL, boolean dnsmasq, String libDIR, String appData, String clientBandwidth, boolean dpiCircumvention) { 370 | if (configureInterface(tetherInterface, tetherLocalPrefix, ipv4Interface, ipv6Interface, ipv4Addr, ipv6Prefix)) { 371 | Log.i("USBTether", "Enabling IP forwarding"); 372 | shellCommand("echo 1 > /proc/sys/net/ipv4/ip_forward"); 373 | shellCommand("echo 1 > /proc/sys/net/ipv6/conf/all/forwarding"); 374 | Log.i("USBTether", "Setting up NAT"); 375 | configureNAT(false, tetherInterface, ipv4Interface, "", false); 376 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 377 | configureNAT(true, tetherInterface, ipv6Interface, upstreamIPv6, ipv6TYPE.equals("SNAT")); 378 | } else if (ipv6TYPE.equals("TPROXY")) { 379 | configureTPROXY(); 380 | } 381 | String ipv4Prefix = ipv4Addr.substring(0, ipv4Addr.lastIndexOf(".")); 382 | if (Integer.parseInt(clientBandwidth) > 0) { // Set the maximum allowed bandwidth per IP address 383 | iptables(false, "filter", "A", "FORWARD -i " + ipv4Interface + " -o " + tetherInterface + " -d " + ipv4Prefix + ".0/24 -m tcp -p tcp -m hashlimit --hashlimit-mode dstip --hashlimit-above " + clientBandwidth + "kb/s --hashlimit-name max_tether_bandwidth -j DROP"); 384 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { // Not supported by TPROXY 385 | iptables(true, "filter", "A", "FORWARD -i " + ipv6Interface + " -o " + tetherInterface + " -d " + ipv6Prefix + "/64 -m tcp -p tcp -m hashlimit --hashlimit-mode dstip --hashlimit-above " + clientBandwidth + "kb/s --hashlimit-name max_tether_bandwidth -j DROP"); 386 | } 387 | } 388 | if (dnsmasq) { 389 | // DNSMasq has a bug with --port when an interface is lost and restored, it will lose it's UDP binding and will never restore it 390 | shellCommand("rm " + appData + "/dnsmasq.pid"); 391 | if (ipv6TYPE.equals("None")) { 392 | shellCommand(libDIR + "/libdnsmasq.so --bind-interfaces --interface=" + tetherInterface + " --keep-in-foreground --no-resolv --no-poll --domain-needed --bogus-priv --dhcp-authoritative --dhcp-range=" + ipv4Prefix + ".10," + ipv4Prefix + ".99,1h --server=8.8.8.8 --server=8.8.4.4 --leasefile-ro --pid-file=" + appData + "/dnsmasq.pid &"); 393 | } else { 394 | shellCommand(libDIR + "/libdnsmasq.so --bind-interfaces --interface=" + tetherInterface + " --keep-in-foreground --no-resolv --no-poll --domain-needed --bogus-priv --dhcp-authoritative --dhcp-range=" + ipv4Prefix + ".10," + ipv4Prefix + ".99,1h --dhcp-range=" + ipv6Prefix + "10," + ipv6Prefix + "99,slaac,64,1h --server=8.8.8.8 --server=8.8.4.4 --server=2001:4860:4860::8888 --server=2001:4860:4860::8844 --leasefile-ro --pid-file=" + appData + "/dnsmasq.pid &"); 395 | } 396 | } 397 | if (ipv6TYPE.equals("TPROXY")) { 398 | shellCommand("rm " + appData + "/socks.pid"); 399 | shellCommand("rm " + appData + "/tproxy.pid"); 400 | shellCommand(libDIR + "/libhevserver.so " + appData + "/socks.yml &"); 401 | shellCommand(libDIR + "/libhevtproxy.so " + appData + "/tproxy.yml &"); 402 | } 403 | if (mangleTTL && hasTTL) { 404 | iptables(false, "mangle", "A", "FORWARD -i " + tetherInterface + " -o " + ipv4Interface + " -j TTL --ttl-set 64"); 405 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 406 | iptables(true, "mangle", "A", "FORWARD -i " + tetherInterface + " -o " + ipv6Interface + " -j HL --hl-set 64"); 407 | } 408 | } 409 | // We don't generate new packets so '-m mark ! --mark 0x40000000/0x40000000' is unnecessary 410 | // Nothing really uses http anymore so don't waste cycles processing it 411 | if (hasNFQUEUE && ((mangleTTL && !hasTTL) || dpiCircumvention)) { 412 | String connBytes = (hasCONNBYTES && !(mangleTTL && !hasTTL)) ? "-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 " : ""; 413 | String connLimit = (mangleTTL && !hasTTL) ? "" : "-p tcp --dport 443 "; 414 | iptables(false, "mangle", "A", "FORWARD -i " + tetherInterface + " -o " + ipv4Interface + " " + connLimit + connBytes + "-j NFQUEUE --queue-num 6465"); 415 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 416 | iptables(true, "mangle", "A", "FORWARD -i " + tetherInterface + " -o " + ipv6Interface + " " + connLimit + connBytes + "-j NFQUEUE --queue-num 6465"); 417 | } 418 | String optTTL = (mangleTTL && !hasTTL) ? "--force-ttl " : ""; 419 | String optDPI = dpiCircumvention ? "--dpi-desync=multisplit --dpi-desync-split-pos=midsld " : ""; 420 | shellCommand("rm " + appData + "/nfqws.pid"); 421 | shellCommand(libDIR + "/libnfqws.so " + optTTL + optDPI + "--qnum=6465 --pidfile=" + appData + "/nfqws.pid &"); 422 | } 423 | if (dpiCircumvention && (!hasNFQUEUE || ipv6TYPE.equals("TPROXY"))) { 424 | if (!hasNFQUEUE) { 425 | iptables(false, "nat", "I", "PREROUTING -i " + tetherInterface + " -p tcp --dport 443 -j DNAT --to " + ipv4Addr + ":8123"); 426 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 427 | iptables(true, "nat", "I", "PREROUTING -i " + tetherInterface + " -p tcp --dport 443 -j DNAT --to [" + ipv6Prefix + "1]:8123"); 428 | } 429 | } 430 | if (ipv6TYPE.equals("TPROXY")) { 431 | // Huh, only need the IP_TRANSPARENT patch for IPv4? 432 | iptables(true, "mangle", "I", "TPROXY_MARK_PREROUTING -p tcp --dport 443 -j TPROXY --on-ip " + ipv6Prefix + "1 --on-port 8123 --tproxy-mark 8123"); 433 | shellCommand("ip -6 rule add pref 520 fwmark 8123 table 998"); 434 | shellCommand("ip -6 route add local default dev lo table 998"); 435 | } 436 | shellCommand("rm " + appData + "/tpws.pid"); 437 | shellCommand(libDIR + "/libtpws.so --bind-addr=" + ipv4Addr + " --bind-addr=" + ipv6Prefix + "1 --port=8123 --pidfile=" + appData + "/tpws.pid --split-pos=midsld --uid 1:3003 &"); 438 | } 439 | } else { 440 | Log.w("USBTether", tetherInterface + " unavailable, aborting tether..."); 441 | return false; 442 | } 443 | return true; 444 | } 445 | 446 | static void unconfigureTether(String tetherInterface, String ipv4Interface, String ipv6Interface, String ipv4Addr, String ipv6Prefix, String ipv6TYPE,/* upstreamIPv4,*/ String upstreamIPv6, boolean mangleTTL, boolean dnsmasq, String appData, String clientBandwidth, boolean dpiCircumvention) { 447 | String ipv4Prefix = ipv4Addr.substring(0, ipv4Addr.lastIndexOf(".")); 448 | Log.i("USBTether", "Restoring tether interface state"); 449 | if (dnsmasq) { 450 | killProcess(appData + "/dnsmasq.pid"); 451 | } 452 | if (ipv6TYPE.equals("TPROXY")) { 453 | killProcess(appData + "/socks.pid"); 454 | killProcess(appData + "/tproxy.pid"); 455 | } 456 | if (mangleTTL && hasTTL) { 457 | iptables(false, "mangle", "D", "FORWARD -i " + tetherInterface + " -o " + ipv4Interface + " -j TTL --ttl-set 64"); 458 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 459 | iptables(true, "mangle", "D", "FORWARD -i " + tetherInterface + " -o " + ipv6Interface + " -j HL --hl-set 64"); 460 | } 461 | } 462 | if (hasNFQUEUE && ((mangleTTL && !hasTTL) || dpiCircumvention)) { 463 | killProcess(appData + "/nfqws.pid"); 464 | String connBytes = (hasCONNBYTES && !(mangleTTL && !hasTTL)) ? "-m connbytes --connbytes-dir=original --connbytes-mode=packets --connbytes 1:6 " : ""; 465 | String connLimit = (mangleTTL && !hasTTL) ? "" : "-p tcp --dport 443 "; 466 | iptables(false, "mangle", "D", "FORWARD -i " + tetherInterface + " -o " + ipv4Interface + " " + connLimit + connBytes + "-j NFQUEUE --queue-num 6465"); 467 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 468 | iptables(true, "mangle", "D", "FORWARD -i " + tetherInterface + " -o " + ipv6Interface + " " + connLimit + connBytes + "-j NFQUEUE --queue-num 6465"); 469 | } 470 | } 471 | if (dpiCircumvention && (!hasNFQUEUE || ipv6TYPE.equals("TPROXY"))) { 472 | killProcess(appData + "/tpws.pid"); 473 | if (!hasNFQUEUE) { 474 | iptables(false, "nat", "D", "PREROUTING -i " + tetherInterface + " -p tcp --dport 443 -j DNAT --to " + ipv4Addr + ":8123"); 475 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 476 | iptables(true, "nat", "D", "PREROUTING -i " + tetherInterface + " -p tcp --dport 443 -j DNAT --to [" + ipv6Prefix + "1]:8123"); 477 | } 478 | } 479 | if (ipv6TYPE.equals("TPROXY")) { 480 | iptables(true, "mangle", "D", "TPROXY_MARK_PREROUTING -p tcp --dport 443 -j TPROXY --on-ip " + ipv6Prefix + "1 --on-port 8123 --tproxy-mark 8123"); 481 | shellCommand("ip -6 rule delete pref 520 fwmark 8123 table 998"); 482 | shellCommand("ip -6 route delete local default dev lo table 998"); 483 | } 484 | } 485 | if (Integer.parseInt(clientBandwidth) > 0) { 486 | iptables(false, "filter", "D", "FORWARD -i " + ipv4Interface + " -o " + tetherInterface + " -d " + ipv4Prefix + ".0/24 -m tcp -p tcp -m hashlimit --hashlimit-mode dstip --hashlimit-above " + clientBandwidth + "kb/s --hashlimit-name max_tether_bandwidth -j DROP"); 487 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { // Not supported by TPROXY 488 | iptables(true, "filter", "D", "FORWARD -i " + ipv6Interface + " -o " + tetherInterface + " -d " + ipv6Prefix + "/64 -m tcp -p tcp -m hashlimit --hashlimit-mode dstip --hashlimit-above " + clientBandwidth + "kb/s --hashlimit-name max_tether_bandwidth -j DROP"); 489 | } 490 | } 491 | unconfigureNAT(false, tetherInterface, ipv4Interface, "", false); 492 | if (ipv6TYPE.equals("MASQUERADE") || ipv6TYPE.equals("SNAT")) { 493 | unconfigureNAT(true, tetherInterface, ipv6Interface, upstreamIPv6, ipv6TYPE.equals("SNAT")); 494 | } else if (ipv6TYPE.equals("TPROXY")) { 495 | unconfigureTPROXY(); 496 | } 497 | unconfigureInterface(tetherInterface); 498 | shellCommand("echo 0 > /proc/sys/net/ipv4/ip_forward"); 499 | shellCommand("echo 0 > /proc/sys/net/ipv6/conf/all/forwarding"); 500 | } 501 | 502 | static void checkProcesses(String tetherInterface, String ipv4Addr, String ipv6Prefix, String ipv6TYPE, boolean mangleTTL, boolean dnsmasq, boolean dpiCircumvention, String libDIR, String appData) { 503 | if (dnsmasq) { 504 | if (!shellCommand("[ -f " + appData + "/dnsmasq.pid -a -d /proc/$(cat " + appData + "/dnsmasq.pid) ]")) { 505 | String ipv4Prefix = ipv4Addr.substring(0, ipv4Addr.lastIndexOf(".")); 506 | Log.w("USBTether", "No dnsmasq process, restarting"); 507 | shellCommand("rm " + appData + "/dnsmasq.pid"); 508 | if (ipv6TYPE.equals("None")) { 509 | shellCommand(libDIR + "/libdnsmasq.so --bind-interfaces --interface=" + tetherInterface + " --keep-in-foreground --no-resolv --no-poll --domain-needed --bogus-priv --dhcp-authoritative --dhcp-range=" + ipv4Prefix + ".10," + ipv4Prefix + ".99,1h --server=8.8.8.8 --server=8.8.4.4 --leasefile-ro --pid-file=" + appData + "/dnsmasq.pid &"); 510 | } else { 511 | shellCommand(libDIR + "/libdnsmasq.so --bind-interfaces --interface=" + tetherInterface + " --keep-in-foreground --no-resolv --no-poll --domain-needed --bogus-priv --dhcp-authoritative --dhcp-range=" + ipv4Prefix + ".10," + ipv4Prefix + ".99,1h --dhcp-range=" + ipv6Prefix + "10," + ipv6Prefix + "99,slaac,64,1h --server=8.8.8.8 --server=8.8.4.4 --server=2001:4860:4860::8888 --server=2001:4860:4860::8844 --leasefile-ro --pid-file=" + appData + "/dnsmasq.pid &"); 512 | } 513 | } 514 | } 515 | if (hasNFQUEUE && ((mangleTTL && !hasTTL) || dpiCircumvention)) { 516 | if (!shellCommand("[ -f " + appData + "/nfqws.pid -a -d /proc/$(cat " + appData + "/nfqws.pid) ]")) { 517 | Log.w("USBTether", "No nfqws process, restarting"); 518 | String optTTL = (mangleTTL && !hasTTL) ? "--force-ttl " : ""; 519 | String optDPI = dpiCircumvention ? "--dpi-desync=multisplit --dpi-desync-split-pos=midsld " : ""; 520 | shellCommand("rm " + appData + "/nfqws.pid"); 521 | shellCommand(libDIR + "/libnfqws.so " + optTTL + optDPI + "--qnum=6465 --pidfile=" + appData + "/nfqws.pid &"); 522 | } 523 | } 524 | if (dpiCircumvention && (!hasNFQUEUE || ipv6TYPE.equals("TPROXY"))) { 525 | if (!shellCommand("[ -f " + appData + "/tpws.pid -a -d /proc/$(cat " + appData + "/tpws.pid) ]")) { 526 | Log.w("USBTether", "No tpws process, restarting"); 527 | shellCommand("rm " + appData + "/tpws.pid"); 528 | shellCommand(libDIR + "/libtpws.so --bind-addr=" + ipv4Addr + " --bind-addr=" + ipv6Prefix + "1 --port=8123 --pidfile=" + appData + "/tpws.pid --split-pos=midsld --uid 1:3003 &"); 529 | } 530 | } 531 | if (ipv6TYPE.equals("TPROXY")) { 532 | if (!shellCommand("[ -f " + appData + "/socks.pid -a -d /proc/$(cat " + appData + "/socks.pid) ]")) { 533 | Log.w("USBTether", "No socks process, restarting"); 534 | shellCommand("rm " + appData + "/socks.pid"); 535 | shellCommand(libDIR + "/libhevserver.so " + appData + "/socks.yml &"); 536 | } 537 | if (!shellCommand("[ -f " + appData + "/tproxy.pid -a -d /proc/$(cat " + appData + "/tproxy.pid) ]")) { 538 | Log.w("USBTether", "No tproxy process, restarting"); 539 | shellCommand("rm " + appData + "/tproxy.pid"); 540 | shellCommand(libDIR + "/libhevtproxy.so " + appData + "/tproxy.yml &"); 541 | } 542 | } 543 | } 544 | 545 | // FIXME!!!!!! need to update IPv4 SNAT too!!!!! 546 | static void refreshSNAT(String tetherInterface, String ipv6Addr, String newAddr) { 547 | Log.w("USBTether", "Refreshing SNAT IPTables Rule"); 548 | String prefix = "natctrl"; 549 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { 550 | prefix = "tetherctrl"; 551 | } 552 | iptables(true, "nat", "D", prefix + "_nat_POSTROUTING -o " + tetherInterface + " -j SNAT --to " + ipv6Addr); 553 | iptables(true, "nat", "A", prefix + "_nat_POSTROUTING -o " + tetherInterface + " -j SNAT --to " + newAddr); 554 | } 555 | 556 | // FIXME these tests can hang on name resolution, fix this and supply binaries if needed 557 | // simple ping tests are not super useful due to false positives 558 | /*static boolean testConnection(String upstreamInterface, boolean isIPv6) { 559 | String protocol = isIPv6 ? "IPv6" : "IPv4"; 560 | if (hasCURL) { 561 | String testSite = "http://connectivitycheck.gstatic.com/generate_204"; 562 | String argument = isIPv6 ? "--ipv6" : "--ipv4"; 563 | if (Shell.cmd("curl " + argument + " --interface " + upstreamInterface + " --retry 2 --retry-max-time 5 " + testSite).exec().isSuccess()) { 564 | Log.i("USBTether", upstreamInterface + " " + protocol + " is online"); 565 | return true; 566 | } 567 | } else { 568 | String testSite = "connectivitycheck.gstatic.com"; 569 | String command = isIPv6 ? "ping6" : "ping"; 570 | if (Shell.cmd(command + " -c 1 -w 3 -I " + upstreamInterface + " " + testSite).exec().isSuccess() 571 | || Shell.cmd(command + " -c 1 -w 3 -I " + upstreamInterface + " " + testSite).exec().isSuccess()) { 572 | Log.i("USBTether", upstreamInterface + " " + protocol + " is online"); 573 | return true; 574 | } 575 | } 576 | Log.w("USBTether", upstreamInterface + " " + protocol + " is offline"); 577 | return false; 578 | }*/ 579 | 580 | static boolean testConnection(String upstreamInterface, boolean isIPv6) { 581 | String protocol = isIPv6 ? "IPv6" : "IPv4"; 582 | String command = isIPv6 ? "ping6" : "ping"; 583 | if (Shell.cmd(command + " -c 1 -w 3 -I " + upstreamInterface + " " + (isIPv6 ? "2001:4860:4860::8888" : "8.8.8.8")).exec().isSuccess() 584 | || Shell.cmd(command + " -c 1 -w 3 -I " + upstreamInterface + " " + (isIPv6 ? "2606:4700:4700::1111" : "1.1.1.1")).exec().isSuccess()) { 585 | Log.i("USBTether", upstreamInterface + " " + protocol + " is online"); 586 | return true; 587 | } 588 | Log.w("USBTether", upstreamInterface + " " + protocol + " is offline"); 589 | return false; 590 | } 591 | 592 | // FIXME - this still has trouble launching on failure, even with no lockscreen 593 | static void startCloudflare1111Warp() { 594 | Log.w("USBTether", "Starting Cloudflare 1.1.1.1 Warp"); 595 | shellCommand("input keyevent KEYCODE_WAKEUP"); 596 | shellCommand("am start -W com.cloudflare.onedotonedotonedotone/com.cloudflare.app.presentation.main.MainActivity"); 597 | shellCommand("am startservice com.cloudflare.onedotonedotonedotone/com.cloudflare.app.vpnservice.CloudflareVpnService"); 598 | } 599 | 600 | static void stopCloudflare1111Warp() { 601 | Log.w("USBTether", "Stopping Cloudflare 1.1.1.1 Warp"); 602 | shellCommand("am force-stop com.cloudflare.onedotonedotonedotone"); 603 | } 604 | 605 | static void recoverDataConnection() { 606 | Log.w("USBTether", "Restarting mobile data"); 607 | // get current id, mcc, mnc 608 | Shell.Result command = Shell.cmd("content query --uri content://telephony/carriers/preferapn --projection _id:mcc:mnc | awk -F '[=,]' '{print $2,$4,$6}'").exec(); 609 | if ( command.isSuccess() ) { 610 | String[] parts = command.getOut().get(0).split(" "); 611 | if ( parts.length == 3) { 612 | // insert dummy apn 613 | shellCommand("content insert --uri content://telephony/carriers --bind name:s:usbt_dummy --bind numeric:s:" + parts[1] + parts[2] + " --bind mcc:s:" + parts[1] + " --bind mnc:s:" + parts[2] + " --bind type:s:default --bind current:s:1 --bind apn:s:test --bind edited:s:1"); 614 | // get dummy id 615 | command = Shell.cmd("content query --uri content://telephony/carriers --where \"name='usbt_dummy'\" --projection _id | awk -F '=' '{print $2}'").exec(); 616 | if ( command.isSuccess() ) { 617 | String id = command.getOut().get(0); 618 | // select dummy apn 619 | shellCommand("content insert --uri content://telephony/carriers/preferapn --bind apn_id:i:" + id); 620 | // restart data 621 | shellCommand("svc data disable"); 622 | shellCommand("svc data enable"); 623 | // select preferred apn 624 | shellCommand("content insert --uri content://telephony/carriers/preferapn --bind apn_id:i:" + parts[0]); 625 | // restart data again 626 | shellCommand("svc data disable"); 627 | shellCommand("svc data enable"); 628 | } 629 | // delete dummy apn 630 | shellCommand("content delete --uri content://telephony/carriers --where \"name='usbt_dummy'\""); 631 | } 632 | } 633 | } 634 | } 635 | -------------------------------------------------------------------------------- /app/src/main/java/com/worstperson/usbtether/ForegroundService.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 worstperson 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package com.worstperson.usbtether; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.app.NotificationChannel; 21 | import android.app.NotificationManager; 22 | import android.app.Service; 23 | import android.content.BroadcastReceiver; 24 | import android.content.Context; 25 | import android.content.Intent; 26 | import android.content.IntentFilter; 27 | import android.content.SharedPreferences; 28 | import android.net.ConnectivityManager; 29 | import android.net.LinkProperties; 30 | import android.net.Network; 31 | import android.net.NetworkCapabilities; 32 | import android.net.NetworkRequest; 33 | import android.os.Build; 34 | import android.os.Bundle; 35 | import android.os.Handler; 36 | import android.os.IBinder; 37 | import android.os.Looper; 38 | import android.os.PowerManager; 39 | import android.os.PowerManager.WakeLock; 40 | import android.os.SystemClock; 41 | import android.util.Log; 42 | import android.widget.Toast; 43 | 44 | import androidx.annotation.NonNull; 45 | import androidx.core.os.HandlerCompat; 46 | import androidx.core.app.NotificationCompat; 47 | 48 | import java.io.File; 49 | import java.io.FileWriter; 50 | import java.io.IOException; 51 | import java.net.Inet6Address; 52 | import java.net.InetAddress; 53 | import java.net.NetworkInterface; 54 | import java.net.SocketException; 55 | import java.util.Collections; 56 | import java.util.HashMap; 57 | 58 | public class ForegroundService extends Service { 59 | 60 | public static final String CHANNEL_ID = "ForegroundServiceChannel"; 61 | public static String networkStatus = "Configuring network"; 62 | public static String usbStatus = "USB disconnected"; 63 | 64 | PowerManager powerManager; 65 | WakeLock wakeLock; 66 | 67 | public static boolean isStarted = false; 68 | private boolean natApplied = false; 69 | private boolean needsReset = false; 70 | private boolean watchdogActive = false; 71 | private int offlineCounter = 0; 72 | 73 | private String usbInterface; 74 | final private String tetherInterface = "usbt0"; 75 | private String tetherLocalPrefix = null; 76 | 77 | private String cellularInterface = null; 78 | 79 | HashMap networkMap = new HashMap<>(); 80 | 81 | NotificationCompat.Builder notification = new NotificationCompat.Builder(this, CHANNEL_ID) 82 | .setSmallIcon(R.mipmap.ic_launcher) 83 | .setOngoing(true) 84 | .setSilent(true); 85 | 86 | final Handler handler = new Handler(Looper.getMainLooper()); 87 | Runnable delayedRestore = new Runnable() { 88 | @Override 89 | public void run() { 90 | watchdogActive = false; 91 | if (isStarted) { 92 | restoreTether(); 93 | } 94 | } 95 | }; 96 | 97 | public boolean hasRebootOccurred() { 98 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 99 | long lastElapsedTime = sharedPref.getLong("lastRun", -1); 100 | long currentElapsedTime = SystemClock.elapsedRealtime(); 101 | SharedPreferences.Editor editor = sharedPref.edit(); 102 | editor.putLong("lastRun", currentElapsedTime); 103 | editor.apply(); 104 | return lastElapsedTime == -1 || currentElapsedTime < lastElapsedTime; 105 | } 106 | 107 | private String getInterfaceName(ConnectivityManager connectivityManager, Network activeNetwork) { 108 | if (activeNetwork != null) { 109 | LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNetwork); 110 | if (linkProperties != null) { 111 | return linkProperties.getInterfaceName(); 112 | } 113 | } 114 | return null; 115 | } 116 | 117 | private String updateNetworkMap(ConnectivityManager connectivityManager, Network activeNetwork) { 118 | if (activeNetwork != null) { 119 | Integer hash; 120 | int hash2 = activeNetwork.hashCode(); 121 | LinkProperties linkProperties = connectivityManager.getLinkProperties(activeNetwork); 122 | if (linkProperties != null) { 123 | String interfaceName = linkProperties.getInterfaceName(); 124 | if (interfaceName != null) { 125 | if (networkMap.containsKey(interfaceName) && (hash = networkMap.get(interfaceName)) != null) { 126 | if (hash != hash2) { 127 | networkMap.replace(interfaceName, hash2); 128 | } 129 | } else { 130 | networkMap.put(interfaceName, hash2); 131 | } 132 | return interfaceName; 133 | } 134 | } 135 | } 136 | return null; 137 | } 138 | 139 | private String pickInterface(String upstreamInterface, String interfaceName, Network activeNetwork, int autostartVPN, ConnectivityManager connectivityManager) { 140 | if (upstreamInterface.equals("Auto") || autostartVPN == 1 || autostartVPN >= 3) { 141 | if (activeNetwork != null) { 142 | // VPN interfaces can be incremented so definitions exclude it 143 | if (interfaceName != null && (upstreamInterface.equals("Auto") || interfaceName.startsWith(upstreamInterface))) { 144 | return interfaceName; 145 | } 146 | } 147 | return null; 148 | } 149 | return upstreamInterface; 150 | } 151 | 152 | private boolean isCellularInterface(Network network, ConnectivityManager connectivityManager) { 153 | if (connectivityManager != null) { 154 | // NOTE - is NET_CAPABILITY_NOT_VPN actually necessary? 155 | NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network); 156 | if (networkCapabilities != null && networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) && 157 | networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && 158 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 159 | return true; 160 | } 161 | } 162 | return false; 163 | } 164 | 165 | private String setupSNAT(String upstreamInterface, boolean ipv6SNAT) { 166 | String ipv6Addr = ""; 167 | try { 168 | if (ipv6SNAT) { 169 | NetworkInterface networkInterface = NetworkInterface.getByName(upstreamInterface); 170 | if (networkInterface != null) { 171 | for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) { 172 | if (inetAddress instanceof Inet6Address && !inetAddress.isLinkLocalAddress()) { 173 | ipv6Addr = inetAddress.getHostAddress(); 174 | break; 175 | } 176 | } 177 | } 178 | } 179 | } catch (SocketException e) { 180 | e.printStackTrace(); 181 | } 182 | return ipv6Addr; 183 | } 184 | 185 | private void startVPN(int autostartVPN, String wireguardProfile) { 186 | if (autostartVPN == 1 || autostartVPN == 2) { 187 | Intent i = new Intent("com.wireguard.android.action.SET_TUNNEL_DOWN"); 188 | i.setPackage("com.wireguard.android"); 189 | i.putExtra("tunnel", wireguardProfile); 190 | sendBroadcast(i); 191 | i = new Intent("com.wireguard.android.action.SET_TUNNEL_UP"); 192 | i.setPackage("com.wireguard.android"); 193 | i.putExtra("tunnel", wireguardProfile); 194 | sendBroadcast(i); 195 | } else if (autostartVPN == 3) { 196 | Script.stopCloudflare1111Warp(); 197 | Script.startCloudflare1111Warp(); 198 | } 199 | } 200 | 201 | private String getAddressPrefix(String ipv6Addr) { 202 | String result = null; 203 | if (ipv6Addr.contains("::")) { 204 | result = ipv6Addr.substring(0, ipv6Addr.indexOf("::")); 205 | } else { 206 | int count = 0; 207 | for (int i = 0; i < ipv6Addr.length(); i++) { 208 | if (ipv6Addr.charAt(i) == ':' && ++count == 4) { 209 | result = ipv6Addr.substring(0, i); 210 | } 211 | } 212 | } 213 | return result; 214 | } 215 | 216 | private String getLocalPrefix(String tetherInterface) { 217 | try { 218 | NetworkInterface networkInterface = NetworkInterface.getByName(tetherInterface); 219 | if (networkInterface != null) { 220 | for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) { 221 | String ipv6Addr; 222 | if (inetAddress instanceof Inet6Address && inetAddress.isLinkLocalAddress() && (ipv6Addr = inetAddress.getHostAddress()) != null) { 223 | return getAddressPrefix(ipv6Addr); 224 | } 225 | } 226 | } 227 | } catch (SocketException e) { 228 | e.printStackTrace(); 229 | } 230 | return null; 231 | } 232 | 233 | private String getGlobalPrefix(String ipv6Interface, String ipv6Prefix) { 234 | try { 235 | NetworkInterface networkInterface = NetworkInterface.getByName(ipv6Interface); 236 | if (networkInterface != null) { 237 | for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) { 238 | String ipv6Addr; 239 | if (inetAddress instanceof Inet6Address && !inetAddress.isLinkLocalAddress() && (ipv6Addr = inetAddress.getHostAddress()) != null && !ipv6Addr.equals(ipv6Prefix + "1")) { 240 | return getAddressPrefix(ipv6Addr); 241 | } 242 | } 243 | } 244 | } catch (SocketException e) { 245 | e.printStackTrace(); 246 | } 247 | return null; 248 | } 249 | 250 | private String hasXlat(String currentInterface) { 251 | String clatInterface = "v4-" + currentInterface; 252 | try { 253 | NetworkInterface networkInterface = NetworkInterface.getByName(clatInterface); 254 | if (networkInterface != null) { 255 | return clatInterface; 256 | } 257 | } catch (SocketException e) { 258 | e.printStackTrace(); 259 | } 260 | return currentInterface; 261 | } 262 | 263 | private void socks5Config(String IPv6Interface) { 264 | File file = new File(getFilesDir().getPath() + "/socks.yml"); 265 | try (FileWriter writer = new FileWriter(file)) { 266 | writer.append("main:\n"); 267 | writer.append(" workers: 15\n"); 268 | writer.append(" port: 1080\n"); 269 | writer.append(" listen-address: '::1'\n"); 270 | writer.append(" listen-ipv6-only: true\n"); 271 | writer.append(" bind-interface: '").append(IPv6Interface).append("'\n"); 272 | writer.append("misc:\n"); 273 | writer.append(" task-stack-size: 30720\n"); 274 | writer.append(" pid-file: ").append(getFilesDir().getPath()).append("/socks.pid\n\n"); 275 | writer.flush(); 276 | } catch (IOException e) { 277 | e.printStackTrace(); 278 | } 279 | } 280 | 281 | private void tproxyConfig(String ipv6Prefix) { 282 | File file = new File(getFilesDir().getPath() + "/tproxy.yml"); 283 | try (FileWriter writer = new FileWriter(file)) { 284 | writer.append("socks5:\n"); 285 | writer.append(" port: 1080\n"); 286 | writer.append(" address: '::1'\n\n"); 287 | writer.append(" udp: 'udp'\n\n"); 288 | writer.append("tcp:\n"); 289 | writer.append(" port: 1088\n"); 290 | writer.append(" address: '::1'\n\n"); 291 | writer.append("udp:\n"); 292 | writer.append(" port: 1088\n"); 293 | writer.append(" address: '::1'\n\n"); 294 | writer.append("dns:\n"); 295 | writer.append(" port: 1053\n"); 296 | writer.append(" address: '::'\n"); 297 | writer.append(" upstream: '").append(ipv6Prefix).append("1'\n"); 298 | writer.append("misc:\n"); 299 | writer.append(" task-stack-size: 30720\n"); 300 | writer.append(" pid-file: ").append(getFilesDir().getPath()).append("/tproxy.pid\n\n"); 301 | writer.flush(); 302 | } catch (IOException e) { 303 | e.printStackTrace(); 304 | } 305 | } 306 | 307 | // FIXME - BUG - disable IPv6 when IPv6 is unavailable 308 | // FIXME - FEATURE - disable IPv6 when MTU is lower than spec allows 309 | // FIXME !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 310 | // FIXME - Increase timer after failed cellular recovery 311 | // FIXME - Increase timer after failed VPN recovery 312 | // FIXME - Add upstream check when starting a VPN service, wait on on network availability 313 | // FIXME - Remove callback when waiting on network availability, use receivers instead 314 | // FIXME !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 315 | private void restoreTether() { 316 | 317 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 318 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 319 | String upstreamInterface = sharedPref.getString("upstreamInterface", "Auto"); 320 | String lastIPv4Interface = sharedPref.getString("lastIPv4Interface", ""); 321 | String lastIPv6Interface = sharedPref.getString("lastIPv6Interface", ""); 322 | String lastIPv6 = sharedPref.getString("lastIPv6", ""); 323 | String ipv6TYPE = sharedPref.getString("ipv6TYPE", "None"); 324 | boolean mangleTTL = sharedPref.getBoolean("mangleTTL", true); 325 | boolean dpiCircumvention = sharedPref.getBoolean("dpiCircumvention", false); 326 | boolean dnsmasq = sharedPref.getBoolean("dnsmasq", true); 327 | String ipv6Prefix = sharedPref.getBoolean("ipv6Default", false) ? "2001:db8::" : "fd00::"; 328 | String ipv4Addr = sharedPref.getString("ipv4Addr", "192.168.42.129"); 329 | int autostartVPN = sharedPref.getInt("autostartVPN", 0); 330 | String wireguardProfile = sharedPref.getString("wireguardProfile", "wgcf-profile"); 331 | String clientBandwidth = sharedPref.getString("clientBandwidth", "0"); 332 | boolean cellularWatchdog = sharedPref.getBoolean("cellularWatchdog", false); 333 | 334 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 335 | Network activeNetwork = null; 336 | if (connectivityManager != null) { 337 | activeNetwork = connectivityManager.getActiveNetwork(); 338 | } 339 | String interfaceName = updateNetworkMap(connectivityManager, activeNetwork); 340 | 341 | cellularInterface = isCellularInterface(activeNetwork, connectivityManager) ? interfaceName : cellularInterface; 342 | String currentIPv6Interface = pickInterface(upstreamInterface, interfaceName, activeNetwork, autostartVPN, connectivityManager); 343 | String currentIPv4Interface = currentIPv6Interface; 344 | 345 | Log.i("USBTether", "Checking connection availability..."); 346 | boolean cellularUP = true; 347 | String cellularIPv6 = cellularInterface; 348 | if (cellularIPv6 != null) { 349 | String cellularIPv4 = hasXlat(cellularIPv6); 350 | if (currentIPv6Interface == null || !currentIPv6Interface.equals(cellularIPv6)) { 351 | if (cellularWatchdog) { 352 | // Only check that any protocol is working, it's not the tethered network so we don't care if just one goes down 353 | // Keeps from having to pull APN configs, check for plat servers, and getting caught in loops during partial outages 354 | cellularUP = Script.testConnection(cellularIPv4, false) || Script.testConnection(cellularIPv6, true); 355 | } 356 | } else { 357 | currentIPv4Interface = cellularIPv4; 358 | } 359 | // can't assume cellularInterface has been assigned so do nothing 360 | /*} else if (cellularWatchdog) { 361 | cellularUP = false;*/ 362 | } 363 | if (cellularUP) { 364 | if (currentIPv6Interface != null && Script.testConnection(currentIPv4Interface, false) && ((!ipv6TYPE.equals("MASQUERADE") && !ipv6TYPE.equals("SNAT")) || Script.testConnection(currentIPv6Interface, true))) { 365 | offlineCounter = 0; 366 | if (!natApplied || ((upstreamInterface.equals("Auto") || autostartVPN > 0) && !currentIPv6Interface.equals(lastIPv6Interface))) { 367 | // Configure Tether 368 | if (!natApplied) { 369 | Log.i("USBTether", "Starting tether operation..."); 370 | } else { 371 | Log.i("USBTether", "Network changed, resetting interface..."); 372 | Script.unconfigureTether(tetherInterface, lastIPv4Interface, lastIPv6Interface, ipv4Addr, ipv6Prefix, ipv6TYPE, lastIPv6, mangleTTL, dnsmasq, getFilesDir().getPath(), clientBandwidth, dpiCircumvention); 373 | natApplied = false; 374 | } 375 | String ipv6Addr = setupSNAT(currentIPv6Interface, ipv6TYPE.equals("SNAT")); 376 | if (ipv6TYPE.equals("TPROXY")) { 377 | socks5Config(currentIPv6Interface); 378 | } 379 | SharedPreferences.Editor edit = sharedPref.edit(); 380 | edit.putString("lastIPv4Interface", currentIPv4Interface); 381 | edit.putString("lastIPv6Interface", currentIPv6Interface); 382 | edit.putString("lastIPv6", ipv6Addr); 383 | edit.apply(); 384 | if (Script.configureTether(tetherInterface, tetherLocalPrefix, currentIPv4Interface, currentIPv6Interface, ipv4Addr, ipv6Prefix, ipv6TYPE, ipv6Addr, mangleTTL, dnsmasq, getApplicationInfo().nativeLibraryDir, getFilesDir().getPath(), clientBandwidth, dpiCircumvention)) { 385 | natApplied = true; 386 | if (ipv6TYPE.equals("TPROXY")) { 387 | // Add TPROXY exception 388 | String prefix = getGlobalPrefix(currentIPv6Interface, ipv6Prefix); 389 | if (prefix != null) { 390 | Log.i("TetherTPROXY", prefix); 391 | Script.setTPROXYRoute(prefix); 392 | } 393 | } 394 | networkStatus = "Tether is configured"; 395 | notification.setContentTitle(networkStatus + ", " + usbStatus); 396 | mNotificationManager.notify(1, notification.build()); 397 | needsReset = false; // clear this if set 398 | } else { 399 | Log.w("USBTether", "Failed configuring tether, resetting interface..."); 400 | Script.unconfigureInterface(tetherInterface); 401 | Script.unconfigureRNDIS(getApplicationInfo().nativeLibraryDir); 402 | usbInterface = Script.configureRNDIS(getApplicationInfo().nativeLibraryDir); 403 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 404 | Log.i("USBTether", "Resetting interface, creating callback to retry tether in 5 seconds..."); 405 | handler.postDelayed(delayedRestore, 5000); 406 | networkStatus = "Retrying after configuration failure"; 407 | notification.setContentTitle(networkStatus + ", " + usbStatus); 408 | mNotificationManager.notify(1, notification.build()); 409 | } 410 | } 411 | } else { 412 | // Restore Tether 413 | if (needsReset) { 414 | Log.i("USBTether", "Restoring tether..."); 415 | // Update SNAT if needed 416 | String newAddr = setupSNAT(currentIPv6Interface, ipv6TYPE.equals("SNAT")); 417 | if (!newAddr.isEmpty() && !newAddr.equals(lastIPv6)) { 418 | Script.refreshSNAT(currentIPv6Interface, lastIPv6, newAddr); 419 | SharedPreferences.Editor edit = sharedPref.edit(); 420 | edit.putString("lastIPv6", newAddr); 421 | edit.apply(); 422 | } 423 | if (ipv6TYPE.equals("TPROXY")) { 424 | // Add TPROXY exception 425 | String prefix = getGlobalPrefix(currentIPv6Interface, ipv6Prefix); 426 | if (prefix != null) { 427 | Log.i("TetherTPROXY", prefix); 428 | Script.setTPROXYRoute(prefix); 429 | } 430 | } 431 | if (Script.checkRules(currentIPv4Interface, currentIPv6Interface)) { 432 | // Brings tether back up on connection change 433 | Script.unconfigureRules(); 434 | Script.configureRules(tetherInterface, currentIPv4Interface, currentIPv6Interface); 435 | } else { 436 | Log.i("USBTether", "ip rules still valid"); 437 | } 438 | networkStatus = "Tether is configured"; 439 | notification.setContentTitle(networkStatus + ", " + usbStatus); 440 | mNotificationManager.notify(1, notification.build()); 441 | needsReset = false; 442 | } else { 443 | if (dnsmasq || mangleTTL || dpiCircumvention || ipv6TYPE.equals("TPROXY")) { 444 | Log.i("USBTether", "Checking processes..."); 445 | Script.checkProcesses(tetherInterface, ipv4Addr, ipv6Prefix, ipv6TYPE, mangleTTL, dnsmasq, dpiCircumvention, getApplicationInfo().nativeLibraryDir, getFilesDir().getPath()); 446 | } 447 | networkStatus = "Tether is configured"; 448 | notification.setContentTitle(networkStatus + ", " + usbStatus); 449 | mNotificationManager.notify(1, notification.build()); 450 | } 451 | } 452 | // Start watchdog if nothing else is scheduled 453 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 454 | watchdogActive = true; 455 | Log.i("USBTether", "Creating callback to check tether in 120 seconds..."); 456 | handler.postDelayed(delayedRestore, 120000); 457 | } 458 | } else if (autostartVPN > 0) { 459 | // Reset VPN if tether hasn't been applied, interface is missing, or has been offline for 25 seconds 460 | boolean resetVPN = !natApplied || currentIPv6Interface == null || offlineCounter >= 5; 461 | if (resetVPN) { 462 | offlineCounter = 0; 463 | Log.w("USBTether", "VPN connection is DOWN, restarting..."); 464 | startVPN(autostartVPN, wireguardProfile); 465 | if (natApplied) { 466 | needsReset = true; 467 | } 468 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 469 | Log.i("USBTether", "Waiting for VPN, creating callback to restore tether in 10 seconds..."); 470 | handler.postDelayed(delayedRestore, 10000); 471 | networkStatus = "Waiting for VPN network"; 472 | notification.setContentTitle(networkStatus + ", " + usbStatus); 473 | mNotificationManager.notify(1, notification.build()); 474 | } 475 | } else { 476 | offlineCounter += 1; 477 | Log.i("USBTether", "VPN offline, waiting on counter"); 478 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 479 | Log.i("USBTether", "Waiting for VPN, creating callback to restore tether in 5 seconds..."); 480 | handler.postDelayed(delayedRestore, 5000); 481 | networkStatus = "Waiting for VPN network"; 482 | notification.setContentTitle(networkStatus + ", " + usbStatus); 483 | mNotificationManager.notify(1, notification.build()); 484 | } 485 | } 486 | } else { 487 | // network down, no recourse 488 | offlineCounter = offlineCounter + 1; 489 | Log.w("USBTether", "Failed, tether interface unavailable"); 490 | if (natApplied) { 491 | needsReset = true; 492 | } 493 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 494 | Log.i("USBTether", "Waiting for network, creating callback to restore tether in 5 seconds..."); 495 | handler.postDelayed(delayedRestore, 5000); 496 | networkStatus = "Waiting for network availability"; 497 | notification.setContentTitle(networkStatus + ", " + usbStatus); 498 | mNotificationManager.notify(1, notification.build()); 499 | } 500 | } 501 | } else { 502 | // Reset cellular if tether hasn't been applied, interface is missing, or has been offline for 25 seconds 503 | if (!natApplied || cellularIPv6 == null || offlineCounter >= 5) { 504 | offlineCounter = 0; 505 | Log.w("USBTether", "Cellular connection is DOWN, attempting recovery"); 506 | Script.recoverDataConnection(); 507 | if (natApplied) { 508 | needsReset = true; 509 | } 510 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 511 | Log.i("USBTether", "Recovering cellular connection, creating callback to restore tether in 25 seconds..."); 512 | handler.postDelayed(delayedRestore, 25000); 513 | networkStatus = "Recovering cellular connection"; 514 | notification.setContentTitle(networkStatus + ", " + usbStatus); 515 | mNotificationManager.notify(1, notification.build()); 516 | } 517 | } else { 518 | offlineCounter += 1; 519 | Log.i("USBTether", "Cellular offline, waiting on counter"); 520 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 521 | Log.i("USBTether", "Waiting for cellular network, creating callback to restore tether in 5 seconds..."); 522 | handler.postDelayed(delayedRestore, 5000); 523 | networkStatus = "Waiting for cellular network"; 524 | notification.setContentTitle(networkStatus + ", " + usbStatus); 525 | mNotificationManager.notify(1, notification.build()); 526 | } 527 | } 528 | } 529 | } 530 | 531 | // Some devices go into Discharging state rather then Not Charging 532 | // when charge control apps are used, so we can't use BatteryManager 533 | // This actually works way better anyway, even though it's undocumented 534 | private final BroadcastReceiver USBReceiver = new BroadcastReceiver() { 535 | @Override 536 | public void onReceive(Context context, Intent intent) { 537 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 538 | Log.i("USBTether", "Recieved USB_STATE broadcast"); 539 | Bundle extras = intent.getExtras(); 540 | if (extras != null) { // Work around this? Can always pull from sysfs 541 | if (extras.getBoolean("connected")) { 542 | Log.i("USBTether", "USB Connected"); 543 | if (extras.getBoolean("configured")) { 544 | Log.i("USBTether", "USB Configured"); 545 | Script.bindBridge(tetherInterface, usbInterface); 546 | usbStatus = "USB connected"; 547 | notification.setContentTitle(networkStatus + ", " + usbStatus); 548 | mNotificationManager.notify(1, notification.build()); 549 | } else { 550 | Log.i("USBTether", "USB Not Configured"); 551 | } 552 | } else { 553 | Log.i("USBTether", "USB Disconnected"); 554 | usbStatus = "USB disconnected"; 555 | notification.setContentTitle(networkStatus + ", " + usbStatus); 556 | mNotificationManager.notify(1, notification.build()); 557 | } 558 | } 559 | } 560 | }; 561 | 562 | // TESTING 563 | // I'm making a big assumption here that a change in hash can be used to detect invalidated routes. 564 | // This is probably not true and will result connections loss without automated recovery. 565 | // The route check is relatively cheap, why am I wasting time doing this? 566 | private final ConnectivityManager.NetworkCallback NETReceiver = new ConnectivityManager.NetworkCallback() { 567 | @Override 568 | public void onLost(@NonNull Network network) { 569 | super.onLost(network); 570 | 571 | Log.i("USBTether", "Received a ConnectivityManager onLost event"); 572 | 573 | if (watchdogActive) { 574 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 575 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 576 | String upstreamInterface = sharedPref.getString("upstreamInterface", "Auto"); 577 | String lastIPv6Interface = sharedPref.getString("lastIPv6Interface", ""); 578 | int autostartVPN = sharedPref.getInt("autostartVPN", 0); 579 | boolean cellularWatchdog = sharedPref.getBoolean("cellularWatchdog", false); 580 | 581 | boolean setCallback = false; 582 | Network activeNetwork = null; 583 | if (connectivityManager != null) { 584 | activeNetwork = connectivityManager.getActiveNetwork(); 585 | } 586 | //String interfaceName = updateNetworkMap(connectivityManager, activeNetwork); 587 | String interfaceName = getInterfaceName(connectivityManager, activeNetwork); 588 | Integer hash; 589 | if ((upstreamInterface.equals("Auto") || autostartVPN > 0) && !lastIPv6Interface.equals(pickInterface(upstreamInterface, interfaceName, activeNetwork, autostartVPN, connectivityManager))) { 590 | Log.i("USBTether", "Active network " + lastIPv6Interface + " changed..."); 591 | setCallback = true; 592 | } else if (networkMap.containsKey(lastIPv6Interface) && (hash = networkMap.get(lastIPv6Interface)) != null && hash == network.hashCode()) { 593 | Log.i("USBTether", "Tethered network " + lastIPv6Interface + " was lost..."); 594 | needsReset = true; 595 | setCallback = true; 596 | } else if (cellularWatchdog && cellularInterface != null && networkMap.containsKey(cellularInterface) && (hash = networkMap.get(cellularInterface)) != null && hash == network.hashCode()) { 597 | Log.i("USBTether", "Cellular network " + cellularInterface + " was lost..."); 598 | needsReset = true; 599 | setCallback = true; 600 | } 601 | // FIXME - manually check network if it's not contained in networkMap? 602 | 603 | if (setCallback) { 604 | if (HandlerCompat.hasCallbacks(handler, delayedRestore)) { 605 | Log.i("USBTether", "Clearing watchdog after network change"); 606 | handler.removeCallbacks(delayedRestore); 607 | watchdogActive = false; 608 | } 609 | Log.i("USBTether", "Network lost, creating callback to restore tether in 5 seconds..."); 610 | handler.postDelayed(delayedRestore, 5000); 611 | networkStatus = "Restoring after lost network"; 612 | notification.setContentTitle(networkStatus + ", " + usbStatus); 613 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 614 | mNotificationManager.notify(1, notification.build()); 615 | } 616 | } 617 | } 618 | 619 | // FIXME - hashCode() doesn't always change, verify it actually triggers on rule loss 620 | // might be better to just check directly? 621 | @Override 622 | public void onLinkPropertiesChanged(@NonNull Network network, @NonNull LinkProperties linkProperties) { 623 | super.onLinkPropertiesChanged(network, linkProperties); 624 | 625 | Log.i("USBTether", "Received a ConnectivityManager onLinkPropertiesChanged event"); 626 | 627 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); 628 | Network activeNetwork = null; 629 | if (connectivityManager != null) { 630 | activeNetwork = connectivityManager.getActiveNetwork(); 631 | } 632 | //String interfaceName = updateNetworkMap(connectivityManager, activeNetwork); 633 | String interfaceName = getInterfaceName(connectivityManager, activeNetwork); 634 | 635 | cellularInterface = isCellularInterface(activeNetwork, connectivityManager) ? interfaceName : cellularInterface; 636 | 637 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 638 | String lastIPv6Interface = sharedPref.getString("lastIPv6Interface", ""); 639 | 640 | Integer hash; 641 | int hash2 = network.hashCode(); 642 | 643 | if (watchdogActive) { 644 | // TODO: update SNAT if onAvailable is not triggered? 645 | String upstreamInterface = sharedPref.getString("upstreamInterface", "Auto"); 646 | int autostartVPN = sharedPref.getInt("autostartVPN", 0); 647 | boolean cellularWatchdog = sharedPref.getBoolean("cellularWatchdog", false); 648 | 649 | boolean setCallback = false; 650 | if ((upstreamInterface.equals("Auto") || autostartVPN > 0) && !lastIPv6Interface.equals(pickInterface(upstreamInterface, interfaceName, activeNetwork, autostartVPN, connectivityManager))) { 651 | Log.i("USBTether", "Active network " + lastIPv6Interface + " changed..."); 652 | setCallback = true; 653 | } else if (cellularWatchdog && cellularInterface != null && networkMap.containsKey(cellularInterface) && (hash = networkMap.get(cellularInterface)) != null && hash == hash2) { 654 | Log.i("USBTether", "Cellular network " + cellularInterface + " changed..."); 655 | needsReset = true; 656 | setCallback = true; 657 | } 658 | if (interfaceName != null) { 659 | if (networkMap.containsKey(interfaceName) && (hash = networkMap.get(interfaceName)) != null) { 660 | if (hash != hash2) { 661 | if (lastIPv6Interface.equals(interfaceName)) { 662 | Log.i("USBTether", "Tethered network " + lastIPv6Interface + " changed..."); 663 | needsReset = true; 664 | setCallback = true; 665 | } 666 | networkMap.replace(interfaceName, hash2); 667 | } 668 | } else { 669 | networkMap.put(interfaceName, hash2); 670 | } 671 | } 672 | 673 | if (setCallback) { 674 | if (HandlerCompat.hasCallbacks(handler, delayedRestore)) { 675 | Log.i("USBTether", "Clearing watchdog after network change"); 676 | handler.removeCallbacks(delayedRestore); 677 | watchdogActive = false; 678 | } 679 | Log.i("USBTether", "Network change, creating callback to restore tether in 5 seconds..."); 680 | handler.postDelayed(delayedRestore, 5000); 681 | networkStatus = "Restoring after network change"; 682 | notification.setContentTitle(networkStatus + ", " + usbStatus); 683 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 684 | mNotificationManager.notify(1, notification.build()); 685 | } 686 | } else if (interfaceName != null) { 687 | if (!networkMap.containsKey(interfaceName) || (hash = networkMap.get(interfaceName)) == null || hash != hash2) { 688 | Log.i("USBTether", "adding " + interfaceName + " to networkMap"); 689 | networkMap.put(interfaceName, hash2); 690 | } 691 | } 692 | } 693 | }; 694 | 695 | @Override 696 | public void onCreate() { 697 | super.onCreate(); 698 | 699 | //isStarted = true; 700 | } 701 | 702 | @SuppressLint("WakelockTimeout") 703 | @Override 704 | public int onStartCommand(Intent intent, int flags, int startId) { 705 | super.onStartCommand(intent, flags, startId); 706 | 707 | boolean hasTTL = Script.hasTTL; 708 | boolean hasNFQUEUE = Script.hasNFQUEUE; 709 | boolean hasTPROXY = Script.hasTPROXY; 710 | boolean hasTable = Script.hasTable; 711 | boolean hasSNAT = Script.hasSNAT; 712 | boolean hasMASQUERADE = Script.hasMASQUERADE; 713 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 714 | boolean mangleTTL = sharedPref.getBoolean("mangleTTL", true); 715 | String ipv6TYPE = sharedPref.getString("ipv6TYPE", "None"); 716 | String ipv6Prefix = sharedPref.getBoolean("ipv6Default", false) ? "2001:db8::" : "fd00::"; 717 | 718 | // Disable previous tether if the service was not properly stopped 719 | if (!hasRebootOccurred()) { 720 | Log.i("USBTether", "Disabling previous USBTether instance..."); 721 | String lastIPv4Interface = sharedPref.getString("lastIPv4Interface", ""); 722 | String lastIPv6Interface = sharedPref.getString("lastIPv6Interface", ""); 723 | String lastIPv6 = sharedPref.getString("lastIPv6", ""); 724 | boolean dpiCircumvention = sharedPref.getBoolean("dpiCircumvention", false); 725 | boolean dnsmasq = sharedPref.getBoolean("dnsmasq", true); 726 | String ipv4Addr = sharedPref.getString("ipv4Addr", "192.168.42.129"); 727 | String clientBandwidth = sharedPref.getString("clientBandwidth", "0"); 728 | Script.unconfigureTether(tetherInterface, lastIPv4Interface, lastIPv6Interface, ipv4Addr, ipv6Prefix, ipv6TYPE, lastIPv6, mangleTTL, dnsmasq, getFilesDir().getPath(), clientBandwidth, dpiCircumvention); 729 | Script.unconfigureRNDIS(getApplicationInfo().nativeLibraryDir); 730 | Script.destroyBridge(tetherInterface); 731 | } 732 | 733 | tproxyConfig(ipv6Prefix); 734 | 735 | SharedPreferences.Editor edit = sharedPref.edit(); 736 | if (mangleTTL && !hasTTL && !hasNFQUEUE) { 737 | edit.putBoolean("mangleTTL", false); 738 | } 739 | if ((ipv6TYPE.equals("TPROXY") && !hasTPROXY) || (ipv6TYPE.equals("SNAT") && (!hasTable || !hasSNAT)) || (ipv6TYPE.equals("MASQUERADE") && (!hasTable || !hasMASQUERADE))) { 740 | edit.putString("ipv6TYPE", "None"); 741 | } 742 | edit.apply(); 743 | 744 | powerManager = (PowerManager) getSystemService(POWER_SERVICE); 745 | if (powerManager != null) { 746 | wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "USB Tether::TetherWakelockTag"); 747 | if (wakeLock != null && !wakeLock.isHeld()) { 748 | wakeLock.acquire(); 749 | } 750 | } 751 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 752 | NotificationChannel serviceChannel = new NotificationChannel(CHANNEL_ID, "Foreground Service Channel", NotificationManager.IMPORTANCE_HIGH); 753 | NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 754 | if (notificationManager != null) { 755 | notificationManager.createNotificationChannel(serviceChannel); 756 | } 757 | } 758 | 759 | Toast.makeText(this, "Service started by user.", Toast.LENGTH_LONG).show(); 760 | 761 | notification.setContentTitle(networkStatus + ", " + usbStatus); 762 | startForeground(1, notification.build()); 763 | 764 | usbInterface = Script.configureRNDIS(getApplicationInfo().nativeLibraryDir); 765 | Script.createBridge(tetherInterface); 766 | tetherLocalPrefix = getLocalPrefix(tetherInterface); 767 | assert tetherLocalPrefix != null; // FIXME should never be null but... 768 | 769 | if (!HandlerCompat.hasCallbacks(handler, delayedRestore)) { 770 | handler.postDelayed(delayedRestore, 5000); 771 | } 772 | 773 | registerReceiver(USBReceiver, new IntentFilter("android.hardware.usb.action.USB_STATE")); 774 | 775 | NetworkRequest.Builder builder = new NetworkRequest.Builder(); 776 | builder = builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) 777 | .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) 778 | .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED); 779 | NetworkRequest networkRequest = builder.build(); 780 | 781 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 782 | connectivityManager.registerNetworkCallback(networkRequest, NETReceiver); 783 | 784 | // Some devices don't broadcast after USBReceiver is registered 785 | if (Script.isUSBConfigured()) { 786 | Script.bindBridge(tetherInterface, usbInterface); 787 | NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 788 | usbStatus = "USB connected"; 789 | notification.setContentTitle(networkStatus + ", " + usbStatus); 790 | mNotificationManager.notify(1, notification.build()); 791 | } 792 | 793 | return Service.START_STICKY; 794 | } 795 | 796 | @Override 797 | public void onDestroy() { 798 | super.onDestroy(); 799 | 800 | if (wakeLock != null && wakeLock.isHeld()) { 801 | wakeLock.release(); 802 | } 803 | 804 | try { 805 | unregisterReceiver(USBReceiver); 806 | } catch (IllegalArgumentException ignored) {} 807 | 808 | ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 809 | connectivityManager.unregisterNetworkCallback(NETReceiver); 810 | 811 | if (HandlerCompat.hasCallbacks(handler, delayedRestore)) { 812 | handler.removeCallbacks(delayedRestore); 813 | } 814 | 815 | SharedPreferences sharedPref = getSharedPreferences("Settings", Context.MODE_PRIVATE); 816 | String lastIPv4Interface = sharedPref.getString("lastIPv4Interface", ""); 817 | String lastIPv6Interface = sharedPref.getString("lastIPv6Interface", ""); 818 | String lastIPv6 = sharedPref.getString("lastIPv6", ""); 819 | String ipv6TYPE = sharedPref.getString("ipv6TYPE", "None"); 820 | boolean mangleTTL = sharedPref.getBoolean("mangleTTL", true); 821 | boolean dpiCircumvention = sharedPref.getBoolean("dpiCircumvention", false); 822 | boolean dnsmasq = sharedPref.getBoolean("dnsmasq", true); 823 | String ipv4Addr = sharedPref.getString("ipv4Addr", "192.168.42.129"); 824 | String ipv6Prefix = sharedPref.getBoolean("ipv6Default", false) ? "2001:db8::" : "fd00::"; 825 | String clientBandwidth = sharedPref.getString("clientBandwidth", "0"); 826 | 827 | SharedPreferences.Editor editor = sharedPref.edit(); 828 | editor.putLong("lastRun", -1); 829 | editor.apply(); 830 | 831 | if (!lastIPv6Interface.isEmpty()) { 832 | Script.unconfigureTether(tetherInterface, lastIPv4Interface, lastIPv6Interface, ipv4Addr, ipv6Prefix, ipv6TYPE, lastIPv6, mangleTTL, dnsmasq, getFilesDir().getPath(), clientBandwidth, dpiCircumvention); 833 | Script.unconfigureRNDIS(getApplicationInfo().nativeLibraryDir); 834 | Script.destroyBridge(tetherInterface); 835 | } 836 | 837 | natApplied = false; 838 | isStarted = false; 839 | 840 | Toast.makeText(this, "Service destroyed by user.", Toast.LENGTH_LONG).show(); 841 | } 842 | 843 | @Override 844 | public IBinder onBind(Intent intent) { 845 | return null; 846 | } 847 | } 848 | --------------------------------------------------------------------------------