diff --git a/.gitignore b/.gitignore index ecec4bea..87b1bdf9 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ local.sbt mobile/src/main/libs mobile/src/main/obj +mobile/src/main/jni/overture diff --git a/.gitmodules b/.gitmodules index c14024db..8dafbfc2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -17,10 +17,6 @@ path = mobile/src/main/jni/redsocks url = https://github.com/shadowsocks/redsocks.git branch = shadowsocks-android -[submodule "mobile/src/main/jni/pdnsd"] - path = mobile/src/main/jni/pdnsd - url = https://github.com/shadowsocks/pdnsd.git - branch = shadowsocks-android [submodule "mobile/src/main/jni/mbedtls"] path = mobile/src/main/jni/mbedtls url = https://github.com/ARMmbed/mbedtls @@ -37,3 +33,6 @@ [submodule "mobile/src/main/jni/libudns"] path = mobile/src/main/jni/libudns url = https://github.com/shadowsocks/libudns.git +[submodule "mobile/src/overture"] + path = mobile/src/overture + url = https://github.com/shadowsocks/overture-android.git diff --git a/.travis.yml b/.travis.yml index 62338c27..a9a15ed4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,9 @@ jdk: env: global: - - NDK_VERSION=r12b + - NDK_VERSION=r14 - NDK_CCACHE=ccache + - GOROOT_BOOTSTRAP=$GOROOT - ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION} - SBTPATH=$HOME/.sbt - PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH} @@ -49,4 +50,4 @@ install: fi script: - - $SBTPATH/sbt android:package + - $SBTPATH/sbt go-build android:package diff --git a/README.md b/README.md index e2a825c3..a873cccd 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,17 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. * JDK 1.8 * SBT 0.13.0+ +* Go 1.4+ * Android SDK - Build Tools 25+ - Android Support Repository and Google Repository (see `build.sbt` for version) -* Android NDK r12b+ + - Android NDK r14+ ### BUILD * Set environment variable `ANDROID_HOME` to `/path/to/android-sdk` * (optional) Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` (default: `$ANDROID_HOME/ndk-bundle`) +* Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go` * Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html * Create `mobile/local.properties` from `mobile/local.properties.example` with your own key information * Invoke the building like this @@ -29,7 +31,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala. git submodule update --init --recursive # Build the App - sbt clean android:package-release + sbt clean go-build android:package-release ``` ### TRANSLATE @@ -50,7 +52,7 @@ Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) t
  • pcre: BSD
  • libancillary: BSD
  • shadowsocks-libev: GPLv3
  • -
  • pdnsd: GPLv3
  • +
  • overture: MIT
  • libev: GPLv2
  • libsodium: ISC
  • libudns: LGPL
  • diff --git a/build.sbt b/build.sbt index bd13b300..b427f9ae 100644 --- a/build.sbt +++ b/build.sbt @@ -18,7 +18,7 @@ lazy val commonSettings = Seq( typedResources := false ) -val supportLibsVersion = "25.1.1" +val supportLibsVersion = "25.2.0" lazy val root = Project(id = "shadowsocks-android", base = file(".")) .settings(commonSettings) .aggregate(plugin, mobile) diff --git a/mobile/build.sbt b/mobile/build.sbt index c52a430b..392f5ea6 100644 --- a/mobile/build.sbt +++ b/mobile/build.sbt @@ -2,8 +2,8 @@ enablePlugins(AndroidApp) android.useSupportVectors name := "shadowsocks" -version := "4.0.4" -versionCode := Some(179) +version := "4.1.3" +versionCode := Some(183) proguardOptions ++= "-keep class com.github.shadowsocks.JniHelper { *; }" :: @@ -19,7 +19,7 @@ val playServicesVersion = "10.2.0" resolvers += Resolver.jcenterRepo libraryDependencies ++= "com.futuremind.recyclerfastscroll" % "fastscroll" % "0.2.5" :: - "com.evernote" % "android-job" % "1.1.6" :: + "com.evernote" % "android-job" % "1.1.7" :: "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" :: "com.google.android.gms" % "play-services-ads" % playServicesVersion :: "com.google.android.gms" % "play-services-analytics" % playServicesVersion :: @@ -28,7 +28,7 @@ libraryDependencies ++= "com.mikepenz" % "crossfader" % "1.5.0" :: "com.mikepenz" % "fastadapter" % "2.1.7" :: "com.mikepenz" % "iconics-core" % "2.8.2" :: - "com.mikepenz" % "materialdrawer" % "5.8.1" :: + "com.mikepenz" % "materialdrawer" % "5.8.2" :: "com.mikepenz" % "materialize" % "1.0.1" :: "com.squareup.okhttp3" % "okhttp" % "3.6.0" :: "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" :: @@ -36,3 +36,11 @@ libraryDependencies ++= "eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" :: "net.glxn.qrgen" % "android" % "2.0" :: Nil + +lazy val goBuild = TaskKey[Unit]("go-build", "Build go and kcptun") +goBuild := { + Process(Seq("mobile/src/overture/make.bash", minSdkVersion.value)) ! streams.value.log match { + case 0 => // Success! + case n => sys.error(s"Native build script exit code: $n") + } +} diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index 0f77f4df..ff448140 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -144,6 +144,8 @@ android:process=":bg"/> + http
  • pcre: BSD
  • libancillary: BSD
  • shadowsocks-libev: GPLv3
  • -
  • pdnsd: GPLv3
  • +
  • overture: MIT
  • libev: GPLv2
  • libsodium: ISC
  • libudns: LGPL
  • diff --git a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java index dfce9080..c2299e58 100755 --- a/mobile/src/main/java/com/github/shadowsocks/JniHelper.java +++ b/mobile/src/main/java/com/github/shadowsocks/JniHelper.java @@ -54,7 +54,19 @@ public class JniHelper { ? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno); } + @Deprecated // only implemented for before API 24 + public static boolean waitForCompat(Process process, long millis) throws Exception { + if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); + final Object mutex = getExitValueMutex(process); + synchronized (mutex) { + if (getExitValue(process) == null) mutex.wait(millis); + return getExitValue(process) != null; + } + } + private static native int sigterm(Process process); + private static native Integer getExitValue(Process process); + private static native Object getExitValueMutex(Process process); public static native int sendFd(int fd, String path); public static native void close(int fd); } diff --git a/mobile/src/main/jni/Android.mk b/mobile/src/main/jni/Android.mk index 1d448261..518dc0d2 100755 --- a/mobile/src/main/jni/Android.mk +++ b/mobile/src/main/jni/Android.mk @@ -97,7 +97,7 @@ LIBEVENT_SOURCES := \ LOCAL_MODULE := event LOCAL_SRC_FILES := $(addprefix libevent/, $(LIBEVENT_SOURCES)) -LOCAL_CFLAGS := -O2 -I$(LOCAL_PATH)/libevent \ +LOCAL_CFLAGS := -O2 -D_EVENT_HAVE_ARC4RANDOM -I$(LOCAL_PATH)/libevent \ -I$(LOCAL_PATH)/libevent/include \ include $(BUILD_STATIC_LIBRARY) @@ -239,23 +239,6 @@ LOCAL_CFLAGS := -O2 -std=gnu99 -DUSE_IPTABLES \ include $(BUILD_SHARED_EXECUTABLE) -######################################################## -## pdnsd -######################################################## - -include $(CLEAR_VARS) - -PDNSD_SOURCES := $(wildcard $(LOCAL_PATH)/pdnsd/src/*.c) - -LOCAL_MODULE := pdnsd -LOCAL_SRC_FILES := $(PDNSD_SOURCES:$(LOCAL_PATH)/%=%) -LOCAL_CFLAGS := -DANDROID -Wall -O2 -I$(LOCAL_PATH)/pdnsd \ - -I$(LOCAL_PATH)/include/pdnsd -I$(LOCAL_PATH)/libancillary -LOCAL_STATIC_LIBRARIES := libancillary -LOCAL_LDLIBS := -llog - -include $(BUILD_SHARED_EXECUTABLE) - ######################################################## ## shadowsocks-libev local ######################################################## @@ -494,6 +477,17 @@ LOCAL_SRC_FILES := $(addprefix pcre/, $(libpcre_src_files)) include $(BUILD_STATIC_LIBRARY) +######################################################## +## overture +######################################################## + +include $(CLEAR_VARS) + +LOCAL_MODULE := overture +LOCAL_SRC_FILES := overture/$(TARGET_ARCH_ABI)/liboverture.so + +include $(PREBUILT_SHARED_LIBRARY) + # Import cpufeatures $(call import-module,android/cpufeatures) diff --git a/mobile/src/main/jni/Application.mk b/mobile/src/main/jni/Application.mk index e32dd558..e38747eb 100644 --- a/mobile/src/main/jni/Application.mk +++ b/mobile/src/main/jni/Application.mk @@ -1,4 +1,4 @@ -APP_ABI := armeabi-v7a x86 +APP_ABI := armeabi-v7a arm64-v8a x86 APP_PLATFORM := android-19 APP_STL := stlport_static NDK_TOOLCHAIN_VERSION := clang diff --git a/mobile/src/main/jni/jni-helper.cpp b/mobile/src/main/jni/jni-helper.cpp index cbba6dbc..60bac6b1 100644 --- a/mobile/src/main/jni/jni-helper.cpp +++ b/mobile/src/main/jni/jni-helper.cpp @@ -22,7 +22,7 @@ #define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0) static jclass ProcessImpl; -static jfieldID ProcessImpl_pid; +static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex; static int sdk_version() { char version[PROP_VALUE_MAX + 1]; @@ -41,6 +41,24 @@ jint Java_com_github_shadowsocks_jnihelper_sigterm(JNIEnv *env, jobject thiz, jo return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0; } +jobject Java_com_github_shadowsocks_jnihelper_getExitValue(JNIEnv *env, jobject thiz, jobject process) { + if (!env->IsInstanceOf(process, ProcessImpl)) { + THROW(env, "java/lang/ClassCastException", + "Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted."); + return NULL; + } + return env->GetObjectField(process, ProcessImpl_exitValue); +} + +jobject Java_com_github_shadowsocks_jnihelper_getExitValueMutex(JNIEnv *env, jobject thiz, jobject process) { + if (!env->IsInstanceOf(process, ProcessImpl)) { + THROW(env, "java/lang/ClassCastException", + "Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted."); + return NULL; + } + return env->GetObjectField(process, ProcessImpl_exitValueMutex); +} + void Java_com_github_shadowsocks_jnihelper_close(JNIEnv *env, jobject thiz, jint fd) { close(fd); } @@ -84,11 +102,13 @@ static JNINativeMethod method_table[] = { { "sendFd", "(ILjava/lang/String;)I", (void*) Java_com_github_shadowsocks_jnihelper_sendfd }, { "sigterm", "(Ljava/lang/Process;)I", - (void*) Java_com_github_shadowsocks_jnihelper_sigterm } + (void*) Java_com_github_shadowsocks_jnihelper_sigterm }, + { "getExitValue", "(Ljava/lang/Process;)Ljava/lang/Integer;", + (void*) Java_com_github_shadowsocks_jnihelper_getExitValue }, + { "getExitValueMutex", "(Ljava/lang/Process;)Ljava/lang/Object;", + (void*) Java_com_github_shadowsocks_jnihelper_getExitValueMutex } }; - - /* * Register several native methods for one class. */ @@ -161,6 +181,14 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found"); goto bail; } + if (!(ProcessImpl_exitValue = env->GetFieldID(ProcessImpl, "exitValue", "Ljava/lang/Integer;"))) { + THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValue not found"); + goto bail; + } + if (!(ProcessImpl_exitValueMutex = env->GetFieldID(ProcessImpl, "exitValueMutex", "Ljava/lang/Object;"))) { + THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValueMutex not found"); + goto bail; + } } result = JNI_VERSION_1_6; diff --git a/mobile/src/main/jni/pdnsd b/mobile/src/main/jni/pdnsd deleted file mode 160000 index f6b0a56c..00000000 --- a/mobile/src/main/jni/pdnsd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f6b0a56c74ff33e3df4c00859bc7ccefa1354fa4 diff --git a/mobile/src/main/jni/shadowsocks-libev b/mobile/src/main/jni/shadowsocks-libev index 857bb8de..de0cebb6 160000 --- a/mobile/src/main/jni/shadowsocks-libev +++ b/mobile/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit 857bb8def744a1c158056d03194cf1ee78184985 +Subproject commit de0cebb62ef3b7854972f65834af7bb8aceb4bcc diff --git a/mobile/src/main/res/values-ja/strings.xml b/mobile/src/main/res/values-ja/strings.xml index 010d53a3..d79d4ca8 100644 --- a/mobile/src/main/res/values-ja/strings.xml +++ b/mobile/src/main/res/values-ja/strings.xml @@ -44,8 +44,8 @@ "システム起動時にバックグラウンドで本サービスを開始" "使用するにはROOT権限が必要" "ご利用のカーネルバージョンはサポートしておりません:%s < 3.7.1" -"UDP転送" -"UDPプロトコルでリモードサーバーにパケットを転送" +"DNS転送" +"DNSプロトコルでリモードサーバーにパケットを転送" "バックグラウンドでサービスを開始しました" @@ -138,4 +138,4 @@ "選択したアプリにプロキシを設定する" "サブネット/ホストネームPCREパターン" "ドメイン及びサブドメイン" - \ No newline at end of file + diff --git a/mobile/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml index 3aa80de3..79e13bcf 100644 --- a/mobile/src/main/res/values-ru/strings.xml +++ b/mobile/src/main/res/values-ru/strings.xml @@ -52,8 +52,8 @@ "Запускать Shadowsocks при включении" "Переключение требует наличия ROOT прав" "Неподдерживаемая версия ядра: %s < 3.7.1" -"Проброс UDP" -"Пробрасывать UDP пакеты на удалённый сервер" +"Проброс DNS" +"Пробрасывать DNS пакеты на удалённый сервер" "Shadowsocks запущен." @@ -121,4 +121,4 @@ "Обход LAN и Китай" "Список Китай" "Установить прокси для выбранных приложений" - \ No newline at end of file + diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 40fe7a52..8979c4fe 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -45,8 +45,8 @@ "随系统启动后台服务" "切换需要 ROOT 权限" "不支持的内核版本:%s < 3.7.1" -"UDP 转发" -"由远程服务器转发 UDP 协议的数据包" +"DNS 转发" +"由远程服务器转发 DNS 协议的数据包" "后台服务已开始运行。" @@ -140,4 +140,4 @@ "为应用程序分别设置代理" "子网/域名 PCRE 正则表达式" "域名及其子域名" - \ No newline at end of file + diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml index caca7aca..7968b2df 100644 --- a/mobile/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/src/main/res/values-zh-rTW/strings.xml @@ -44,8 +44,8 @@ "在裝置啟動時啟用 Shadowsocks" "切換需要 ROOT 權限" "不支援的核心版本:%s < 3.7.1" -"UDP 轉送" -"向遠端轉送 UDP 封包" +"DNS 轉送" +"向遠端轉送 DNS 封包" "Shadowsocks 已啟動。" @@ -139,4 +139,4 @@ "為已選擇的應用程式設定 Proxy" "子網路/主機名稱 PCRE 模式" "網域及其所有子網域" - \ No newline at end of file + diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index 17d2f7fe..23ed5dc6 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -53,8 +53,8 @@ Enable Shadowsocks on startup Toggling requires ROOT permission Unsupported kernel version: %s < 3.7.1 - UDP Forwarding - Forward UDP packets to remote + DNS Forwarding + Force to forward DNS packets to remote Shadowsocks started. diff --git a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala index 5d5ec16d..f5e6fdb1 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/BaseService.scala @@ -39,11 +39,11 @@ import com.github.shadowsocks.database.Profile import com.github.shadowsocks.plugin.{PluginConfiguration, PluginManager, PluginOptions} import com.github.shadowsocks.utils._ import okhttp3.{Dns, FormBody, OkHttpClient, Request} -import org.json.JSONObject +import org.json.{JSONArray, JSONObject} +import scala.collection.JavaConversions._ import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import scala.collection.JavaConversions._ import scala.util.Random trait BaseService extends Service { @@ -328,4 +328,44 @@ trait BaseService extends Service { IOUtils.writeString(new File(getFilesDir, file), config.toString) file } + + protected final def buildOvertureConfig(file: String): String = { + val config = new JSONObject() + .put("BindAddress", ":" + (profile.localPort + 53)) + .put("RedirectIPv6Record", true) + .put("DomainBase64Decode", true) + .put("HostsFile", "hosts") + .put("MinimumTTL", 3600) + .put("CacheSize", 4096) + def makeDns(name: String, address: String, edns: Boolean = true) = { + val dns = new JSONObject() + .put("Name", name) + .put("Address", address + ":53") + .put("Timeout", 6) + .put("EDNSClientSubnet", new JSONObject().put("Policy", "disable")) + if (edns) dns + .put("Protocol", "tcp") + .put("Socks5Address", "127.0.0.1:" + profile.localPort) + else dns.put("Protocol", "udp") + dns + } + profile.route match { + case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => config + .put("PrimaryDNS", new JSONArray(Array( + makeDns("Primary-1", "119.29.29.29", edns = false), + makeDns("Primary-2", "114.114.114.114", edns = false) + ))) + .put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", profile.remoteDns))) + .put("IPNetworkFile", "china_ip_list.txt") + .put("DomainFile", "gfwlist.txt") + case Acl.CHINALIST => config + .put("PrimaryDNS", new JSONArray().put(makeDns("Primary", "119.29.29.29"))) + .put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", profile.remoteDns))) + case _ => config + .put("PrimaryDNS", new JSONArray().put(makeDns("Primary", profile.remoteDns))) + .put("AlternativeDNS", new JSONArray().put(makeDns("Alternative", "208.67.222.222"))) + } + IOUtils.writeString(new File(getFilesDir, file), config.toString) + file + } } diff --git a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala index 7ff41694..bdd6cd7c 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/GuardedProcess.scala @@ -115,7 +115,7 @@ class GuardedProcess(cmd: String*) { private def destroyProcess() { if (Build.VERSION.SDK_INT < 24) { JniHelper.sigtermCompat(process) - process.waitFor() + JniHelper.waitForCompat(process, 500) } process.destroy() } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala index 5d79a7c6..4b533e85 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala @@ -197,15 +197,17 @@ class ShadowsocksApplication extends Application { def copyAssets() { val assetManager = getAssets - var files: Array[String] = null - try files = assetManager.list("acl") catch { - case e: IOException => - Log.e(TAG, e.getMessage) - app.track(e) + for (dir <- List("acl", "overture")) { + var files: Array[String] = null + try files = assetManager.list(dir) catch { + case e: IOException => + Log.e(TAG, e.getMessage) + app.track(e) + } + if (files != null) for (file <- files) autoClose(assetManager.open(dir + "/" + file))(in => + autoClose(new FileOutputStream(new File(getFilesDir, file)))(out => + IOUtils.copy(in, out))) } - if (files != null) for (file <- files) autoClose(assetManager.open("acl/" + file))(in => - autoClose(new FileOutputStream(new File(getFilesDir, file)))(out => - IOUtils.copy(in, out))) editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply() } diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index 4a4204b9..3ba848a5 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -49,7 +49,7 @@ class ShadowsocksNatService extends BaseService { var sslocalProcess: GuardedProcess = _ var sstunnelProcess: GuardedProcess = _ var redsocksProcess: GuardedProcess = _ - var pdnsdProcess: GuardedProcess = _ + var overtureProcess: GuardedProcess = _ var su: Shell.Interactive = _ def startShadowsocksDaemon() { @@ -83,21 +83,8 @@ class ShadowsocksNatService extends BaseService { } def startDnsDaemon() { - val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" - IOUtils.writeString(new File(getFilesDir, "pdnsd-nat.conf"), profile.route match { - case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath, - "127.0.0.1", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8", - getBlackList, reject, profile.localPort + 63, reject) - case Acl.CHINALIST => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath, - "127.0.0.1", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222", - "", reject, profile.localPort + 63, reject) - case _ => - ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "", getCacheDir.getAbsolutePath, - "127.0.0.1", profile.localPort + 53, profile.localPort + 63, reject) - }) - pdnsdProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", "pdnsd-nat.conf") + overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so", + "-c", buildOvertureConfig("overture-nat.conf")) .start() } @@ -111,11 +98,13 @@ class ShadowsocksNatService extends BaseService { /** Called when the activity is first created. */ def handleConnection() { - startDNSTunnel() startRedsocksDaemon() startShadowsocksDaemon() - if (!profile.udpdns) startDnsDaemon() + if (!profile.udpdns) + startDnsDaemon() + else + startDNSTunnel() setupIptables() @@ -143,9 +132,9 @@ class ShadowsocksNatService extends BaseService { redsocksProcess.destroy() redsocksProcess = null } - if (pdnsdProcess != null) { - pdnsdProcess.destroy() - pdnsdProcess = null + if (overtureProcess != null) { + overtureProcess.destroy() + overtureProcess = null } su.addCommand("iptables -t nat -F OUTPUT") diff --git a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala index 220d8333..1efe035f 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala @@ -45,8 +45,7 @@ class ShadowsocksVpnService extends VpnService with BaseService { private var notification: ShadowsocksNotification = _ var sslocalProcess: GuardedProcess = _ - var sstunnelProcess: GuardedProcess = _ - var pdnsdProcess: GuardedProcess = _ + var overtureProcess: GuardedProcess = _ var tun2socksProcess: GuardedProcess = _ override def onBind(intent: Intent): IBinder = { @@ -94,17 +93,13 @@ class ShadowsocksVpnService extends VpnService with BaseService { sslocalProcess.destroy() sslocalProcess = null } - if (sstunnelProcess != null) { - sstunnelProcess.destroy() - sstunnelProcess = null - } if (tun2socksProcess != null) { tun2socksProcess.destroy() tun2socksProcess = null } - if (pdnsdProcess != null) { - pdnsdProcess.destroy() - pdnsdProcess = null + if (overtureProcess != null) { + overtureProcess.destroy() + overtureProcess = null } } @@ -149,15 +144,14 @@ class ShadowsocksVpnService extends VpnService with BaseService { /** Called when the activity is first created. */ def handleConnection() { - val fd = startVpn() - if (!sendFd(fd)) throw new Exception("sendFd failed") - startShadowsocksDaemon() if (!profile.udpdns) { startDnsDaemon() - startDnsTunnel() } + + val fd = startVpn() + if (!sendFd(fd)) throw new Exception("sendFd failed") } override protected def buildPluginCommandLine(): ArrayBuffer[String] = super.buildPluginCommandLine() += "-V" @@ -165,13 +159,12 @@ class ShadowsocksVpnService extends VpnService with BaseService { def startShadowsocksDaemon() { val cmd = ArrayBuffer[String](getApplicationInfo.nativeLibraryDir + "/libss-local.so", "-V", + "-u", "-b", "127.0.0.1", "-l", profile.localPort.toString, "-t", "600", "-c", buildShadowsocksConfig("ss-local-vpn.conf")) - if (profile.udpdns) cmd += "-u" - if (profile.route != Acl.ALL) { cmd += "--acl" cmd += Acl.getFile(profile.route).getAbsolutePath @@ -182,33 +175,10 @@ class ShadowsocksVpnService extends VpnService with BaseService { sslocalProcess = new GuardedProcess(cmd: _*).start() } - def startDnsTunnel(): Unit = - sstunnelProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/libss-tunnel.so", - "-V", - "-t", "10", - "-b", "127.0.0.1", - "-l", (profile.localPort + 63).toString, - "-L" , profile.remoteDns.trim + ":53", - "-c", buildShadowsocksConfig("ss-tunnel-vpn.conf")).start() - def startDnsDaemon() { - val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0" - IOUtils.writeString(new File(getFilesDir, "pdnsd-vpn.conf"), profile.route match { - case Acl.BYPASS_CHN | Acl.BYPASS_LAN_CHN | Acl.GFWLIST | Acl.CUSTOM_RULES => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath, - "0.0.0.0", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8", - getBlackList, reject, profile.localPort + 63, reject) - case Acl.CHINALIST => - ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath, - "0.0.0.0", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222", - "", reject, profile.localPort + 63, reject) - case _ => - ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "protect = \"protect_path\";", getCacheDir.getAbsolutePath, - "0.0.0.0", profile.localPort + 53, profile.localPort + 63, reject) - }) - val cmd = Array(getApplicationInfo.nativeLibraryDir + "/libpdnsd.so", "-c", "pdnsd-vpn.conf") - - pdnsdProcess = new GuardedProcess(cmd: _*).start() + overtureProcess = new GuardedProcess(getApplicationInfo.nativeLibraryDir + "/liboverture.so", + "-c", buildOvertureConfig("overture-vpn.conf"), "-V") + .start() } def startVpn(): Int = { @@ -271,9 +241,9 @@ class ShadowsocksVpnService extends VpnService with BaseService { if (profile.ipv6) cmd += ("--netif-ip6addr", PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2")) - if (profile.udpdns) - cmd += "--enable-udprelay" - else + cmd += "--enable-udprelay" + + if (!profile.udpdns) cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"), profile.localPort + 53)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala index 53581e85..fa347065 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/database/Profile.scala @@ -98,7 +98,8 @@ class Profile { .scheme("ss") .encodedAuthority("%s@%s:%d".formatLocal(Locale.ENGLISH, Base64.encodeToString("%s:%s".formatLocal(Locale.ENGLISH, method, password).getBytes, - Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), host, remotePort)) + Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE), + if (host.contains(':')) '[' + host + ']' else host, remotePort)) val configuration = new PluginConfiguration(plugin) if (configuration.selected.nonEmpty) builder.appendQueryParameter(Key.plugin, configuration.selectedOptions.toString(false)) diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala index d1d9fe21..80bd0f4a 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Constants.scala @@ -45,86 +45,6 @@ object ConfigUtils { " port = %d;\n" + " type = socks5;\n" + "}\n" - - val PDNSD_LOCAL = - """ - |global { - | perm_cache = 2048; - | %s - | cache_dir = "%s"; - | server_ip = %s; - | server_port = %d; - | query_method = tcp_only; - | min_ttl = 15m; - | max_ttl = 1w; - | timeout = 10; - | daemon = off; - |} - | - |server { - | label = "local"; - | ip = 127.0.0.1; - | port = %d; - | reject = %s; - | reject_policy = negate; - | reject_recursively = on; - |} - | - |rr { - | name=localhost; - | reverse=on; - | a=127.0.0.1; - | owner=localhost; - | soa=localhost,root.localhost,42,86400,900,86400,86400; - |} - """.stripMargin - - val PDNSD_DIRECT = - """ - |global { - | perm_cache = 2048; - | %s - | cache_dir = "%s"; - | server_ip = %s; - | server_port = %d; - | query_method = udp_only; - | min_ttl = 15m; - | max_ttl = 1w; - | timeout = 10; - | daemon = off; - | par_queries = 4; - |} - | - |server { - | label = "remote-servers"; - | ip = %s; - | timeout = 3; - | query_method = udp_only; - | %s - | policy = included; - | reject = %s; - | reject_policy = fail; - | reject_recursively = on; - |} - | - |server { - | label = "local-server"; - | ip = 127.0.0.1; - | query_method = tcp_only; - | port = %d; - | reject = %s; - | reject_policy = negate; - | reject_recursively = on; - |} - | - |rr { - | name=localhost; - | reverse=on; - | a=127.0.0.1; - | owner=localhost; - | soa=localhost,root.localhost,42,86400,900,86400,86400; - |} - """.stripMargin } object Key { diff --git a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala index 64d1ed88..3742af60 100644 --- a/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala +++ b/mobile/src/main/scala/com/github/shadowsocks/utils/Parser.scala @@ -20,13 +20,15 @@ package com.github.shadowsocks.utils +import java.net.URI + import android.net.Uri import android.util.{Base64, Log} import com.github.shadowsocks.database.Profile object Parser { val TAG = "ShadowParser" - private val pattern = "(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]".r + private val pattern = "(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\\[\\]]*[-a-zA-Z0-9+&@#/%=~_|\\[\\]]".r private val userInfoPattern = "^(.+?):(.*)$".r private val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".r @@ -54,8 +56,12 @@ object Parser { val profile = new Profile profile.method = method profile.password = password - profile.host = uri.getHost - profile.remotePort = uri.getPort + // bug in Android: https://code.google.com/p/android/issues/detail?id=192855 + val javaURI = new URI(m.matched) + profile.host = javaURI.getHost + if (profile.host.headOption.contains('[') && profile.host.lastOption.contains(']')) + profile.host = profile.host.substring(1, profile.host.length - 1) + profile.remotePort = javaURI.getPort profile.plugin = uri.getQueryParameter(Key.plugin) profile.name = uri.getFragment profile diff --git a/mobile/src/overture b/mobile/src/overture new file mode 160000 index 00000000..862ce347 --- /dev/null +++ b/mobile/src/overture @@ -0,0 +1 @@ +Subproject commit 862ce347e19483fdd6f0a5c84f173c2efdc7d920 diff --git a/plugin/CHANGES.md b/plugin/CHANGES.md index 78ee27d1..9e277fee 100644 --- a/plugin/CHANGES.md +++ b/plugin/CHANGES.md @@ -1,3 +1,5 @@ +* 0.0.3: + * Update support library version to 25.2.0. * 0.0.2: * Add `getOrDefault` to `PluginOptions`; * Update support library version to 25.1.1. diff --git a/plugin/build.sbt b/plugin/build.sbt index ca3bc9b4..93e5f588 100644 --- a/plugin/build.sbt +++ b/plugin/build.sbt @@ -2,7 +2,7 @@ enablePlugins(AndroidLib) android.useSupportVectors name := "plugin" -version := "0.0.2" +version := "0.0.3" pomExtra in Global := { https://github.com/shadowsocks/shadowsocks-android diff --git a/project/plugins.sbt b/project/plugins.sbt index 27caeb6d..f48545c8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.5") +addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.6") addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")