Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11d827c6ba | ||
|
|
dc46cd971a | ||
|
|
876e92f275 | ||
|
|
41747c976b | ||
|
|
8277b07d24 | ||
|
|
b982867130 | ||
|
|
491710679a | ||
|
|
24c6aca85c | ||
|
|
293e9b5dff | ||
|
|
3ac77e1f8d | ||
|
|
fd2c67d0b8 | ||
|
|
684698c8e4 | ||
|
|
6a02d4b985 | ||
|
|
7219ce429c | ||
|
|
9975b135d0 | ||
|
|
5d46ef45e3 | ||
|
|
018aa781d3 | ||
|
|
f7e2f8694d | ||
|
|
2d86122297 | ||
|
|
ad0578d073 | ||
|
|
b671bc2c50 | ||
|
|
be5036183d | ||
|
|
212c3f666b | ||
|
|
deba1028c0 | ||
|
|
5559295beb | ||
|
|
41eec3e351 | ||
|
|
56709385b1 | ||
|
|
dc13249a1e | ||
|
|
d579660682 | ||
|
|
1e96a13ab1 | ||
|
|
b71b27fbdf | ||
|
|
3fa8a0c90c | ||
|
|
f121ea2549 | ||
|
|
df1e40d60b | ||
|
|
ee08f02660 | ||
|
|
7af26d1a4b | ||
|
|
88319ecac7 | ||
|
|
b397ab2bbd | ||
|
|
33c087f13f | ||
|
|
4996d4fc52 | ||
|
|
14fd86c716 | ||
|
|
3b9a35d3e6 | ||
|
|
3b93302539 | ||
|
|
8de1dc1f7a | ||
|
|
55667c9e76 | ||
|
|
e81c4f257e | ||
|
|
61a926ff5e | ||
|
|
8d1f2da1f0 | ||
|
|
30c8c7e6eb | ||
|
|
dadeffa903 | ||
|
|
caf5731ff6 | ||
|
|
437d8c3026 | ||
|
|
44b47e6cd3 |
+2
-1
@@ -29,7 +29,8 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails
|
|||||||
1. Broken VPNService implementation, especially for IPv6;
|
1. Broken VPNService implementation, especially for IPv6;
|
||||||
2. Aggressive (or called broken) background service killing policy.
|
2. Aggressive (or called broken) background service killing policy.
|
||||||
|
|
||||||
Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
|
* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
|
||||||
|
* Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
|
||||||
|
|
||||||
### How to pause Shadowsocks service?
|
### How to pause Shadowsocks service?
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -29,7 +29,8 @@
|
|||||||
url = https://android.googlesource.com/platform/external/pcre
|
url = https://android.googlesource.com/platform/external/pcre
|
||||||
[submodule "mobile/src/main/jni/libsodium"]
|
[submodule "mobile/src/main/jni/libsodium"]
|
||||||
path = mobile/src/main/jni/libsodium
|
path = mobile/src/main/jni/libsodium
|
||||||
url = https://github.com/shadowsocks/libsodium.git
|
url = https://github.com/jedisct1/libsodium.git
|
||||||
|
branch = stable
|
||||||
[submodule "mobile/src/main/jni/libev"]
|
[submodule "mobile/src/main/jni/libev"]
|
||||||
path = mobile/src/main/jni/libev
|
path = mobile/src/main/jni/libev
|
||||||
url = https://github.com/shadowsocks/libev.git
|
url = https://github.com/shadowsocks/libev.git
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ lazy val commonSettings = Seq(
|
|||||||
typedResources := false
|
typedResources := false
|
||||||
)
|
)
|
||||||
|
|
||||||
val supportLibsVersion = "25.1.0"
|
val supportLibsVersion = "25.1.1"
|
||||||
lazy val root = Project(id = "shadowsocks-android", base = file("."))
|
lazy val root = Project(id = "shadowsocks-android", base = file("."))
|
||||||
.settings(commonSettings)
|
.settings(commonSettings)
|
||||||
.aggregate(plugin, mobile)
|
.aggregate(plugin, mobile)
|
||||||
|
|||||||
+7
-7
@@ -2,11 +2,11 @@ enablePlugins(AndroidApp)
|
|||||||
android.useSupportVectors
|
android.useSupportVectors
|
||||||
|
|
||||||
name := "shadowsocks"
|
name := "shadowsocks"
|
||||||
version := "4.0.0"
|
version := "4.0.3"
|
||||||
versionCode := Some(175)
|
versionCode := Some(178)
|
||||||
|
|
||||||
proguardOptions ++=
|
proguardOptions ++=
|
||||||
"-keep class com.github.shadowsocks.System { *; }" ::
|
"-keep class com.github.shadowsocks.JniHelper { *; }" ::
|
||||||
"-dontwarn com.google.android.gms.internal.**" ::
|
"-dontwarn com.google.android.gms.internal.**" ::
|
||||||
"-dontwarn com.j256.ormlite.**" ::
|
"-dontwarn com.j256.ormlite.**" ::
|
||||||
"-dontwarn okio.**" ::
|
"-dontwarn okio.**" ::
|
||||||
@@ -19,20 +19,20 @@ val playServicesVersion = "10.0.1"
|
|||||||
resolvers += Resolver.jcenterRepo
|
resolvers += Resolver.jcenterRepo
|
||||||
libraryDependencies ++=
|
libraryDependencies ++=
|
||||||
"com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" ::
|
"com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" ::
|
||||||
"com.evernote" % "android-job" % "1.1.4" ::
|
"com.evernote" % "android-job" % "1.1.5" ::
|
||||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" ::
|
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" ::
|
||||||
"com.google.android.gms" % "play-services-ads" % playServicesVersion ::
|
"com.google.android.gms" % "play-services-ads" % playServicesVersion ::
|
||||||
"com.google.android.gms" % "play-services-analytics" % playServicesVersion ::
|
"com.google.android.gms" % "play-services-analytics" % playServicesVersion ::
|
||||||
"com.google.android.gms" % "play-services-gcm" % playServicesVersion ::
|
"com.google.android.gms" % "play-services-gcm" % playServicesVersion ::
|
||||||
"com.j256.ormlite" % "ormlite-android" % "5.0" ::
|
"com.j256.ormlite" % "ormlite-android" % "5.0" ::
|
||||||
"com.mikepenz" % "crossfader" % "1.5.0" ::
|
"com.mikepenz" % "crossfader" % "1.5.0" ::
|
||||||
"com.mikepenz" % "fastadapter" % "2.1.5" ::
|
"com.mikepenz" % "fastadapter" % "2.1.6" ::
|
||||||
"com.mikepenz" % "iconics-core" % "2.8.2" ::
|
"com.mikepenz" % "iconics-core" % "2.8.2" ::
|
||||||
"com.mikepenz" % "materialdrawer" % "5.8.1" ::
|
"com.mikepenz" % "materialdrawer" % "5.8.1" ::
|
||||||
"com.mikepenz" % "materialize" % "1.0.0" ::
|
"com.mikepenz" % "materialize" % "1.0.0" ::
|
||||||
"com.squareup.okhttp3" % "okhttp" % "3.5.0" ::
|
"com.squareup.okhttp3" % "okhttp" % "3.6.0" ::
|
||||||
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" ::
|
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" ::
|
||||||
"dnsjava" % "dnsjava" % "2.1.7" ::
|
"dnsjava" % "dnsjava" % "2.1.8" ::
|
||||||
"eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" ::
|
"eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" ::
|
||||||
"net.glxn.qrgen" % "android" % "2.0" ::
|
"net.glxn.qrgen" % "android" % "2.0" ::
|
||||||
Nil
|
Nil
|
||||||
|
|||||||
+18
-7
@@ -38,12 +38,23 @@
|
|||||||
|
|
||||||
package com.github.shadowsocks;
|
package com.github.shadowsocks;
|
||||||
|
|
||||||
public class System {
|
import android.os.Build;
|
||||||
static {
|
import android.system.ErrnoException;
|
||||||
java.lang.System.loadLibrary("system");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native int exec(String cmd);
|
public class JniHelper {
|
||||||
public static native int sendfd(int fd, String path);
|
static {
|
||||||
public static native void jniclose(int fd);
|
System.loadLibrary("jni-helper");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated // Use Process.destroy() since API 24
|
||||||
|
public static void sigtermCompat(Process process) throws Exception {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
|
||||||
|
int errno = sigterm(process);
|
||||||
|
if (errno != 0) throw Build.VERSION.SDK_INT >= 21
|
||||||
|
? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native int sigterm(Process process);
|
||||||
|
public static native int sendFd(int fd, String path);
|
||||||
|
public static native void close(int fd);
|
||||||
}
|
}
|
||||||
@@ -26,115 +26,32 @@ include $(CLEAR_VARS)
|
|||||||
|
|
||||||
SODIUM_SOURCE := \
|
SODIUM_SOURCE := \
|
||||||
crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \
|
crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c \
|
||||||
crypto_auth/crypto_auth.c \
|
|
||||||
crypto_auth/hmacsha256/auth_hmacsha256_api.c \
|
|
||||||
crypto_auth/hmacsha256/cp/hmac_hmacsha256.c \
|
|
||||||
crypto_auth/hmacsha256/cp/verify_hmacsha256.c \
|
|
||||||
crypto_auth/hmacsha512/auth_hmacsha512_api.c \
|
|
||||||
crypto_auth/hmacsha512/cp/hmac_hmacsha512.c \
|
|
||||||
crypto_auth/hmacsha512/cp/verify_hmacsha512.c \
|
|
||||||
crypto_auth/hmacsha512256/auth_hmacsha512256_api.c \
|
|
||||||
crypto_auth/hmacsha512256/cp/hmac_hmacsha512256.c \
|
|
||||||
crypto_auth/hmacsha512256/cp/verify_hmacsha512256.c \
|
|
||||||
crypto_box/crypto_box.c \
|
|
||||||
crypto_box/crypto_box_easy.c \
|
|
||||||
crypto_box/crypto_box_seal.c \
|
|
||||||
crypto_box/curve25519xsalsa20poly1305/box_curve25519xsalsa20poly1305_api.c \
|
|
||||||
crypto_box/curve25519xsalsa20poly1305/ref/after_curve25519xsalsa20poly1305.c \
|
|
||||||
crypto_box/curve25519xsalsa20poly1305/ref/before_curve25519xsalsa20poly1305.c \
|
|
||||||
crypto_box/curve25519xsalsa20poly1305/ref/box_curve25519xsalsa20poly1305.c \
|
|
||||||
crypto_box/curve25519xsalsa20poly1305/ref/keypair_curve25519xsalsa20poly1305.c \
|
|
||||||
crypto_core/hsalsa20/ref2/core_hsalsa20.c \
|
|
||||||
crypto_core/hsalsa20/core_hsalsa20_api.c \
|
|
||||||
crypto_core/salsa20/ref/core_salsa20.c \
|
crypto_core/salsa20/ref/core_salsa20.c \
|
||||||
crypto_core/salsa20/core_salsa20_api.c \
|
|
||||||
crypto_generichash/crypto_generichash.c \
|
|
||||||
crypto_generichash/blake2/generichash_blake2_api.c \
|
|
||||||
crypto_generichash/blake2/ref/blake2-impl.h \
|
|
||||||
crypto_generichash/blake2/ref/blake2.h \
|
|
||||||
crypto_generichash/blake2/ref/blake2b-compress-ref.c \
|
crypto_generichash/blake2/ref/blake2b-compress-ref.c \
|
||||||
crypto_generichash/blake2/ref/blake2b-load-sse2.h \
|
|
||||||
crypto_generichash/blake2/ref/blake2b-load-sse41.h \
|
|
||||||
crypto_generichash/blake2/ref/blake2b-ref.c \
|
crypto_generichash/blake2/ref/blake2b-ref.c \
|
||||||
crypto_generichash/blake2/ref/blake2b-round.h \
|
|
||||||
crypto_generichash/blake2/ref/generichash_blake2b.c \
|
crypto_generichash/blake2/ref/generichash_blake2b.c \
|
||||||
crypto_hash/crypto_hash.c \
|
|
||||||
crypto_hash/sha256/hash_sha256_api.c \
|
|
||||||
crypto_hash/sha256/cp/hash_sha256.c \
|
|
||||||
crypto_hash/sha512/hash_sha512_api.c \
|
|
||||||
crypto_hash/sha512/cp/hash_sha512.c \
|
|
||||||
crypto_onetimeauth/crypto_onetimeauth.c \
|
|
||||||
crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \
|
crypto_onetimeauth/poly1305/onetimeauth_poly1305.c \
|
||||||
crypto_onetimeauth/poly1305/onetimeauth_poly1305.h \
|
|
||||||
crypto_onetimeauth/poly1305/donna/poly1305_donna.h \
|
|
||||||
crypto_onetimeauth/poly1305/donna/poly1305_donna32.h \
|
|
||||||
crypto_onetimeauth/poly1305/donna/poly1305_donna64.h \
|
|
||||||
crypto_onetimeauth/poly1305/donna/poly1305_donna.c \
|
crypto_onetimeauth/poly1305/donna/poly1305_donna.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c \
|
crypto_pwhash/crypto_pwhash.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/crypto_scrypt.h \
|
crypto_pwhash/argon2/argon2-core.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c \
|
crypto_pwhash/argon2/argon2.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c \
|
crypto_pwhash/argon2/argon2-encoding.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.h \
|
crypto_pwhash/argon2/argon2-fill-block-ref.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c \
|
crypto_pwhash/argon2/blake2b-long.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/sysendian.h \
|
crypto_pwhash/argon2/pwhash_argon2i.c \
|
||||||
crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c \
|
|
||||||
crypto_scalarmult/crypto_scalarmult.c \
|
|
||||||
crypto_scalarmult/curve25519/scalarmult_curve25519.c \
|
crypto_scalarmult/curve25519/scalarmult_curve25519.c \
|
||||||
crypto_scalarmult/curve25519/scalarmult_curve25519.h \
|
|
||||||
crypto_secretbox/crypto_secretbox.c \
|
|
||||||
crypto_secretbox/crypto_secretbox_easy.c \
|
|
||||||
crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c \
|
|
||||||
crypto_shorthash/crypto_shorthash.c \
|
|
||||||
crypto_shorthash/siphash24/shorthash_siphash24_api.c \
|
|
||||||
crypto_shorthash/siphash24/ref/shorthash_siphash24.c \
|
|
||||||
crypto_sign/crypto_sign.c \
|
|
||||||
crypto_sign/ed25519/ref10/base.h \
|
|
||||||
crypto_sign/ed25519/ref10/base2.h \
|
|
||||||
crypto_sign/ed25519/sign_ed25519_api.c \
|
|
||||||
crypto_sign/ed25519/ref10/keypair.c \
|
|
||||||
crypto_sign/ed25519/ref10/open.c \
|
|
||||||
crypto_sign/ed25519/ref10/obsolete.c \
|
|
||||||
crypto_sign/ed25519/ref10/sign.c \
|
|
||||||
crypto_stream/crypto_stream.c \
|
|
||||||
crypto_stream/chacha20/stream_chacha20.c \
|
crypto_stream/chacha20/stream_chacha20.c \
|
||||||
crypto_stream/chacha20/stream_chacha20.h \
|
|
||||||
crypto_stream/chacha20/ref/stream_chacha20_ref.h \
|
|
||||||
crypto_stream/chacha20/ref/stream_chacha20_ref.c \
|
crypto_stream/chacha20/ref/stream_chacha20_ref.c \
|
||||||
crypto_stream/salsa20/stream_salsa20_api.c \
|
crypto_stream/salsa20/ref/stream_salsa20_ref.c \
|
||||||
crypto_stream/xsalsa20/stream_xsalsa20_api.c \
|
crypto_stream/salsa20/ref/xor_salsa20_ref.c \
|
||||||
crypto_stream/xsalsa20/ref/stream_xsalsa20.c \
|
|
||||||
crypto_stream/xsalsa20/ref/xor_xsalsa20.c \
|
|
||||||
crypto_verify/16/verify_16_api.c \
|
|
||||||
crypto_verify/16/ref/verify_16.c \
|
crypto_verify/16/ref/verify_16.c \
|
||||||
crypto_verify/32/verify_32_api.c \
|
|
||||||
crypto_verify/32/ref/verify_32.c \
|
|
||||||
crypto_verify/64/verify_64_api.c \
|
|
||||||
crypto_verify/64/ref/verify_64.c \
|
|
||||||
randombytes/randombytes.c \
|
randombytes/randombytes.c \
|
||||||
|
randombytes/sysrandom/randombytes_sysrandom.c \
|
||||||
sodium/core.c \
|
sodium/core.c \
|
||||||
sodium/runtime.c \
|
sodium/runtime.c \
|
||||||
sodium/utils.c \
|
sodium/utils.c \
|
||||||
sodium/version.c
|
sodium/version.c
|
||||||
|
|
||||||
SODIUM_SOURCE += \
|
|
||||||
crypto_scalarmult/curve25519/ref10/x25519_ref10.c
|
|
||||||
|
|
||||||
SODIUM_SOURCE += \
|
|
||||||
crypto_pwhash/argon2/argon2-core.c \
|
|
||||||
crypto_pwhash/argon2/argon2.c \
|
|
||||||
crypto_pwhash/argon2/argon2-encoding.c \
|
|
||||||
crypto_pwhash/argon2/blake2b-long.c \
|
|
||||||
crypto_pwhash/argon2/argon2-fill-block-ref.c \
|
|
||||||
crypto_pwhash/argon2/pwhash_argon2i.c \
|
|
||||||
crypto_pwhash/argon2/argon2-fill-block-ssse3.c
|
|
||||||
|
|
||||||
SODIUM_SOURCE += \
|
|
||||||
crypto_stream/salsa20/ref/stream_salsa20_ref.c \
|
|
||||||
crypto_stream/salsa20/ref/xor_salsa20_ref.c
|
|
||||||
|
|
||||||
SODIUM_SOURCE += \
|
|
||||||
randombytes/sysrandom/randombytes_sysrandom.c
|
|
||||||
|
|
||||||
LOCAL_MODULE := sodium
|
LOCAL_MODULE := sodium
|
||||||
LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libsodium/src/libsodium/include \
|
LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/libsodium/src/libsodium/include \
|
||||||
-I$(LOCAL_PATH)/include \
|
-I$(LOCAL_PATH)/include \
|
||||||
@@ -329,8 +246,10 @@ include $(BUILD_SHARED_EXECUTABLE)
|
|||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c \
|
SHADOWSOCKS_SOURCES := local.c \
|
||||||
utils.c netutils.c json.c jconf.c acl.c http.c tls.c rule.c \
|
cache.c udprelay.c utils.c netutils.c json.c jconf.c \
|
||||||
|
acl.c http.c tls.c rule.c \
|
||||||
|
crypto.c aead.c stream.c base64.c \
|
||||||
plugin.c \
|
plugin.c \
|
||||||
android.c
|
android.c
|
||||||
|
|
||||||
@@ -364,7 +283,11 @@ include $(BUILD_SHARED_EXECUTABLE)
|
|||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c netutils.c json.c jconf.c android.c plugin.c
|
SHADOWSOCKS_SOURCES := tunnel.c \
|
||||||
|
cache.c udprelay.c utils.c netutils.c json.c jconf.c \
|
||||||
|
crypto.c aead.c stream.c base64.c \
|
||||||
|
plugin.c \
|
||||||
|
android.c
|
||||||
|
|
||||||
LOCAL_MODULE := ss-tunnel
|
LOCAL_MODULE := ss-tunnel
|
||||||
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
|
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
|
||||||
@@ -388,16 +311,16 @@ LOCAL_LDLIBS := -llog
|
|||||||
include $(BUILD_SHARED_EXECUTABLE)
|
include $(BUILD_SHARED_EXECUTABLE)
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
## system
|
## jni-helper
|
||||||
########################################################
|
########################################################
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
LOCAL_MODULE:= system
|
LOCAL_MODULE:= jni-helper
|
||||||
|
|
||||||
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary
|
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary
|
||||||
|
|
||||||
LOCAL_SRC_FILES:= system.cpp
|
LOCAL_SRC_FILES:= jni-helper.cpp
|
||||||
|
|
||||||
LOCAL_LDLIBS := -ldl -llog
|
LOCAL_LDLIBS := -ldl -llog
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#define LOG_TAG "Shadowsocks"
|
#define LOG_TAG "JniHelper"
|
||||||
|
|
||||||
#include "jni.h"
|
#include "jni.h"
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
|
#include <sys/system_properties.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
@@ -16,35 +19,33 @@
|
|||||||
#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
|
#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
|
||||||
#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
|
#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
|
||||||
#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
|
#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
|
||||||
|
#define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0)
|
||||||
|
|
||||||
jint Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring cmd) {
|
static jclass ProcessImpl;
|
||||||
const char *cmd_str = env->GetStringUTFChars(cmd, 0);
|
static jfieldID ProcessImpl_pid;
|
||||||
|
|
||||||
pid_t pid;
|
static int sdk_version() {
|
||||||
|
char version[PROP_VALUE_MAX + 1];
|
||||||
/* Fork off the parent process */
|
__system_property_get("ro.build.version.sdk", version);
|
||||||
pid = fork();
|
return atoi(version);
|
||||||
if (pid < 0) {
|
|
||||||
env->ReleaseStringUTFChars(cmd, cmd_str);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pid > 0) {
|
|
||||||
env->ReleaseStringUTFChars(cmd, cmd_str);
|
|
||||||
return pid;
|
|
||||||
}
|
|
||||||
|
|
||||||
execl("/system/bin/sh", "sh", "-c", cmd_str, NULL);
|
|
||||||
env->ReleaseStringUTFChars(cmd, cmd_str);
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Java_com_github_shadowsocks_system_jniclose(JNIEnv *env, jobject thiz, jint fd) {
|
jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jobject process) {
|
||||||
|
if (!env->IsInstanceOf(process, ProcessImpl)) {
|
||||||
|
THROW(env, "java/lang/ClassCastException",
|
||||||
|
"Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
jint pid = env->GetIntField(process, ProcessImpl_pid);
|
||||||
|
// Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed.
|
||||||
|
return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) {
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) {
|
jint Java_com_github_shadowsocks_jnihelper_sendfd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) {
|
||||||
int fd;
|
int fd;
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
const char *sock_str = env->GetStringUTFChars(path, 0);
|
const char *sock_str = env->GetStringUTFChars(path, 0);
|
||||||
@@ -75,15 +76,15 @@ jint Java_com_github_shadowsocks_system_sendfd(JNIEnv *env, jobject thiz, jint t
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *classPathName = "com/github/shadowsocks/System";
|
static const char *classPathName = "com/github/shadowsocks/JniHelper";
|
||||||
|
|
||||||
static JNINativeMethod method_table[] = {
|
static JNINativeMethod method_table[] = {
|
||||||
{ "jniclose", "(I)V",
|
{ "close", "(I)V",
|
||||||
(void*) Java_com_github_shadowsocks_system_jniclose },
|
(void*) Java_com_github_shadowsocks_jnihelper_close },
|
||||||
{ "sendfd", "(ILjava/lang/String;)I",
|
{ "sendFd", "(ILjava/lang/String;)I",
|
||||||
(void*) Java_com_github_shadowsocks_system_sendfd },
|
(void*) Java_com_github_shadowsocks_jnihelper_sendfd },
|
||||||
{ "exec", "(Ljava/lang/String;)I",
|
{ "sigterm", "(Ljava/lang/Process;)I",
|
||||||
(void*) Java_com_github_shadowsocks_system_exec }
|
(void*) Java_com_github_shadowsocks_jnihelper_sigterm }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -139,20 +140,30 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||||||
jint result = -1;
|
jint result = -1;
|
||||||
JNIEnv* env = NULL;
|
JNIEnv* env = NULL;
|
||||||
|
|
||||||
LOGI("JNI_OnLoad");
|
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
|
||||||
|
THROW(env, "java/lang/RuntimeException", "GetEnv failed");
|
||||||
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
|
|
||||||
LOGE("ERROR: GetEnv failed");
|
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
env = uenv.env;
|
env = uenv.env;
|
||||||
|
|
||||||
if (registerNatives(env) != JNI_TRUE) {
|
if (registerNatives(env) != JNI_TRUE) {
|
||||||
LOGE("ERROR: registerNatives failed");
|
THROW(env, "java/lang/RuntimeException", "registerNativeMethods failed");
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = JNI_VERSION_1_4;
|
if (sdk_version() < 24) {
|
||||||
|
if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) {
|
||||||
|
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found");
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
ProcessImpl = (jclass) env->NewGlobalRef((jobject) ProcessImpl);
|
||||||
|
if (!(ProcessImpl_pid = env->GetFieldID(ProcessImpl, "pid", "I"))) {
|
||||||
|
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found");
|
||||||
|
goto bail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = JNI_VERSION_1_6;
|
||||||
|
|
||||||
bail:
|
bail:
|
||||||
return result;
|
return result;
|
||||||
Submodule mobile/src/main/jni/libsodium updated: a81cea2366...e160de24fd
Submodule mobile/src/main/jni/shadowsocks-libev updated: 1c2b6170b9...46fa5de9e4
@@ -10,6 +10,5 @@
|
|||||||
<EditText android:id="@+id/content"
|
<EditText android:id="@+id/content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="textNoSuggestions"
|
android:inputType="textNoSuggestions|textMultiLine"/>
|
||||||
android:lines="1"/>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string-array name="enc_method_entry">
|
<string-array name="enc_method_entry">
|
||||||
<item>TABLE</item>
|
|
||||||
<item>RC4</item>
|
|
||||||
<item>RC4-MD5</item>
|
<item>RC4-MD5</item>
|
||||||
<item>AES-128-CFB</item>
|
<item>AES-128-CFB</item>
|
||||||
<item>AES-192-CFB</item>
|
<item>AES-192-CFB</item>
|
||||||
@@ -17,11 +15,13 @@
|
|||||||
<item>SALSA20</item>
|
<item>SALSA20</item>
|
||||||
<item>CHACHA20</item>
|
<item>CHACHA20</item>
|
||||||
<item>CHACHA20-IETF</item>
|
<item>CHACHA20-IETF</item>
|
||||||
|
<item>AES-128-GCM</item>
|
||||||
|
<item>AES-192-GCM</item>
|
||||||
|
<item>AES-256-GCM</item>
|
||||||
|
<item>CHACHA20-IETF-POLY1305</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="enc_method_value">
|
<string-array name="enc_method_value">
|
||||||
<item>table</item>
|
|
||||||
<item>rc4</item>
|
|
||||||
<item>rc4-md5</item>
|
<item>rc4-md5</item>
|
||||||
<item>aes-128-cfb</item>
|
<item>aes-128-cfb</item>
|
||||||
<item>aes-192-cfb</item>
|
<item>aes-192-cfb</item>
|
||||||
@@ -36,6 +36,10 @@
|
|||||||
<item>salsa20</item>
|
<item>salsa20</item>
|
||||||
<item>chacha20</item>
|
<item>chacha20</item>
|
||||||
<item>chacha20-ietf</item>
|
<item>chacha20-ietf</item>
|
||||||
|
<item>aes-128-gcm</item>
|
||||||
|
<item>aes-192-gcm</item>
|
||||||
|
<item>aes-256-gcm</item>
|
||||||
|
<item>chacha20-ietf-poly1305</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="route_entry">
|
<string-array name="route_entry">
|
||||||
|
|||||||
@@ -36,8 +36,6 @@
|
|||||||
<!-- feature category -->
|
<!-- feature category -->
|
||||||
<string name="ipv6">IPv6 Route</string>
|
<string name="ipv6">IPv6 Route</string>
|
||||||
<string name="ipv6_summary">Redirect IPv6 traffic to remote</string>
|
<string name="ipv6_summary">Redirect IPv6 traffic to remote</string>
|
||||||
<string name="onetime_auth">One-time Authentication</string>
|
|
||||||
<string name="onetime_auth_summary">Enable one-time authentication</string>
|
|
||||||
<string name="route_list">Route</string>
|
<string name="route_list">Route</string>
|
||||||
<string name="route_entry_all">All</string>
|
<string name="route_entry_all">All</string>
|
||||||
<string name="route_entry_bypass_lan">Bypass LAN</string>
|
<string name="route_entry_bypass_lan">Bypass LAN</string>
|
||||||
@@ -62,7 +60,6 @@
|
|||||||
<string name="forward_success">Shadowsocks started.</string>
|
<string name="forward_success">Shadowsocks started.</string>
|
||||||
<string name="invalid_server">Invalid server name</string>
|
<string name="invalid_server">Invalid server name</string>
|
||||||
<string name="service_failed">Failed to connect the remote server</string>
|
<string name="service_failed">Failed to connect the remote server</string>
|
||||||
<string name="method_unsafe">WARNING: The current encryption method is unsafe</string>
|
|
||||||
<string name="nat_deprecated">WARNING: NAT mode has been deprecated since Android 5.0</string>
|
<string name="nat_deprecated">WARNING: NAT mode has been deprecated since Android 5.0</string>
|
||||||
<string name="nat_no_root">NAT mode requires ROOT permission</string>
|
<string name="nat_no_root">NAT mode requires ROOT permission</string>
|
||||||
<string name="switch_to_vpn">Switch to VPN mode</string>
|
<string name="switch_to_vpn">Switch to VPN mode</string>
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
<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/com.github.shadowsocks">
|
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
|
||||||
|
|
||||||
|
<be.mygod.preference.EditTextPreference
|
||||||
|
android:key="profileName"
|
||||||
|
android:summary="%s"
|
||||||
|
android:title="@string/profile_name"/>
|
||||||
|
|
||||||
<be.mygod.preference.PreferenceCategory
|
<be.mygod.preference.PreferenceCategory
|
||||||
android:title="@string/proxy_cat">
|
android:title="@string/proxy_cat">
|
||||||
|
|
||||||
<be.mygod.preference.EditTextPreference
|
|
||||||
android:key="profileName"
|
|
||||||
android:summary="%s"
|
|
||||||
android:title="@string/profile_name"/>
|
|
||||||
<be.mygod.preference.EditTextPreference
|
<be.mygod.preference.EditTextPreference
|
||||||
android:key="proxy"
|
android:key="proxy"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
@@ -36,10 +37,6 @@
|
|||||||
android:entryValues="@array/enc_method_value"
|
android:entryValues="@array/enc_method_value"
|
||||||
android:summary="%s"
|
android:summary="%s"
|
||||||
android:title="@string/enc_method"/>
|
android:title="@string/enc_method"/>
|
||||||
<SwitchPreference
|
|
||||||
android:key="isAuth"
|
|
||||||
android:summary="@string/onetime_auth_summary"
|
|
||||||
android:title="@string/onetime_auth"/>
|
|
||||||
|
|
||||||
</be.mygod.preference.PreferenceCategory>
|
</be.mygod.preference.PreferenceCategory>
|
||||||
|
|
||||||
|
|||||||
@@ -183,9 +183,6 @@ trait BaseService extends Service {
|
|||||||
|
|
||||||
changeState(State.CONNECTING)
|
changeState(State.CONNECTING)
|
||||||
|
|
||||||
if (profile.isMethodUnsafe)
|
|
||||||
handler.post(() => Toast.makeText(this, R.string.method_unsafe, Toast.LENGTH_LONG).show())
|
|
||||||
|
|
||||||
Utils.ThrowableFuture(try connect() catch {
|
Utils.ThrowableFuture(try connect() catch {
|
||||||
case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
|
case _: NameNotResolvedException => stopRunner(stopService = true, getString(R.string.invalid_server))
|
||||||
case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
|
case _: NullConnectionException => stopRunner(stopService = true, getString(R.string.reboot_required))
|
||||||
@@ -325,7 +322,6 @@ trait BaseService extends Service {
|
|||||||
.put("server_port", profile.remotePort)
|
.put("server_port", profile.remotePort)
|
||||||
.put("password", profile.password)
|
.put("password", profile.password)
|
||||||
.put("method", profile.method)
|
.put("method", profile.method)
|
||||||
if (profile.auth) config.put("auth", true)
|
|
||||||
if (pluginPath != null) config
|
if (pluginPath != null) config
|
||||||
.put("plugin", Commandline.toString(buildPluginCommandLine()))
|
.put("plugin", Commandline.toString(buildPluginCommandLine()))
|
||||||
.put("plugin_opts", plugin.toString)
|
.put("plugin_opts", plugin.toString)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import java.io._
|
|||||||
import java.lang.System.currentTimeMillis
|
import java.lang.System.currentTimeMillis
|
||||||
import java.util.concurrent.Semaphore
|
import java.util.concurrent.Semaphore
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.github.shadowsocks.utils.CloseUtils._
|
import com.github.shadowsocks.utils.CloseUtils._
|
||||||
import com.github.shadowsocks.utils.Commandline
|
import com.github.shadowsocks.utils.Commandline
|
||||||
@@ -89,7 +90,7 @@ class GuardedProcess(cmd: String*) {
|
|||||||
} catch {
|
} catch {
|
||||||
case _: InterruptedException =>
|
case _: InterruptedException =>
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd))
|
if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd))
|
||||||
process.destroy()
|
destroyProcess()
|
||||||
case e: IOException => ioException = e
|
case e: IOException => ioException = e
|
||||||
} finally semaphore.release()
|
} finally semaphore.release()
|
||||||
}, "GuardThread-" + cmd.head)
|
}, "GuardThread-" + cmd.head)
|
||||||
@@ -105,16 +106,24 @@ class GuardedProcess(cmd: String*) {
|
|||||||
def destroy() {
|
def destroy() {
|
||||||
isDestroyed = true
|
isDestroyed = true
|
||||||
guardThread.interrupt()
|
guardThread.interrupt()
|
||||||
process.destroy()
|
destroyProcess()
|
||||||
try guardThread.join() catch {
|
try guardThread.join() catch {
|
||||||
case _: InterruptedException =>
|
case _: InterruptedException =>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def destroyProcess() {
|
||||||
|
if (Build.VERSION.SDK_INT < 24) {
|
||||||
|
JniHelper.sigtermCompat(process)
|
||||||
|
process.waitFor()
|
||||||
|
}
|
||||||
|
process.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
def restart() {
|
def restart() {
|
||||||
this.synchronized {
|
this.synchronized {
|
||||||
isRestart = true
|
isRestart = true
|
||||||
process.destroy()
|
destroyProcess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,11 +84,6 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
|
|||||||
private var txRateText: TextView = _
|
private var txRateText: TextView = _
|
||||||
private var rxRateText: TextView = _
|
private var rxRateText: TextView = _
|
||||||
|
|
||||||
private var currentFragment: ToolbarFragment = _
|
|
||||||
private lazy val profilesFragment = new ProfilesFragment()
|
|
||||||
private lazy val customRulesFragment = new CustomRulesFragment()
|
|
||||||
private lazy val globalSettingsFragment = new GlobalSettingsFragment()
|
|
||||||
private lazy val aboutFragment = new AboutFragment()
|
|
||||||
private lazy val customTabsIntent = new CustomTabsIntent.Builder()
|
private lazy val customTabsIntent = new CustomTabsIntent.Builder()
|
||||||
.setToolbarColor(ContextCompat.getColor(this, R.color.material_primary_500))
|
.setToolbarColor(ContextCompat.getColor(this, R.color.material_primary_500))
|
||||||
.build()
|
.build()
|
||||||
@@ -172,11 +167,11 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
|
|||||||
private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ =>
|
private def addDisableNatToSnackbar(snackbar: Snackbar) = snackbar.setAction(R.string.switch_to_vpn, (_ =>
|
||||||
if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener)
|
if (state == State.STOPPED) app.editor.putBoolean(Key.isNAT, false).apply()): View.OnClickListener)
|
||||||
|
|
||||||
override def binderDied() {
|
override def binderDied(): Unit = handler.post(() => {
|
||||||
detachService()
|
detachService()
|
||||||
app.crashRecovery()
|
app.crashRecovery()
|
||||||
attachService(callback)
|
attachService(callback)
|
||||||
}
|
})
|
||||||
|
|
||||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match {
|
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent): Unit = resultCode match {
|
||||||
case Activity.RESULT_OK => bgService.use(app.profileId)
|
case Activity.RESULT_OK => bgService.use(app.profileId)
|
||||||
@@ -255,7 +250,7 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
|
|||||||
val tf = Typefaces.get(this, "fonts/Iceland.ttf")
|
val tf = Typefaces.get(this, "fonts/Iceland.ttf")
|
||||||
if (tf != null) title.setTypeface(tf)
|
if (tf != null) title.setTypeface(tf)
|
||||||
|
|
||||||
if (savedInstanceState == null) displayFragment(profilesFragment)
|
if (savedInstanceState == null) displayFragment(new ProfilesFragment)
|
||||||
statusText = findViewById(R.id.status).asInstanceOf[TextView]
|
statusText = findViewById(R.id.status).asInstanceOf[TextView]
|
||||||
txText = findViewById(R.id.tx).asInstanceOf[TextView]
|
txText = findViewById(R.id.tx).asInstanceOf[TextView]
|
||||||
txRateText = findViewById(R.id.txRate).asInstanceOf[TextView]
|
txRateText = findViewById(R.id.txRate).asInstanceOf[TextView]
|
||||||
@@ -364,14 +359,13 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def displayFragment(fragment: ToolbarFragment) {
|
private def displayFragment(fragment: ToolbarFragment) {
|
||||||
currentFragment = fragment
|
|
||||||
getFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss()
|
getFragmentManager.beginTransaction().replace(R.id.fragment_holder, fragment).commitAllowingStateLoss()
|
||||||
drawer.closeDrawer()
|
drawer.closeDrawer()
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = {
|
override def onItemClick(view: View, position: Int, drawerItem: IDrawerItem[_, _ <: ViewHolder]): Boolean = {
|
||||||
drawerItem.getIdentifier match {
|
drawerItem.getIdentifier match {
|
||||||
case DRAWER_PROFILES => displayFragment(profilesFragment)
|
case DRAWER_PROFILES => displayFragment(new ProfilesFragment)
|
||||||
case DRAWER_RECOVERY =>
|
case DRAWER_RECOVERY =>
|
||||||
app.track("GlobalConfigFragment", "reset")
|
app.track("GlobalConfigFragment", "reset")
|
||||||
if (bgService != null) bgService.use(-1)
|
if (bgService != null) bgService.use(-1)
|
||||||
@@ -384,12 +378,12 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
|
|||||||
app.copyAssets()
|
app.copyAssets()
|
||||||
handler.sendEmptyMessage(0)
|
handler.sendEmptyMessage(0)
|
||||||
}
|
}
|
||||||
case DRAWER_GLOBAL_SETTINGS => displayFragment(globalSettingsFragment)
|
case DRAWER_GLOBAL_SETTINGS => displayFragment(new GlobalSettingsFragment)
|
||||||
case DRAWER_ABOUT =>
|
case DRAWER_ABOUT =>
|
||||||
app.track(TAG, "about")
|
app.track(TAG, "about")
|
||||||
displayFragment(aboutFragment)
|
displayFragment(new AboutFragment)
|
||||||
case DRAWER_FAQ => launchUrl(getString(R.string.faq_url))
|
case DRAWER_FAQ => launchUrl(getString(R.string.faq_url))
|
||||||
case DRAWER_CUSTOM_RULES => displayFragment(customRulesFragment)
|
case DRAWER_CUSTOM_RULES => displayFragment(new CustomRulesFragment)
|
||||||
}
|
}
|
||||||
true // unexpected cases will throw exception
|
true // unexpected cases will throw exception
|
||||||
}
|
}
|
||||||
@@ -408,12 +402,14 @@ class MainActivity extends Activity with ServiceBoundContext with Drawer.OnDrawe
|
|||||||
setListeningForBandwidth(true)
|
setListeningForBandwidth(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onBackPressed(): Unit =
|
override def onBackPressed(): Unit = if (drawer.isDrawerOpen) drawer.closeDrawer() else {
|
||||||
if (drawer.isDrawerOpen) drawer.closeDrawer() else if (!currentFragment.onBackPressed())
|
val currentFragment = getFragmentManager.findFragmentById(R.id.fragment_holder).asInstanceOf[ToolbarFragment]
|
||||||
if (currentFragment != profilesFragment) {
|
if (!currentFragment.onBackPressed())
|
||||||
displayFragment(profilesFragment)
|
if (currentFragment.isInstanceOf[ProfilesFragment]) super.onBackPressed() else {
|
||||||
|
displayFragment(new ProfilesFragment)
|
||||||
drawer.setSelection(DRAWER_PROFILES)
|
drawer.setSelection(DRAWER_PROFILES)
|
||||||
} else super.onBackPressed()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override def onStop() {
|
override def onStop() {
|
||||||
setListeningForBandwidth(false)
|
setListeningForBandwidth(false)
|
||||||
|
|||||||
@@ -358,7 +358,9 @@ final class ProfilesFragment extends ToolbarFragment with Toolbar.OnMenuItemClic
|
|||||||
REQUEST_SCAN_QR_CODE) catch {
|
REQUEST_SCAN_QR_CODE) catch {
|
||||||
case _: ActivityNotFoundException =>
|
case _: ActivityNotFoundException =>
|
||||||
Toast.makeText(getActivity, R.string.add_profile_scanner_not_installed, Toast.LENGTH_LONG).show()
|
Toast.makeText(getActivity, R.string.add_profile_scanner_not_installed, Toast.LENGTH_LONG).show()
|
||||||
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tw.com.quickmark")))
|
try startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=tw.com.quickmark"))) catch {
|
||||||
|
case exc: ActivityNotFoundException => exc.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
case R.id.action_import =>
|
case R.id.action_import =>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class ShadowsocksNatService extends BaseService {
|
|||||||
"-t", "10",
|
"-t", "10",
|
||||||
"-b", "127.0.0.1",
|
"-b", "127.0.0.1",
|
||||||
"-l", (profile.localPort + 63).toString,
|
"-l", (profile.localPort + 63).toString,
|
||||||
"-L", if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53",
|
"-L", profile.remoteDns + ":53",
|
||||||
"-c", buildShadowsocksConfig("ss-tunnel-nat.conf"))
|
"-c", buildShadowsocksConfig("ss-tunnel-nat.conf"))
|
||||||
|
|
||||||
if (profile.udpdns) cmd.append("-u")
|
if (profile.udpdns) cmd.append("-u")
|
||||||
|
|||||||
@@ -23,14 +23,13 @@ package com.github.shadowsocks
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content._
|
import android.content._
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os._
|
import android.os._
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||||
import com.github.shadowsocks.acl.{Acl, AclSyncJob}
|
import com.github.shadowsocks.acl.{Acl, AclSyncJob, Subnet}
|
||||||
import com.github.shadowsocks.database.Profile
|
import com.github.shadowsocks.database.Profile
|
||||||
import com.github.shadowsocks.utils._
|
import com.github.shadowsocks.utils._
|
||||||
|
|
||||||
@@ -189,7 +188,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
"-t", "10",
|
"-t", "10",
|
||||||
"-b", "127.0.0.1",
|
"-b", "127.0.0.1",
|
||||||
"-l", (profile.localPort + 63).toString,
|
"-l", (profile.localPort + 63).toString,
|
||||||
"-L" , if (profile.remoteDns == null) "8.8.8.8:53" else profile.remoteDns + ":53",
|
"-L" , profile.remoteDns + ":53",
|
||||||
"-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start()
|
"-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start()
|
||||||
|
|
||||||
def startDnsDaemon() {
|
def startDnsDaemon() {
|
||||||
@@ -212,7 +211,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
pdnsdProcess = new GuardedProcess(cmd: _*).start()
|
pdnsdProcess = new GuardedProcess(cmd: _*).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint(Array("NewApi"))
|
|
||||||
def startVpn(): Int = {
|
def startVpn(): Int = {
|
||||||
|
|
||||||
val builder = new Builder()
|
val builder = new Builder()
|
||||||
@@ -221,7 +219,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
.setMtu(VPN_MTU)
|
.setMtu(VPN_MTU)
|
||||||
.addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24)
|
.addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24)
|
||||||
|
|
||||||
builder.addDnsServer("8.8.8.8")
|
builder.addDnsServer(profile.remoteDns)
|
||||||
|
|
||||||
if (profile.ipv6) {
|
if (profile.ipv6) {
|
||||||
builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126)
|
builder.addAddress(PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "1"), 126)
|
||||||
@@ -249,15 +247,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_CHN) {
|
if (profile.route == Acl.ALL || profile.route == Acl.BYPASS_CHN) {
|
||||||
builder.addRoute("0.0.0.0", 0)
|
builder.addRoute("0.0.0.0", 0)
|
||||||
} else {
|
} else {
|
||||||
val privateList = getResources.getStringArray(R.array.bypass_private_route)
|
getResources.getStringArray(R.array.bypass_private_route).foreach(cidr => {
|
||||||
privateList.foreach(cidr => {
|
val subnet = Subnet.fromString(cidr)
|
||||||
val addr = cidr.split('/')
|
builder.addRoute(subnet.address.getHostAddress, subnet.prefixSize)
|
||||||
builder.addRoute(addr(0), addr(1).toInt)
|
|
||||||
})
|
})
|
||||||
|
builder.addRoute(profile.remoteDns, 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.addRoute("8.8.0.0", 16)
|
|
||||||
|
|
||||||
conn = builder.establish()
|
conn = builder.establish()
|
||||||
if (conn == null) throw new NullConnectionException
|
if (conn == null) throw new NullConnectionException
|
||||||
|
|
||||||
@@ -291,7 +287,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
var tries = 1
|
var tries = 1
|
||||||
while (tries < 5) {
|
while (tries < 5) {
|
||||||
Thread.sleep(1000 * tries)
|
Thread.sleep(1000 * tries)
|
||||||
if (System.sendfd(fd, new File(getFilesDir, "sock_path").getAbsolutePath) != -1) {
|
if (JniHelper.sendFd(fd, new File(getFilesDir, "sock_path").getAbsolutePath) != -1) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
tries += 1
|
tries += 1
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class ShadowsocksVpnThread(service: ShadowsocksVpnService) extends Thread {
|
|||||||
val ret = service.protect(fd)
|
val ret = service.protect(fd)
|
||||||
|
|
||||||
// Trick to close file decriptor
|
// Trick to close file decriptor
|
||||||
System.jniclose(fd)
|
JniHelper.close(fd)
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
output.write(0)
|
output.write(0)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ object AclSyncJob {
|
|||||||
final val TAG = "AclSyncJob"
|
final val TAG = "AclSyncJob"
|
||||||
|
|
||||||
def schedule(route: String): Int = new JobRequest.Builder(AclSyncJob.TAG + ':' + route)
|
def schedule(route: String): Int = new JobRequest.Builder(AclSyncJob.TAG + ':' + route)
|
||||||
.setExecutionWindow(1, TimeUnit.DAYS.toMillis(28))
|
.setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.DAYS.toMillis(28))
|
||||||
.setRequirementsEnforced(true)
|
.setRequirementsEnforced(true)
|
||||||
.setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
|
.setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
|
||||||
.setRequiresCharging(true)
|
.setRequiresCharging(true)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.github.shadowsocks.widget.UndoSnackbarManager
|
|||||||
import com.github.shadowsocks.{MainActivity, R, ToolbarFragment}
|
import com.github.shadowsocks.{MainActivity, R, ToolbarFragment}
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
import scala.io.Source
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Mygod
|
* @author Mygod
|
||||||
@@ -144,8 +145,12 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
|
|||||||
apply()
|
apply()
|
||||||
index
|
index
|
||||||
} else -1
|
} else -1
|
||||||
def addToProxy(input: String): Int = try addSubnet(Subnet.fromString(input)) catch {
|
def addToProxy(input: String): Int = {
|
||||||
case _: IllegalArgumentException => addHostname(input)
|
val acl = new Acl().fromSource(Source.fromString(input), defaultBypass = true)
|
||||||
|
var result = -1
|
||||||
|
for (hostname <- acl.proxyHostnames) result = addHostname(hostname)
|
||||||
|
if (acl.bypass) for (subnet <- acl.subnets) result = addSubnet(subnet)
|
||||||
|
result
|
||||||
}
|
}
|
||||||
def addFromTemplate(template: Int, text: CharSequence): Int = template match {
|
def addFromTemplate(template: Int, text: CharSequence): Int = template match {
|
||||||
case GENERIC => addToProxy(text.toString)
|
case GENERIC => addToProxy(text.toString)
|
||||||
@@ -298,13 +303,7 @@ class CustomRulesFragment extends ToolbarFragment with Toolbar.OnMenuItemClickLi
|
|||||||
.create().show()
|
.create().show()
|
||||||
true
|
true
|
||||||
case R.id.action_import =>
|
case R.id.action_import =>
|
||||||
try {
|
try if (adapter.addToProxy(clipboard.getPrimaryClip.getItemAt(0).getText.toString) != -1) return true catch {
|
||||||
val items = clipboard.getPrimaryClip.getItemAt(0).getText.toString.split("\n")
|
|
||||||
if (items.nonEmpty) {
|
|
||||||
items.foreach(adapter.addToProxy)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case _: Exception =>
|
case _: Exception =>
|
||||||
}
|
}
|
||||||
Snackbar.make(getActivity.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(getActivity.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG).show()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.github.shadowsocks.acl
|
package com.github.shadowsocks.acl
|
||||||
|
|
||||||
import java.net.{Inet4Address, Inet6Address, InetAddress}
|
import java.net.InetAddress
|
||||||
|
import java.util.Objects
|
||||||
|
|
||||||
import com.github.shadowsocks.utils.Utils
|
import com.github.shadowsocks.utils.Utils
|
||||||
|
|
||||||
@@ -9,16 +10,11 @@ import com.github.shadowsocks.utils.Utils
|
|||||||
*/
|
*/
|
||||||
@throws[IllegalArgumentException]
|
@throws[IllegalArgumentException]
|
||||||
class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[Subnet] {
|
class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[Subnet] {
|
||||||
if (prefixSize < 0) throw new IllegalArgumentException
|
private def addressLength = address.getAddress.length << 3
|
||||||
address match {
|
if (prefixSize < 0 || prefixSize > addressLength) throw new IllegalArgumentException
|
||||||
case _: Inet4Address => if (prefixSize > 32) throw new IllegalArgumentException
|
|
||||||
case _: Inet6Address => if (prefixSize > 128) throw new IllegalArgumentException
|
|
||||||
}
|
|
||||||
|
|
||||||
override def toString: String = if (address match {
|
override def toString: String =
|
||||||
case _: Inet4Address => prefixSize == 32
|
if (prefixSize == addressLength) address.getHostAddress else address.getHostAddress + '/' + prefixSize
|
||||||
case _: Inet6Address => prefixSize == 128
|
|
||||||
}) address.getHostAddress else address.getHostAddress + '/' + prefixSize
|
|
||||||
|
|
||||||
override def compareTo(that: Subnet): Int = {
|
override def compareTo(that: Subnet): Int = {
|
||||||
val addrThis = address.getAddress
|
val addrThis = address.getAddress
|
||||||
@@ -31,19 +27,21 @@ class Subnet(val address: InetAddress, val prefixSize: Int) extends Comparable[S
|
|||||||
}
|
}
|
||||||
prefixSize compare that.prefixSize
|
prefixSize compare that.prefixSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def equals(other: Any): Boolean = other match {
|
||||||
|
case that: Subnet => address == that.address && prefixSize == that.prefixSize
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
override def hashCode: Int = Objects.hash(address, prefixSize: Integer)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Subnet {
|
object Subnet {
|
||||||
@throws[IllegalArgumentException]
|
@throws[IllegalArgumentException]
|
||||||
def fromString(value: String): Subnet = {
|
def fromString(value: String): Subnet = {
|
||||||
val parts = value.split("/")
|
val parts = value.split("/")
|
||||||
if (!Utils.isNumeric(parts(0))) throw new IllegalArgumentException()
|
val addr = Utils.parseNumericAddress(parts(0))
|
||||||
val addr = InetAddress.getByName(parts(0))
|
|
||||||
parts.length match {
|
parts.length match {
|
||||||
case 1 => new Subnet(addr, addr match {
|
case 1 => new Subnet(addr, addr.getAddress.length << 3)
|
||||||
case _: Inet4Address => 32
|
|
||||||
case _: Inet6Address => 128
|
|
||||||
})
|
|
||||||
case 2 => new Subnet(addr, parts(1).toInt)
|
case 2 => new Subnet(addr, parts(1).toInt)
|
||||||
case _ => throw new IllegalArgumentException()
|
case _ => throw new IllegalArgumentException()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,9 +77,6 @@ class DBHelper(val context: Context)
|
|||||||
} else if (oldVersion < 19) {
|
} else if (oldVersion < 19) {
|
||||||
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
|
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
|
||||||
}
|
}
|
||||||
if (oldVersion < 10) {
|
|
||||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;")
|
|
||||||
}
|
|
||||||
if (oldVersion < 11) {
|
if (oldVersion < 11) {
|
||||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
|
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
|
||||||
}
|
}
|
||||||
@@ -89,9 +86,9 @@ class DBHelper(val context: Context)
|
|||||||
TableUtils.createTable(connectionSource, classOf[Profile])
|
TableUtils.createTable(connectionSource, classOf[Profile])
|
||||||
profileDao.executeRawNoArgs(
|
profileDao.executeRawNoArgs(
|
||||||
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
|
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
|
||||||
" udpdns, auth, ipv6, individual) " +
|
" udpdns, ipv6, individual) " +
|
||||||
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," +
|
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," +
|
||||||
" ipv6, individual FROM `tmp`;")
|
" individual FROM `tmp`;")
|
||||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||||
profileDao.executeRawNoArgs("COMMIT;")
|
profileDao.executeRawNoArgs("COMMIT;")
|
||||||
} else if (oldVersion < 13) {
|
} else if (oldVersion < 13) {
|
||||||
@@ -127,10 +124,10 @@ class DBHelper(val context: Context)
|
|||||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||||
TableUtils.createTable(connectionSource, classOf[Profile])
|
TableUtils.createTable(connectionSource, classOf[Profile])
|
||||||
profileDao.executeRawNoArgs(
|
profileDao.executeRawNoArgs(
|
||||||
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, proxyApps," +
|
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, remoteDns, " +
|
||||||
" bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, plugin) " +
|
"proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, plugin) " +
|
||||||
"SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " +
|
"SELECT id, name, host, localPort, CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, " +
|
||||||
"route, remoteDns, proxyApps, bypass, udpdns, auth, ipv6, individual, tx, rx, date, userOrder, " +
|
"route, remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
|
||||||
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
|
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
|
||||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||||
profileDao.executeRawNoArgs("COMMIT;")
|
profileDao.executeRawNoArgs("COMMIT;")
|
||||||
|
|||||||
@@ -20,13 +20,13 @@
|
|||||||
|
|
||||||
package com.github.shadowsocks.database
|
package com.github.shadowsocks.database
|
||||||
|
|
||||||
import java.net.URLEncoder
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.text.TextUtils
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import com.github.shadowsocks.plugin.PluginConfiguration
|
||||||
import com.github.shadowsocks.utils.Key
|
import com.github.shadowsocks.utils.Key
|
||||||
import com.j256.ormlite.field.{DataType, DatabaseField}
|
import com.j256.ormlite.field.{DataType, DatabaseField}
|
||||||
|
|
||||||
@@ -68,9 +68,6 @@ class Profile {
|
|||||||
@DatabaseField
|
@DatabaseField
|
||||||
var udpdns: Boolean = _
|
var udpdns: Boolean = _
|
||||||
|
|
||||||
@DatabaseField
|
|
||||||
var auth: Boolean = _
|
|
||||||
|
|
||||||
@DatabaseField
|
@DatabaseField
|
||||||
var ipv6: Boolean = _
|
var ipv6: Boolean = _
|
||||||
|
|
||||||
@@ -96,11 +93,19 @@ class Profile {
|
|||||||
def nameIsEmpty: Boolean = name == null || name.isEmpty
|
def nameIsEmpty: Boolean = name == null || name.isEmpty
|
||||||
def getName: String = if (nameIsEmpty) formattedAddress else name
|
def getName: String = if (nameIsEmpty) formattedAddress else name
|
||||||
|
|
||||||
override def toString: String = "ss://" + Base64.encodeToString("%s%s:%s@%s:%d".formatLocal(Locale.ENGLISH,
|
def toUri: Uri = {
|
||||||
method, if (auth) "-auth" else "", password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP) +
|
val builder = new Uri.Builder()
|
||||||
(if (TextUtils.isEmpty(name)) "" else '#' + URLEncoder.encode(name, "utf-8"))
|
.scheme("ss")
|
||||||
|
.encodedAuthority("%s@%s:%d".formatLocal(Locale.ENGLISH,
|
||||||
def isMethodUnsafe: Boolean = "table".equalsIgnoreCase(method) || "rc4".equalsIgnoreCase(method)
|
Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes,
|
||||||
|
Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort))
|
||||||
|
val configuration = new PluginConfiguration(plugin)
|
||||||
|
if (configuration.selected.nonEmpty)
|
||||||
|
builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false))
|
||||||
|
if (!nameIsEmpty) builder.fragment(name)
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
override def toString: String = toUri.toString
|
||||||
|
|
||||||
def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor
|
def serialize(editor: SharedPreferences.Editor): SharedPreferences.Editor = editor
|
||||||
.putString(Key.name, name)
|
.putString(Key.name, name)
|
||||||
@@ -114,7 +119,6 @@ class Profile {
|
|||||||
.putBoolean(Key.proxyApps, proxyApps)
|
.putBoolean(Key.proxyApps, proxyApps)
|
||||||
.putBoolean(Key.bypass, bypass)
|
.putBoolean(Key.bypass, bypass)
|
||||||
.putBoolean(Key.udpdns, udpdns)
|
.putBoolean(Key.udpdns, udpdns)
|
||||||
.putBoolean(Key.auth, auth)
|
|
||||||
.putBoolean(Key.ipv6, ipv6)
|
.putBoolean(Key.ipv6, ipv6)
|
||||||
.putString(Key.individual, individual)
|
.putString(Key.individual, individual)
|
||||||
.putString(Key.plugin, plugin)
|
.putString(Key.plugin, plugin)
|
||||||
@@ -132,7 +136,6 @@ class Profile {
|
|||||||
proxyApps = pref.getBoolean(Key.proxyApps, false)
|
proxyApps = pref.getBoolean(Key.proxyApps, false)
|
||||||
bypass = pref.getBoolean(Key.bypass, false)
|
bypass = pref.getBoolean(Key.bypass, false)
|
||||||
udpdns = pref.getBoolean(Key.udpdns, false)
|
udpdns = pref.getBoolean(Key.udpdns, false)
|
||||||
auth = pref.getBoolean(Key.auth, false)
|
|
||||||
ipv6 = pref.getBoolean(Key.ipv6, false)
|
ipv6 = pref.getBoolean(Key.ipv6, false)
|
||||||
individual = pref.getString(Key.individual, null)
|
individual = pref.getString(Key.individual, null)
|
||||||
plugin = pref.getString(Key.plugin, null)
|
plugin = pref.getString(Key.plugin, null)
|
||||||
|
|||||||
@@ -50,6 +50,20 @@ object PluginManager {
|
|||||||
|RSo6EooQ7+NBejOXysqIF1q0BJs8Y5s/CaTOmgbL7uPCkzArB6SS/hzXgDk5gw6v
|
|RSo6EooQ7+NBejOXysqIF1q0BJs8Y5s/CaTOmgbL7uPCkzArB6SS/hzXgDk5gw6v
|
||||||
|wkGeOtzcj1DlbUTvt1s5GlnwBTGUmkbLx+YUje+n+IBgMbohLUDYBtUHylRVgMsc
|
|wkGeOtzcj1DlbUTvt1s5GlnwBTGUmkbLx+YUje+n+IBgMbohLUDYBtUHylRVgMsc
|
||||||
|1WS67kDqeJiiQZvrxvyW6CZZ/MIGI+uAkkj3DqJpaZirkwPgvpcOIrjZy0uFvQM=
|
|1WS67kDqeJiiQZvrxvyW6CZZ/MIGI+uAkkj3DqJpaZirkwPgvpcOIrjZy0uFvQM=
|
||||||
|
""", Base64.DEFAULT)) +
|
||||||
|
new Signature(Base64.decode( // @madeye
|
||||||
|
"""
|
||||||
|
|MIICQzCCAaygAwIBAgIETV9OhjANBgkqhkiG9w0BAQUFADBmMQswCQYDVQQGEwJjbjERMA8GA1UE
|
||||||
|
|CBMIU2hhbmdoYWkxDzANBgNVBAcTBlB1ZG9uZzEUMBIGA1UEChMLRnVkYW4gVW5pdi4xDDAKBgNV
|
||||||
|
|BAsTA1BQSTEPMA0GA1UEAxMGTWF4IEx2MB4XDTExMDIxOTA1MDA1NFoXDTM2MDIxMzA1MDA1NFow
|
||||||
|
|ZjELMAkGA1UEBhMCY24xETAPBgNVBAgTCFNoYW5naGFpMQ8wDQYDVQQHEwZQdWRvbmcxFDASBgNV
|
||||||
|
|BAoTC0Z1ZGFuIFVuaXYuMQwwCgYDVQQLEwNQUEkxDzANBgNVBAMTBk1heCBMdjCBnzANBgkqhkiG
|
||||||
|
|9w0BAQEFAAOBjQAwgYkCgYEAq6lA8LqdeEI+es9SDX85aIcx8LoL3cc//iRRi+2mFIWvzvZ+bLKr
|
||||||
|
|4Wd0rhu/iU7OeMm2GvySFyw/GdMh1bqh5nNPLiRxAlZxpaZxLOdRcxuvh5Nc5yzjM+QBv8ECmuvu
|
||||||
|
|AOvvT3UDmA0AMQjZqSCmxWIxc/cClZ/0DubreBo2st0CAwEAATANBgkqhkiG9w0BAQUFAAOBgQAQ
|
||||||
|
|Iqonxpwk2ay+Dm5RhFfZyG9SatM/JNFx2OdErU16WzuK1ItotXGVJaxCZv3u/tTwM5aaMACGED5n
|
||||||
|
|AvHaDGCWynY74oDAopM4liF/yLe1wmZDu6Zo/7fXrH+T03LBgj2fcIkUfN1AA4dvnBo8XWAm9VrI
|
||||||
|
|1iNuLIssdhDz3IL9Yg==
|
||||||
""", Base64.DEFAULT))
|
""", Base64.DEFAULT))
|
||||||
|
|
||||||
private var receiver: BroadcastReceiver = _
|
private var receiver: BroadcastReceiver = _
|
||||||
|
|||||||
@@ -141,7 +141,6 @@ object Key {
|
|||||||
val proxyApps = "isProxyApps"
|
val proxyApps = "isProxyApps"
|
||||||
val bypass = "isBypassApps"
|
val bypass = "isBypassApps"
|
||||||
val udpdns = "isUdpDns"
|
val udpdns = "isUdpDns"
|
||||||
val auth = "isAuth"
|
|
||||||
val ipv6 = "isIpv6"
|
val ipv6 = "isIpv6"
|
||||||
|
|
||||||
val host = "proxy"
|
val host = "proxy"
|
||||||
|
|||||||
@@ -20,32 +20,49 @@
|
|||||||
|
|
||||||
package com.github.shadowsocks.utils
|
package com.github.shadowsocks.utils
|
||||||
|
|
||||||
import java.net.URLDecoder
|
import android.net.Uri
|
||||||
|
|
||||||
import android.util.{Base64, Log}
|
import android.util.{Base64, Log}
|
||||||
import com.github.shadowsocks.database.Profile
|
import com.github.shadowsocks.database.Profile
|
||||||
|
|
||||||
object Parser {
|
object Parser {
|
||||||
val TAG = "ShadowParser"
|
val TAG = "ShadowParser"
|
||||||
private val pattern = "(?i)ss://([A-Za-z0-9+-/=_]+)(#(.+))?".r
|
private val pattern = "(?i)^ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]$".r
|
||||||
private val decodedPattern = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$".r
|
private val userInfoPattern = "^(.+?):(.*)$".r
|
||||||
|
private val legacyPattern = "^((.+?):(.*)@(.+?):(\\d+?))$".r
|
||||||
|
|
||||||
def findAll(data: CharSequence): Iterator[Profile] =
|
def findAll(data: CharSequence): Iterator[Profile] =
|
||||||
pattern.findAllMatchIn(if (data == null) "" else data).map(m => try
|
pattern.findAllMatchIn(if (data == null) "" else data).map(m => {
|
||||||
decodedPattern.findFirstMatchIn(new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")) match {
|
val uri = Uri.parse(m.matched)
|
||||||
case Some(ss) =>
|
uri.getUserInfo match {
|
||||||
val profile = new Profile
|
case null => new String(Base64.decode(uri.getHost, Base64.NO_PADDING), "UTF-8") match {
|
||||||
profile.method = ss.group(2).toLowerCase
|
case legacyPattern(_, method, password, host, port) => // legacy uri
|
||||||
if (ss.group(3) != null) profile.auth = true
|
val profile = new Profile
|
||||||
profile.password = ss.group(4)
|
profile.method = method.toLowerCase
|
||||||
profile.host = ss.group(5)
|
profile.password = password
|
||||||
profile.remotePort = ss.group(6).toInt
|
profile.host = host
|
||||||
if (m.group(2) != null) profile.name = URLDecoder.decode(m.group(3), "utf-8")
|
profile.remotePort = port.toInt
|
||||||
profile
|
profile.plugin = uri.getQueryParameter(Key.plugin)
|
||||||
case _ => null
|
profile.name = uri.getFragment
|
||||||
} catch {
|
profile
|
||||||
case ex: Exception =>
|
case _ =>
|
||||||
Log.e(TAG, "parser error: " + m.source, ex)// Ignore
|
Log.e(TAG, "Unrecognized URI: " + m.matched)
|
||||||
null
|
null
|
||||||
}).filter(_ != null)
|
}
|
||||||
|
case userInfo =>
|
||||||
|
new String(Base64.decode(userInfo, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), "UTF-8") match {
|
||||||
|
case userInfoPattern(method, password) =>
|
||||||
|
val profile = new Profile
|
||||||
|
profile.method = method
|
||||||
|
profile.password = password
|
||||||
|
profile.host = uri.getHost
|
||||||
|
profile.remotePort = uri.getPort
|
||||||
|
profile.plugin = uri.getQueryParameter(Key.plugin)
|
||||||
|
profile.name = uri.getFragment
|
||||||
|
profile
|
||||||
|
case _ =>
|
||||||
|
Log.e(TAG, "Unknown user info: " + m.matched)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).filter(_ != null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ object TrafficMonitor {
|
|||||||
def formatTraffic(size: Long): String = {
|
def formatTraffic(size: Long): String = {
|
||||||
var n: Double = size
|
var n: Double = size
|
||||||
var i = -1
|
var i = -1
|
||||||
while (n >= 1000) {
|
while (n >= 999.5) {
|
||||||
n /= 1024
|
n /= 1024
|
||||||
i = i + 1
|
i = i + 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
package com.github.shadowsocks.utils
|
package com.github.shadowsocks.utils
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.net._
|
import java.net._
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@@ -108,7 +109,15 @@ object Utils {
|
|||||||
.orElse(resolve(host))
|
.orElse(resolve(host))
|
||||||
|
|
||||||
private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String])
|
private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String])
|
||||||
|
private lazy val parseNumericAddressMethod = classOf[InetAddress].getMethod("parseNumericAddress", classOf[String])
|
||||||
def isNumeric(address: String): Boolean = isNumericMethod.invoke(null, address).asInstanceOf[Boolean]
|
def isNumeric(address: String): Boolean = isNumericMethod.invoke(null, address).asInstanceOf[Boolean]
|
||||||
|
def parseNumericAddress(address: String): InetAddress =
|
||||||
|
try parseNumericAddressMethod.invoke(null, address).asInstanceOf[InetAddress] catch {
|
||||||
|
case exc: InvocationTargetException => throw exc.getCause match {
|
||||||
|
case null => exc
|
||||||
|
case cause => cause
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there exists a valid IPv6 interface
|
* If there exists a valid IPv6 interface
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
* 0.0.2:
|
||||||
|
* Add `getOrDefault` to `PluginOptions`;
|
||||||
|
* Update support library version to 25.1.1.
|
||||||
|
* 0.0.1: Initial release.
|
||||||
+4
-5
@@ -1,5 +1,7 @@
|
|||||||
# shadowsocks-android plugin framework
|
# shadowsocks-android plugin framework
|
||||||
|
|
||||||
|
[Documentation](doc.md) | [Change log](CHANGES.md)
|
||||||
|
|
||||||
Support library for easier development on [shadowsocks
|
Support library for easier development on [shadowsocks
|
||||||
plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some
|
plugin](https://github.com/shadowsocks/shadowsocks-org/issues/28) for Android. Also includes some
|
||||||
useful resources to easily get consistent styling with the main app.
|
useful resources to easily get consistent styling with the main app.
|
||||||
@@ -19,9 +21,6 @@ This library is designed with Java interoperability in mind so theoretically you
|
|||||||
library with other languages and/or build tools but there isn't documentation for that yet. This
|
library with other languages and/or build tools but there isn't documentation for that yet. This
|
||||||
guide is written for Scala + SBT. Contributions are welcome.
|
guide is written for Scala + SBT. Contributions are welcome.
|
||||||
|
|
||||||
The following is just a simple guide to get you started, for more technical details, see
|
|
||||||
[doc.md](https://github.com/shadowsocks/shadowsocks-android/blob/develop/plugin/doc.md).
|
|
||||||
|
|
||||||
### Package name
|
### Package name
|
||||||
|
|
||||||
There are no arbitrary restrictions/requirements on package name, component name and content
|
There are no arbitrary restrictions/requirements on package name, component name and content
|
||||||
@@ -37,7 +36,7 @@ First you need to add this library to your dependencies. This library is written
|
|||||||
and it's most convenient to use it with SBT:
|
and it's most convenient to use it with SBT:
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.1-SNAPSHOT"
|
libraryDependencies += "com.github.shadowsocks" %% "plugin" % "0.0.2"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Native binary configuration
|
### Native binary configuration
|
||||||
@@ -88,7 +87,7 @@ Then add it to your manifest:
|
|||||||
...
|
...
|
||||||
<provider android:name=".BinaryProvider"
|
<provider android:name=".BinaryProvider"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID">
|
android:authorities="$FULLY_QUALIFIED_NAME_OF_YOUR_CONTENTPROVIDER">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
|
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|||||||
+28
-1
@@ -2,4 +2,31 @@ enablePlugins(AndroidLib)
|
|||||||
android.useSupportVectors
|
android.useSupportVectors
|
||||||
|
|
||||||
name := "plugin"
|
name := "plugin"
|
||||||
version := "0.0.1"
|
version := "0.0.2"
|
||||||
|
|
||||||
|
pomExtra in Global := {
|
||||||
|
<url>https://github.com/shadowsocks/shadowsocks-android</url>
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>GPLv3</name>
|
||||||
|
<url>https://www.gnu.org/licenses/gpl-3.0.html</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
<scm>
|
||||||
|
<connection>scm:git:git://github.com/shadowsocks/shadowsocks-android.git</connection>
|
||||||
|
<developerConnection>scm:git:git@github.com:shadowsocks/shadowsocks-android.git</developerConnection>
|
||||||
|
<url>github.com/shadowsocks/shadowsocks-android</url>
|
||||||
|
</scm>
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>madeye</id>
|
||||||
|
<name>Max Lv</name>
|
||||||
|
<url>https://github.com/madeye</url>
|
||||||
|
</developer>
|
||||||
|
<developer>
|
||||||
|
<id>Mygod</id>
|
||||||
|
<name>Mygod Studio</name>
|
||||||
|
<url>https://github.com/Mygod</url>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -153,7 +153,7 @@ This corresponds to `com.github.shadowsocks.plugin.NativePluginProvider` in the
|
|||||||
...
|
...
|
||||||
<provider android:name=".BinaryProvider"
|
<provider android:name=".BinaryProvider"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID">
|
android:authorities="$FULLY_QUALIFIED_NAME_OF_YOUR_CONTENTPROVIDER">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
|
<action android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -196,7 +196,7 @@ This feature hasn't been implemented yet.
|
|||||||
Plugins are certified using package signatures and shadowsocks-android will consider these
|
Plugins are certified using package signatures and shadowsocks-android will consider these
|
||||||
signatures as trusted:
|
signatures as trusted:
|
||||||
|
|
||||||
* Signatures by [trusted sources](https://github.com/shadowsocks/shadowsocks-android/blob/develop/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22)
|
* Signatures by [trusted sources](/mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala#L22)
|
||||||
which includes:
|
which includes:
|
||||||
- @madeye, i.e. the signer of the main repo;
|
- @madeye, i.e. the signer of the main repo;
|
||||||
- The main repo doesn't contain any other trusted signatures. Third-party forks should add their
|
- The main repo doesn't contain any other trusted signatures. Third-party forks should add their
|
||||||
@@ -226,5 +226,5 @@ Plugin app must include this in their application tag: (which should be automati
|
|||||||
|
|
||||||
```
|
```
|
||||||
<meta-data android:name="com.github.shadowsocks.plugin.version"
|
<meta-data android:name="com.github.shadowsocks.plugin.version"
|
||||||
android:value="0.0.1"/>
|
android:value="0.0.2"/>
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
<application
|
<application
|
||||||
android:theme="@style/Theme.Shadowsocks">
|
android:theme="@style/Theme.Shadowsocks">
|
||||||
<meta-data android:name="com.github.shadowsocks.plugin.version"
|
<meta-data android:name="com.github.shadowsocks.plugin.version"
|
||||||
android:value="0.0.1"/>
|
android:value="0.0.2"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ public final class PluginOptions extends HashMap<String, String> {
|
|||||||
super(initialCapacity, loadFactor);
|
super(initialCapacity, loadFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: this method is not needed since API 24
|
||||||
|
public String getOrDefault(Object key, String defaultValue) {
|
||||||
|
String v;
|
||||||
|
return (((v = get(key)) != null) || containsKey(key))
|
||||||
|
? v
|
||||||
|
: defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
private PluginOptions(String options, boolean parseId) {
|
private PluginOptions(String options, boolean parseId) {
|
||||||
this();
|
this();
|
||||||
if (TextUtils.isEmpty(options)) return;
|
if (TextUtils.isEmpty(options)) return;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import android.os.{Bundle, ParcelFileDescriptor}
|
|||||||
* <application>
|
* <application>
|
||||||
* ...
|
* ...
|
||||||
* <provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider"
|
* <provider android:name="com.github.shadowsocks.$PLUGIN_ID.BinaryProvider"
|
||||||
* android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID">
|
* android:authorities="com.github.shadowsocks.plugin.$PLUGIN_ID.BinaryProvider">
|
||||||
* <intent-filter>
|
* <intent-filter>
|
||||||
* <category android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" />
|
* <category android:name="com.github.shadowsocks.plugin.ACTION_NATIVE_PLUGIN" />
|
||||||
* </intent-filter>
|
* </intent-filter>
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.3")
|
addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.4")
|
||||||
|
|
||||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
|
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
sonatypeProfileName := "com.github.shadowsocks"
|
|
||||||
|
|
||||||
pomExtra in Global := {
|
|
||||||
<url>https://github.com/shadowsocks/shadowsocks-android</url>
|
|
||||||
<licenses>
|
|
||||||
<license>
|
|
||||||
<name>GPLv3</name>
|
|
||||||
<url>https://www.gnu.org/licenses/gpl-3.0.html</url>
|
|
||||||
</license>
|
|
||||||
</licenses>
|
|
||||||
<scm>
|
|
||||||
<connection>scm:git:git://github.com/shadowsocks/shadowsocks-android.git</connection>
|
|
||||||
<developerConnection>scm:git:git@github.com:shadowsocks/shadowsocks-android.git</developerConnection>
|
|
||||||
<url>github.com/shadowsocks/shadowsocks-android</url>
|
|
||||||
</scm>
|
|
||||||
<developers>
|
|
||||||
<developer>
|
|
||||||
<id>madeye</id>
|
|
||||||
<name>Max Lv</name>
|
|
||||||
<url>https://github.com/madeye/</url>
|
|
||||||
</developer>
|
|
||||||
</developers>
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user