mirror of
https://github.com/dovecoteescapee/ByeDPIAndroid.git
synced 2024-12-22 06:15:44 +00:00
Release 0.1.1-alpha:
- Fix bug with UDP - Fix some crashes - Add DNS setting - Add button reset settings - Add log saving - Update UI
This commit is contained in:
parent
cf8031115e
commit
54cc788ccd
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,6 +2,7 @@
|
|||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea/caches
|
/.idea/caches
|
||||||
|
/.idea/deploymentTargetDropDown.xml
|
||||||
/.idea/libraries
|
/.idea/libraries
|
||||||
/.idea/modules.xml
|
/.idea/modules.xml
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetDropDown">
|
|
||||||
<value>
|
|
||||||
<entry key="app">
|
|
||||||
<State />
|
|
||||||
</entry>
|
|
||||||
</value>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
@ -1,6 +1,6 @@
|
|||||||
# ByeDPI for Android
|
# ByeDPI for Android
|
||||||
|
|
||||||
Application for Android that start a local VPN service to bypass DPI (Deep Packet Inspection) and unblock the internet
|
Application for Android that start a local VPN service to bypass DPI (Deep Packet Inspection) and censorship.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
2
app/.gitignore
vendored
2
app/.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
/build
|
/build
|
||||||
|
/debug
|
||||||
|
/release
|
||||||
*.aar
|
*.aar
|
||||||
*.jar
|
*.jar
|
@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "io.github.dovecoteescapee.byedpi"
|
applicationId = "io.github.dovecoteescapee.byedpi"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 2
|
||||||
versionName = "0.1.0-alpha"
|
versionName = "0.1.1-alpha"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 792ee44a8efa7ec4daadd2cbfda762329d1cbff2
|
Subproject commit da7fa48a3784a5dca6714d4734a3eb3b181a96aa
|
Binary file not shown.
@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 3,
|
|
||||||
"artifactType": {
|
|
||||||
"type": "APK",
|
|
||||||
"kind": "Directory"
|
|
||||||
},
|
|
||||||
"applicationId": "io.github.dovecoteescapee.byedpi",
|
|
||||||
"variantName": "release",
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"type": "SINGLE",
|
|
||||||
"filters": [],
|
|
||||||
"attributes": [],
|
|
||||||
"versionCode": 1,
|
|
||||||
"versionName": "0.1.0-alpha",
|
|
||||||
"outputFile": "app-release.apk"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"elementType": "File"
|
|
||||||
}
|
|
@ -29,7 +29,8 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:exported="true" />
|
android:label="@string/title_settings"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<service android:name=".ByeDpiVpnService"
|
<service android:name=".ByeDpiVpnService"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
|
@ -3,9 +3,12 @@
|
|||||||
#include <params.h>
|
#include <params.h>
|
||||||
#include <packets.h>
|
#include <packets.h>
|
||||||
#include <jni.h>
|
#include <jni.h>
|
||||||
|
#include <android/log.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
extern int big_loop(int fd);
|
extern int NOT_EXIT;
|
||||||
|
|
||||||
struct packet fake_tls = {
|
struct packet fake_tls = {
|
||||||
sizeof(tls_data), tls_data
|
sizeof(tls_data), tls_data
|
||||||
@ -33,7 +36,7 @@ struct params params = {
|
|||||||
.max_open = 512,
|
.max_open = 512,
|
||||||
.bfsize = 16384,
|
.bfsize = 16384,
|
||||||
.baddr = {
|
.baddr = {
|
||||||
.sin6_family = AF_INET6
|
.sin6_family = AF_INET
|
||||||
},
|
},
|
||||||
.debug = 2
|
.debug = 2
|
||||||
};
|
};
|
||||||
@ -55,8 +58,16 @@ int get_default_ttl()
|
|||||||
return orig_ttl;
|
return orig_ttl;
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
void *run(void *srv) {
|
||||||
Java_io_github_dovecoteescapee_byedpi_ByeDpiVpnService_startProxy__IIIIZZIIZIZZZIZ(
|
LOG(LOG_S, "Start proxy thread");
|
||||||
|
listener(*((struct sockaddr_ina *) srv));
|
||||||
|
free(srv);
|
||||||
|
LOG(LOG_S, "Stop proxy thread");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jlong JNICALL
|
||||||
|
Java_io_github_dovecoteescapee_byedpi_ByeDpiVpnService_jniStartProxy(
|
||||||
JNIEnv *env,
|
JNIEnv *env,
|
||||||
jobject thiz,
|
jobject thiz,
|
||||||
jint port,
|
jint port,
|
||||||
@ -72,7 +83,8 @@ Java_io_github_dovecoteescapee_byedpi_ByeDpiVpnService_startProxy__IIIIZZIIZIZZZ
|
|||||||
jboolean host_mixed_case,
|
jboolean host_mixed_case,
|
||||||
jboolean domain_mixed_case,
|
jboolean domain_mixed_case,
|
||||||
jboolean host_remove_space,
|
jboolean host_remove_space,
|
||||||
jint tls_record_split,
|
jboolean tls_record_split,
|
||||||
|
jint tls_record_split_position,
|
||||||
jboolean tls_record_split_at_sni) {
|
jboolean tls_record_split_at_sni) {
|
||||||
enum demode desync_methods[] = {DESYNC_NONE, DESYNC_SPLIT, DESYNC_DISORDER, DESYNC_FAKE};
|
enum demode desync_methods[] = {DESYNC_NONE, DESYNC_SPLIT, DESYNC_DISORDER, DESYNC_FAKE};
|
||||||
|
|
||||||
@ -89,6 +101,7 @@ Java_io_github_dovecoteescapee_byedpi_ByeDpiVpnService_startProxy__IIIIZZIIZIZZZ
|
|||||||
params.mod_http |= domain_mixed_case ? MH_DMIX : 0;
|
params.mod_http |= domain_mixed_case ? MH_DMIX : 0;
|
||||||
params.mod_http |= host_remove_space ? MH_SPACE : 0;
|
params.mod_http |= host_remove_space ? MH_SPACE : 0;
|
||||||
params.tlsrec = tls_record_split;
|
params.tlsrec = tls_record_split;
|
||||||
|
params.tlsrec_pos = tls_record_split_position;
|
||||||
params.tlsrec_sni = tls_record_split_at_sni;
|
params.tlsrec_sni = tls_record_split_at_sni;
|
||||||
|
|
||||||
if (!params.def_ttl && params.attack != DESYNC_NONE) {
|
if (!params.def_ttl && params.attack != DESYNC_NONE) {
|
||||||
@ -97,12 +110,23 @@ Java_io_github_dovecoteescapee_byedpi_ByeDpiVpnService_startProxy__IIIIZZIIZIZZZ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sockaddr_ina srv = {
|
struct sockaddr_ina *srv = malloc(sizeof(struct sockaddr_ina));
|
||||||
.in = {
|
srv->in.sin_family = AF_INET;
|
||||||
.sin_family = AF_INET,
|
srv->in.sin_addr.s_addr = inet_addr("0.0.0.0");
|
||||||
.sin_port = htons(port),
|
srv->in.sin_port = htons(port);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return listener(srv);
|
NOT_EXIT = 1;
|
||||||
|
|
||||||
|
pthread_t proxy_thread;
|
||||||
|
if (pthread_create(&proxy_thread, NULL, run, srv) != 0) {
|
||||||
|
LOG(LOG_S, "Failed to start proxy thread");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy_thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_io_github_dovecoteescapee_byedpi_ByeDpiVpnService_jniStopProxy(JNIEnv *env, jobject thiz, jlong proxy_thread) {
|
||||||
|
NOT_EXIT = 0;
|
||||||
}
|
}
|
@ -14,7 +14,8 @@ class ByeDpiProxyPreferences(
|
|||||||
hostMixedCase: Boolean? = null,
|
hostMixedCase: Boolean? = null,
|
||||||
domainMixedCase: Boolean? = null,
|
domainMixedCase: Boolean? = null,
|
||||||
hostRemoveSpaces: Boolean? = null,
|
hostRemoveSpaces: Boolean? = null,
|
||||||
tlsRecordSplit: Int? = null,
|
tlsRecordSplit: Boolean? = null,
|
||||||
|
tlsRecordSplitPosition: Int? = null,
|
||||||
tlsRecordSplitAtSni: Boolean? = null,
|
tlsRecordSplitAtSni: Boolean? = null,
|
||||||
) {
|
) {
|
||||||
val port: Int = port ?: 1080
|
val port: Int = port ?: 1080
|
||||||
@ -23,14 +24,15 @@ class ByeDpiProxyPreferences(
|
|||||||
val defaultTtl: Int = defaultTtl ?: 0
|
val defaultTtl: Int = defaultTtl ?: 0
|
||||||
val noDomain: Boolean = noDomain ?: false
|
val noDomain: Boolean = noDomain ?: false
|
||||||
val desyncKnown: Boolean = desyncKnown ?: false
|
val desyncKnown: Boolean = desyncKnown ?: false
|
||||||
val desyncMethod: DesyncMethod = desyncMethod ?: DesyncMethod.None
|
val desyncMethod: DesyncMethod = desyncMethod ?: DesyncMethod.Disorder
|
||||||
val splitPosition: Int = splitPosition ?: 3
|
val splitPosition: Int = splitPosition ?: 3
|
||||||
val splitAtHost: Boolean = splitAtHost ?: false
|
val splitAtHost: Boolean = splitAtHost ?: false
|
||||||
val fakeTtl: Int = fakeTtl ?: 8
|
val fakeTtl: Int = fakeTtl ?: 8
|
||||||
val hostMixedCase: Boolean = hostMixedCase ?: false
|
val hostMixedCase: Boolean = hostMixedCase ?: false
|
||||||
val domainMixedCase: Boolean = domainMixedCase ?: false
|
val domainMixedCase: Boolean = domainMixedCase ?: false
|
||||||
val hostRemoveSpaces: Boolean = hostRemoveSpaces ?: false
|
val hostRemoveSpaces: Boolean = hostRemoveSpaces ?: false
|
||||||
val tlsRecordSplit: Int = tlsRecordSplit ?: 0
|
val tlsRecordSplit: Boolean = tlsRecordSplit ?: false
|
||||||
|
val tlsRecordSplitPosition: Int = tlsRecordSplitPosition ?: 0
|
||||||
val tlsRecordSplitAtSni: Boolean = tlsRecordSplitAtSni ?: false
|
val tlsRecordSplitAtSni: Boolean = tlsRecordSplitAtSni ?: false
|
||||||
|
|
||||||
enum class DesyncMethod {
|
enum class DesyncMethod {
|
||||||
|
@ -5,28 +5,26 @@ import android.content.Intent
|
|||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.ParcelFileDescriptor
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ServiceLifecycleDispatcher
|
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import engine.Engine
|
import engine.Engine
|
||||||
import engine.Key
|
import engine.Key
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
||||||
private val TAG: String = this::class.java.simpleName
|
private var proxyThread: Long = -1
|
||||||
private var proxyJob: Job? = null
|
private var vpn: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
private val dispatcher = ServiceLifecycleDispatcher(this)
|
private val dispatcher = ServiceLifecycleDispatcher(this)
|
||||||
override val lifecycle: Lifecycle
|
override val lifecycle: Lifecycle
|
||||||
get() = dispatcher.lifecycle
|
get() = dispatcher.lifecycle
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val TAG: String = ByeDpiVpnService::class.java.simpleName
|
||||||
|
|
||||||
var status: Status = Status.STOPPED
|
var status: Status = Status.STOPPED
|
||||||
private set
|
private set
|
||||||
}
|
}
|
||||||
@ -48,6 +46,7 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
run()
|
run()
|
||||||
START_STICKY
|
START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
"stop" -> {
|
"stop" -> {
|
||||||
stop()
|
stop()
|
||||||
START_NOT_STICKY
|
START_NOT_STICKY
|
||||||
@ -75,26 +74,30 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun run() {
|
private fun run() {
|
||||||
val preferences = getPreferences();
|
val preferences = getPreferences()
|
||||||
|
|
||||||
status = Status.RUNNING
|
status = Status.RUNNING
|
||||||
|
|
||||||
if (proxyJob != null) {
|
if (proxyThread >= 0) {
|
||||||
Log.w(TAG, "Proxy already running")
|
Log.w(TAG, "Proxy already running")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyJob = lifecycleScope.launch(Dispatchers.IO) {
|
proxyThread = startProxy(preferences)
|
||||||
runProxy(preferences)
|
if (proxyThread < 0) {
|
||||||
|
status = Status.STOPPED
|
||||||
|
Log.e(TAG, "Proxy failed to start")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val vpn = getBuilder().establish()
|
val vpn = getBuilder().establish()
|
||||||
|
this.vpn = vpn
|
||||||
if (vpn == null) {
|
if (vpn == null) {
|
||||||
Log.e(TAG, "VPN connection failed")
|
Log.e(TAG, "VPN connection failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
startTun2Socks(vpn.detachFd(), preferences.port)
|
Log.d(TAG, "fd: ${vpn.fd}")
|
||||||
|
startTun2Socks(vpn.fd, preferences.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPreferences(): ByeDpiProxyPreferences {
|
private fun getPreferences(): ByeDpiProxyPreferences {
|
||||||
@ -129,7 +132,9 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
hostRemoveSpaces =
|
hostRemoveSpaces =
|
||||||
sharedPreferences.getBoolean("byedpi_host_remove_spaces", false),
|
sharedPreferences.getBoolean("byedpi_host_remove_spaces", false),
|
||||||
tlsRecordSplit =
|
tlsRecordSplit =
|
||||||
sharedPreferences.getString("byedpi_tlsrec", null)?.toInt(),
|
sharedPreferences.getBoolean("byedpi_tlsrec", false),
|
||||||
|
tlsRecordSplitPosition =
|
||||||
|
sharedPreferences.getString("byedpi_tlsrec_position", null)?.toInt(),
|
||||||
tlsRecordSplitAtSni =
|
tlsRecordSplitAtSni =
|
||||||
sharedPreferences.getBoolean("byedpi_tlsrec_at_sni", false),
|
sharedPreferences.getBoolean("byedpi_tlsrec_at_sni", false),
|
||||||
)
|
)
|
||||||
@ -141,9 +146,9 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
stopProxy()
|
stopProxy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runProxy(preferences: ByeDpiProxyPreferences) : Int {
|
private fun startProxy(preferences: ByeDpiProxyPreferences): Long {
|
||||||
Log.i(TAG, "Proxy started")
|
Log.i(TAG, "Proxy started")
|
||||||
val res = startProxy(
|
return jniStartProxy(
|
||||||
port = preferences.port,
|
port = preferences.port,
|
||||||
maxConnections = preferences.maxConnections,
|
maxConnections = preferences.maxConnections,
|
||||||
bufferSize = preferences.bufferSize,
|
bufferSize = preferences.bufferSize,
|
||||||
@ -158,21 +163,20 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
domainMixedCase = preferences.domainMixedCase,
|
domainMixedCase = preferences.domainMixedCase,
|
||||||
hostRemoveSpace = preferences.hostRemoveSpaces,
|
hostRemoveSpace = preferences.hostRemoveSpaces,
|
||||||
tlsRecordSplit = preferences.tlsRecordSplit,
|
tlsRecordSplit = preferences.tlsRecordSplit,
|
||||||
|
tlsRecordSplitPosition = preferences.tlsRecordSplitPosition,
|
||||||
tlsRecordSplitAtSni = preferences.tlsRecordSplitAtSni,
|
tlsRecordSplitAtSni = preferences.tlsRecordSplitAtSni,
|
||||||
)
|
)
|
||||||
Log.i(TAG, "Proxy stopped")
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopProxy() {
|
private fun stopProxy() {
|
||||||
proxyJob?.let {
|
if (proxyThread < 0) {
|
||||||
Log.i(TAG, "Proxy stopped")
|
Log.w(TAG, "Proxy not running")
|
||||||
it.cancel()
|
}
|
||||||
proxyJob = null
|
jniStopProxy(proxyThread)
|
||||||
} ?: Log.w(TAG, "Proxy not running")
|
proxyThread = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startTun2Socks(fd: Int, port:Int) {
|
private fun startTun2Socks(fd: Int, port: Int) {
|
||||||
val key = Key().apply {
|
val key = Key().apply {
|
||||||
mark = 0
|
mark = 0
|
||||||
mtu = 0
|
mtu = 0
|
||||||
@ -197,10 +201,10 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
|
|
||||||
private fun stopTun2Socks() {
|
private fun stopTun2Socks() {
|
||||||
Log.i(TAG, "Tun2socks stopped")
|
Log.i(TAG, "Tun2socks stopped")
|
||||||
Engine.stop()
|
vpn?.close() ?: Log.w(TAG, "VPN not running")
|
||||||
}
|
}
|
||||||
|
|
||||||
private external fun startProxy(
|
private external fun jniStartProxy(
|
||||||
port: Int,
|
port: Int,
|
||||||
maxConnections: Int,
|
maxConnections: Int,
|
||||||
bufferSize: Int,
|
bufferSize: Int,
|
||||||
@ -214,9 +218,12 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
hostMixedCase: Boolean,
|
hostMixedCase: Boolean,
|
||||||
domainMixedCase: Boolean,
|
domainMixedCase: Boolean,
|
||||||
hostRemoveSpace: Boolean,
|
hostRemoveSpace: Boolean,
|
||||||
tlsRecordSplit: Int,
|
tlsRecordSplit: Boolean,
|
||||||
|
tlsRecordSplitPosition: Int,
|
||||||
tlsRecordSplitAtSni: Boolean,
|
tlsRecordSplitAtSni: Boolean,
|
||||||
): Int
|
): Long
|
||||||
|
|
||||||
|
private external fun jniStopProxy(proxyThread: Long)
|
||||||
|
|
||||||
private fun getBuilder(): Builder {
|
private fun getBuilder(): Builder {
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
@ -230,11 +237,13 @@ class ByeDpiVpnService : VpnService(), LifecycleOwner {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
val dns = sharedPreferences.getString("dns_ip", "9.9.9.9")!!
|
||||||
|
|
||||||
builder.addAddress("10.10.10.10", 32)
|
builder.addAddress("10.10.10.10", 32)
|
||||||
builder.addRoute("0.0.0.0", 0)
|
builder.addRoute("0.0.0.0", 0)
|
||||||
builder.addRoute("0:0:0:0:0:0:0:0", 0)
|
builder.addRoute("0:0:0:0:0:0:0:0", 0)
|
||||||
builder.addDnsServer("1.1.1.1")
|
builder.addDnsServer(dns)
|
||||||
builder.addDnsServer("1.0.0.1")
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
builder.setMetered(false)
|
builder.setMetered(false)
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,17 @@ import android.content.Intent
|
|||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding
|
import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
private val register = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val register =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
startVpnService()
|
startVpnService()
|
||||||
} else {
|
} else {
|
||||||
@ -44,7 +46,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.settingsButton.setOnClickListener {
|
updateStatus(ByeDpiVpnService.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.menu_main, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_settings -> {
|
||||||
val intent = Intent(this, SettingsActivity::class.java)
|
val intent = Intent(this, SettingsActivity::class.java)
|
||||||
if (ByeDpiVpnService.status == Status.RUNNING) {
|
if (ByeDpiVpnService.status == Status.RUNNING) {
|
||||||
Toast.makeText(this, R.string.settings_unavailable, Toast.LENGTH_SHORT)
|
Toast.makeText(this, R.string.settings_unavailable, Toast.LENGTH_SHORT)
|
||||||
@ -52,9 +64,11 @@ class MainActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus(ByeDpiVpnService.status)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startVpnService() {
|
private fun startVpnService() {
|
||||||
@ -67,6 +81,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val intent = Intent(this, ByeDpiVpnService::class.java)
|
val intent = Intent(this, ByeDpiVpnService::class.java)
|
||||||
intent.action = "stop"
|
intent.action = "stop"
|
||||||
startService(intent)
|
startService(intent)
|
||||||
|
stopService(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateStatus(status : Status) {
|
private fun updateStatus(status : Status) {
|
||||||
|
@ -1,9 +1,65 @@
|
|||||||
package io.github.dovecoteescapee.byedpi
|
package io.github.dovecoteescapee.byedpi
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
private val register =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val logs = collectLogs()
|
||||||
|
|
||||||
|
if (logs == null) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@SettingsActivity,
|
||||||
|
R.string.logs_failed,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
val uri = it.data?.data ?: run {
|
||||||
|
Log.e(TAG, "No data in result")
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
contentResolver.openOutputStream(uri)?.use {
|
||||||
|
try {
|
||||||
|
it.write(logs.toByteArray())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Failed to save logs", e)
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
Log.e(TAG, "Failed to open output stream")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val TAG: String = SettingsActivity::class.java.simpleName
|
||||||
|
|
||||||
|
private fun collectLogs(): String? =
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime()
|
||||||
|
.exec("logcat *:I -d")
|
||||||
|
.inputStream.bufferedReader()
|
||||||
|
.use { it.readText() }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to collect logs", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_settings)
|
setContentView(R.layout.activity_settings)
|
||||||
@ -12,5 +68,48 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(R.id.settings, SettingsFragment())
|
.replace(R.id.settings, SettingsFragment())
|
||||||
.commit()
|
.commit()
|
||||||
|
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.menu_settings, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||||
|
android.R.id.home -> {
|
||||||
|
onBackPressedDispatcher.onBackPressed()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.action_save_logs -> {
|
||||||
|
collectLogs()
|
||||||
|
val intent =
|
||||||
|
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
setType("text/plain")
|
||||||
|
putExtra(Intent.EXTRA_TITLE, "byedpi.log")
|
||||||
|
}
|
||||||
|
|
||||||
|
register.launch(intent)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.action_reset_settings -> {
|
||||||
|
PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(this)
|
||||||
|
.edit()
|
||||||
|
.clear()
|
||||||
|
.apply()
|
||||||
|
|
||||||
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.settings, SettingsFragment())
|
||||||
|
.commit()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,61 @@
|
|||||||
package io.github.dovecoteescapee.byedpi
|
package io.github.dovecoteescapee.byedpi
|
||||||
|
|
||||||
|
import android.net.InetAddresses
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.util.Patterns
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
companion object {
|
||||||
|
private val TAG: String = SettingsFragment::class.java.simpleName
|
||||||
|
|
||||||
|
private fun checkIp(ip: String): Boolean =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
InetAddresses.isNumericAddress(ip)
|
||||||
|
} else {
|
||||||
|
Patterns.IP_ADDRESS.matcher(ip).matches()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkPort(port: String): Boolean =
|
||||||
|
port.toIntOrNull()?.let { it in 1..65535 } ?: false
|
||||||
|
}
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.settings, rootKey)
|
setPreferencesFromResource(R.xml.settings, rootKey)
|
||||||
|
|
||||||
|
setEditTestPreferenceListener("dns_ip") { checkIp(it) }
|
||||||
|
setEditTestPreferenceListener("byedpi_port") { checkPort(it) }
|
||||||
|
setEditTestPreferenceListener("byedpi_max_connections") { value ->
|
||||||
|
value.toIntOrNull()?.let { it > 0 } ?: false
|
||||||
|
}
|
||||||
|
setEditTestPreferenceListener("byedpi_buffer_size") { value ->
|
||||||
|
value.toIntOrNull()?.let { it > 0 } ?: false
|
||||||
|
}
|
||||||
|
setEditTestPreferenceListener("byedpi_default_ttl") { value ->
|
||||||
|
value.toIntOrNull()?.let { it >= 0 } ?: false
|
||||||
|
}
|
||||||
|
setEditTestPreferenceListener("byedpi_split_position") { value ->
|
||||||
|
value.toIntOrNull() != null
|
||||||
|
}
|
||||||
|
setEditTestPreferenceListener("byedpi_fake_ttl") { value ->
|
||||||
|
value.toIntOrNull()?.let { it >= 0 } ?: false
|
||||||
|
}
|
||||||
|
setEditTestPreferenceListener("byedpi_tlsrec_position") {
|
||||||
|
it.toIntOrNull()?.let { it >= 0 } ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setEditTestPreferenceListener(key: String, check: (String) -> Boolean) {
|
||||||
|
findPreference<EditTextPreference>(key)
|
||||||
|
?.setOnPreferenceChangeListener { preference, newValue ->
|
||||||
|
newValue as String
|
||||||
|
val valid = check(newValue)
|
||||||
|
if (!valid) {
|
||||||
|
Log.e(TAG, "Invalid ${preference.title}: $newValue")
|
||||||
|
}
|
||||||
|
valid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
5
app/src/main/res/drawable/baseline_settings_24.xml
Normal file
5
app/src/main/res/drawable/baseline_settings_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
||||||
|
</vector>
|
@ -23,16 +23,4 @@
|
|||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton" />
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton" />
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/settings_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/outline_settings_24"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:contentDescription="@string/settings"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
14
app/src/main/res/menu/menu_main.xml
Normal file
14
app/src/main/res/menu/menu_main.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:orderInCategory="1"
|
||||||
|
android:title="@string/title_settings"
|
||||||
|
android:icon="@drawable/baseline_settings_24"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
</menu>
|
19
app/src/main/res/menu/menu_settings.xml
Normal file
19
app/src/main/res/menu/menu_settings.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:context=".MainActivity">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_reset_settings"
|
||||||
|
android:orderInCategory="1"
|
||||||
|
android:title="@string/reset_settings"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_save_logs"
|
||||||
|
android:orderInCategory="2"
|
||||||
|
android:title="@string/save_logs"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
16
app/src/main/res/values-night/themes.xml
Normal file
16
app/src/main/res/values-night/themes.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.ByeDPI" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/white</item>
|
||||||
|
<item name="colorPrimaryVariant">#E5E5E5</item>
|
||||||
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">#38AF42</item>
|
||||||
|
<item name="colorSecondaryVariant">#1F8C28</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
@ -5,4 +5,8 @@
|
|||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="vpn_permission_denied">VPN permission denied</string>
|
<string name="vpn_permission_denied">VPN permission denied</string>
|
||||||
<string name="settings_unavailable">Please stop the VPN service before changing settings</string>
|
<string name="settings_unavailable">Please stop the VPN service before changing settings</string>
|
||||||
|
<string name="title_settings">Settings</string>
|
||||||
|
<string name="save_logs">Save logs</string>
|
||||||
|
<string name="reset_settings">Reset</string>
|
||||||
|
<string name="logs_failed">Failed to collect logs</string>
|
||||||
</resources>
|
</resources>
|
@ -1,6 +1,6 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.ByeDPI" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
<style name="Theme.ByeDPI" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
<item name="colorPrimary">#5976DF</item>
|
<item name="colorPrimary">#5976DF</item>
|
||||||
<item name="colorPrimaryVariant">#3C52A3</item>
|
<item name="colorPrimaryVariant">#3C52A3</item>
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:tag="settings_screen">
|
||||||
|
|
||||||
|
<com.takisoft.preferencex.EditTextPreference
|
||||||
|
android:key="dns_ip"
|
||||||
|
android:title="DNS"
|
||||||
|
android:defaultValue="9.9.9.9"
|
||||||
|
app:useSimpleSummaryProvider="true"/>
|
||||||
|
|
||||||
<com.takisoft.preferencex.EditTextPreference
|
<com.takisoft.preferencex.EditTextPreference
|
||||||
android:key="byedpi_port"
|
android:key="byedpi_port"
|
||||||
@ -45,13 +52,13 @@
|
|||||||
android:title="Desync method"
|
android:title="Desync method"
|
||||||
android:entries="@array/byedpi_desync_methods"
|
android:entries="@array/byedpi_desync_methods"
|
||||||
android:entryValues="@array/byedpi_desync_methods_entries"
|
android:entryValues="@array/byedpi_desync_methods_entries"
|
||||||
android:defaultValue="none"
|
android:defaultValue="disorder"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
<com.takisoft.preferencex.EditTextPreference
|
<com.takisoft.preferencex.EditTextPreference
|
||||||
android:key="byedpi_split_position"
|
android:key="byedpi_split_position"
|
||||||
android:title="Split position"
|
android:title="Split position"
|
||||||
android:inputType="number"
|
android:inputType="numberSigned"
|
||||||
android:defaultValue="3"
|
android:defaultValue="3"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
@ -82,10 +89,15 @@
|
|||||||
android:title="Host remove spaces"
|
android:title="Host remove spaces"
|
||||||
android:defaultValue="false"/>
|
android:defaultValue="false"/>
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="byedpi_tlsrec_enabled"
|
||||||
|
android:title="Split TLS record"
|
||||||
|
android:defaultValue="false"/>
|
||||||
|
|
||||||
<com.takisoft.preferencex.EditTextPreference
|
<com.takisoft.preferencex.EditTextPreference
|
||||||
android:key="byedpi_tlsrec"
|
android:key="byedpi_tlsrec_position"
|
||||||
android:title="TLS record split position"
|
android:title="TLS record split position"
|
||||||
android:inputType="number"
|
android:inputType="numberSigned"
|
||||||
android:defaultValue="0"
|
android:defaultValue="0"
|
||||||
app:useSimpleSummaryProvider="true" />
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user