├── 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 |
--------------------------------------------------------------------------------