Commit 1010edc5 authored by Mygod's avatar Mygod

Speed up Acl.fromReader

It should run 9x faster than before now.

Past mistakes:

* InetAddress.parseNumericAddress throws TWO exceptions underthehood if the string passed is invalid. This caused a huge overhead;
* CharSequence.split in Kotlin is EXTREMELY SLOW.
parent bc22b0c4
...@@ -39,15 +39,19 @@ ...@@ -39,15 +39,19 @@
package com.github.shadowsocks; package com.github.shadowsocks;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.system.ErrnoException; import android.system.ErrnoException;
import java.net.InetAddress;
public class JniHelper { public class JniHelper {
static { static {
System.loadLibrary("jni-helper"); System.loadLibrary("jni-helper");
} }
@Deprecated // Use Process.destroy() since API 24 @Deprecated // Use Process.destroy() since API 24
public static void sigtermCompat(Process process) throws Exception { public static void sigtermCompat(@NonNull Process process) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
int errno = sigterm(process); int errno = sigterm(process);
if (errno != 0) throw Build.VERSION.SDK_INT >= 21 if (errno != 0) throw Build.VERSION.SDK_INT >= 21
...@@ -55,7 +59,7 @@ public class JniHelper { ...@@ -55,7 +59,7 @@ public class JniHelper {
} }
@Deprecated // only implemented for before API 24 @Deprecated // only implemented for before API 24
public static boolean waitForCompat(Process process, long millis) throws Exception { public static boolean waitForCompat(@NonNull Process process, long millis) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
final Object mutex = getExitValueMutex(process); final Object mutex = getExitValueMutex(process);
synchronized (mutex) { synchronized (mutex) {
...@@ -68,6 +72,8 @@ public class JniHelper { ...@@ -68,6 +72,8 @@ public class JniHelper {
private static native int sigterm(Process process); private static native int sigterm(Process process);
private static native Integer getExitValue(Process process); private static native Integer getExitValue(Process process);
private static native Object getExitValueMutex(Process process); private static native Object getExitValueMutex(Process process);
public static native int sendFd(int fd, String path); public static native int sendFd(int fd, @NonNull String path);
public static native void close(int fd); public static native void close(int fd);
@Nullable
public static native InetAddress parseNumericAddress(@NonNull String str);
} }
...@@ -318,6 +318,8 @@ include $(CLEAR_VARS) ...@@ -318,6 +318,8 @@ include $(CLEAR_VARS)
LOCAL_MODULE:= jni-helper LOCAL_MODULE:= jni-helper
LOCAL_CFLAGS := -std=c++11
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary
LOCAL_SRC_FILES:= jni-helper.cpp LOCAL_SRC_FILES:= jni-helper.cpp
......
APP_ABI := armeabi-v7a arm64-v8a x86
APP_PLATFORM := android-19
APP_STL := c++_static
NDK_TOOLCHAIN_VERSION := clang
...@@ -4,32 +4,32 @@ ...@@ -4,32 +4,32 @@
#include <android/log.h> #include <android/log.h>
#include <sys/system_properties.h> #include <sys/system_properties.h>
#include <stdio.h> #include <algorithm>
#include <stdlib.h> #include <cerrno>
#include <string.h> #include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <ancillary.h> #include <ancillary.h>
using namespace std;
#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0) #define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0) #define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0) #define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
#define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0) #define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0)
static jclass ProcessImpl; static int sdk_version;
static jclass ProcessImpl, Inet4Address, Inet6Address;
static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex; static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex;
static jmethodID Inet4Address__init_, Inet6Address__init_;
static int sdk_version() {
char version[PROP_VALUE_MAX + 1];
__system_property_get("ro.build.version.sdk", version);
return atoi(version);
}
extern "C" { extern "C" {
JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigkill(JNIEnv *env, jobject thiz, jint pid) { JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigkill(JNIEnv *env, jobject thiz, jint pid) {
...@@ -103,6 +103,26 @@ JNIEXPORT jint JNICALL ...@@ -103,6 +103,26 @@ JNIEXPORT jint JNICALL
env->ReleaseStringUTFChars(path, sock_str); env->ReleaseStringUTFChars(path, sock_str);
return 0; return 0;
} }
JNIEXPORT jobject JNICALL
Java_com_github_shadowsocks_JniHelper_parseNumericAddress(JNIEnv *env, jobject thiz, jstring str) {
const char *src = env->GetStringUTFChars(str, 0);
jbyte dst[max(sizeof(in_addr), sizeof(in6_addr))];
jobject result = nullptr;
if (inet_pton(AF_INET, src, dst) == 1) {
jbyteArray arr = env->NewByteArray(sizeof(in_addr));
env->SetByteArrayRegion(arr, 0, sizeof(in_addr), dst);
result = sdk_version < 24 ? env->NewObject(Inet4Address, Inet4Address__init_, arr, str)
: env->NewObject(Inet4Address, Inet4Address__init_, str, arr);
} else if (inet_pton(AF_INET6, src, dst) == 1) {
jbyteArray arr = env->NewByteArray(sizeof(in6_addr));
env->SetByteArrayRegion(arr, 0, sizeof(in6_addr), dst);
result = sdk_version < 24 ? env->NewObject(Inet6Address, Inet6Address__init_, arr, str, 0)
: env->NewObject(Inet6Address, Inet6Address__init_, str, arr, 0);
}
env->ReleaseStringUTFChars(str, src);
return result;
}
} }
/* /*
...@@ -126,24 +146,39 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { ...@@ -126,24 +146,39 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
} }
env = uenv.env; env = uenv.env;
if (sdk_version() < 24) { char version[PROP_VALUE_MAX + 1];
if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) { __system_property_get("ro.build.version.sdk", version);
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found"); sdk_version = atoi(version);
goto bail;
} #define FIND_CLASS(out, name) \
ProcessImpl = (jclass) env->NewGlobalRef((jobject) ProcessImpl); if (!(out = env->FindClass(name))) { \
if (!(ProcessImpl_pid = env->GetFieldID(ProcessImpl, "pid", "I"))) { THROW(env, "java/lang/RuntimeException", name " not found"); \
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found"); goto bail; \
goto bail; } \
} out = reinterpret_cast<jclass>(env->NewGlobalRef(reinterpret_cast<jobject>(out)))
if (!(ProcessImpl_exitValue = env->GetFieldID(ProcessImpl, "exitValue", "Ljava/lang/Integer;"))) { #define GET_FIELD(out, clazz, name, sig) \
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValue not found"); if (!(out = env->GetFieldID(clazz, name, sig))) { \
goto bail; THROW(env, "java/lang/RuntimeException", "Field " #clazz "." name " with type " sig " not found"); \
} goto bail; \
if (!(ProcessImpl_exitValueMutex = env->GetFieldID(ProcessImpl, "exitValueMutex", "Ljava/lang/Object;"))) { }
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValueMutex not found"); #define GET_METHOD(out, clazz, name, sig) \
goto bail; if (!(out = env->GetMethodID(clazz, name, sig))) { \
} THROW(env, "java/lang/RuntimeException", "Method " #clazz "." name sig " not found"); \
goto bail; \
}
FIND_CLASS(Inet4Address, "java/net/Inet4Address");
FIND_CLASS(Inet6Address, "java/net/Inet6Address");
if (sdk_version < 24) {
FIND_CLASS(ProcessImpl, "java/lang/ProcessManager$ProcessImpl");
GET_FIELD(ProcessImpl_pid, ProcessImpl, "pid", "I")
GET_FIELD(ProcessImpl_exitValue, ProcessImpl, "exitValue", "Ljava/lang/Integer;")
GET_FIELD(ProcessImpl_exitValueMutex, ProcessImpl, "exitValueMutex", "Ljava/lang/Object;")
GET_METHOD(Inet4Address__init_, Inet4Address, "<init>", "([BLjava/lang/String;)V")
GET_METHOD(Inet6Address__init_, Inet6Address, "<init>", "([BLjava/lang/String;I)V")
} else {
GET_METHOD(Inet4Address__init_, Inet4Address, "<init>", "(Ljava/lang/String;[B)V")
GET_METHOD(Inet6Address__init_, Inet6Address, "<init>", "(Ljava/lang/String;[BI)V")
} }
result = JNI_VERSION_1_6; result = JNI_VERSION_1_6;
......
...@@ -116,7 +116,8 @@ class Acl { ...@@ -116,7 +116,8 @@ class Acl {
var subnets: SortedList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets var subnets: SortedList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets
reader.useLines { reader.useLines {
for (line in it) { for (line in it) {
val blocks = line.split('#', limit = 2) @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
val blocks = (line as java.lang.String).split("#", 2)
val url = networkAclParser.matchEntire(blocks.getOrElse(1, {""}))?.groupValues?.getOrNull(1) val url = networkAclParser.matchEntire(blocks.getOrElse(1, {""}))?.groupValues?.getOrNull(1)
if (url != null) urls.add(URL(url)) if (url != null) urls.add(URL(url))
val input = blocks[0].trim() val input = blocks[0].trim()
...@@ -135,10 +136,9 @@ class Acl { ...@@ -135,10 +136,9 @@ class Acl {
} }
"[reject_all]", "[bypass_all]" -> bypass = true "[reject_all]", "[bypass_all]" -> bypass = true
"[accept_all]", "[proxy_all]" -> bypass = false "[accept_all]", "[proxy_all]" -> bypass = false
else -> if (subnets != null && input.isNotEmpty()) try { else -> if (subnets != null && input.isNotEmpty()) {
subnets!!.add(Subnet.fromString(input)) val subnet = Subnet.fromString(input)
} catch (_: IllegalArgumentException) { if (subnet == null) hostnames!!.add(input) else subnets!!.add(subnet)
hostnames!!.add(input)
} }
} }
} }
......
...@@ -333,8 +333,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -333,8 +333,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
if (savedInstanceState != null) { if (savedInstanceState != null) {
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_SUBNETS)?.map(Subnet.Companion::fromString) selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_SUBNETS)
?: listOf()) ?.mapNotNull(Subnet.Companion::fromString) ?: listOf())
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_HOSTNAMES) selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_HOSTNAMES)
?: arrayOf()) ?: arrayOf())
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_URLS)?.map { URL(it) } selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_URLS)?.map { URL(it) }
......
...@@ -159,10 +159,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -159,10 +159,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0) Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0)
else -> { else -> {
resources.getStringArray(R.array.bypass_private_route).forEach { resources.getStringArray(R.array.bypass_private_route).forEach {
val subnet = Subnet.fromString(it) val subnet = Subnet.fromString(it)!!
builder.addRoute(subnet.address.hostAddress, subnet.prefixSize) builder.addRoute(subnet.address.hostAddress, subnet.prefixSize)
} }
profile.remoteDns.split(",").map { it.trim().parseNumericAddress() } profile.remoteDns.split(",").mapNotNull { it.trim().parseNumericAddress() }
.forEach { builder.addRoute(it, it.address.size shl 3) } .forEach { builder.addRoute(it, it.address.size shl 3) }
} }
} }
......
...@@ -25,15 +25,15 @@ import java.util.* ...@@ -25,15 +25,15 @@ import java.util.*
class Subnet(val address: InetAddress, val prefixSize: Int) : Comparable<Subnet> { class Subnet(val address: InetAddress, val prefixSize: Int) : Comparable<Subnet> {
companion object { companion object {
@Throws(IllegalArgumentException::class) fun fromString(value: String): Subnet? {
fun fromString(value: String): Subnet { @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
val parts = value.split('/') val parts = (value as java.lang.String).split("/", 2)
val addr = parts[0].parseNumericAddress() val addr = parts[0].parseNumericAddress() ?: return null
return when (parts.size) { return if (parts.size == 2) try {
1 -> Subnet(addr, addr.address.size shl 3) Subnet(addr, parts[1].toInt())
2 -> Subnet(addr, parts[1].toInt()) } catch (_: NumberFormatException) {
else -> throw IllegalArgumentException() null
} } else Subnet(addr, addr.address.size shl 3)
} }
} }
......
...@@ -11,31 +11,20 @@ import android.support.v4.app.FragmentManager ...@@ -11,31 +11,20 @@ import android.support.v4.app.FragmentManager
import android.support.v7.util.SortedList import android.support.v7.util.SortedList
import android.util.TypedValue import android.util.TypedValue
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.net.Inet4Address
import java.net.InetAddress import java.net.InetAddress
import java.net.URLConnection import java.net.URLConnection
private val isNumericMethod by lazy { InetAddress::class.java.getMethod("isNumeric", String::class.java) }
private val parseNumericAddressMethod by lazy {
try {
InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
} catch (_: NoSuchMethodException) {
null
}
}
private val fieldChildFragmentManager by lazy { private val fieldChildFragmentManager by lazy {
val field = Fragment::class.java.getDeclaredField("mChildFragmentManager") val field = Fragment::class.java.getDeclaredField("mChildFragmentManager")
field.isAccessible = true field.isAccessible = true
field field
} }
fun String.isNumericAddress(): Boolean = isNumericMethod.invoke(null, this) as Boolean fun String.isNumericAddress() = parseNumericAddress() != null
fun String.parseNumericAddress(): InetAddress = fun String.parseNumericAddress() = JniHelper.parseNumericAddress(this)
if (parseNumericAddressMethod == null) InetAddress.getByName(this) else try {
parseNumericAddressMethod!!.invoke(null, this) as InetAddress
} catch (exc: InvocationTargetException) {
throw exc.cause ?: exc
}
fun parsePort(str: String?, default: Int, min: Int = 1025): Int { fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
val x = str?.toIntOrNull() ?: default val x = str?.toIntOrNull() ?: default
......
...@@ -16,16 +16,13 @@ class SubnetTest { ...@@ -16,16 +16,13 @@ class SubnetTest {
Assert.assertNotEquals(Subnet.fromString("1.2.3.4/12"), Subnet.fromString("1.2.3.5/12")) Assert.assertNotEquals(Subnet.fromString("1.2.3.4/12"), Subnet.fromString("1.2.3.5/12"))
} }
@Test(expected = IllegalArgumentException::class) @Test
fun invalidParsing1() { fun invalidParsings() {
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129") Assert.assertEquals(null, Subnet.fromString("1.2.3.456"))
} Assert.assertEquals(null, Subnet.fromString("ffff::f0000"))
@Test(expected = IllegalArgumentException::class) Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129"))
fun invalidParsing2() { Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129"))
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/-99") Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/-99"))
} Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/1/0"))
@Test(expected = IllegalArgumentException::class)
fun invalidParsing3() {
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/1/0")
} }
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment