diff --git a/.gitignore b/.gitignore index aedf400..0b776bf 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ .externalNativeBuild .cxx local.properties +/app/src/main/jniLibs diff --git a/.gitmodules b/.gitmodules index b87085c..f712c59 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "app/libs/tun2socks"] - path = app/libs/tun2socks - url = https://github.com/dovecoteescapee/tun2socks.git -[submodule "app/src/main/cpp/byedpi"] +[submodule "hev-socks5-tunnel"] + path = app/src/main/jni/hev-socks5-tunnel + url = https://github.com/heiher/hev-socks5-tunnel.git +[submodule "byedpi"] path = app/src/main/cpp/byedpi url = https://github.com/hufrea/byedpi.git diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 31f9673..d76c0bb 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,7 +2,11 @@ - + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 005d47b..354635f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,3 @@ -import com.android.build.gradle.internal.tasks.factory.dependsOn - plugins { id("com.android.application") id("org.jetbrains.kotlin.android") @@ -13,10 +11,17 @@ android { applicationId = "io.github.dovecoteescapee.byedpi" minSdk = 21 targetSdk = 34 - versionCode = 7 - versionName = "1.0.2" + versionCode = 8 + versionName = "1.1.0-beta" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + ndk { + abiFilters.add("armeabi-v7a") + abiFilters.add("arm64-v8a") + abiFilters.add("x86") + abiFilters.add("x86_64") + } } buildFeatures { @@ -61,8 +66,6 @@ android { } dependencies { - implementation(files("libs/tun2socks.aar")) - implementation("androidx.fragment:fragment-ktx:1.8.2") implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.appcompat:appcompat:1.7.0") @@ -77,75 +80,25 @@ dependencies { androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") } -abstract class BaseTun2SocksTask : DefaultTask() { - @get:InputDirectory - val tun2socksDir: File - get() = project.file("libs/tun2socks") - - @get:OutputFile - val tun2socksOutput: File - get() = project.file("libs/tun2socks.aar") - - @Internal - protected fun isUpToDate(): Boolean { - if (tun2socksOutput.exists()) { - val lastModified = tun2socksOutput.lastModified() - return !tun2socksDir.walkTopDown().any { - it.isFile && it.lastModified() > lastModified - } - } - return false - } -} - -abstract class GetGomobileBind : BaseTun2SocksTask() { - @TaskAction - fun getBind() { - if (isUpToDate()) { - logger.lifecycle("No changes detected, skipping getBind.") - return - } - - project.exec { - workingDir = tun2socksDir - - commandLine("go", "get", "golang.org/x/mobile/bind") - } - } -} - -abstract class BuildTun2Socks : BaseTun2SocksTask() { - @TaskAction - fun buildTun2Socks() { - if (isUpToDate()) { - logger.lifecycle("No changes detected, skipping buildTun2Socks.") - return - } - - project.exec { - workingDir = tun2socksDir - - commandLine( - "gomobile", "bind", - "-target", "android", - "-androidapi", "21", - "-o", tun2socksOutput, - "-trimpath", - "./engine" - ) - } - } -} - -val getGomobileBind = tasks.register("getGomobileBind") { +tasks.register("runNdkBuild") { group = "build" - description = "Get gomobile bind for compiling tun2socks" + + val ndkDir = android.ndkDirectory + executable = if (System.getProperty("os.name").startsWith("Windows", ignoreCase = true)) { + "$ndkDir\\ndk-build.cmd" + } else { + "$ndkDir/ndk-build" + } + setArgs(listOf( + "NDK_PROJECT_PATH=build/intermediates/ndkBuild", + "NDK_LIBS_OUT=src/main/jniLibs", + "APP_BUILD_SCRIPT=src/main/jni/Android.mk", + "NDK_APPLICATION_MK=src/main/jni/Application.mk" + )) + + println("Command: $commandLine") } -val buildTun2Socks = tasks.register("buildTun2Socks") { - group = "build" - description = "Build tun2socks for Android" - dependsOn(getGomobileBind) -} - -tasks.preBuild.dependsOn(buildTun2Socks) +tasks.preBuild { + dependsOn("runNdkBuild") +} \ No newline at end of file diff --git a/app/libs/tun2socks b/app/libs/tun2socks deleted file mode 160000 index e083daf..0000000 --- a/app/libs/tun2socks +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e083dafcf534d85e7fce86b3d48ee5c7a63c604e diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 2e1d16f..dee3304 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -1,53 +1,14 @@ -# For more information about using CMake with Android Studio, read the -# documentation: https://d.android.com/studio/projects/add-native-code.html. -# For more examples on how to use CMake, see https://github.com/android/ndk-samples. - -# Sets the minimum CMake version required for this project. cmake_minimum_required(VERSION 3.22.1) -# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, -# Since this is the top level CMakeLists.txt, the project name is also accessible -# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level -# build script scope). -project("byedpi") +project(byedpi_native) -# Creates and names a library, sets it as either STATIC -# or SHARED, and provides the relative paths to its source code. -# You can define multiple libraries, and CMake builds them for you. -# Gradle automatically packages shared libraries with your APK. -# -# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define -# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} -# is preferred for the same purpose. -# -# In order to load a library into your app from Java/Kotlin, you must call -# System.loadLibrary() and pass the name of the library defined here; -# for GameActivity/NativeActivity derived applications, the same library name must be -# used in the AndroidManifest.xml file. -add_library(${CMAKE_PROJECT_NAME} SHARED - # List C/C++ source files with relative paths to this CMakeLists.txt. - byedpi/conev.c - byedpi/desync.c - byedpi/extend.c - byedpi/packets.c - byedpi/proxy.c - byedpi/main.c - byedpi/mpool.c - native-lib.c - utils.c - ) +file(GLOB BYE_DPI_SRC byedpi/*.c) +list(REMOVE_ITEM BYE_DPI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/byedpi/win_service.c) -include_directories("byedpi") +add_library(byedpi SHARED ${BYE_DPI_SRC} native-lib.c utils.c) +target_include_directories(byedpi PRIVATE byedpi) -set(CMAKE_C_FLAGS "-std=c99 -O2 -D_XOPEN_SOURCE=500") +target_compile_options(byedpi PRIVATE -std=c99 -O2 -D_XOPEN_SOURCE=500) +target_compile_definitions(byedpi PRIVATE ANDROID_APP) -add_compile_definitions(ANDROID_APP) - -# Specifies libraries CMake should link to your target library. You -# can link libraries from various origins, such as libraries defined in this -# build script, prebuilt third-party libraries, or Android system libraries. -target_link_libraries(${CMAKE_PROJECT_NAME} - # List libraries link to the target library - android - log - ) \ No newline at end of file +target_link_libraries(byedpi PRIVATE android log) diff --git a/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ByeDpiVpnService.kt b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ByeDpiVpnService.kt index 45e6714..f5fdb7c 100644 --- a/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ByeDpiVpnService.kt +++ b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/ByeDpiVpnService.kt @@ -8,9 +8,6 @@ import android.os.Build import android.os.ParcelFileDescriptor import android.util.Log import androidx.lifecycle.lifecycleScope -import engine.Engine -import engine.Key -import io.github.dovecoteescapee.byedpi.BuildConfig import io.github.dovecoteescapee.byedpi.R import io.github.dovecoteescapee.byedpi.activities.MainActivity import io.github.dovecoteescapee.byedpi.core.ByeDpiProxy @@ -23,11 +20,12 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext +import java.io.File class ByeDpiVpnService : LifecycleVpnService() { - private val proxy = ByeDpiProxy() + private val byeDpiProxy = ByeDpiProxy() private var proxyJob: Job? = null - private var vpn: ParcelFileDescriptor? = null + private var tunFd: ParcelFileDescriptor? = null private val mutex = Mutex() private var stopping: Boolean = false @@ -138,7 +136,7 @@ class ByeDpiVpnService : LifecycleVpnService() { val preferences = getByeDpiPreferences() proxyJob = lifecycleScope.launch(Dispatchers.IO) { - val code = proxy.startProxy(preferences) + val code = byeDpiProxy.startProxy(preferences) withContext(Dispatchers.Main) { if (code != 0) { @@ -164,7 +162,7 @@ class ByeDpiVpnService : LifecycleVpnService() { return } - proxy.stopProxy() + byeDpiProxy.stopProxy() proxyJob?.join() ?: throw IllegalStateException("ProxyJob field null") proxyJob = null @@ -174,7 +172,7 @@ class ByeDpiVpnService : LifecycleVpnService() { private fun startTun2Socks() { Log.i(TAG, "Starting tun2socks") - if (vpn != null) { + if (tunFd != null) { throw IllegalStateException("VPN field not null") } @@ -182,22 +180,49 @@ class ByeDpiVpnService : LifecycleVpnService() { val port = sharedPreferences.getString("byedpi_proxy_port", null)?.toInt() ?: 1080 val dns = sharedPreferences.getStringNotNull("dns_ip", "1.1.1.1") + val tun2socksConfig = """ + | misc: + | task-stack-size: 81920 + | socks5: + | mtu: 8500 + | address: 127.0.0.1 + | port: $port + | udp: udp + """.trimMargin("| ") + + val configPath = try { + File.createTempFile("config", "tmp", cacheDir).apply { + writeText(tun2socksConfig) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to create config file", e) + throw e + } + val vpn = createBuilder(dns).establish() ?: throw IllegalStateException("VPN connection failed") - this.vpn = vpn -// val fd = vpn.detachFd() - Engine.insert(createKey(vpn.fd, port)) - Engine.start() + this.tunFd = vpn + + Log.d(TAG, "Native tun2socks start") + TProxyService.TProxyStartService(configPath.absolutePath, vpn.fd) Log.i(TAG, "Tun2Socks started") } private fun stopTun2Socks() { Log.i(TAG, "Stopping tun2socks") -// Engine.stop() // sometimes crashes with fdsan - vpn?.close() ?: Log.w(TAG, "VPN not running") // Is engine close sockets? - vpn = null + + TProxyService.TProxyStopService() + Log.d(TAG, "Native tun2socks stopped done") + + tunFd?.close() ?: Log.w(TAG, "VPN not running") + tunFd = null + try { + File(cacheDir, "config.tmp").delete() + } catch (e: SecurityException) { + Log.e(TAG, "Failed to delete config file", e) + } Log.i(TAG, "Tun2socks stopped") } @@ -212,6 +237,7 @@ class ByeDpiVpnService : LifecycleVpnService() { setStatus( when (newStatus) { ServiceStatus.Connected -> AppStatus.Running + ServiceStatus.Disconnected, ServiceStatus.Failed -> { proxyJob = null @@ -261,23 +287,8 @@ class ByeDpiVpnService : LifecycleVpnService() { builder.setMetered(false) } - builder.addDisallowedApplication("io.github.dovecoteescapee.byedpi") + builder.addDisallowedApplication(applicationContext.packageName) return builder } - - private fun createKey(fd: Int, port: Int): Key = Key().apply { - mark = 0 - mtu = 0 - device = "fd://${fd}" - - setInterface("") - logLevel = if (BuildConfig.DEBUG) "debug" else "info" - proxy = "socks5://127.0.0.1:$port" - - restAPI = "" - tcpSendBufferSize = "" - tcpReceiveBufferSize = "" - tcpModerateReceiveBuffer = false - } } diff --git a/app/src/main/java/io/github/dovecoteescapee/byedpi/services/LifecycleVpnService.kt b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/LifecycleVpnService.kt index d6dc85d..49010f5 100644 --- a/app/src/main/java/io/github/dovecoteescapee/byedpi/services/LifecycleVpnService.kt +++ b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/LifecycleVpnService.kt @@ -9,7 +9,7 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ServiceLifecycleDispatcher /** - * Based on @link [androidx.lifecycle.LifecycleService] + * Based on [androidx.lifecycle.LifecycleService] */ open class LifecycleVpnService : VpnService(), LifecycleOwner { @Suppress("LeakingThis") @@ -28,10 +28,10 @@ open class LifecycleVpnService : VpnService(), LifecycleOwner { } @Deprecated("Deprecated in Java") - @Suppress("DEPRECATION") @CallSuper override fun onStart(intent: Intent?, startId: Int) { dispatcher.onServicePreSuperOnStart() + @Suppress("DEPRECATION") super.onStart(intent, startId) } diff --git a/app/src/main/java/io/github/dovecoteescapee/byedpi/services/TProxyService.kt b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/TProxyService.kt new file mode 100644 index 0000000..17775a6 --- /dev/null +++ b/app/src/main/java/io/github/dovecoteescapee/byedpi/services/TProxyService.kt @@ -0,0 +1,17 @@ +package io.github.dovecoteescapee.byedpi.services + +object TProxyService { + init { + System.loadLibrary("hev-socks5-tunnel") + } + + @JvmStatic + external fun TProxyStartService(configPath: String, fd: Int) + + @JvmStatic + external fun TProxyStopService() + + @JvmStatic + @Suppress("unused") + external fun TProxyGetStats(): LongArray +} diff --git a/app/src/main/jni/Android.mk b/app/src/main/jni/Android.mk new file mode 100644 index 0000000..548efc9 --- /dev/null +++ b/app/src/main/jni/Android.mk @@ -0,0 +1,16 @@ +# Copyright (C) 2023 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +include $(call all-subdir-makefiles) diff --git a/app/src/main/jni/Application.mk b/app/src/main/jni/Application.mk new file mode 100644 index 0000000..4960cc5 --- /dev/null +++ b/app/src/main/jni/Application.mk @@ -0,0 +1,21 @@ +# Copyright (C) 2023 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +APP_OPTIM := release +APP_PLATFORM := android-21 +APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 +APP_CFLAGS := -O3 -DPKGNAME=io/github/dovecoteescapee/byedpi/services +APP_CPPFLAGS := -O3 -std=c++11 +NDK_TOOLCHAIN_VERSION := clang diff --git a/app/src/main/jni/hev-socks5-tunnel b/app/src/main/jni/hev-socks5-tunnel new file mode 160000 index 0000000..677bb45 --- /dev/null +++ b/app/src/main/jni/hev-socks5-tunnel @@ -0,0 +1 @@ +Subproject commit 677bb4530cfc867cd44d88d298960c8d8d9fbfad