Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5427e00012 | ||
|
|
7f226d3f0c | ||
|
|
ee3f3c2338 | ||
|
|
6ac90abbf7 | ||
|
|
2f553b156a | ||
|
|
dec098fbc5 | ||
|
|
000d03ad4c | ||
|
|
667619f701 | ||
|
|
afd91b7155 | ||
|
|
e10272c8e5 | ||
|
|
0f5651fef7 | ||
|
|
f258de36d7 | ||
|
|
e71db2160e | ||
|
|
f1cbfb7ea7 | ||
|
|
8c2a2f032a | ||
|
|
24d45cc2a2 | ||
|
|
5220f302c5 | ||
|
|
f0ef6c8e35 | ||
|
|
10de6ea6d9 | ||
|
|
c1f55c80f9 | ||
|
|
3503e7233f | ||
|
|
3db8e9d775 | ||
|
|
e708e117be | ||
|
|
836088534b | ||
|
|
6ae97e630e | ||
|
|
bae0b9e746 | ||
|
|
15262343f4 | ||
|
|
be31a0ce82 | ||
|
|
0b0b6137b8 | ||
|
|
156b0c27f5 | ||
|
|
ca99596270 | ||
|
|
e22ffa8601 | ||
|
|
24233f104a | ||
|
|
01306d527d | ||
|
|
5547b42283 | ||
|
|
3dba966355 | ||
|
|
c9a7dc8deb | ||
|
|
4df7634fae | ||
|
|
97dd8fa15e | ||
|
|
4b37d88f74 | ||
|
|
8ee5d77c6f | ||
|
|
8cbfad80f4 | ||
|
|
9857e42776 | ||
|
|
2f84c74d06 | ||
|
|
bf207eb9b8 | ||
|
|
7e8dad5c76 | ||
|
|
185e7608ae | ||
|
|
d844fc2457 | ||
|
|
a1bdfa5bd9 | ||
|
|
6c7f62c237 | ||
|
|
383e9404ec | ||
|
|
16d2cd9a29 | ||
|
|
9ee2345fd6 | ||
|
|
567543d00e | ||
|
|
7c2b717141 | ||
|
|
08d5d56e46 | ||
|
|
e22b145023 |
+1
-3
@@ -1,7 +1,6 @@
|
|||||||
classes
|
classes
|
||||||
bin
|
bin
|
||||||
gen
|
gen
|
||||||
obj
|
|
||||||
target
|
target
|
||||||
local.properties
|
local.properties
|
||||||
.classpath
|
.classpath
|
||||||
@@ -24,11 +23,10 @@ local.sbt
|
|||||||
out/
|
out/
|
||||||
|
|
||||||
#NDK
|
#NDK
|
||||||
|
src/main/obj
|
||||||
src/main/libs
|
src/main/libs
|
||||||
src/main/assets/armeabi
|
src/main/assets/armeabi
|
||||||
src/main/assets/armeabi-v7a
|
src/main/assets/armeabi-v7a
|
||||||
src/main/assets/x86
|
src/main/assets/x86
|
||||||
src/main/assets/api-16/
|
src/main/assets/api-16/
|
||||||
jni/Application.mk
|
jni/Application.mk
|
||||||
obj
|
|
||||||
libs
|
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ install:
|
|||||||
- echo "y" | android update sdk --filter extra-android-m2repository --no-ui --no-https -a
|
- echo "y" | android update sdk --filter extra-android-m2repository --no-ui --no-https -a
|
||||||
script:
|
script:
|
||||||
- cp local.properties.travis local.properties
|
- cp local.properties.travis local.properties
|
||||||
|
- ./build.sh
|
||||||
- sbt android:package-release
|
- sbt android:package-release
|
||||||
- exit 0
|
- exit 0
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ Help to translate shadowsocks: http://crowdin.net/project/shadowsocks/invite
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
|
|
||||||
|
# Build native binaries
|
||||||
|
./build.sh
|
||||||
|
|
||||||
# Build the App
|
# Build the App
|
||||||
sbt clean android:package-release
|
sbt clean android:package-release
|
||||||
@@ -47,6 +50,9 @@ Help to translate shadowsocks: http://crowdin.net/project/shadowsocks/invite
|
|||||||
```bash
|
```bash
|
||||||
git submodule update --init
|
git submodule update --init
|
||||||
|
|
||||||
|
# Build native binaries
|
||||||
|
./build.sh
|
||||||
|
|
||||||
# Build the apk
|
# Build the apk
|
||||||
sbt clean android:package-release
|
sbt clean android:package-release
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -12,25 +12,29 @@ compileOrder in Compile := CompileOrder.JavaThenScala
|
|||||||
|
|
||||||
ndkJavah in Android := List()
|
ndkJavah in Android := List()
|
||||||
|
|
||||||
|
ndkBuild in Android := List()
|
||||||
|
|
||||||
|
typedResources in Android := false
|
||||||
|
|
||||||
resolvers += "JRAF" at "http://JRAF.org/static/maven/2"
|
resolvers += "JRAF" at "http://JRAF.org/static/maven/2"
|
||||||
|
|
||||||
resolvers += "madeye private releases" at "http://madeye-maven-repository.googlecode.com/git/"
|
resolvers += "madeye private releases" at "http://madeye-maven-repository.googlecode.com/git/"
|
||||||
|
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"dnsjava" % "dnsjava" % "2.1.5",
|
"dnsjava" % "dnsjava" % "2.1.5",
|
||||||
"org.scalaj" %% "scalaj-http" % "0.3.10",
|
"com.github.kevinsawicki" % "http-request" % "5.6",
|
||||||
"commons-net" % "commons-net" % "3.3",
|
"commons-net" % "commons-net" % "3.3",
|
||||||
"com.google.zxing" % "android-integration" % "2.2"
|
"com.google.zxing" % "android-integration" % "3.1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"net.simonvt.menudrawer" % "menudrawer" % "3.0.6",
|
"net.simonvt.menudrawer" % "menudrawer" % "3.0.6",
|
||||||
"com.google.android.gms" % "play-services" % "5.0.89",
|
"com.google.android.gms" % "play-services" % "6.1.71",
|
||||||
"com.android.support" % "support-v4" % "19.1.0"
|
"com.android.support" % "support-v4" % "21.0.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"de.keyboardsurfer.android.widget" % "crouton" % "1.8.4" exclude("com.google.android", "support-v4"),
|
"com.github.mrengineer13" % "snackbar" % "0.5.0",
|
||||||
"com.nostra13.universalimageloader" % "universal-image-loader" % "1.8.4",
|
"com.nostra13.universalimageloader" % "universal-image-loader" % "1.8.4",
|
||||||
"com.j256.ormlite" % "ormlite-core" % "4.47",
|
"com.j256.ormlite" % "ormlite-core" % "4.47",
|
||||||
"com.j256.ormlite" % "ormlite-android" % "4.47"
|
"com.j256.ormlite" % "ormlite-android" % "4.47"
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function try () {
|
||||||
|
"$@" || exit -1
|
||||||
|
}
|
||||||
|
|
||||||
|
try pushd src/main
|
||||||
|
|
||||||
|
# Build
|
||||||
|
try $ANDROID_NDK_HOME/ndk-build clean
|
||||||
|
try $ANDROID_NDK_HOME/ndk-build
|
||||||
|
|
||||||
|
# copy executables
|
||||||
|
rm -rf assets/armeabi-v7a
|
||||||
|
rm -rf assets/x86
|
||||||
|
mkdir -p assets/armeabi-v7a
|
||||||
|
mkdir -p assets/x86
|
||||||
|
for app in pdnsd redsocks ss-local ss-tunnel tun2socks
|
||||||
|
do
|
||||||
|
try mv libs/armeabi-v7a/$app assets/armeabi-v7a/
|
||||||
|
try mv libs/x86/$app assets/x86/
|
||||||
|
done
|
||||||
|
|
||||||
|
try popd
|
||||||
+3
-1
@@ -2,4 +2,6 @@ resolvers += Resolver.url("scalasbt releases", new URL("http://scalasbt.artifact
|
|||||||
|
|
||||||
addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.3.10")
|
addSbtPlugin("com.hanhuy.sbt" % "android-sdk-plugin" % "1.3.10")
|
||||||
|
|
||||||
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
|
resolvers += Resolver.sbtPluginRepo("snapshots")
|
||||||
|
|
||||||
|
addSbtPlugin("com.hanhuy.sbt" % "sbt-idea" % "1.7.0-SNAPSHOT")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.github.shadowsocks"
|
package="com.github.shadowsocks"
|
||||||
android:versionCode="83"
|
android:versionCode="96"
|
||||||
android:versionName="2.4.3">
|
android:versionName="2.5.6">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
@@ -74,11 +74,13 @@
|
|||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".ShadowsocksNatService"
|
android:name=".ShadowsocksNatService"
|
||||||
|
android:process=":nat"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".ShadowsocksVpnService"
|
android:name=".ShadowsocksVpnService"
|
||||||
|
android:process=":vpn"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
/* Shadowsocks - A shadowsocks client for Android
|
|
||||||
* Copyright (C) 2012 <max.c.lv@gmail.com>
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* ___====-_ _-====___
|
|
||||||
* _--^^^#####// \\#####^^^--_
|
|
||||||
* _-^##########// ( ) \\##########^-_
|
|
||||||
* -############// |\^^/| \\############-
|
|
||||||
* _/############// (@::@) \\############\_
|
|
||||||
* /#############(( \\// ))#############\
|
|
||||||
* -###############\\ (oo) //###############-
|
|
||||||
* -#################\\ / VV \ //#################-
|
|
||||||
* -###################\\/ \//###################-
|
|
||||||
* _#/|##########/\######( /\ )######/\##########|\#_
|
|
||||||
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
|
|
||||||
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
|
|
||||||
* ` ` ` ` / | | | | \ ' ' ' '
|
|
||||||
* ( | | | | )
|
|
||||||
* __\ | | | | /__
|
|
||||||
* (vvv(VVV)(VVV)vvv)
|
|
||||||
*
|
|
||||||
* HERE BE DRAGONS
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.github.shadowsocks;
|
|
||||||
|
|
||||||
public class Core {
|
|
||||||
static {
|
|
||||||
java.lang.System.loadLibrary("ss-local-jni");
|
|
||||||
java.lang.System.loadLibrary("ss-tunnel-jni");
|
|
||||||
java.lang.System.loadLibrary("tun2socks-jni");
|
|
||||||
java.lang.System.loadLibrary("pdnsd-jni");
|
|
||||||
java.lang.System.loadLibrary("redsocks-jni");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static native void sslocal(String[] cmd);
|
|
||||||
public static native void sstunnel(String[] cmd);
|
|
||||||
public static native void tun2socks(String[] cmd);
|
|
||||||
public static native void pdnsd(String[] cmd);
|
|
||||||
public static native void redsocks(String[] cmd);
|
|
||||||
}
|
|
||||||
+93
-26
@@ -16,6 +16,73 @@
|
|||||||
LOCAL_PATH := $(call my-dir)
|
LOCAL_PATH := $(call my-dir)
|
||||||
ROOT_PATH := $(LOCAL_PATH)
|
ROOT_PATH := $(LOCAL_PATH)
|
||||||
|
|
||||||
|
########################################################
|
||||||
|
## libsodium
|
||||||
|
########################################################
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
crypto_aead_src := crypto_aead/chacha20poly1305/sodium/aead_chacha20poly1305.c
|
||||||
|
|
||||||
|
crypto_auth_src := 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_src := crypto_box/crypto_box.c crypto_box/crypto_box_easy.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_src := crypto_core/hsalsa20/core_hsalsa20_api.c crypto_core/hsalsa20/ref2/core_hsalsa20.c crypto_core/salsa20/core_salsa20_api.c crypto_core/salsa20/ref/core_salsa20.c crypto_core/salsa2012/core_salsa2012_api.c crypto_core/salsa2012/ref/core_salsa2012.c crypto_core/salsa208/core_salsa208_api.c crypto_core/salsa208/ref/core_salsa208.c
|
||||||
|
|
||||||
|
crypto_generichash_src := crypto_generichash/blake2/generichash_blake2_api.c crypto_generichash/blake2/ref/blake2b-ref.c crypto_generichash/blake2/ref/generichash_blake2b.c crypto_generichash/crypto_generichash.c
|
||||||
|
|
||||||
|
crypto_hash_src := crypto_hash/crypto_hash.c crypto_hash/sha256/cp/hash_sha256.c crypto_hash/sha256/hash_sha256_api.c crypto_hash/sha512/cp/hash_sha512.c crypto_hash/sha512/hash_sha512_api.c
|
||||||
|
|
||||||
|
crypto_onetimeauth_src := crypto_onetimeauth/crypto_onetimeauth.c crypto_onetimeauth/poly1305/donna/auth_poly1305_donna.c crypto_onetimeauth/poly1305/donna/verify_poly1305_donna.c crypto_onetimeauth/poly1305/onetimeauth_poly1305.c crypto_onetimeauth/poly1305/onetimeauth_poly1305_api.c crypto_onetimeauth/poly1305/onetimeauth_poly1305_try.c
|
||||||
|
|
||||||
|
crypto_pwhash_src := crypto_pwhash/scryptsalsa208sha256/crypto_scrypt-common.c crypto_pwhash/scryptsalsa208sha256/nosse/pwhash_scryptsalsa208sha256_nosse.c crypto_pwhash/scryptsalsa208sha256/pbkdf2-sha256.c crypto_pwhash/scryptsalsa208sha256/pwhash_scryptsalsa208sha256.c crypto_pwhash/scryptsalsa208sha256/scrypt_platform.c
|
||||||
|
|
||||||
|
crypto_scalarmult_src := crypto_scalarmult/crypto_scalarmult.c crypto_scalarmult/curve25519/donna_c64/base_curve25519_donna_c64.c crypto_scalarmult/curve25519/donna_c64/smult_curve25519_donna_c64.c crypto_scalarmult/curve25519/ref10/base_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_0_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_1_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_add_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_copy_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_cswap_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_frombytes_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_invert_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_mul121666_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_mul_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_sq_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_sub_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/fe_tobytes_curve25519_ref10.c crypto_scalarmult/curve25519/ref10/scalarmult_curve25519_ref10.c crypto_scalarmult/curve25519/scalarmult_curve25519_api.c
|
||||||
|
|
||||||
|
crypto_secretbox_src := crypto_secretbox/crypto_secretbox.c crypto_secretbox/crypto_secretbox_easy.c crypto_secretbox/xsalsa20poly1305/ref/box_xsalsa20poly1305.c crypto_secretbox/xsalsa20poly1305/secretbox_xsalsa20poly1305_api.c
|
||||||
|
|
||||||
|
crypto_shorthash_src := crypto_shorthash/crypto_shorthash.c crypto_shorthash/siphash24/ref/shorthash_siphash24.c crypto_shorthash/siphash24/shorthash_siphash24_api.c
|
||||||
|
|
||||||
|
crypto_sign_src := crypto_sign/crypto_sign.c crypto_sign/ed25519/ref10/fe_0.c crypto_sign/ed25519/ref10/fe_1.c crypto_sign/ed25519/ref10/fe_add.c crypto_sign/ed25519/ref10/fe_cmov.c crypto_sign/ed25519/ref10/fe_copy.c crypto_sign/ed25519/ref10/fe_frombytes.c crypto_sign/ed25519/ref10/fe_invert.c crypto_sign/ed25519/ref10/fe_isnegative.c crypto_sign/ed25519/ref10/fe_isnonzero.c crypto_sign/ed25519/ref10/fe_mul.c crypto_sign/ed25519/ref10/fe_neg.c crypto_sign/ed25519/ref10/fe_pow22523.c crypto_sign/ed25519/ref10/fe_sq.c crypto_sign/ed25519/ref10/fe_sq2.c crypto_sign/ed25519/ref10/fe_sub.c crypto_sign/ed25519/ref10/fe_tobytes.c crypto_sign/ed25519/ref10/ge_add.c crypto_sign/ed25519/ref10/ge_double_scalarmult.c crypto_sign/ed25519/ref10/ge_frombytes.c crypto_sign/ed25519/ref10/ge_madd.c crypto_sign/ed25519/ref10/ge_msub.c crypto_sign/ed25519/ref10/ge_p1p1_to_p2.c crypto_sign/ed25519/ref10/ge_p1p1_to_p3.c crypto_sign/ed25519/ref10/ge_p2_0.c crypto_sign/ed25519/ref10/ge_p2_dbl.c crypto_sign/ed25519/ref10/ge_p3_0.c crypto_sign/ed25519/ref10/ge_p3_dbl.c crypto_sign/ed25519/ref10/ge_p3_to_cached.c crypto_sign/ed25519/ref10/ge_p3_to_p2.c crypto_sign/ed25519/ref10/ge_p3_tobytes.c crypto_sign/ed25519/ref10/ge_precomp_0.c crypto_sign/ed25519/ref10/ge_scalarmult_base.c crypto_sign/ed25519/ref10/ge_sub.c crypto_sign/ed25519/ref10/ge_tobytes.c crypto_sign/ed25519/ref10/keypair.c crypto_sign/ed25519/ref10/open.c crypto_sign/ed25519/ref10/sc_muladd.c crypto_sign/ed25519/ref10/sc_reduce.c crypto_sign/ed25519/ref10/sign.c crypto_sign/ed25519/sign_ed25519_api.c crypto_sign/edwards25519sha512batch/ref/fe25519_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/ref/ge25519_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/ref/sc25519_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/ref/sign_edwards25519sha512batch.c crypto_sign/edwards25519sha512batch/sign_edwards25519sha512batch_api.c
|
||||||
|
|
||||||
|
crypto_stream_src := crypto_stream/aes128ctr/portable/afternm_aes128ctr.c crypto_stream/aes128ctr/portable/beforenm_aes128ctr.c crypto_stream/aes128ctr/portable/common_aes128ctr.c crypto_stream/aes128ctr/portable/consts_aes128ctr.c crypto_stream/aes128ctr/portable/int128_aes128ctr.c crypto_stream/aes128ctr/portable/stream_aes128ctr.c crypto_stream/aes128ctr/portable/xor_afternm_aes128ctr.c crypto_stream/aes128ctr/stream_aes128ctr_api.c crypto_stream/chacha20/ref/stream_chacha20_ref.c crypto_stream/chacha20/stream_chacha20_api.c crypto_stream/crypto_stream.c crypto_stream/salsa20/ref/stream_salsa20_ref.c crypto_stream/salsa20/ref/xor_salsa20_ref.c crypto_stream/salsa20/stream_salsa20_api.c crypto_stream/salsa2012/ref/stream_salsa2012.c crypto_stream/salsa2012/ref/xor_salsa2012.c crypto_stream/salsa2012/stream_salsa2012_api.c crypto_stream/salsa208/ref/stream_salsa208.c crypto_stream/salsa208/ref/xor_salsa208.c crypto_stream/salsa208/stream_salsa208_api.c crypto_stream/xsalsa20/ref/stream_xsalsa20.c crypto_stream/xsalsa20/ref/xor_xsalsa20.c crypto_stream/xsalsa20/stream_xsalsa20_api.c
|
||||||
|
|
||||||
|
crypto_verify_src := crypto_verify/16/ref/verify_16.c crypto_verify/16/verify_16_api.c crypto_verify/32/ref/verify_32.c crypto_verify/32/verify_32_api.c crypto_verify/64/ref/verify_64.c crypto_verify/64/verify_64_api.c
|
||||||
|
|
||||||
|
randombytes_src := randombytes/randombytes.c randombytes/salsa20/randombytes_salsa20_random.c randombytes/sysrandom/randombytes_sysrandom.c
|
||||||
|
|
||||||
|
sodium_src := sodium/core.c sodium/runtime.c sodium/utils.c sodium/version.c
|
||||||
|
|
||||||
|
SODIUM_SOURCE := $(crypto_aead_src) $(crypto_auth_src) $(crypto_box_src) \
|
||||||
|
$(crypto_core_src) $(crypto_generichash_src) $(crypto_hash_src) \
|
||||||
|
$(crypto_onetimeauth_src) $(crypto_pwhash_src) $(crypto_scalarmult_src) \
|
||||||
|
$(crypto_secretbox_src) $(crypto_shorthash_src) $(crypto_sign_src) \
|
||||||
|
$(crypto_stream_src) $(crypto_verify_src) $(randombytes_src) $(sodium_src)
|
||||||
|
|
||||||
|
LOCAL_MODULE := sodium
|
||||||
|
LOCAL_CFLAGS += -O2 -I$(LOCAL_PATH)/shadowsocks/libsodium/src/libsodium/include \
|
||||||
|
-I$(LOCAL_PATH)/shadowsocks/libsodium/src/libsodium/include/sodium \
|
||||||
|
-DPACKAGE_NAME=\"libsodium\" -DPACKAGE_TARNAME=\"libsodium\" \
|
||||||
|
-DPACKAGE_VERSION=\"1.0.1\" -DPACKAGE_STRING=\"libsodium\ 1.0.1\" \
|
||||||
|
-DPACKAGE_BUGREPORT=\"https://github.com/jedisct1/libsodium/issues\" \
|
||||||
|
-DPACKAGE_URL=\"https://github.com/jedisct1/libsodium\" \
|
||||||
|
-DPACKAGE=\"libsodium\" -DVERSION=\"1.0.1\" -DSTDC_HEADERS=1 \
|
||||||
|
-DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 \
|
||||||
|
-DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 \
|
||||||
|
-DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 \
|
||||||
|
-D__EXTENSIONS__=1 -D_ALL_SOURCE=1 -D_GNU_SOURCE=1 \
|
||||||
|
-D_POSIX_PTHREAD_SEMANTICS=1 -D_TANDEM_SOURCE=1 \
|
||||||
|
-DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -DHAVE_MMINTRIN_H=1 \
|
||||||
|
-DHAVE_EMMINTRIN_H=1 -DHAVE_PMMINTRIN_H=1 -DHAVE_TMMINTRIN_H=1 \
|
||||||
|
-DHAVE_SYS_MMAN_H=1 -DNATIVE_LITTLE_ENDIAN=1 \
|
||||||
|
-DHAVE_WEAK_SYMBOLS=1 -DHAVE_ARC4RANDOM=1 -DHAVE_ARC4RANDOM_BUF=1 \
|
||||||
|
-DHAVE_MLOCK=1 -DHAVE_MPROTECT=1 -DHAVE_POSIX_MEMALIGN=1
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := $(addprefix shadowsocks/libsodium/src/libsodium/,$(SODIUM_SOURCE))
|
||||||
|
|
||||||
|
include $(BUILD_STATIC_LIBRARY)
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
## libevent
|
## libevent
|
||||||
########################################################
|
########################################################
|
||||||
@@ -120,13 +187,13 @@ REDSOCKS_SOURCES := base.c dnstc.c http-connect.c \
|
|||||||
|
|
||||||
LOCAL_STATIC_LIBRARIES := libevent
|
LOCAL_STATIC_LIBRARIES := libevent
|
||||||
|
|
||||||
LOCAL_MODULE := redsocks-jni
|
LOCAL_MODULE := redsocks
|
||||||
LOCAL_SRC_FILES := $(addprefix redsocks/, $(REDSOCKS_SOURCES)) main-jni.cpp
|
LOCAL_SRC_FILES := $(addprefix redsocks/, $(REDSOCKS_SOURCES))
|
||||||
LOCAL_CFLAGS := -O2 -std=gnu99 -DREDSOCKS_JNI -I$(LOCAL_PATH)/redsocks \
|
LOCAL_CFLAGS := -O2 -std=gnu99 -I$(LOCAL_PATH)/redsocks \
|
||||||
-I$(LOCAL_PATH)/libevent/include \
|
-I$(LOCAL_PATH)/libevent/include \
|
||||||
-I$(LOCAL_PATH)/libevent
|
-I$(LOCAL_PATH)/libevent
|
||||||
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
include $(BUILD_EXECUTABLE)
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
## pdnsd library
|
## pdnsd library
|
||||||
@@ -134,13 +201,13 @@ include $(BUILD_SHARED_LIBRARY)
|
|||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/src/*.c) main-jni.cpp
|
PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/src/*.c)
|
||||||
|
|
||||||
LOCAL_MODULE := pdnsd-jni
|
LOCAL_MODULE := pdnsd
|
||||||
LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)/%=%)
|
LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)/%=%)
|
||||||
LOCAL_CFLAGS := -Wall -O2 -DPDNSD_JNI -I$(LOCAL_PATH)/pdnsd
|
LOCAL_CFLAGS := -Wall -O2 -I$(LOCAL_PATH)/pdnsd
|
||||||
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
include $(BUILD_EXECUTABLE)
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
## shadowsocks local library
|
## shadowsocks local library
|
||||||
@@ -150,45 +217,45 @@ include $(CLEAR_VARS)
|
|||||||
|
|
||||||
SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c utils.c json.c jconf.c acl.c
|
SHADOWSOCKS_SOURCES := local.c cache.c udprelay.c encrypt.c utils.c json.c jconf.c acl.c
|
||||||
|
|
||||||
LOCAL_MODULE := ss-local-jni
|
LOCAL_MODULE := ss-local
|
||||||
LOCAL_SRC_FILES := $(addprefix shadowsocks/src/, $(SHADOWSOCKS_SOURCES)) main-jni.cpp
|
LOCAL_SRC_FILES := $(addprefix shadowsocks/src/, $(SHADOWSOCKS_SOURCES))
|
||||||
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DUDPRELAY_LOCAL -DSSLOCAL_JNI \
|
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DUDPRELAY_LOCAL \
|
||||||
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H \
|
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H \
|
||||||
-I$(LOCAL_PATH)/libev/ \
|
-I$(LOCAL_PATH)/libev/ \
|
||||||
-I$(LOCAL_PATH)/openssl/include \
|
-I$(LOCAL_PATH)/openssl/include \
|
||||||
-I$(LOCAL_PATH)/shadowsocks/libcork/include \
|
-I$(LOCAL_PATH)/shadowsocks/libcork/include \
|
||||||
|
-I$(LOCAL_PATH)/shadowsocks/libsodium/src/libsodium/include \
|
||||||
|
-I$(LOCAL_PATH)/shadowsocks/libsodium/src/libsodium/include/sodium \
|
||||||
-I$(LOCAL_PATH)/shadowsocks/libipset/include
|
-I$(LOCAL_PATH)/shadowsocks/libipset/include
|
||||||
|
|
||||||
LOCAL_STATIC_LIBRARIES := libev libcrypto libipset libcork
|
LOCAL_STATIC_LIBRARIES := libev libcrypto libipset libcork libsodium
|
||||||
|
|
||||||
LOCAL_LDLIBS := -llog
|
LOCAL_LDLIBS := -llog
|
||||||
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
include $(BUILD_EXECUTABLE)
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
## shadowsocks tunnel
|
## shadowsocks tunnel
|
||||||
########################################################
|
########################################################
|
||||||
|
|
||||||
########################################################
|
|
||||||
## shadowsocks tunnel library
|
|
||||||
########################################################
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c json.c jconf.c
|
SHADOWSOCKS_SOURCES := tunnel.c cache.c udprelay.c encrypt.c utils.c json.c jconf.c
|
||||||
|
|
||||||
LOCAL_MODULE := ss-tunnel-jni
|
LOCAL_MODULE := ss-tunnel
|
||||||
LOCAL_SRC_FILES := $(addprefix shadowsocks/src/, $(SHADOWSOCKS_SOURCES)) main-jni.cpp
|
LOCAL_SRC_FILES := $(addprefix shadowsocks/src/, $(SHADOWSOCKS_SOURCES))
|
||||||
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DUDPRELAY_LOCAL -DUDPRELAY_TUNNEL \
|
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DUDPRELAY_LOCAL -DUDPRELAY_TUNNEL \
|
||||||
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H -DSSTUNNEL_JNI \
|
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H -DSSTUNNEL_JNI \
|
||||||
-I$(LOCAL_PATH)/libev/ \
|
-I$(LOCAL_PATH)/libev/ \
|
||||||
|
-I$(LOCAL_PATH)/shadowsocks/libsodium/src/libsodium/include \
|
||||||
|
-I$(LOCAL_PATH)/shadowsocks/libsodium/src/libsodium/include/sodium \
|
||||||
-I$(LOCAL_PATH)/openssl/include
|
-I$(LOCAL_PATH)/openssl/include
|
||||||
|
|
||||||
LOCAL_STATIC_LIBRARIES := libev libcrypto
|
LOCAL_STATIC_LIBRARIES := libev libcrypto libsodium
|
||||||
|
|
||||||
LOCAL_LDLIBS := -llog
|
LOCAL_LDLIBS := -llog
|
||||||
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
include $(BUILD_EXECUTABLE)
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
## system
|
## system
|
||||||
@@ -208,7 +275,7 @@ LOCAL_STATIC_LIBRARIES := cpufeatures
|
|||||||
include $(BUILD_SHARED_LIBRARY)
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
########################################################
|
########################################################
|
||||||
## tun2socks-library
|
## tun2socks
|
||||||
########################################################
|
########################################################
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
@@ -218,7 +285,7 @@ LOCAL_CFLAGS += -DBADVPN_THREADWORK_USE_PTHREAD -DBADVPN_LINUX -DBADVPN_BREACTOR
|
|||||||
LOCAL_CFLAGS += -DBADVPN_USE_SELFPIPE -DBADVPN_USE_EPOLL
|
LOCAL_CFLAGS += -DBADVPN_USE_SELFPIPE -DBADVPN_USE_EPOLL
|
||||||
LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN -DBADVPN_THREAD_SAFE
|
LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN -DBADVPN_THREAD_SAFE
|
||||||
LOCAL_CFLAGS += -DNDEBUG -DANDROID
|
LOCAL_CFLAGS += -DNDEBUG -DANDROID
|
||||||
LOCAL_CFLAGS += -DTUN2SOCKS_JNI
|
# LOCAL_CFLAGS += -DTUN2SOCKS_JNI
|
||||||
|
|
||||||
LOCAL_C_INCLUDES:= \
|
LOCAL_C_INCLUDES:= \
|
||||||
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv4 \
|
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv4 \
|
||||||
@@ -288,13 +355,13 @@ TUN2SOCKS_SOURCES := \
|
|||||||
tun2socks/SocksUdpGwClient.c \
|
tun2socks/SocksUdpGwClient.c \
|
||||||
udpgw_client/UdpGwClient.c
|
udpgw_client/UdpGwClient.c
|
||||||
|
|
||||||
LOCAL_MODULE := tun2socks-jni
|
LOCAL_MODULE := tun2socks
|
||||||
|
|
||||||
LOCAL_LDLIBS := -ldl -llog
|
LOCAL_LDLIBS := -ldl -llog
|
||||||
|
|
||||||
LOCAL_SRC_FILES := $(addprefix badvpn/, $(TUN2SOCKS_SOURCES)) main-jni.cpp
|
LOCAL_SRC_FILES := $(addprefix badvpn/, $(TUN2SOCKS_SOURCES))
|
||||||
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
include $(BUILD_EXECUTABLE)
|
||||||
|
|
||||||
# OpenSSL
|
# OpenSSL
|
||||||
openssl_subdirs := $(addprefix $(LOCAL_PATH)/openssl/,$(addsuffix /Android.mk, \
|
openssl_subdirs := $(addprefix $(LOCAL_PATH)/openssl/,$(addsuffix /Android.mk, \
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
|
APP_ABI := armeabi-v7a x86
|
||||||
APP_PLATFORM := android-16
|
APP_PLATFORM := android-16
|
||||||
APP_STL := stlport_static
|
APP_STL := stlport_static
|
||||||
NDK_TOOLCHAIN_VERSION := 4.9
|
NDK_TOOLCHAIN_VERSION := 4.9
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -59,7 +59,8 @@ static void
|
|||||||
ev_arc4random_buf(void *buf, size_t n)
|
ev_arc4random_buf(void *buf, size_t n)
|
||||||
{
|
{
|
||||||
#if defined(_EVENT_HAVE_ARC4RANDOM_BUF) && !defined(__APPLE__)
|
#if defined(_EVENT_HAVE_ARC4RANDOM_BUF) && !defined(__APPLE__)
|
||||||
return arc4random_buf(buf, n);
|
arc4random_buf(buf, n);
|
||||||
|
return;
|
||||||
#else
|
#else
|
||||||
unsigned char *b = buf;
|
unsigned char *b = buf;
|
||||||
|
|
||||||
@@ -71,7 +72,8 @@ ev_arc4random_buf(void *buf, size_t n)
|
|||||||
* trickery.)
|
* trickery.)
|
||||||
*/
|
*/
|
||||||
if (arc4random_buf != NULL) {
|
if (arc4random_buf != NULL) {
|
||||||
return arc4random_buf(buf, n);
|
arc4random_buf(buf, n);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
/* Make sure that we start out with b at a 4-byte alignment; plenty
|
/* Make sure that we start out with b at a 4-byte alignment; plenty
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
#include <jni.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
typedef unsigned short char16_t;
|
|
||||||
|
|
||||||
class String8 {
|
|
||||||
public:
|
|
||||||
String8() {
|
|
||||||
mString = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
~String8() {
|
|
||||||
if (mString) {
|
|
||||||
free(mString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void set(const char16_t* o, size_t numChars) {
|
|
||||||
if (mString) {
|
|
||||||
free(mString);
|
|
||||||
}
|
|
||||||
mString = (char*) malloc(numChars + 1);
|
|
||||||
if (!mString) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < numChars; i++) {
|
|
||||||
mString[i] = (char) o[i];
|
|
||||||
}
|
|
||||||
mString[numChars] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* string() {
|
|
||||||
return mString;
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
char* mString;
|
|
||||||
};
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
int main(int argc, char* args[]);
|
|
||||||
|
|
||||||
#if defined(PDNSD_JNI)
|
|
||||||
jint Java_com_github_shadowsocks_Core_pdnsd(JNIEnv *env, jobject thiz, jobjectArray argv) {
|
|
||||||
#elif defined(SSLOCAL_JNI)
|
|
||||||
jint Java_com_github_shadowsocks_Core_sslocal(JNIEnv *env, jobject thiz, jobjectArray argv) {
|
|
||||||
#elif defined(SSTUNNEL_JNI)
|
|
||||||
jint Java_com_github_shadowsocks_Core_sstunnel(JNIEnv *env, jobject thiz, jobjectArray argv) {
|
|
||||||
#elif defined(REDSOCKS_JNI)
|
|
||||||
jint Java_com_github_shadowsocks_Core_redsocks(JNIEnv *env, jobject thiz, jobjectArray argv) {
|
|
||||||
#elif defined(TUN2SOCKS_JNI)
|
|
||||||
jint Java_com_github_shadowsocks_Core_tun2socks(JNIEnv *env, jobject thiz, jobjectArray argv) {
|
|
||||||
#else
|
|
||||||
#error "invalid header"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int argc = argv ? env->GetArrayLength(argv) : 0;
|
|
||||||
char **args = NULL;
|
|
||||||
String8 tmp_8;
|
|
||||||
|
|
||||||
if (argc > 0) {
|
|
||||||
args = (char **)malloc((argc+1)*sizeof(char *));
|
|
||||||
for (int i = 0; i < argc; ++i) {
|
|
||||||
jstring arg = reinterpret_cast<jstring>(env->GetObjectArrayElement(argv, i));
|
|
||||||
const jchar *str = env->GetStringCritical(arg, 0);
|
|
||||||
tmp_8.set(str, env->GetStringLength(arg));
|
|
||||||
env->ReleaseStringCritical(arg, str);
|
|
||||||
args[i] = strdup(tmp_8.string());
|
|
||||||
}
|
|
||||||
args[argc] = NULL;
|
|
||||||
|
|
||||||
pid_t pid;
|
|
||||||
pid = fork();
|
|
||||||
if (pid == 0) {
|
|
||||||
int ret = main(argc, args);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args != NULL) free(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ LOCAL_CFLAGS += -DOPENSSL_NO_EC_NISTP_64_GCC_128 -DOPENSSL_NO_SCTP \
|
|||||||
# LOCAL_CFLAGS += -DOPENSSL_NO_DEPRECATED
|
# LOCAL_CFLAGS += -DOPENSSL_NO_DEPRECATED
|
||||||
|
|
||||||
# Extra
|
# Extra
|
||||||
LOCAL_CFLAGS += -DOPENSSL_NO_HW -DOPENSSL_NO_ASM -DOPENSSL_NO_ENGINE -DZLIB
|
LOCAL_CFLAGS += -DOPENSSL_NO_HW -DOPENSSL_NO_ENGINE -DZLIB
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
# LOCAL_CFLAGS += -DCIPHER_DEBUG
|
# LOCAL_CFLAGS += -DCIPHER_DEBUG
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ static int rc4_hmac_md5_init_key(EVP_CIPHER_CTX *ctx,
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable asm for Android x86_64
|
||||||
#if !defined(OPENSSL_NO_ASM) && ( \
|
#if !defined(OPENSSL_NO_ASM) && ( \
|
||||||
defined(__x86_64) || defined(__x86_64__) || \
|
|
||||||
defined(_M_AMD64) || defined(_M_X64) || \
|
defined(_M_AMD64) || defined(_M_X64) || \
|
||||||
defined(__INTEL__) ) && \
|
defined(__INTEL__) ) && \
|
||||||
!(defined(__APPLE__) && defined(__MACH__))
|
!(defined(__APPLE__) && defined(__MACH__))
|
||||||
|
|||||||
@@ -25,11 +25,11 @@
|
|||||||
* (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for
|
* (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for
|
||||||
* major minor fix final patch/beta)
|
* major minor fix final patch/beta)
|
||||||
*/
|
*/
|
||||||
#define OPENSSL_VERSION_NUMBER 0x1000103fL
|
#define OPENSSL_VERSION_NUMBER 0x100010afL
|
||||||
#ifdef OPENSSL_FIPS
|
#ifdef OPENSSL_FIPS
|
||||||
#define OPENSSL_VERSION_TEXT "OpenSSL 1.0.1c-fips 10 May 2012"
|
#define OPENSSL_VERSION_TEXT "OpenSSL 1.0.1j-fips 15 Oct 2014"
|
||||||
#else
|
#else
|
||||||
#define OPENSSL_VERSION_TEXT "OpenSSL 1.0.1c 10 May 2012"
|
#define OPENSSL_VERSION_TEXT "OpenSSL 1.0.1j 15 Oct 2014"
|
||||||
#endif
|
#endif
|
||||||
#define OPENSSL_VERSION_PTEXT " part of " OPENSSL_VERSION_TEXT
|
#define OPENSSL_VERSION_PTEXT " part of " OPENSSL_VERSION_TEXT
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule src/main/jni/shadowsocks updated: bf902a573a...888c2438c3
@@ -32,9 +32,6 @@ jstring Java_com_github_shadowsocks_system_getabi(JNIEnv *env, jobject thiz) {
|
|||||||
|
|
||||||
void Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring cmd) {
|
void Java_com_github_shadowsocks_system_exec(JNIEnv *env, jobject thiz, jstring cmd) {
|
||||||
const char *str = env->GetStringUTFChars(cmd, 0);
|
const char *str = env->GetStringUTFChars(cmd, 0);
|
||||||
setenv("LD_LIBRARY_PATH", "/vendor/lib:/system/lib", 1);
|
|
||||||
setegid(getgid());
|
|
||||||
seteuid(getuid());
|
|
||||||
system(str);
|
system(str);
|
||||||
env->ReleaseStringUTFChars(cmd, str);
|
env->ReleaseStringUTFChars(cmd, str);
|
||||||
}
|
}
|
||||||
@@ -48,6 +45,8 @@ static JNINativeMethod method_table[] = {
|
|||||||
(void*) Java_com_github_shadowsocks_system_getabi }
|
(void*) Java_com_github_shadowsocks_system_getabi }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register several native methods for one class.
|
* Register several native methods for one class.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<item android:id="@+id/action_export"
|
||||||
|
android:title="@string/action_export"
|
||||||
|
android:showAsAction="never" />
|
||||||
|
<item android:id="@+id/action_import"
|
||||||
|
android:title="@string/action_import"
|
||||||
|
android:showAsAction="never" />
|
||||||
|
</menu>
|
||||||
@@ -46,7 +46,8 @@
|
|||||||
<string name="forward_stop">后台服务已停止。</string>
|
<string name="forward_stop">后台服务已停止。</string>
|
||||||
<string name="service_failed">无法连接远程服务器</string>
|
<string name="service_failed">无法连接远程服务器</string>
|
||||||
<string name="stop">停止服务</string>
|
<string name="stop">停止服务</string>
|
||||||
<string name="vpn_error">后台服务启动失败:\"%s\"。</string>
|
<string name="stopping">正在关闭...</string>
|
||||||
|
<string name="vpn_error">后台服务启动失败:%s</string>
|
||||||
<string name="ok_iknow">确定</string>
|
<string name="ok_iknow">确定</string>
|
||||||
<string name="port_alert">端口号应大于1024</string>
|
<string name="port_alert">端口号应大于1024</string>
|
||||||
<string name="port_empty">端口号不能为空</string>
|
<string name="port_empty">端口号不能为空</string>
|
||||||
@@ -68,6 +69,9 @@
|
|||||||
<string name="flushing">刷新中...</string>
|
<string name="flushing">刷新中...</string>
|
||||||
<string name="add_profile">添加配置文件</string>
|
<string name="add_profile">添加配置文件</string>
|
||||||
|
|
||||||
|
<string name="enable_nat">启用 NAT 模式.</string>
|
||||||
|
<string name="disable_nat">禁用 NAT 模式.</string>
|
||||||
|
|
||||||
<string name="udp_dns">UDP 转发</string>
|
<string name="udp_dns">UDP 转发</string>
|
||||||
<string name="udp_dns_summary">由远程服务器转发 UDP 协议的数据包</string>
|
<string name="udp_dns_summary">由远程服务器转发 UDP 协议的数据包</string>
|
||||||
|
|
||||||
@@ -84,4 +88,12 @@
|
|||||||
<item>绕过局域网及中国大陆地址</item>
|
<item>绕过局域网及中国大陆地址</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string name="action_export">导出至剪贴板</string>
|
||||||
|
<string name="action_import">从剪贴板导入</string>
|
||||||
|
<string name="action_export_msg">导出成功</string>
|
||||||
|
<string name="action_export_err">导出失败</string>
|
||||||
|
<string name="action_import_msg">导入成功</string>
|
||||||
|
<string name="action_import_err">导入失败</string>
|
||||||
|
<string name="error">错误</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
<item>IDEA-CFB</item>
|
<item>IDEA-CFB</item>
|
||||||
<item>RC2-CFB</item>
|
<item>RC2-CFB</item>
|
||||||
<item>SEED-CFB</item>
|
<item>SEED-CFB</item>
|
||||||
|
<item>SALSA20</item>
|
||||||
|
<item>CHACHA20</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="enc_method_value">
|
<string-array name="enc_method_value">
|
||||||
@@ -36,6 +38,8 @@
|
|||||||
<item>idea-cfb</item>
|
<item>idea-cfb</item>
|
||||||
<item>rc2-cfb</item>
|
<item>rc2-cfb</item>
|
||||||
<item>seed-cfb</item>
|
<item>seed-cfb</item>
|
||||||
|
<item>salsa20</item>
|
||||||
|
<item>chacha20</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<string-array name="route_entry">
|
<string-array name="route_entry">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
<color name="title_background">#ff5e5e5e</color>
|
<color name="title_background">#ff5e5e5e</color>
|
||||||
<color name="dialog_title_background">#17170443</color>
|
<color name="dialog_title_background">#17170443</color>
|
||||||
|
<color name="white">#FFFFFF</color>
|
||||||
<color name="black">#000000</color>
|
<color name="black">#000000</color>
|
||||||
<color name="grey1">#646464</color>
|
<color name="grey1">#646464</color>
|
||||||
<color name="grey2">#c8c8c8</color>
|
<color name="grey2">#c8c8c8</color>
|
||||||
@@ -11,4 +12,4 @@
|
|||||||
<color name="md__transparent">#00000000</color>
|
<color name="md__transparent">#00000000</color>
|
||||||
<color name="md__defaultBackground">#FF555555</color>
|
<color name="md__defaultBackground">#FF555555</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -57,7 +57,11 @@
|
|||||||
<string name="forward_stop">Shadowsocks stopped.</string>
|
<string name="forward_stop">Shadowsocks stopped.</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="stop">Stop the service</string>
|
<string name="stop">Stop the service</string>
|
||||||
<string name="vpn_error">Error: \"%s\".</string>
|
<string name="stopping">Shutting down...</string>
|
||||||
|
<string name="vpn_error">%s</string>
|
||||||
|
<string name="enable_nat">Turn on NAT mode.</string>
|
||||||
|
<string name="disable_nat">Turn off NAT mode.</string>
|
||||||
|
<string name="error">ERROR</string>
|
||||||
|
|
||||||
<!-- alert category -->
|
<!-- alert category -->
|
||||||
<string name="ok_iknow">OK</string>
|
<string name="ok_iknow">OK</string>
|
||||||
@@ -83,6 +87,12 @@
|
|||||||
<string name="flush_dnscache">Flush DNS Cache</string>
|
<string name="flush_dnscache">Flush DNS Cache</string>
|
||||||
<string name="flushing">Flushing</string>
|
<string name="flushing">Flushing</string>
|
||||||
<string name="add_profile">Add Profile</string>
|
<string name="add_profile">Add Profile</string>
|
||||||
|
<string name="action_export">Export to Clipboard</string>
|
||||||
|
<string name="action_import">Import from Clipboard</string>
|
||||||
|
<string name="action_export_msg">Successfully export!</string>
|
||||||
|
<string name="action_export_err">Failed to export.</string>
|
||||||
|
<string name="action_import_msg">Successfully import!</string>
|
||||||
|
<string name="action_import_err">Failed to import.</string>
|
||||||
|
|
||||||
<!-- profile -->
|
<!-- profile -->
|
||||||
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
|
<string name="add_profile_dialog">Add this Shadowsocks Profile?</string>
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
<item name="android:actionModeBackground">@drawable/cab_background_top_shadow</item>
|
<item name="android:actionModeBackground">@drawable/cab_background_top_shadow</item>
|
||||||
<item name="android:actionModeSplitBackground">@drawable/cab_background_bottom_shadow</item>
|
<item name="android:actionModeSplitBackground">@drawable/cab_background_bottom_shadow</item>
|
||||||
<item name="android:actionModeCloseButtonStyle">@style/ActionButton.CloseMode.Shadow</item>
|
<item name="android:actionModeCloseButtonStyle">@style/ActionButton.CloseMode.Shadow</item>
|
||||||
|
<!-- Light.DarkActionBar specific -->
|
||||||
<!-- Light.DarkActionBar specific -->
|
|
||||||
<item name="android:actionBarWidgetTheme">@style/Theme.Shadow.Widget</item>
|
<item name="android:actionBarWidgetTheme">@style/Theme.Shadow.Widget</item>
|
||||||
|
<item name="android:textAppearanceLargePopupMenu">@style/m_textAppearanceLargePopupMenu</item>
|
||||||
|
<item name="android:textAppearanceSmallPopupMenu" >@style/m_textAppearanceSmallPopupMenu</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ActionBar.Solid.Shadow" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
|
<style name="ActionBar.Solid.Shadow" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
|
||||||
@@ -51,6 +51,14 @@
|
|||||||
<item name="android:popupBackground">@drawable/menu_dropdown_panel_shadow</item>
|
<item name="android:popupBackground">@drawable/menu_dropdown_panel_shadow</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="m_textAppearanceSmallPopupMenu" parent="@android:style/TextAppearance.Holo.Widget.PopupMenu.Small">
|
||||||
|
<item name="android:textColor">@android:color/white</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="m_textAppearanceLargePopupMenu" parent="@android:style/TextAppearance.Holo.Widget.PopupMenu.Large">
|
||||||
|
<item name="android:textColor">@android:color/white</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="DropDownListView.Shadow" parent="@android:style/Widget.Holo.ListView.DropDown">
|
<style name="DropDownListView.Shadow" parent="@android:style/Widget.Holo.ListView.DropDown">
|
||||||
<item name="android:listSelector">@drawable/selectable_background_shadow</item>
|
<item name="android:listSelector">@drawable/selectable_background_shadow</item>
|
||||||
</style>
|
</style>
|
||||||
@@ -77,6 +85,9 @@
|
|||||||
<style name="Theme.Shadow.Widget" parent="@android:style/Theme.Holo">
|
<style name="Theme.Shadow.Widget" parent="@android:style/Theme.Holo">
|
||||||
<item name="android:popupMenuStyle">@style/PopupMenu.Shadow</item>
|
<item name="android:popupMenuStyle">@style/PopupMenu.Shadow</item>
|
||||||
<item name="android:dropDownListViewStyle">@style/DropDownListView.Shadow</item>
|
<item name="android:dropDownListViewStyle">@style/DropDownListView.Shadow</item>
|
||||||
|
<item name="android:textColor">@android:color/white</item>
|
||||||
|
<item name="android:textAppearanceLargePopupMenu">@style/m_textAppearanceLargePopupMenu</item>
|
||||||
|
<item name="android:textAppearanceSmallPopupMenu" >@style/m_textAppearanceSmallPopupMenu</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -40,14 +40,17 @@
|
|||||||
package com.github.shadowsocks
|
package com.github.shadowsocks
|
||||||
|
|
||||||
import android.app.{Activity, ProgressDialog}
|
import android.app.{Activity, ProgressDialog}
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.ClipData
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.{Bitmap, PixelFormat}
|
import android.graphics.{Bitmap, PixelFormat}
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Message
|
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.OnClickListener
|
import android.view.View.OnClickListener
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -62,7 +65,10 @@ import com.nostra13.universalimageloader.core.{DisplayImageOptions, ImageLoader,
|
|||||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
|
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer
|
||||||
import com.github.shadowsocks.utils.{Utils, Scheme, Key}
|
import com.github.shadowsocks.utils.{Utils, Scheme, Key}
|
||||||
|
|
||||||
case class ProxiedApp(uid: Int, name: String, var proxied: Boolean)
|
import scala.concurrent.ops._
|
||||||
|
import scala.language.implicitConversions
|
||||||
|
|
||||||
|
case class ProxiedApp(uid: Int, name: String, packageName: String, var proxied: Boolean)
|
||||||
|
|
||||||
class ObjectArrayTools[T <: AnyRef](a: Array[T]) {
|
class ObjectArrayTools[T <: AnyRef](a: Array[T]) {
|
||||||
def binarySearch(key: T) = {
|
def binarySearch(key: T) = {
|
||||||
@@ -74,7 +80,7 @@ case class ListEntry(box: CheckBox, text: TextView, icon: ImageView)
|
|||||||
|
|
||||||
object AppManager {
|
object AppManager {
|
||||||
|
|
||||||
implicit def anyrefarray_tools[T <: AnyRef](a: Array[T]) = new ObjectArrayTools(a)
|
implicit def anyrefarray_tools[T <: AnyRef](a: Array[T]): ObjectArrayTools[T] = new ObjectArrayTools(a)
|
||||||
|
|
||||||
def getProxiedApps(context: Context, proxiedAppString: String): Array[ProxiedApp] = {
|
def getProxiedApps(context: Context, proxiedAppString: String): Array[ProxiedApp] = {
|
||||||
|
|
||||||
@@ -86,13 +92,13 @@ object AppManager {
|
|||||||
val appList = packageManager.getInstalledApplications(0)
|
val appList = packageManager.getInstalledApplications(0)
|
||||||
|
|
||||||
appList.filter(_.uid >= 10000).map {
|
appList.filter(_.uid >= 10000).map {
|
||||||
case a => {
|
case a =>
|
||||||
val uid = a.uid
|
val uid = a.uid
|
||||||
val userName = uid.toString
|
val userName = uid.toString
|
||||||
|
val name = packageManager.getApplicationLabel(a).toString
|
||||||
val packageName = a.packageName
|
val packageName = a.packageName
|
||||||
val proxied = proxiedApps.binarySearch(userName) >= 0
|
val proxied = proxiedApps.binarySearch(userName) >= 0
|
||||||
new ProxiedApp(uid, packageName, proxied)
|
new ProxiedApp(uid, name, packageName, proxied)
|
||||||
}
|
|
||||||
}.toArray
|
}.toArray
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +109,7 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
|||||||
val MSG_LOAD_FINISH = 2
|
val MSG_LOAD_FINISH = 2
|
||||||
val STUB = android.R.drawable.sym_def_app_icon
|
val STUB = android.R.drawable.sym_def_app_icon
|
||||||
|
|
||||||
implicit def anyrefarray_tools[T <: AnyRef](a: Array[T]) = new ObjectArrayTools(a)
|
implicit def anyrefarray_tools[T <: AnyRef](a: Array[T]): ObjectArrayTools[T] = new ObjectArrayTools(a)
|
||||||
|
|
||||||
var apps: Array[ProxiedApp] = null
|
var apps: Array[ProxiedApp] = null
|
||||||
var appListView: ListView = null
|
var appListView: ListView = null
|
||||||
@@ -113,7 +119,7 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
|||||||
var appsLoaded: Boolean = false
|
var appsLoaded: Boolean = false
|
||||||
|
|
||||||
def loadApps(context: Context): Array[ProxiedApp] = {
|
def loadApps(context: Context): Array[ProxiedApp] = {
|
||||||
val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
val proxiedAppString = prefs.getString(Key.proxied, "")
|
val proxiedAppString = prefs.getString(Key.proxied, "")
|
||||||
val proxiedApps = proxiedAppString.split('|').sortWith(_ < _)
|
val proxiedApps = proxiedAppString.split('|').sortWith(_ < _)
|
||||||
|
|
||||||
@@ -129,8 +135,9 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
|||||||
val uid = a.uid
|
val uid = a.uid
|
||||||
val userName = uid.toString
|
val userName = uid.toString
|
||||||
val name = packageManager.getApplicationLabel(a).toString
|
val name = packageManager.getApplicationLabel(a).toString
|
||||||
|
val packageName = a.packageName
|
||||||
val proxied = (proxiedApps binarySearch userName) >= 0
|
val proxied = (proxiedApps binarySearch userName) >= 0
|
||||||
new ProxiedApp(uid, name, proxied)
|
new ProxiedApp(uid, name, packageName, proxied)
|
||||||
}.toArray
|
}.toArray
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +182,7 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
|||||||
.cacheOnDisc()
|
.cacheOnDisc()
|
||||||
.displayer(new FadeInBitmapDisplayer(300))
|
.displayer(new FadeInBitmapDisplayer(300))
|
||||||
.build()
|
.build()
|
||||||
ImageLoader.getInstance().displayImage(Scheme.APP + app.uid, entry.icon, options)
|
ImageLoader.getInstance().displayImage(Scheme.APP + app.packageName, entry.icon, options)
|
||||||
|
|
||||||
entry.text.setText(app.name)
|
entry.text.setText(app.name)
|
||||||
val box: CheckBox = entry.box
|
val box: CheckBox = entry.box
|
||||||
@@ -215,6 +222,53 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override def onOptionsItemSelected(item: MenuItem): Boolean = {
|
||||||
|
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE).asInstanceOf[ClipboardManager]
|
||||||
|
val prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext)
|
||||||
|
item.getItemId match {
|
||||||
|
case R.id.action_export =>
|
||||||
|
val bypass = prefs.getBoolean(Key.isBypassApps, false)
|
||||||
|
val proxiedAppString = prefs.getString(Key.proxied, "")
|
||||||
|
val clip = ClipData.newPlainText(Key.proxied, bypass + " " + proxiedAppString)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show()
|
||||||
|
return true
|
||||||
|
case R.id.action_import =>
|
||||||
|
if (clipboard.hasPrimaryClip) {
|
||||||
|
val clipdata = clipboard.getPrimaryClip
|
||||||
|
val label = clipdata.getDescription.getLabel
|
||||||
|
if (label == Key.proxied) {
|
||||||
|
val proxiedAppSequence = clipdata.getItemAt(0).getText
|
||||||
|
if (proxiedAppSequence != null) {
|
||||||
|
val proxiedAppString = proxiedAppSequence.toString
|
||||||
|
if (!proxiedAppString.isEmpty) {
|
||||||
|
val array = proxiedAppString.split(" ")
|
||||||
|
val bypass = array(0).toBoolean
|
||||||
|
val apps = if (array.size > 1) array(1) else ""
|
||||||
|
prefs.edit.putBoolean(Key.isBypassApps, bypass).commit()
|
||||||
|
prefs.edit.putString(Key.proxied, apps).commit()
|
||||||
|
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
|
||||||
|
// Restart activity
|
||||||
|
val intent = getIntent
|
||||||
|
finish()
|
||||||
|
startActivity(intent)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Toast.makeText(this, R.string.action_import_err, Toast.LENGTH_SHORT).show()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override def onCreateOptionsMenu(menu: Menu): Boolean = {
|
||||||
|
val inflater = getMenuInflater()
|
||||||
|
inflater.inflate(R.menu.app_manager_menu, menu)
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
protected override def onCreate(savedInstanceState: Bundle) {
|
protected override def onCreate(savedInstanceState: Bundle) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -236,30 +290,29 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
|||||||
ImageLoader.getInstance().init(config)
|
ImageLoader.getInstance().init(config)
|
||||||
|
|
||||||
val bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
|
val bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
|
||||||
val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext)
|
||||||
bypassSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener {
|
bypassSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener {
|
||||||
def onCheckedChanged(button: CompoundButton, checked: Boolean) {
|
def onCheckedChanged(button: CompoundButton, checked: Boolean) {
|
||||||
prefs.edit().putBoolean(Key.isBypassApps, checked).commit()
|
prefs.edit().putBoolean(Key.isBypassApps, checked).commit()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
bypassSwitch.setChecked(prefs.getBoolean(Key.isBypassApps, false))
|
bypassSwitch.setChecked(prefs.getBoolean(Key.isBypassApps, false))
|
||||||
|
|
||||||
|
appListView = findViewById(R.id.applistview).asInstanceOf[ListView]
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override def onResume() {
|
protected override def onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
new Thread {
|
spawn {
|
||||||
override def run() {
|
handler.post(loadStartRunnable)
|
||||||
handler.post(loadStartRunnable)
|
if (!appsLoaded) loadApps()
|
||||||
appListView = findViewById(R.id.applistview).asInstanceOf[ListView]
|
handler.post(loadFinishRunnable)
|
||||||
if (!appsLoaded) loadApps()
|
}
|
||||||
handler.post(loadFinishRunnable)
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def saveAppSettings(context: Context) {
|
def saveAppSettings(context: Context) {
|
||||||
if (apps == null) return
|
if (apps == null) return
|
||||||
val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
val proxiedApps = new StringBuilder
|
val proxiedApps = new StringBuilder
|
||||||
apps.foreach(app =>
|
apps.foreach(app =>
|
||||||
if (app.proxied) {
|
if (app.proxied) {
|
||||||
@@ -319,8 +372,8 @@ class AppManager extends Activity with OnCheckedChangeListener with OnClickListe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def getStreamFromOtherSource(imageUri: String, extra: AnyRef): InputStream = {
|
override def getStreamFromOtherSource(imageUri: String, extra: AnyRef): InputStream = {
|
||||||
val uid = imageUri.substring(Scheme.APP.length).toInt
|
val packageName = imageUri.substring(Scheme.APP.length)
|
||||||
val drawable = Utils.getAppIcon(getBaseContext, uid)
|
val drawable = Utils.getAppIcon(getBaseContext, packageName)
|
||||||
val bitmap = Utils.drawableToBitmap(drawable)
|
val bitmap = Utils.drawableToBitmap(drawable)
|
||||||
|
|
||||||
val os = new ByteArrayOutputStream()
|
val os = new ByteArrayOutputStream()
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ import android.content.Context
|
|||||||
|
|
||||||
trait BaseService {
|
trait BaseService {
|
||||||
|
|
||||||
@volatile var state = State.INIT
|
@volatile private var state = State.INIT
|
||||||
@volatile var callbackCount = 0
|
@volatile private var callbackCount = 0
|
||||||
|
|
||||||
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
|
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
|
||||||
|
|
||||||
@@ -81,11 +81,15 @@ trait BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def stop() {
|
override def stop() {
|
||||||
stopRunner()
|
if (state != State.CONNECTING && state != State.STOPPING) {
|
||||||
|
stopRunner()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def start(config: Config) {
|
override def start(config: Config) {
|
||||||
startRunner(config)
|
if (state != State.CONNECTING && state != State.STOPPING) {
|
||||||
|
startRunner(config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +100,12 @@ trait BaseService {
|
|||||||
def getTag: String
|
def getTag: String
|
||||||
def getContext: Context
|
def getContext: Context
|
||||||
|
|
||||||
|
def getCallbackCount(): Int = {
|
||||||
|
callbackCount
|
||||||
|
}
|
||||||
|
def getState(): Int = {
|
||||||
|
state
|
||||||
|
}
|
||||||
def changeState(s: Int) {
|
def changeState(s: Int) {
|
||||||
changeState(s, null)
|
changeState(s, null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ package com.github.shadowsocks
|
|||||||
|
|
||||||
import java.util
|
import java.util
|
||||||
import java.io.{OutputStream, InputStream, ByteArrayInputStream, ByteArrayOutputStream, IOException, FileOutputStream}
|
import java.io.{OutputStream, InputStream, ByteArrayInputStream, ByteArrayOutputStream, IOException, FileOutputStream}
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
import android.app.backup.BackupManager
|
import android.app.backup.BackupManager
|
||||||
import android.app.{Activity, AlertDialog, ProgressDialog}
|
import android.app.{Activity, AlertDialog, ProgressDialog}
|
||||||
@@ -51,9 +52,11 @@ import android.net.{Uri, VpnService}
|
|||||||
import android.os._
|
import android.os._
|
||||||
import android.preference._
|
import android.preference._
|
||||||
import android.util.{DisplayMetrics, Log}
|
import android.util.{DisplayMetrics, Log}
|
||||||
|
import android.view.View.OnLongClickListener
|
||||||
import android.view._
|
import android.view._
|
||||||
import android.webkit.{WebView, WebViewClient}
|
import android.webkit.{WebView, WebViewClient}
|
||||||
import android.widget._
|
import android.widget._
|
||||||
|
import com.github.mrengineer13.snackbar._
|
||||||
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
|
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
|
||||||
import com.github.shadowsocks.database._
|
import com.github.shadowsocks.database._
|
||||||
import com.github.shadowsocks.preferences.{PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference}
|
import com.github.shadowsocks.preferences.{PasswordEditTextPreference, ProfileEditTextPreference, SummaryEditTextPreference}
|
||||||
@@ -62,12 +65,10 @@ import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
|
|||||||
import com.google.android.gms.analytics.HitBuilders
|
import com.google.android.gms.analytics.HitBuilders
|
||||||
import com.google.zxing.integration.android.IntentIntegrator
|
import com.google.zxing.integration.android.IntentIntegrator
|
||||||
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
|
import com.nostra13.universalimageloader.core.download.BaseImageDownloader
|
||||||
import de.keyboardsurfer.android.widget.crouton.{Configuration, Crouton, Style}
|
|
||||||
import net.simonvt.menudrawer.MenuDrawer
|
import net.simonvt.menudrawer.MenuDrawer
|
||||||
|
|
||||||
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
|
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
|
||||||
import scala.concurrent.ops._
|
import scala.concurrent.ops._
|
||||||
import scala.ref.WeakReference
|
|
||||||
|
|
||||||
class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout: Int)
|
class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout: Int)
|
||||||
extends BaseImageDownloader(context, connectTimeout, readTimeout) {
|
extends BaseImageDownloader(context, connectTimeout, readTimeout) {
|
||||||
@@ -123,7 +124,7 @@ object Shadowsocks {
|
|||||||
val FEATRUE_PREFS = Array(Key.route, Key.isGlobalProxy, Key.proxyedApps,
|
val FEATRUE_PREFS = Array(Key.route, Key.isGlobalProxy, Key.proxyedApps,
|
||||||
Key.isUdpDns, Key.isAutoConnect)
|
Key.isUdpDns, Key.isAutoConnect)
|
||||||
|
|
||||||
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.IPTABLES)
|
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL, Executable.TUN2SOCKS)
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
def updateListPreference(pref: Preference, value: String) {
|
def updateListPreference(pref: Preference, value: String) {
|
||||||
@@ -204,7 +205,6 @@ class Shadowsocks
|
|||||||
// Update the UI
|
// Update the UI
|
||||||
if (switchButton != null) switchButton.setEnabled(true)
|
if (switchButton != null) switchButton.setEnabled(true)
|
||||||
if (State.isAvailable(bgService.getState)) {
|
if (State.isAvailable(bgService.getState)) {
|
||||||
Crouton.cancelAllCroutons()
|
|
||||||
setPreferenceEnabled(enabled = true)
|
setPreferenceEnabled(enabled = true)
|
||||||
} else {
|
} else {
|
||||||
changeSwitch(checked = true)
|
changeSwitch(checked = true)
|
||||||
@@ -322,35 +322,34 @@ class Shadowsocks
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def crash_recovery() {
|
private def crash_recovery() {
|
||||||
val ab = new ArrayBuffer[String]
|
val cmd = new ArrayBuffer[String]()
|
||||||
|
|
||||||
ab.append("kill -9 `cat /data/data/com.github.shadowsocks/ss-local.pid`")
|
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
||||||
ab.append("kill -9 `cat /data/data/com.github.shadowsocks/ss-tunnel.pid`")
|
cmd.append("chmod 666 %s%s-nat.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
ab.append("kill -9 `cat /data/data/com.github.shadowsocks/tun2socks.pid`")
|
cmd.append("chmod 666 %s%s-vpn.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
|
||||||
ab.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/pdnsd.pid")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/pdnsd.conf")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/pdnsd.cache")
|
|
||||||
|
|
||||||
Console.runCommand(ab.toArray)
|
|
||||||
|
|
||||||
if (!Utils.isLollipopOrAbove) {
|
|
||||||
ab.clear()
|
|
||||||
|
|
||||||
ab.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/redsocks.conf")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/redsocks.pid")
|
|
||||||
|
|
||||||
ab.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/pdnsd.pid")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/pdnsd.conf")
|
|
||||||
ab.append("rm /data/data/com.github.shadowsocks/pdnsd.cache")
|
|
||||||
|
|
||||||
ab.append(Utils.getIptables + " -t nat -F OUTPUT")
|
|
||||||
|
|
||||||
Console.runRootCommand(ab.toArray)
|
|
||||||
}
|
}
|
||||||
|
Console.runRootCommand(cmd.toArray)
|
||||||
|
cmd.clear()
|
||||||
|
|
||||||
|
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks")) {
|
||||||
|
try {
|
||||||
|
val pid_nat = scala.io.Source.fromFile(Path.BASE + task + "-nat.pid").mkString.trim.toInt
|
||||||
|
val pid_vpn = scala.io.Source.fromFile(Path.BASE + task + "-vpn.pid").mkString.trim.toInt
|
||||||
|
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_nat))
|
||||||
|
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid_vpn))
|
||||||
|
Process.killProcess(pid_nat)
|
||||||
|
Process.killProcess(pid_vpn)
|
||||||
|
} catch {
|
||||||
|
case e: Throwable => Log.e(Shadowsocks.TAG, "unable to kill " + task)
|
||||||
|
}
|
||||||
|
cmd.append("rm -f %s%s-nat.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
cmd.append("rm -f %s%s-nat.conf".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
cmd.append("rm -f %s%s-vpn.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
cmd.append("rm -f %s%s-vpn.conf".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
}
|
||||||
|
Console.runCommand(cmd.toArray)
|
||||||
|
Console.runRootCommand(cmd.toArray)
|
||||||
|
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getVersionName: String = {
|
private def getVersionName: String = {
|
||||||
@@ -367,7 +366,7 @@ class Shadowsocks
|
|||||||
|
|
||||||
def isTextEmpty(s: String, msg: String): Boolean = {
|
def isTextEmpty(s: String, msg: String): Boolean = {
|
||||||
if (s == null || s.length <= 0) {
|
if (s == null || s.length <= 0) {
|
||||||
Crouton.makeText(this, msg, Style.ALERT).show()
|
new SnackBar.Builder(this).withMessage(msg).withStyle(SnackBar.Style.ALERT).show()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
@@ -500,13 +499,30 @@ class Shadowsocks
|
|||||||
getActionBar.setDisplayShowTitleEnabled(false)
|
getActionBar.setDisplayShowTitleEnabled(false)
|
||||||
getActionBar.setDisplayShowCustomEnabled(true)
|
getActionBar.setDisplayShowCustomEnabled(true)
|
||||||
getActionBar.setIcon(R.drawable.ic_stat_shadowsocks)
|
getActionBar.setIcon(R.drawable.ic_stat_shadowsocks)
|
||||||
|
title.setOnLongClickListener(new OnLongClickListener {
|
||||||
|
override def onLongClick(v: View): Boolean = {
|
||||||
|
if (Utils.isLollipopOrAbove && bgService != null
|
||||||
|
&& (bgService.getState == State.INIT || bgService.getState == State.STOPPED)) {
|
||||||
|
val natEnabled = status.getBoolean(Key.isNAT, false)
|
||||||
|
status.edit().putBoolean(Key.isNAT, !natEnabled).commit()
|
||||||
|
if (!natEnabled) {
|
||||||
|
Toast.makeText(getBaseContext, R.string.enable_nat, Toast.LENGTH_LONG).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getBaseContext, R.string.disable_nat, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Register broadcast receiver
|
// Register broadcast receiver
|
||||||
registerReceiver(preferenceReceiver, new IntentFilter(Action.UPDATE_PREFS))
|
registerReceiver(preferenceReceiver, new IntentFilter(Action.UPDATE_PREFS))
|
||||||
|
|
||||||
// Bind to the service
|
// Bind to the service
|
||||||
spawn {
|
spawn {
|
||||||
val isRoot = !Utils.isLollipopOrAbove && Console.isRoot
|
val isRoot = (!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot
|
||||||
handler.post(new Runnable {
|
handler.post(new Runnable {
|
||||||
override def run() {
|
override def run() {
|
||||||
status.edit.putBoolean(Key.isRoot, isRoot).commit()
|
status.edit.putBoolean(Key.isRoot, isRoot).commit()
|
||||||
@@ -576,6 +592,9 @@ class Shadowsocks
|
|||||||
h.postDelayed(new Runnable() {
|
h.postDelayed(new Runnable() {
|
||||||
def run() {
|
def run() {
|
||||||
val integrator = new IntentIntegrator(Shadowsocks.this)
|
val integrator = new IntentIntegrator(Shadowsocks.this)
|
||||||
|
val list = new java.util.ArrayList(IntentIntegrator.TARGET_ALL_KNOWN)
|
||||||
|
list.add("tw.com.quickmark")
|
||||||
|
integrator.setTargetApplications(list)
|
||||||
integrator.initiateScan()
|
integrator.initiateScan()
|
||||||
h.sendEmptyMessage(0)
|
h.sendEmptyMessage(0)
|
||||||
}
|
}
|
||||||
@@ -653,7 +672,7 @@ class Shadowsocks
|
|||||||
if (!profile.isDefined) return false
|
if (!profile.isDefined) return false
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setMessage(String.format(getString(R.string.remove_profile), profile.get.name))
|
.setMessage(String.format(Locale.ENGLISH, getString(R.string.remove_profile), profile.get.name))
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
||||||
override def onClick(dialog: DialogInterface, i: Int) = dialog.cancel()
|
override def onClick(dialog: DialogInterface, i: Int) = dialog.cancel()
|
||||||
@@ -668,7 +687,7 @@ class Shadowsocks
|
|||||||
currentProfile = profileManager.load(profileId)
|
currentProfile = profileManager.load(profileId)
|
||||||
menuAdapter.updateList(getMenuList, currentProfile.id)
|
menuAdapter.updateList(getMenuList, currentProfile.id)
|
||||||
|
|
||||||
sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
|
updatePreferenceScreen()
|
||||||
|
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
@@ -808,7 +827,6 @@ class Shadowsocks
|
|||||||
override def onDestroy() {
|
override def onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
deattachService()
|
deattachService()
|
||||||
Crouton.cancelAllCroutons()
|
|
||||||
unregisterReceiver(preferenceReceiver)
|
unregisterReceiver(preferenceReceiver)
|
||||||
new BackupManager(this).dataChanged()
|
new BackupManager(this).dataChanged()
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
@@ -821,19 +839,26 @@ class Shadowsocks
|
|||||||
val ab = new ArrayBuffer[String]
|
val ab = new ArrayBuffer[String]
|
||||||
ab.append("mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system")
|
ab.append("mount -o rw,remount -t yaffs2 /dev/block/mtdblock3 /system")
|
||||||
for (executable <- Shadowsocks.EXECUTABLES) {
|
for (executable <- Shadowsocks.EXECUTABLES) {
|
||||||
ab.append("cp %s%s /system/bin/".format(Path.BASE, executable))
|
ab.append("cp %s%s /system/bin/".formatLocal(Locale.ENGLISH, Path.BASE, executable))
|
||||||
ab.append("chmod 755 /system/bin/" + executable)
|
ab.append("chmod 755 /system/bin/" + executable)
|
||||||
ab.append("chown root:shell /system/bin/" + executable)
|
ab.append("chown root:shell /system/bin/" + executable)
|
||||||
}
|
}
|
||||||
ab.append("mount -o ro,remount -t yaffs2 /dev/block/mtdblock3 /system")
|
ab.append("mount -o ro,remount -t yaffs2 /dev/block/mtdblock3 /system")
|
||||||
Console.runRootCommand(ab.toArray)
|
Console.runRootCommand(ab.toArray)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def reset() {
|
def reset() {
|
||||||
|
|
||||||
crash_recovery()
|
crash_recovery()
|
||||||
|
|
||||||
|
copyAssets(System.getABI)
|
||||||
|
|
||||||
|
val ab = new ArrayBuffer[String]
|
||||||
|
for (executable <- Shadowsocks.EXECUTABLES) {
|
||||||
|
ab.append("chmod 755 " + Path.BASE + executable)
|
||||||
|
}
|
||||||
|
Console.runCommand(ab.toArray)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def recovery() {
|
private def recovery() {
|
||||||
@@ -900,12 +925,12 @@ class Shadowsocks
|
|||||||
try {
|
try {
|
||||||
val port: Int = Integer.valueOf(text)
|
val port: Int = Integer.valueOf(text)
|
||||||
if (!low && port <= 1024) {
|
if (!low && port <= 1024) {
|
||||||
Crouton.makeText(this, R.string.port_alert, Style.ALERT).show()
|
new SnackBar.Builder(this).withMessageId(R.string.port_alert).withStyle(SnackBar.Style.ALERT).show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case ex: Exception =>
|
case ex: Exception =>
|
||||||
Crouton.makeText(this, R.string.port_alert, Style.ALERT).show()
|
new SnackBar.Builder(this).withMessageId(R.string.port_alert).withStyle(SnackBar.Style.ALERT).show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@@ -949,7 +974,7 @@ class Shadowsocks
|
|||||||
}
|
}
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(getString(R.string.about_title).format(versionName))
|
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, versionName))
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setNegativeButton(getString(R.string.ok_iknow), new DialogInterface.OnClickListener() {
|
.setNegativeButton(getString(R.string.ok_iknow), new DialogInterface.OnClickListener() {
|
||||||
override def onClick(dialog: DialogInterface, id: Int) {
|
override def onClick(dialog: DialogInterface, id: Int) {
|
||||||
@@ -987,18 +1012,20 @@ class Shadowsocks
|
|||||||
case State.STOPPED =>
|
case State.STOPPED =>
|
||||||
clearDialog()
|
clearDialog()
|
||||||
changeSwitch(checked = false)
|
changeSwitch(checked = false)
|
||||||
Crouton.cancelAllCroutons()
|
|
||||||
if (m != null) {
|
if (m != null) {
|
||||||
val style = new Style.Builder().setBackgroundColorValue(Style.holoRedLight).build()
|
new SnackBar.Builder(Shadowsocks.this)
|
||||||
val config = new Configuration.Builder()
|
.withMessage(getString(R.string.vpn_error).formatLocal(Locale.ENGLISH, m))
|
||||||
.setDuration(Configuration.DURATION_LONG)
|
.withActionMessageId(R.string.error)
|
||||||
.build()
|
.withStyle(SnackBar.Style.ALERT)
|
||||||
Crouton
|
.withDuration(SnackBar.LONG_SNACK)
|
||||||
.makeText(Shadowsocks.this, getString(R.string.vpn_error).format(m), style)
|
|
||||||
.setConfiguration(config)
|
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
setPreferenceEnabled(enabled = true)
|
setPreferenceEnabled(enabled = true)
|
||||||
|
case State.STOPPING =>
|
||||||
|
if (progressDialog == null) {
|
||||||
|
progressDialog = ProgressDialog
|
||||||
|
.show(Shadowsocks.this, "", getString(R.string.stopping), true, true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,14 +41,16 @@ package com.github.shadowsocks
|
|||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.reflect.{InvocationTargetException, Method}
|
import java.lang.reflect.{InvocationTargetException, Method}
|
||||||
import java.util.{Timer, TimerTask}
|
import java.util.Locale
|
||||||
|
|
||||||
import android.app.{Notification, NotificationManager, PendingIntent, Service}
|
import android.app._
|
||||||
import android.content._
|
import android.content._
|
||||||
import android.content.pm.{PackageInfo, PackageManager}
|
import android.content.pm.{PackageInfo, PackageManager}
|
||||||
|
import android.net.{Network, ConnectivityManager}
|
||||||
import android.os._
|
import android.os._
|
||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
import android.util.Log
|
import android.util.{SparseArray, Log}
|
||||||
|
import android.widget.Toast
|
||||||
import com.github.shadowsocks.aidl.Config
|
import com.github.shadowsocks.aidl.Config
|
||||||
import com.github.shadowsocks.utils._
|
import com.github.shadowsocks.utils._
|
||||||
import com.google.android.gms.analytics.HitBuilders
|
import com.google.android.gms.analytics.HitBuilders
|
||||||
@@ -65,13 +67,17 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
val CMD_IPTABLES_RETURN = " -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN"
|
val CMD_IPTABLES_RETURN = " -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN"
|
||||||
val CMD_IPTABLES_DNAT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp " +
|
val CMD_IPTABLES_DNAT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp " +
|
||||||
"-j DNAT --to-destination 127.0.0.1:8123"
|
"-j DNAT --to-destination 127.0.0.1:8123"
|
||||||
val DNS_PORT = 8153
|
|
||||||
|
|
||||||
private val mStartForegroundSignature = Array[Class[_]](classOf[Int], classOf[Notification])
|
private val mStartForegroundSignature = Array[Class[_]](classOf[Int], classOf[Notification])
|
||||||
private val mStopForegroundSignature = Array[Class[_]](classOf[Boolean])
|
private val mStopForegroundSignature = Array[Class[_]](classOf[Boolean])
|
||||||
private val mSetForegroundSignature = Array[Class[_]](classOf[Boolean])
|
private val mSetForegroundSignature = Array[Class[_]](classOf[Boolean])
|
||||||
|
private val mSetForegroundArgs = new Array[AnyRef](1)
|
||||||
|
private val mStartForegroundArgs = new Array[AnyRef](2)
|
||||||
|
private val mStopForegroundArgs = new Array[AnyRef](1)
|
||||||
|
|
||||||
var receiver: BroadcastReceiver = null
|
var lockReceiver: BroadcastReceiver = null
|
||||||
|
var closeReceiver: BroadcastReceiver = null
|
||||||
|
var connReceiver: BroadcastReceiver = null
|
||||||
var notificationManager: NotificationManager = null
|
var notificationManager: NotificationManager = null
|
||||||
var config: Config = null
|
var config: Config = null
|
||||||
var apps: Array[ProxiedApp] = null
|
var apps: Array[ProxiedApp] = null
|
||||||
@@ -80,17 +86,100 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
private var mSetForeground: Method = null
|
private var mSetForeground: Method = null
|
||||||
private var mStartForeground: Method = null
|
private var mStartForeground: Method = null
|
||||||
private var mStopForeground: Method = null
|
private var mStopForeground: Method = null
|
||||||
private var mSetForegroundArgs = new Array[AnyRef](1)
|
|
||||||
private var mStartForegroundArgs = new Array[AnyRef](2)
|
|
||||||
private var mStopForegroundArgs = new Array[AnyRef](1)
|
|
||||||
|
|
||||||
private var lastTxRate = 0
|
|
||||||
private var lastRxRate = 0
|
|
||||||
private var timer: Timer = null
|
|
||||||
private val TIMER_INTERVAL = 2
|
|
||||||
|
|
||||||
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
|
private lazy val application = getApplication.asInstanceOf[ShadowsocksApplication]
|
||||||
|
|
||||||
|
private val dnsAddressCache = new SparseArray[String]
|
||||||
|
|
||||||
|
def getNetId(network: Network): Int = {
|
||||||
|
network.getClass.getDeclaredField("netId").get(network).asInstanceOf[Int]
|
||||||
|
}
|
||||||
|
|
||||||
|
def restoreDnsForAllNetwork() {
|
||||||
|
val manager = getSystemService(Context.CONNECTIVITY_SERVICE).asInstanceOf[ConnectivityManager]
|
||||||
|
val networks = manager.getAllNetworks
|
||||||
|
val cmdBuf = new ArrayBuffer[String]()
|
||||||
|
networks.foreach(network => {
|
||||||
|
val netId = getNetId(network)
|
||||||
|
val oldDns = dnsAddressCache.get(netId)
|
||||||
|
if (oldDns != null) {
|
||||||
|
cmdBuf.append("ndc resolver setnetdns %d \"\" %s".formatLocal(Locale.ENGLISH, netId, oldDns))
|
||||||
|
dnsAddressCache.remove(netId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (cmdBuf.nonEmpty) Console.runRootCommand(cmdBuf.toArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setDnsForAllNetwork(dns: String) {
|
||||||
|
val manager = getSystemService(Context.CONNECTIVITY_SERVICE).asInstanceOf[ConnectivityManager]
|
||||||
|
val networks = manager.getAllNetworks
|
||||||
|
if (networks == null) return
|
||||||
|
|
||||||
|
val cmdBuf = new ArrayBuffer[String]()
|
||||||
|
networks.foreach(network => {
|
||||||
|
val networkInfo = manager.getNetworkInfo(network)
|
||||||
|
if (networkInfo == null) return
|
||||||
|
if (networkInfo.isConnected) {
|
||||||
|
val netId = getNetId(network)
|
||||||
|
val curDnsList = manager.getLinkProperties(network).getDnsServers
|
||||||
|
if (curDnsList != null) {
|
||||||
|
import scala.collection.JavaConverters._
|
||||||
|
val curDns = curDnsList.asScala.map(ip => ip.getHostAddress).mkString(" ")
|
||||||
|
if (curDns != dns) {
|
||||||
|
dnsAddressCache.put(netId, curDns)
|
||||||
|
cmdBuf.append("ndc resolver setnetdns %d \"\" %s".formatLocal(Locale.ENGLISH, netId, dns))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (cmdBuf.nonEmpty) Console.runRootCommand(cmdBuf.toArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setupDns() {
|
||||||
|
setDnsForAllNetwork("127.0.0.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
def resetDns() = {
|
||||||
|
restoreDnsForAllNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
def flushDns() {
|
||||||
|
if (Utils.isLollipopOrAbove) {
|
||||||
|
val manager = getSystemService(Context.CONNECTIVITY_SERVICE).asInstanceOf[ConnectivityManager]
|
||||||
|
val networks = manager.getAllNetworks
|
||||||
|
val cmdBuf = new ArrayBuffer[String]()
|
||||||
|
networks.foreach(network => {
|
||||||
|
val networkInfo = manager.getNetworkInfo(network)
|
||||||
|
if (networkInfo.isAvailable) {
|
||||||
|
val netId = network.getClass.getDeclaredField("netId").get(network).asInstanceOf[Int]
|
||||||
|
cmdBuf.append("ndc resolver flushnet %d".formatLocal(Locale.ENGLISH, netId))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Console.runRootCommand(cmdBuf.toArray)
|
||||||
|
} else {
|
||||||
|
Console.runRootCommand(Array("ndc resolver flushdefaultif", "ndc resolver flushif wlan0"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def destroyConnectionReceiver() {
|
||||||
|
if (connReceiver != null) {
|
||||||
|
unregisterReceiver(connReceiver)
|
||||||
|
connReceiver = null
|
||||||
|
}
|
||||||
|
resetDns()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initConnectionReceiver() {
|
||||||
|
val filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||||
|
connReceiver = new BroadcastReceiver {
|
||||||
|
override def onReceive(context: Context, intent: Intent) = {
|
||||||
|
setupDns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerReceiver(connReceiver, filter)
|
||||||
|
}
|
||||||
|
|
||||||
def startShadowsocksDaemon() {
|
def startShadowsocksDaemon() {
|
||||||
if (config.route != Route.ALL) {
|
if (config.route != Route.ALL) {
|
||||||
val acl: Array[String] = config.route match {
|
val acl: Array[String] = config.route match {
|
||||||
@@ -102,15 +191,19 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val conf = ConfigUtils
|
||||||
|
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, config.localPort,
|
||||||
|
config.sitekey, config.encMethod, 10)
|
||||||
|
ConfigUtils.printToFile(new File(Path.BASE + "ss-local-nat.conf"))(p => {
|
||||||
|
p.println(conf)
|
||||||
|
})
|
||||||
|
|
||||||
val cmd = new ArrayBuffer[String]
|
val cmd = new ArrayBuffer[String]
|
||||||
cmd += ("ss-local"
|
cmd += (Path.BASE + "ss-local"
|
||||||
, "-b" , "127.0.0.1"
|
, "-b" , "127.0.0.1"
|
||||||
, "-s" , config.proxy
|
, "-t" , "600"
|
||||||
, "-p" , config.remotePort.toString
|
, "-c" , Path.BASE + "ss-local-nat.conf"
|
||||||
, "-l" , config.localPort.toString
|
, "-f" , Path.BASE + "ss-local-nat.pid")
|
||||||
, "-k" , config.sitekey
|
|
||||||
, "-m" , config.encMethod
|
|
||||||
, "-f" , Path.BASE + "ss-local.pid")
|
|
||||||
|
|
||||||
if (config.route != Route.ALL) {
|
if (config.route != Route.ALL) {
|
||||||
cmd += "--acl"
|
cmd += "--acl"
|
||||||
@@ -118,47 +211,74 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||||
|
Console.runCommand(cmd.mkString(" "))
|
||||||
|
}
|
||||||
|
|
||||||
Core.sslocal(cmd.toArray)
|
def startTunnel() {
|
||||||
|
if (config.isUdpDns) {
|
||||||
|
val conf = ConfigUtils
|
||||||
|
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8153,
|
||||||
|
config.sitekey, config.encMethod, 10)
|
||||||
|
ConfigUtils.printToFile(new File(Path.BASE + "ss-tunnel-nat.conf"))(p => {
|
||||||
|
p.println(conf)
|
||||||
|
})
|
||||||
|
val cmd = new ArrayBuffer[String]
|
||||||
|
cmd += (Path.BASE + "ss-tunnel"
|
||||||
|
, "-u"
|
||||||
|
, "-t" , "10"
|
||||||
|
, "-b" , "127.0.0.1"
|
||||||
|
, "-L" , "8.8.8.8:53"
|
||||||
|
, "-c" , Path.BASE + "ss-tunnel-nat.conf"
|
||||||
|
, "-f" , Path.BASE + "ss-tunnel-nat.pid")
|
||||||
|
|
||||||
|
cmd += ("-l" , "8153")
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||||
|
|
||||||
|
Console.runCommand(cmd.mkString(" "))
|
||||||
|
|
||||||
|
} else {
|
||||||
|
val conf = ConfigUtils
|
||||||
|
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8163,
|
||||||
|
config.sitekey, config.encMethod, 10)
|
||||||
|
ConfigUtils.printToFile(new File(Path.BASE + "ss-tunnel-nat.conf"))(p => {
|
||||||
|
p.println(conf)
|
||||||
|
})
|
||||||
|
val cmdBuf = new ArrayBuffer[String]
|
||||||
|
cmdBuf += (Path.BASE + "ss-tunnel"
|
||||||
|
, "-u"
|
||||||
|
, "-t" , "10"
|
||||||
|
, "-b" , "127.0.0.1"
|
||||||
|
, "-l" , "8163"
|
||||||
|
, "-L" , "8.8.8.8:53"
|
||||||
|
, "-c" , Path.BASE + "ss-tunnel-nat.conf"
|
||||||
|
, "-f" , Path.BASE + "ss-tunnel-nat.pid")
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) Log.d(TAG, cmdBuf.mkString(" "))
|
||||||
|
Console.runCommand(cmdBuf.mkString(" "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def startDnsDaemon() {
|
def startDnsDaemon() {
|
||||||
if (config.isUdpDns) {
|
|
||||||
val cmd = new ArrayBuffer[String]
|
|
||||||
cmd += ("ss-tunnel" , "-u"
|
|
||||||
, "-b" , "127.0.0.1"
|
|
||||||
, "-l" , "8153"
|
|
||||||
, "-L" , "8.8.8.8:53"
|
|
||||||
, "-s" , config.proxy
|
|
||||||
, "-p" , config.remotePort.toString
|
|
||||||
, "-k" , config.sitekey
|
|
||||||
, "-m" , config.encMethod
|
|
||||||
, "-f" , Path.BASE + "ss-tunnel.pid")
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
|
||||||
Core.sstunnel(cmd.toArray)
|
|
||||||
} else {
|
|
||||||
val cmdBuf = new ArrayBuffer[String]
|
|
||||||
cmdBuf += ("ss-tunnel"
|
|
||||||
, "-b", "127.0.0.1"
|
|
||||||
, "-l", "8163"
|
|
||||||
, "-L", "8.8.8.8:53"
|
|
||||||
, "-s", config.proxy
|
|
||||||
, "-p", config.remotePort.toString
|
|
||||||
, "-k", config.sitekey
|
|
||||||
, "-m", config.encMethod
|
|
||||||
, "-f", Path.BASE + "ss-tunnel.pid")
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmdBuf.mkString(" "))
|
|
||||||
Core.sstunnel(cmdBuf.toArray)
|
|
||||||
|
|
||||||
val conf = ConfigUtils
|
val conf = if (config.route == Route.BYPASS_CHN) {
|
||||||
.PDNSD_BYPASS.format("127.0.0.1", getString(R.string.exclude), 8163)
|
val reject = ConfigUtils.getRejectList(getContext, application)
|
||||||
ConfigUtils.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => {
|
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "127.0.0.1", 8153,
|
||||||
p.println(conf)
|
Path.BASE + "pdnsd-nat.pid", reject, reject, 8163)
|
||||||
})
|
} else {
|
||||||
val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd.conf"
|
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "127.0.0.1", 8153,
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
Path.BASE + "pdnsd-nat.pid", 8163)
|
||||||
Core.pdnsd(cmd.split(" "))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConfigUtils.printToFile(new File(Path.BASE + "pdnsd-nat.conf"))(p => {
|
||||||
|
p.println(conf)
|
||||||
|
})
|
||||||
|
val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd-nat.conf"
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||||
|
|
||||||
|
Console.runCommand(cmd)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def getVersionName: String = {
|
def getVersionName: String = {
|
||||||
@@ -174,25 +294,25 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def startRedsocksDaemon() {
|
def startRedsocksDaemon() {
|
||||||
val conf = ConfigUtils.REDSOCKS.format(config.localPort)
|
val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, config.localPort)
|
||||||
val cmd = "redsocks -p %sredsocks.pid -c %sredsocks.conf"
|
val cmd = Path.BASE + "redsocks -p %sredsocks-nat.pid -c %sredsocks-nat.conf"
|
||||||
.format(Path.BASE, Path.BASE)
|
.formatLocal(Locale.ENGLISH, Path.BASE, Path.BASE)
|
||||||
ConfigUtils.printToFile(new File(Path.BASE + "redsocks.conf"))(p => {
|
ConfigUtils.printToFile(new File(Path.BASE + "redsocks-nat.conf"))(p => {
|
||||||
p.println(conf)
|
p.println(conf)
|
||||||
})
|
})
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
|
||||||
|
|
||||||
Core.redsocks(cmd.split(" "))
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||||
|
Console.runCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when the activity is first created. */
|
/** Called when the activity is first created. */
|
||||||
def handleConnection: Boolean = {
|
def handleConnection: Boolean = {
|
||||||
|
|
||||||
startDnsDaemon()
|
startTunnel()
|
||||||
|
if (!config.isUdpDns) startDnsDaemon()
|
||||||
startRedsocksDaemon()
|
startRedsocksDaemon()
|
||||||
startShadowsocksDaemon()
|
startShadowsocksDaemon()
|
||||||
setupIptables()
|
setupIptables()
|
||||||
flushDNS()
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -208,7 +328,7 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def notifyForegroundAlert(title: String, info: String) {
|
def notifyForegroundAlert(title: String, info: String, visible: Boolean) {
|
||||||
val openIntent = new Intent(this, classOf[Shadowsocks])
|
val openIntent = new Intent(this, classOf[Shadowsocks])
|
||||||
openIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
openIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
|
||||||
val contentIntent = PendingIntent.getActivity(this, 0, openIntent, 0)
|
val contentIntent = PendingIntent.getActivity(this, 0, openIntent, 0)
|
||||||
@@ -226,6 +346,11 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.stop),
|
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.stop),
|
||||||
actionIntent)
|
actionIntent)
|
||||||
|
|
||||||
|
if (visible)
|
||||||
|
builder.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||||
|
else
|
||||||
|
builder.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||||
|
|
||||||
startForegroundCompat(1, builder.build)
|
startForegroundCompat(1, builder.build)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,43 +391,48 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def killProcesses() {
|
def killProcesses() {
|
||||||
|
val cmd = new ArrayBuffer[String]()
|
||||||
|
|
||||||
|
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks")) {
|
||||||
|
cmd.append("chmod 666 %s%s-nat.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
}
|
||||||
|
Console.runRootCommand(cmd.toArray)
|
||||||
|
cmd.clear()
|
||||||
|
|
||||||
|
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks")) {
|
||||||
|
try {
|
||||||
|
val pid = scala.io.Source.fromFile(Path.BASE + task + "-nat.pid").mkString.trim.toInt
|
||||||
|
cmd.append("kill -9 %d".formatLocal(Locale.ENGLISH, pid))
|
||||||
|
Process.killProcess(pid)
|
||||||
|
} catch {
|
||||||
|
case e: Throwable => Log.e(TAG, "unable to kill " + task)
|
||||||
|
}
|
||||||
|
cmd.append("rm -f %s%s-nat.pid".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
cmd.append("rm -f %s%s-nat.conf".formatLocal(Locale.ENGLISH, Path.BASE, task))
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.runRootCommand(cmd.toArray)
|
||||||
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
|
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
|
||||||
|
|
||||||
val ab = new ArrayBuffer[String]
|
|
||||||
|
|
||||||
ab.append("kill -9 `cat " + Path.BASE + "ss-tunnel.pid`")
|
|
||||||
ab.append("kill -9 `cat " + Path.BASE +"redsocks.pid`")
|
|
||||||
ab.append("killall -9 redsocks")
|
|
||||||
ab.append("kill -15 `cat " + Path.BASE + "pdnsd.pid`")
|
|
||||||
ab.append("killall -15 pdnsd")
|
|
||||||
|
|
||||||
Console.runRootCommand(ab.toArray)
|
|
||||||
|
|
||||||
ab.clear()
|
|
||||||
ab.append("kill -9 `cat " + Path.BASE + "ss-local.pid`")
|
|
||||||
|
|
||||||
Console.runCommand(ab.toArray)
|
|
||||||
}
|
|
||||||
|
|
||||||
def flushDNS() {
|
|
||||||
Console.runRootCommand(Array("ndc resolver flushdefaultif", "ndc resolver flushif wlan0"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def setupIptables() = {
|
def setupIptables() = {
|
||||||
val init_sb = new ArrayBuffer[String]
|
val init_sb = new ArrayBuffer[String]
|
||||||
val http_sb = new ArrayBuffer[String]
|
val http_sb = new ArrayBuffer[String]
|
||||||
|
|
||||||
|
init_sb.append("ulimit -n 4096")
|
||||||
init_sb.append(Utils.getIptables + " -t nat -F OUTPUT")
|
init_sb.append(Utils.getIptables + " -t nat -F OUTPUT")
|
||||||
|
|
||||||
val cmd_bypass = Utils.getIptables + CMD_IPTABLES_RETURN
|
val cmd_bypass = Utils.getIptables + CMD_IPTABLES_RETURN
|
||||||
if (!InetAddressUtils.isIPv6Address(config.proxy.toUpperCase)) {
|
if (!InetAddressUtils.isIPv6Address(config.proxy.toUpperCase)) {
|
||||||
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-d " + config.proxy))
|
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + config.proxy))
|
||||||
}
|
}
|
||||||
init_sb.append(cmd_bypass.replace("0.0.0.0", "127.0.0.1"))
|
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d 127.0.0.1"))
|
||||||
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + myUid))
|
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid))
|
||||||
|
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53"))
|
||||||
|
|
||||||
init_sb.append(Utils.getIptables
|
init_sb.append(Utils.getIptables
|
||||||
+ " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:" + DNS_PORT)
|
+ " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:" + 8153)
|
||||||
|
|
||||||
if (config.isGlobalProxy || config.isBypassApps) {
|
if (config.isGlobalProxy || config.isBypassApps) {
|
||||||
http_sb.append(Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
|
http_sb.append(Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
|
||||||
}
|
}
|
||||||
@@ -374,13 +504,39 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
val filter = new IntentFilter()
|
val filter = new IntentFilter()
|
||||||
filter.addAction(Intent.ACTION_SHUTDOWN)
|
filter.addAction(Intent.ACTION_SHUTDOWN)
|
||||||
filter.addAction(Action.CLOSE)
|
filter.addAction(Action.CLOSE)
|
||||||
receiver = new BroadcastReceiver() {
|
closeReceiver = new BroadcastReceiver() {
|
||||||
def onReceive(p1: Context, p2: Intent) {
|
def onReceive(context: Context, intent: Intent) {
|
||||||
|
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
|
||||||
stopRunner()
|
stopRunner()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
registerReceiver(receiver, filter)
|
registerReceiver(closeReceiver, filter)
|
||||||
|
|
||||||
|
val screenFilter = new IntentFilter()
|
||||||
|
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||||
|
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||||
|
screenFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||||
|
lockReceiver = new BroadcastReceiver() {
|
||||||
|
def onReceive(context: Context, intent: Intent) {
|
||||||
|
if (getState == State.CONNECTED) {
|
||||||
|
val action = intent.getAction
|
||||||
|
if (action == Intent.ACTION_SCREEN_OFF) {
|
||||||
|
notifyForegroundAlert(getString(R.string.forward_success),
|
||||||
|
getString(R.string.service_running).formatLocal(Locale.ENGLISH, config.profileName), false)
|
||||||
|
} else if (action == Intent.ACTION_SCREEN_ON) {
|
||||||
|
val keyGuard = getSystemService(Context.KEYGUARD_SERVICE).asInstanceOf[KeyguardManager]
|
||||||
|
if (!keyGuard.inKeyguardRestrictedInputMode) {
|
||||||
|
notifyForegroundAlert(getString(R.string.forward_success),
|
||||||
|
getString(R.string.service_running).formatLocal(Locale.ENGLISH, config.profileName), true)
|
||||||
|
}
|
||||||
|
} else if (action == Intent.ACTION_USER_PRESENT) {
|
||||||
|
notifyForegroundAlert(getString(R.string.forward_success),
|
||||||
|
getString(R.string.service_running).formatLocal(Locale.ENGLISH, config.profileName), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerReceiver(lockReceiver, screenFilter)
|
||||||
|
|
||||||
// send event
|
// send event
|
||||||
application.tracker.send(new HitBuilders.EventBuilder()
|
application.tracker.send(new HitBuilders.EventBuilder()
|
||||||
@@ -407,6 +563,7 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
|
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
|
|
||||||
|
// Clean up
|
||||||
killProcesses()
|
killProcesses()
|
||||||
|
|
||||||
var resolved: Boolean = false
|
var resolved: Boolean = false
|
||||||
@@ -423,8 +580,12 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resolved && handleConnection) {
|
if (resolved && handleConnection) {
|
||||||
|
|
||||||
|
// Set DNS
|
||||||
|
flushDns()
|
||||||
|
|
||||||
notifyForegroundAlert(getString(R.string.forward_success),
|
notifyForegroundAlert(getString(R.string.forward_success),
|
||||||
getString(R.string.service_running).format(config.profileName))
|
getString(R.string.service_running).formatLocal(Locale.ENGLISH, config.profileName), true)
|
||||||
changeState(State.CONNECTED)
|
changeState(State.CONNECTED)
|
||||||
} else {
|
} else {
|
||||||
changeState(State.STOPPED, getString(R.string.service_failed))
|
changeState(State.STOPPED, getString(R.string.service_failed))
|
||||||
@@ -436,8 +597,18 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
|
|
||||||
override def stopRunner() {
|
override def stopRunner() {
|
||||||
|
|
||||||
// change the state
|
// channge the state
|
||||||
changeState(State.STOPPED)
|
changeState(State.STOPPING)
|
||||||
|
|
||||||
|
// clean up recevier
|
||||||
|
if (closeReceiver != null) {
|
||||||
|
unregisterReceiver(closeReceiver)
|
||||||
|
closeReceiver = null
|
||||||
|
}
|
||||||
|
if (lockReceiver != null) {
|
||||||
|
unregisterReceiver(lockReceiver)
|
||||||
|
lockReceiver = null
|
||||||
|
}
|
||||||
|
|
||||||
// send event
|
// send event
|
||||||
application.tracker.send(new HitBuilders.EventBuilder()
|
application.tracker.send(new HitBuilders.EventBuilder()
|
||||||
@@ -446,31 +617,22 @@ class ShadowsocksNatService extends Service with BaseService {
|
|||||||
.setLabel(getVersionName)
|
.setLabel(getVersionName)
|
||||||
.build())
|
.build())
|
||||||
|
|
||||||
// reset timer
|
|
||||||
if (timer != null) {
|
|
||||||
timer.cancel()
|
|
||||||
timer = null
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset NAT
|
// reset NAT
|
||||||
killProcesses()
|
killProcesses()
|
||||||
|
|
||||||
// stop the service if no callback registered
|
// stop the service if no callback registered
|
||||||
if (callbackCount == 0) {
|
if (getCallbackCount == 0) {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up context
|
|
||||||
if (receiver != null) {
|
|
||||||
unregisterReceiver(receiver)
|
|
||||||
receiver = null
|
|
||||||
}
|
|
||||||
|
|
||||||
stopForegroundCompat(1)
|
stopForegroundCompat(1)
|
||||||
|
|
||||||
|
// change the state
|
||||||
|
changeState(State.STOPPED)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def stopBackgroundService() {
|
override def stopBackgroundService() {
|
||||||
stopRunner()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override def getTag = TAG
|
override def getTag = TAG
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ import com.github.shadowsocks.aidl.IShadowsocksService
|
|||||||
|
|
||||||
class ShadowsocksRunnerActivity extends Activity {
|
class ShadowsocksRunnerActivity extends Activity {
|
||||||
|
|
||||||
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
|
private lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
private lazy val status = getSharedPreferences(Key.status, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
val handler = new Handler()
|
val handler = new Handler()
|
||||||
val connection = new ServiceConnection {
|
val connection = new ServiceConnection {
|
||||||
@@ -72,10 +73,10 @@ class ShadowsocksRunnerActivity extends Activity {
|
|||||||
|
|
||||||
def isVpnEnabled: Boolean = {
|
def isVpnEnabled: Boolean = {
|
||||||
if (vpnEnabled < 0) {
|
if (vpnEnabled < 0) {
|
||||||
vpnEnabled = if (Utils.isLollipopOrAbove || !Console.isRoot) {
|
vpnEnabled = if ((!Utils.isLollipopOrAbove || status.getBoolean(Key.isNAT, false)) && Console.isRoot) {
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
0
|
||||||
|
} else {
|
||||||
|
1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (vpnEnabled == 1) true else false
|
if (vpnEnabled == 1) true else false
|
||||||
|
|||||||
@@ -40,15 +40,15 @@
|
|||||||
package com.github.shadowsocks
|
package com.github.shadowsocks
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.InetAddress
|
import java.util.Locale
|
||||||
|
|
||||||
import android.app._
|
import android.app._
|
||||||
import android.content._
|
import android.content._
|
||||||
import android.content.pm.{PackageInfo, PackageManager}
|
import android.content.pm.{PackageInfo, PackageManager}
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os._
|
import android.os._
|
||||||
import android.support.v4.app.NotificationCompat
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import com.github.shadowsocks.aidl.Config
|
import com.github.shadowsocks.aidl.Config
|
||||||
import com.github.shadowsocks.utils._
|
import com.github.shadowsocks.utils._
|
||||||
import com.google.android.gms.analytics.HitBuilders
|
import com.google.android.gms.analytics.HitBuilders
|
||||||
@@ -107,15 +107,19 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val conf = ConfigUtils
|
||||||
|
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, config.localPort,
|
||||||
|
config.sitekey, config.encMethod, 10)
|
||||||
|
ConfigUtils.printToFile(new File(Path.BASE + "ss-local-vpn.conf"))(p => {
|
||||||
|
p.println(conf)
|
||||||
|
})
|
||||||
|
|
||||||
val cmd = new ArrayBuffer[String]
|
val cmd = new ArrayBuffer[String]
|
||||||
cmd +=("ss-local", "-u"
|
cmd +=(Path.BASE + "ss-local", "-u"
|
||||||
, "-b", "127.0.0.1"
|
, "-b" , "127.0.0.1"
|
||||||
, "-s", config.proxy
|
, "-t" , "600"
|
||||||
, "-p", config.remotePort.toString
|
, "-c" , Path.BASE + "ss-local-vpn.conf"
|
||||||
, "-l", config.localPort.toString
|
, "-f" , Path.BASE + "ss-local-vpn.pid")
|
||||||
, "-k", config.sitekey
|
|
||||||
, "-m", config.encMethod
|
|
||||||
, "-f", Path.BASE + "ss-local.pid")
|
|
||||||
|
|
||||||
if (Utils.isLollipopOrAbove && config.route != Route.ALL) {
|
if (Utils.isLollipopOrAbove && config.route != Route.ALL) {
|
||||||
cmd += "--acl"
|
cmd += "--acl"
|
||||||
@@ -123,38 +127,48 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||||
Core.sslocal(cmd.toArray)
|
Console.runCommand(cmd.mkString(" "))
|
||||||
}
|
}
|
||||||
|
|
||||||
def startDnsTunnel() = {
|
def startDnsTunnel() = {
|
||||||
|
val conf = ConfigUtils
|
||||||
|
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, config.proxy, config.remotePort, 8163,
|
||||||
|
config.sitekey, config.encMethod, 10)
|
||||||
|
ConfigUtils.printToFile(new File(Path.BASE + "ss-tunnel-vpn.conf"))(p => {
|
||||||
|
p.println(conf)
|
||||||
|
})
|
||||||
val cmd = new ArrayBuffer[String]
|
val cmd = new ArrayBuffer[String]
|
||||||
cmd +=("ss-tunnel"
|
cmd +=(Path.BASE + "ss-tunnel"
|
||||||
, "-b", "127.0.0.1"
|
, "-u"
|
||||||
, "-l", "8163"
|
, "-t" , "10"
|
||||||
, "-L", "8.8.8.8:53"
|
, "-b" , "127.0.0.1"
|
||||||
, "-s", config.proxy
|
, "-l" , "8163"
|
||||||
, "-p", config.remotePort.toString
|
, "-L" , "8.8.8.8:53"
|
||||||
, "-k", config.sitekey
|
, "-c" , Path.BASE + "ss-tunnel-vpn.conf"
|
||||||
, "-m", config.encMethod
|
, "-f" , Path.BASE + "ss-tunnel-vpn.pid")
|
||||||
, "-f", Path.BASE + "ss-tunnel.pid")
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||||
Core.sstunnel(cmd.toArray)
|
Console.runCommand(cmd.mkString(" "))
|
||||||
}
|
}
|
||||||
|
|
||||||
def startDnsDaemon() {
|
def startDnsDaemon() {
|
||||||
val conf = {
|
val conf = {
|
||||||
if (Utils.isLollipopOrAbove) {
|
if (Utils.isLollipopOrAbove && config.route == Route.BYPASS_CHN) {
|
||||||
ConfigUtils.PDNSD_BYPASS.format("0.0.0.0", getString(R.string.exclude), 8163)
|
val reject = ConfigUtils.getRejectList(getContext, application)
|
||||||
|
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153,
|
||||||
|
Path.BASE + "pdnsd-vpn.pid", reject, reject, 8163)
|
||||||
} else {
|
} else {
|
||||||
ConfigUtils.PDNSD_LOCAL.format("0.0.0.0", 8163)
|
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "0.0.0.0", 8153,
|
||||||
|
Path.BASE + "pdnsd-vpn.pid", 8163)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ConfigUtils.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => {
|
ConfigUtils.printToFile(new File(Path.BASE + "pdnsd-vpn.conf"))(p => {
|
||||||
p.println(conf)
|
p.println(conf)
|
||||||
})
|
})
|
||||||
val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd.conf"
|
val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd-vpn.conf"
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||||
Core.pdnsd(cmd.split(" "))
|
Console.runCommand(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getVersionName: String = {
|
def getVersionName: String = {
|
||||||
@@ -175,16 +189,19 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
builder
|
builder
|
||||||
.setSession(config.profileName)
|
.setSession(config.profileName)
|
||||||
.setMtu(VPN_MTU)
|
.setMtu(VPN_MTU)
|
||||||
.addAddress(PRIVATE_VLAN.format("1"), 24)
|
.addAddress(PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), 24)
|
||||||
.addDnsServer("8.8.8.8")
|
.addDnsServer("8.8.8.8")
|
||||||
|
|
||||||
if (Utils.isLollipopOrAbove) {
|
if (Utils.isLollipopOrAbove) {
|
||||||
|
|
||||||
|
builder.allowFamily(android.system.OsConstants.AF_INET6)
|
||||||
|
|
||||||
if (!config.isGlobalProxy) {
|
if (!config.isGlobalProxy) {
|
||||||
val apps = AppManager.getProxiedApps(this, config.proxiedAppString)
|
val apps = AppManager.getProxiedApps(this, config.proxiedAppString)
|
||||||
val pkgSet: mutable.HashSet[String] = new mutable.HashSet[String]
|
val pkgSet: mutable.HashSet[String] = new mutable.HashSet[String]
|
||||||
for (app <- apps) {
|
for (app <- apps) {
|
||||||
if (app.proxied) {
|
if (app.proxied) {
|
||||||
pkgSet.add(app.name)
|
pkgSet.add(app.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (pkg <- pkgSet) {
|
for (pkg <- pkgSet) {
|
||||||
@@ -208,27 +225,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
} else {
|
} else {
|
||||||
if (!Utils.isLollipopOrAbove) {
|
if (!Utils.isLollipopOrAbove) {
|
||||||
config.route match {
|
config.route match {
|
||||||
case Route.ALL =>
|
|
||||||
for (i <- 1 to 223) {
|
|
||||||
if (i != 26 && i != 127) {
|
|
||||||
val addr = i.toString + ".0.0.0"
|
|
||||||
val cidr = addr + "/8"
|
|
||||||
val net = new SubnetUtils(cidr)
|
|
||||||
|
|
||||||
if (!isByass(net)) {
|
|
||||||
builder.addRoute(addr, 8)
|
|
||||||
} else {
|
|
||||||
for (j <- 0 to 255) {
|
|
||||||
val subAddr = i.toString + "." + j.toString + ".0.0"
|
|
||||||
val subCidr = subAddr + "/16"
|
|
||||||
val subNet = new SubnetUtils(subCidr)
|
|
||||||
if (!isByass(subNet)) {
|
|
||||||
builder.addRoute(subAddr, 16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case Route.BYPASS_LAN =>
|
case Route.BYPASS_LAN =>
|
||||||
for (i <- 1 to 223) {
|
for (i <- 1 to 223) {
|
||||||
if (i != 26 && i != 127) {
|
if (i != 26 && i != 127) {
|
||||||
@@ -265,6 +261,27 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
builder.addRoute(addr(0), addr(1).toInt)
|
builder.addRoute(addr(0), addr(1).toInt)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
case Route.ALL =>
|
||||||
|
for (i <- 1 to 223) {
|
||||||
|
if (i != 26 && i != 127) {
|
||||||
|
val addr = i.toString + ".0.0.0"
|
||||||
|
val cidr = addr + "/8"
|
||||||
|
val net = new SubnetUtils(cidr)
|
||||||
|
|
||||||
|
if (!isByass(net)) {
|
||||||
|
builder.addRoute(addr, 8)
|
||||||
|
} else {
|
||||||
|
for (j <- 0 to 255) {
|
||||||
|
val subAddr = i.toString + "." + j.toString + ".0.0"
|
||||||
|
val subCidr = subAddr + "/16"
|
||||||
|
val subNet = new SubnetUtils(subCidr)
|
||||||
|
if (!isByass(subNet)) {
|
||||||
|
builder.addRoute(subAddr, 16)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (config.route == Route.ALL) {
|
if (config.route == Route.ALL) {
|
||||||
@@ -304,27 +321,31 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
+ "--tunfd %d "
|
+ "--tunfd %d "
|
||||||
+ "--tunmtu %d "
|
+ "--tunmtu %d "
|
||||||
+ "--loglevel 3 "
|
+ "--loglevel 3 "
|
||||||
+ "--pid %stun2socks.pid")
|
+ "--pid %stun2socks-vpn.pid")
|
||||||
.format(PRIVATE_VLAN.format("2"), config.localPort, fd, VPN_MTU, Path.BASE)
|
.formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"), config.localPort, fd, VPN_MTU, Path.BASE)
|
||||||
|
|
||||||
if (config.isUdpDns)
|
if (config.isUdpDns)
|
||||||
cmd += " --enable-udprelay"
|
cmd += " --enable-udprelay"
|
||||||
else
|
else
|
||||||
cmd += " --dnsgw %s:8153".format(PRIVATE_VLAN.format("1"))
|
cmd += " --dnsgw %s:8153".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"))
|
||||||
|
|
||||||
|
if (Utils.isLollipopOrAbove) {
|
||||||
|
cmd += " --fake-proc"
|
||||||
|
}
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||||
|
|
||||||
Core.tun2socks(cmd.split(" "))
|
System.exec(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called when the activity is first created. */
|
/** Called when the activity is first created. */
|
||||||
def handleConnection: Boolean = {
|
def handleConnection: Boolean = {
|
||||||
startVpn()
|
|
||||||
startShadowsocksDaemon()
|
startShadowsocksDaemon()
|
||||||
if (!config.isUdpDns) {
|
if (!config.isUdpDns) {
|
||||||
startDnsDaemon()
|
startDnsDaemon()
|
||||||
startDnsTunnel()
|
startDnsTunnel()
|
||||||
}
|
}
|
||||||
|
startVpn()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,13 +373,12 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def killProcesses() {
|
def killProcesses() {
|
||||||
for (task <- Array("ss-local", "ss-tunnel", "tun2socks", "pdnsd")) {
|
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "tun2socks")) {
|
||||||
try {
|
try {
|
||||||
val pid = scala.io.Source.fromFile(Path.BASE + task + ".pid").mkString.trim.toInt
|
val pid = scala.io.Source.fromFile(Path.BASE + task + "-vpn.pid").mkString.trim.toInt
|
||||||
Process.killProcess(pid)
|
Process.killProcess(pid)
|
||||||
Log.d(TAG, "kill pid: " + pid)
|
|
||||||
} catch {
|
} catch {
|
||||||
case e: Throwable => Log.e(TAG, "unable to kill " + task, e)
|
case e: Throwable => Log.e(TAG, "unable to kill " + task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,6 +407,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
filter.addAction(Intent.ACTION_SHUTDOWN)
|
filter.addAction(Intent.ACTION_SHUTDOWN)
|
||||||
receiver = new BroadcastReceiver {
|
receiver = new BroadcastReceiver {
|
||||||
def onReceive(p1: Context, p2: Intent) {
|
def onReceive(p1: Context, p2: Intent) {
|
||||||
|
Toast.makeText(p1, R.string.stopping, Toast.LENGTH_SHORT)
|
||||||
stopRunner()
|
stopRunner()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,7 +460,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
override def stopRunner() {
|
override def stopRunner() {
|
||||||
|
|
||||||
// channge the state
|
// channge the state
|
||||||
changeState(State.STOPPED)
|
changeState(State.STOPPING)
|
||||||
|
|
||||||
// send event
|
// send event
|
||||||
application.tracker.send(new HitBuilders.EventBuilder()
|
application.tracker.send(new HitBuilders.EventBuilder()
|
||||||
@@ -458,7 +479,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// stop the service if no callback registered
|
// stop the service if no callback registered
|
||||||
if (callbackCount == 0) {
|
if (getCallbackCount == 0) {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,10 +488,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
|||||||
unregisterReceiver(receiver)
|
unregisterReceiver(receiver)
|
||||||
receiver = null
|
receiver = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// channge the state
|
||||||
|
changeState(State.STOPPED)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def stopBackgroundService() {
|
override def stopBackgroundService() {
|
||||||
stopRunner()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override def getTag = TAG
|
override def getTag = TAG
|
||||||
|
|||||||
@@ -40,13 +40,13 @@
|
|||||||
package com.github.shadowsocks.utils
|
package com.github.shadowsocks.utils
|
||||||
|
|
||||||
import android.content.{SharedPreferences, Context}
|
import android.content.{SharedPreferences, Context}
|
||||||
import com.github.shadowsocks.ShadowsocksApplication
|
import com.github.shadowsocks.{R, ShadowsocksApplication}
|
||||||
import com.google.android.gms.tagmanager.Container
|
import com.google.android.gms.tagmanager.Container
|
||||||
import scalaj.http.{HttpOptions, Http}
|
import com.github.kevinsawicki.http.HttpRequest
|
||||||
import com.github.shadowsocks.aidl.Config
|
import com.github.shadowsocks.aidl.Config
|
||||||
|
|
||||||
object ConfigUtils {
|
object ConfigUtils {
|
||||||
val SHADOWSOCKS = "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}"
|
val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d}"
|
||||||
val REDSOCKS = "base {" +
|
val REDSOCKS = "base {" +
|
||||||
" log_debug = off;" +
|
" log_debug = off;" +
|
||||||
" log_info = off;" +
|
" log_info = off;" +
|
||||||
@@ -61,36 +61,6 @@ object ConfigUtils {
|
|||||||
" port = %d;" +
|
" port = %d;" +
|
||||||
" type = socks5;" +
|
" type = socks5;" +
|
||||||
"}"
|
"}"
|
||||||
val PDNSD =
|
|
||||||
"""
|
|
||||||
|global {
|
|
||||||
| perm_cache = 2048;
|
|
||||||
| cache_dir = "/data/data/com.github.shadowsocks";
|
|
||||||
| server_ip = %s;
|
|
||||||
| server_port = 8153;
|
|
||||||
| query_method = tcp_only;
|
|
||||||
| run_ipv4 = on;
|
|
||||||
| min_ttl = 15m;
|
|
||||||
| max_ttl = 1w;
|
|
||||||
| timeout = 10;
|
|
||||||
| daemon = on;
|
|
||||||
| pid_file = "/data/data/com.github.shadowsocks/pdnsd.pid";
|
|
||||||
|}
|
|
||||||
|
|
|
||||||
|server {
|
|
||||||
| label = "google-servers";
|
|
||||||
| ip = 8.8.8.8, 8.8.4.4;
|
|
||||||
| timeout = 5;
|
|
||||||
|}
|
|
||||||
|
|
|
||||||
|rr {
|
|
||||||
| name=localhost;
|
|
||||||
| reverse=on;
|
|
||||||
| a=127.0.0.1;
|
|
||||||
| owner=localhost;
|
|
||||||
| soa=localhost,root.localhost,42,86400,900,86400,86400;
|
|
||||||
|}
|
|
||||||
""".stripMargin
|
|
||||||
|
|
||||||
val PDNSD_LOCAL =
|
val PDNSD_LOCAL =
|
||||||
"""
|
"""
|
||||||
@@ -98,14 +68,14 @@ object ConfigUtils {
|
|||||||
| perm_cache = 2048;
|
| perm_cache = 2048;
|
||||||
| cache_dir = "/data/data/com.github.shadowsocks";
|
| cache_dir = "/data/data/com.github.shadowsocks";
|
||||||
| server_ip = %s;
|
| server_ip = %s;
|
||||||
| server_port = 8153;
|
| server_port = %d;
|
||||||
| query_method = tcp_only;
|
| query_method = tcp_only;
|
||||||
| run_ipv4 = on;
|
| run_ipv4 = on;
|
||||||
| min_ttl = 15m;
|
| min_ttl = 15m;
|
||||||
| max_ttl = 1w;
|
| max_ttl = 1w;
|
||||||
| timeout = 10;
|
| timeout = 10;
|
||||||
| daemon = on;
|
| daemon = on;
|
||||||
| pid_file = "/data/data/com.github.shadowsocks/pdnsd.pid";
|
| pid_file = %s;
|
||||||
|}
|
|}
|
||||||
|
|
|
|
||||||
|server {
|
|server {
|
||||||
@@ -130,14 +100,14 @@ object ConfigUtils {
|
|||||||
| perm_cache = 2048;
|
| perm_cache = 2048;
|
||||||
| cache_dir = "/data/data/com.github.shadowsocks";
|
| cache_dir = "/data/data/com.github.shadowsocks";
|
||||||
| server_ip = %s;
|
| server_ip = %s;
|
||||||
| server_port = 8153;
|
| server_port = %d;
|
||||||
| query_method = tcp_only;
|
| query_method = tcp_only;
|
||||||
| run_ipv4 = on;
|
| run_ipv4 = on;
|
||||||
| min_ttl = 15m;
|
| min_ttl = 15m;
|
||||||
| max_ttl = 1w;
|
| max_ttl = 1w;
|
||||||
| timeout = 10;
|
| timeout = 10;
|
||||||
| daemon = on;
|
| daemon = on;
|
||||||
| pid_file = "/data/data/com.github.shadowsocks/pdnsd.pid";
|
| pid_file = "%s";
|
||||||
|}
|
|}
|
||||||
|
|
|
|
||||||
|server {
|
|server {
|
||||||
@@ -153,6 +123,8 @@ object ConfigUtils {
|
|||||||
|server {
|
|server {
|
||||||
| label = "local-server";
|
| label = "local-server";
|
||||||
| ip = 127.0.0.1;
|
| ip = 127.0.0.1;
|
||||||
|
| uptest = none;
|
||||||
|
| preset = on;
|
||||||
| port = %d;
|
| port = %d;
|
||||||
| timeout = 5;
|
| timeout = 5;
|
||||||
|}
|
|}
|
||||||
@@ -166,6 +138,61 @@ object ConfigUtils {
|
|||||||
|}
|
|}
|
||||||
""".stripMargin
|
""".stripMargin
|
||||||
|
|
||||||
|
val PDNSD_DIRECT =
|
||||||
|
"""
|
||||||
|
|global {
|
||||||
|
| perm_cache = 2048;
|
||||||
|
| cache_dir = "/data/data/com.github.shadowsocks";
|
||||||
|
| server_ip = %s;
|
||||||
|
| server_port = %d;
|
||||||
|
| query_method = udp_tcp;
|
||||||
|
| run_ipv4 = on;
|
||||||
|
| min_ttl = 15m;
|
||||||
|
| max_ttl = 1w;
|
||||||
|
| timeout = 10;
|
||||||
|
| daemon = on;
|
||||||
|
| pid_file = "%s";
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
|server {
|
||||||
|
| label = "china-servers";
|
||||||
|
| ip = 223.5.5.5, 114.114.114.114;
|
||||||
|
| timeout = 2;
|
||||||
|
| reject = %s;
|
||||||
|
| reject_policy = fail;
|
||||||
|
| reject_recursively = on;
|
||||||
|
| uptest = none;
|
||||||
|
| preset = on;
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
|server {
|
||||||
|
| label= "secure";
|
||||||
|
| port = 443;
|
||||||
|
| ip = 208.67.220.220,113.20.8.17,77.66.84.233,176.56.237.171;
|
||||||
|
| timeout = 3;
|
||||||
|
| reject = %s;
|
||||||
|
| reject_policy = fail;
|
||||||
|
| reject_recursively = on;
|
||||||
|
| uptest = none;
|
||||||
|
| preset = on;
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
|server {
|
||||||
|
| label = "local-server";
|
||||||
|
| ip = 127.0.0.1;
|
||||||
|
| port = %d;
|
||||||
|
| timeout = 3;
|
||||||
|
|}
|
||||||
|
|
|
||||||
|
|rr {
|
||||||
|
| name=localhost;
|
||||||
|
| reverse=on;
|
||||||
|
| a=127.0.0.1;
|
||||||
|
| owner=localhost;
|
||||||
|
| soa=localhost,root.localhost,42,86400,900,86400,86400;
|
||||||
|
|}
|
||||||
|
""".stripMargin
|
||||||
|
|
||||||
def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
|
def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
|
||||||
val p = new java.io.PrintWriter(f)
|
val p = new java.io.PrintWriter(f)
|
||||||
try {
|
try {
|
||||||
@@ -180,15 +207,26 @@ object ConfigUtils {
|
|||||||
if (holder != null) holder.refresh()
|
if (holder != null) holder.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getRejectList(context: Context, app: ShadowsocksApplication): String = {
|
||||||
|
val default = context.getString(R.string.reject)
|
||||||
|
try {
|
||||||
|
val container = app.containerHolder.getContainer
|
||||||
|
val update = container.getString("reject")
|
||||||
|
if (update == null || update.isEmpty) default else update
|
||||||
|
} catch {
|
||||||
|
case ex: Exception => default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def getPublicConfig(context: Context, container: Container, config: Config): Config = {
|
def getPublicConfig(context: Context, container: Container, config: Config): Config = {
|
||||||
val url = container.getString("proxy_url")
|
val url = container.getString("proxy_url")
|
||||||
val sig = Utils.getSignature(context)
|
val sig = Utils.getSignature(context)
|
||||||
val list = Http
|
val list = HttpRequest
|
||||||
.post(url)
|
.post(url)
|
||||||
.params("sig" -> sig)
|
.connectTimeout(2000)
|
||||||
.option(HttpOptions.connTimeout(1000))
|
.readTimeout(2000)
|
||||||
.option(HttpOptions.readTimeout(5000))
|
.send("sig="+sig)
|
||||||
.asString
|
.body
|
||||||
val proxies = util.Random.shuffle(list.split('|').toSeq).toSeq
|
val proxies = util.Random.shuffle(list.split('|').toSeq).toSeq
|
||||||
val proxy = proxies(0).split(':')
|
val proxy = proxies(0).split(':')
|
||||||
|
|
||||||
|
|||||||
@@ -45,36 +45,21 @@ import java.util
|
|||||||
|
|
||||||
object Console {
|
object Console {
|
||||||
|
|
||||||
private var shell: Shell.Interactive = null
|
private def openShell(): Shell.Interactive = {
|
||||||
private var rootShell: Shell.Interactive = null
|
val builder = new Builder()
|
||||||
|
builder
|
||||||
private def openShell() {
|
.useSH()
|
||||||
if (shell == null) {
|
.setWatchdogTimeout(10)
|
||||||
val builder = new Builder()
|
.open()
|
||||||
shell = builder
|
|
||||||
.useSH()
|
|
||||||
.setWatchdogTimeout(10)
|
|
||||||
.open(new OnCommandResultListener {
|
|
||||||
override def onCommandResult(commandCode: Int, exitCode: Int,
|
|
||||||
output: util.List[String]) {
|
|
||||||
if (exitCode < 0) {
|
|
||||||
shell.close()
|
|
||||||
shell = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private def openRootShell() {
|
private def openRootShell(context: String): Shell.Interactive = {
|
||||||
if (rootShell == null) {
|
val builder = new Builder()
|
||||||
val builder = new Builder()
|
builder
|
||||||
rootShell = builder
|
.setShell(SU.shell(0, context))
|
||||||
.setShell(SU.shell(0, "u:r:untrusted_app:s0"))
|
.setWantSTDERR(true)
|
||||||
.setWantSTDERR(true)
|
.setWatchdogTimeout(10)
|
||||||
.setWatchdogTimeout(10)
|
.open()
|
||||||
.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def runCommand(command: String) {
|
def runCommand(command: String) {
|
||||||
@@ -82,39 +67,48 @@ object Console {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def runCommand(commands: Array[String]) {
|
def runCommand(commands: Array[String]) {
|
||||||
if (shell == null) {
|
val shell = openShell()
|
||||||
openShell()
|
shell.addCommand(commands, 0, new OnCommandResultListener {
|
||||||
}
|
override def onCommandResult(commandCode: Int, exitCode: Int,
|
||||||
val ts = shell
|
output: util.List[String]) {
|
||||||
ts.addCommand(commands)
|
if (exitCode < 0) {
|
||||||
ts.waitForIdle()
|
shell.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
shell.waitForIdle()
|
||||||
|
shell.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
def runRootCommand(command: String): String = runRootCommand(Array(command))
|
def runRootCommand(command: String): String = runRootCommand(Array(command))
|
||||||
|
def runRootCommand(command: String, context: String): String = runRootCommand(Array(command), context)
|
||||||
|
def runRootCommand(commands: Array[String]): String = runRootCommand(commands, "u:r:init_shell:s0")
|
||||||
|
|
||||||
def runRootCommand(commands: Array[String]): String = {
|
def runRootCommand(commands: Array[String], context: String): String = {
|
||||||
if (!isRoot) {
|
if (!isRoot) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (rootShell == null) {
|
val shell = openRootShell(context)
|
||||||
openRootShell()
|
|
||||||
}
|
|
||||||
val ts = rootShell
|
|
||||||
val sb = new StringBuilder
|
val sb = new StringBuilder
|
||||||
ts.addCommand(commands, 0, new OnCommandResultListener {
|
shell.addCommand(commands, 0, new OnCommandResultListener {
|
||||||
override def onCommandResult(commandCode: Int, exitCode: Int,
|
override def onCommandResult(commandCode: Int, exitCode: Int,
|
||||||
output: util.List[String]) {
|
output: util.List[String]) {
|
||||||
if (exitCode < 0) {
|
if (exitCode < 0) {
|
||||||
rootShell.close()
|
shell.close()
|
||||||
rootShell = null
|
|
||||||
} else {
|
} else {
|
||||||
import scala.collection.JavaConversions._
|
import scala.collection.JavaConversions._
|
||||||
output.foreach(line => sb.append(line).append('\n'))
|
output.foreach(line => sb.append(line).append('\n'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (ts.waitForIdle()) sb.toString()
|
if (shell.waitForIdle()) {
|
||||||
else null
|
shell.close()
|
||||||
|
sb.toString()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
shell.close()
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def isRoot: Boolean = SU.available()
|
def isRoot: Boolean = SU.available()
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ object Key {
|
|||||||
|
|
||||||
val proxied = "Proxyed"
|
val proxied = "Proxyed"
|
||||||
|
|
||||||
|
val isNAT = "isNAT"
|
||||||
val isRoot = "isRoot"
|
val isRoot = "isRoot"
|
||||||
val status = "status"
|
val status = "status"
|
||||||
val proxyedApps = "proxyedApps"
|
val proxyedApps = "proxyedApps"
|
||||||
@@ -101,7 +102,8 @@ object State {
|
|||||||
val INIT = 0
|
val INIT = 0
|
||||||
val CONNECTING = 1
|
val CONNECTING = 1
|
||||||
val CONNECTED = 2
|
val CONNECTED = 2
|
||||||
val STOPPED = 3
|
val STOPPING = 3
|
||||||
|
val STOPPED = 4
|
||||||
def isAvailable(state: Int): Boolean = state != CONNECTED && state != CONNECTING
|
def isAvailable(state: Int): Boolean = state != CONNECTED && state != CONNECTING
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ object Utils {
|
|||||||
val DEFAULT_ROOT: String = "/system/bin/su"
|
val DEFAULT_ROOT: String = "/system/bin/su"
|
||||||
val ALTERNATIVE_ROOT: String = "/system/xbin/su"
|
val ALTERNATIVE_ROOT: String = "/system/xbin/su"
|
||||||
val DEFAULT_IPTABLES: String = "/system/bin/iptables"
|
val DEFAULT_IPTABLES: String = "/system/bin/iptables"
|
||||||
val ALTERNATIVE_IPTABLES: String = "/data/data/com.github.shadowsocks/iptables"
|
val ALTERNATIVE_IPTABLES: String = "iptables"
|
||||||
val TIME_OUT: Int = -99
|
val TIME_OUT: Int = -99
|
||||||
var initialized: Boolean = false
|
var initialized: Boolean = false
|
||||||
var hasRedirectSupport: Int = -1
|
var hasRedirectSupport: Int = -1
|
||||||
@@ -181,20 +181,24 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def resolve(host: String, addrType: Int): Option[String] = {
|
def resolve(host: String, addrType: Int): Option[String] = {
|
||||||
val lookup = new Lookup(host, addrType)
|
try {
|
||||||
val resolver = new SimpleResolver("114.114.114.114")
|
val lookup = new Lookup(host, addrType)
|
||||||
resolver.setTimeout(5)
|
val resolver = new SimpleResolver("114.114.114.114")
|
||||||
lookup.setResolver(resolver)
|
resolver.setTimeout(5)
|
||||||
val result = lookup.run()
|
lookup.setResolver(resolver)
|
||||||
if (result == null) return None
|
val result = lookup.run()
|
||||||
val records = scala.util.Random.shuffle(result.toList)
|
if (result == null) return None
|
||||||
for (r <- records) {
|
val records = scala.util.Random.shuffle(result.toList)
|
||||||
addrType match {
|
for (r <- records) {
|
||||||
case Type.A =>
|
addrType match {
|
||||||
return Some(r.asInstanceOf[ARecord].getAddress.getHostAddress)
|
case Type.A =>
|
||||||
case Type.AAAA =>
|
return Some(r.asInstanceOf[ARecord].getAddress.getHostAddress)
|
||||||
return Some(r.asInstanceOf[AAAARecord].getAddress.getHostAddress)
|
case Type.AAAA =>
|
||||||
|
return Some(r.asInstanceOf[AAAARecord].getAddress.getHostAddress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
case e: Exception => None
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -332,6 +336,16 @@ object Utils {
|
|||||||
bitmap
|
bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getAppIcon(c: Context, packageName: String): Drawable = {
|
||||||
|
val pm: PackageManager = c.getPackageManager
|
||||||
|
val icon: Drawable = c.getResources.getDrawable(android.R.drawable.sym_def_app_icon)
|
||||||
|
try {
|
||||||
|
pm.getApplicationIcon(packageName)
|
||||||
|
} catch {
|
||||||
|
case e: PackageManager.NameNotFoundException => icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def getAppIcon(c: Context, uid: Int): Drawable = {
|
def getAppIcon(c: Context, uid: Int): Drawable = {
|
||||||
val pm: PackageManager = c.getPackageManager
|
val pm: PackageManager = c.getPackageManager
|
||||||
val icon: Drawable = c.getResources.getDrawable(android.R.drawable.sym_def_app_icon)
|
val icon: Drawable = c.getResources.getDrawable(android.R.drawable.sym_def_app_icon)
|
||||||
|
|||||||
Reference in New Issue
Block a user