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.
This commit is contained in:
@@ -39,15 +39,19 @@
|
||||
package com.github.shadowsocks;
|
||||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.system.ErrnoException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
public class JniHelper {
|
||||
static {
|
||||
System.loadLibrary("jni-helper");
|
||||
}
|
||||
|
||||
@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!");
|
||||
int errno = sigterm(process);
|
||||
if (errno != 0) throw Build.VERSION.SDK_INT >= 21
|
||||
@@ -55,7 +59,7 @@ public class JniHelper {
|
||||
}
|
||||
|
||||
@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!");
|
||||
final Object mutex = getExitValueMutex(process);
|
||||
synchronized (mutex) {
|
||||
@@ -68,6 +72,8 @@ public class JniHelper {
|
||||
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 int sendFd(int fd, @NonNull String path);
|
||||
public static native void close(int fd);
|
||||
@Nullable
|
||||
public static native InetAddress parseNumericAddress(@NonNull String str);
|
||||
}
|
||||
|
||||
@@ -318,6 +318,8 @@ include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE:= jni-helper
|
||||
|
||||
LOCAL_CFLAGS := -std=c++11
|
||||
|
||||
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary
|
||||
|
||||
LOCAL_SRC_FILES:= jni-helper.cpp
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
APP_ABI := armeabi-v7a arm64-v8a x86
|
||||
APP_PLATFORM := android-19
|
||||
APP_STL := c++_static
|
||||
NDK_TOOLCHAIN_VERSION := clang
|
||||
@@ -4,32 +4,32 @@
|
||||
#include <android/log.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <ancillary.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#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 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)
|
||||
|
||||
static jclass ProcessImpl;
|
||||
static int sdk_version;
|
||||
static jclass ProcessImpl, Inet4Address, Inet6Address;
|
||||
static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex;
|
||||
|
||||
static int sdk_version() {
|
||||
char version[PROP_VALUE_MAX + 1];
|
||||
__system_property_get("ro.build.version.sdk", version);
|
||||
return atoi(version);
|
||||
}
|
||||
static jmethodID Inet4Address__init_, Inet6Address__init_;
|
||||
|
||||
extern "C" {
|
||||
JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigkill(JNIEnv *env, jobject thiz, jint pid) {
|
||||
@@ -103,6 +103,26 @@ JNIEXPORT jint JNICALL
|
||||
env->ReleaseStringUTFChars(path, sock_str);
|
||||
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) {
|
||||
}
|
||||
env = uenv.env;
|
||||
|
||||
if (sdk_version() < 24) {
|
||||
if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) {
|
||||
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found");
|
||||
goto bail;
|
||||
}
|
||||
ProcessImpl = (jclass) env->NewGlobalRef((jobject) ProcessImpl);
|
||||
if (!(ProcessImpl_pid = env->GetFieldID(ProcessImpl, "pid", "I"))) {
|
||||
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found");
|
||||
goto bail;
|
||||
}
|
||||
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;
|
||||
}
|
||||
char version[PROP_VALUE_MAX + 1];
|
||||
__system_property_get("ro.build.version.sdk", version);
|
||||
sdk_version = atoi(version);
|
||||
|
||||
#define FIND_CLASS(out, name) \
|
||||
if (!(out = env->FindClass(name))) { \
|
||||
THROW(env, "java/lang/RuntimeException", name " not found"); \
|
||||
goto bail; \
|
||||
} \
|
||||
out = reinterpret_cast<jclass>(env->NewGlobalRef(reinterpret_cast<jobject>(out)))
|
||||
#define GET_FIELD(out, clazz, name, sig) \
|
||||
if (!(out = env->GetFieldID(clazz, name, sig))) { \
|
||||
THROW(env, "java/lang/RuntimeException", "Field " #clazz "." name " with type " sig " not found"); \
|
||||
goto bail; \
|
||||
}
|
||||
#define GET_METHOD(out, clazz, name, sig) \
|
||||
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;
|
||||
|
||||
@@ -116,7 +116,8 @@ class Acl {
|
||||
var subnets: SortedList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets
|
||||
reader.useLines {
|
||||
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)
|
||||
if (url != null) urls.add(URL(url))
|
||||
val input = blocks[0].trim()
|
||||
@@ -135,10 +136,9 @@ class Acl {
|
||||
}
|
||||
"[reject_all]", "[bypass_all]" -> bypass = true
|
||||
"[accept_all]", "[proxy_all]" -> bypass = false
|
||||
else -> if (subnets != null && input.isNotEmpty()) try {
|
||||
subnets!!.add(Subnet.fromString(input))
|
||||
} catch (_: IllegalArgumentException) {
|
||||
hostnames!!.add(input)
|
||||
else -> if (subnets != null && input.isNotEmpty()) {
|
||||
val subnet = Subnet.fromString(input)
|
||||
if (subnet == null) hostnames!!.add(input) else subnets!!.add(subnet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,8 +333,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (savedInstanceState != null) {
|
||||
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_SUBNETS)?.map(Subnet.Companion::fromString)
|
||||
?: listOf())
|
||||
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_SUBNETS)
|
||||
?.mapNotNull(Subnet.Companion::fromString) ?: listOf())
|
||||
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_HOSTNAMES)
|
||||
?: arrayOf())
|
||||
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_URLS)?.map { URL(it) }
|
||||
|
||||
@@ -159,10 +159,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
|
||||
Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0)
|
||||
else -> {
|
||||
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)
|
||||
}
|
||||
profile.remoteDns.split(",").map { it.trim().parseNumericAddress() }
|
||||
profile.remoteDns.split(",").mapNotNull { it.trim().parseNumericAddress() }
|
||||
.forEach { builder.addRoute(it, it.address.size shl 3) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,15 @@ import java.util.*
|
||||
|
||||
class Subnet(val address: InetAddress, val prefixSize: Int) : Comparable<Subnet> {
|
||||
companion object {
|
||||
@Throws(IllegalArgumentException::class)
|
||||
fun fromString(value: String): Subnet {
|
||||
val parts = value.split('/')
|
||||
val addr = parts[0].parseNumericAddress()
|
||||
return when (parts.size) {
|
||||
1 -> Subnet(addr, addr.address.size shl 3)
|
||||
2 -> Subnet(addr, parts[1].toInt())
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
fun fromString(value: String): Subnet? {
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
val parts = (value as java.lang.String).split("/", 2)
|
||||
val addr = parts[0].parseNumericAddress() ?: return null
|
||||
return if (parts.size == 2) try {
|
||||
Subnet(addr, parts[1].toInt())
|
||||
} catch (_: NumberFormatException) {
|
||||
null
|
||||
} else Subnet(addr, addr.address.size shl 3)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,31 +11,20 @@ import android.support.v4.app.FragmentManager
|
||||
import android.support.v7.util.SortedList
|
||||
import android.util.TypedValue
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.JniHelper
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
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 {
|
||||
val field = Fragment::class.java.getDeclaredField("mChildFragmentManager")
|
||||
field.isAccessible = true
|
||||
field
|
||||
}
|
||||
|
||||
fun String.isNumericAddress(): Boolean = isNumericMethod.invoke(null, this) as Boolean
|
||||
fun String.parseNumericAddress(): InetAddress =
|
||||
if (parseNumericAddressMethod == null) InetAddress.getByName(this) else try {
|
||||
parseNumericAddressMethod!!.invoke(null, this) as InetAddress
|
||||
} catch (exc: InvocationTargetException) {
|
||||
throw exc.cause ?: exc
|
||||
}
|
||||
fun String.isNumericAddress() = parseNumericAddress() != null
|
||||
fun String.parseNumericAddress() = JniHelper.parseNumericAddress(this)
|
||||
|
||||
fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
|
||||
val x = str?.toIntOrNull() ?: default
|
||||
|
||||
@@ -16,16 +16,13 @@ class SubnetTest {
|
||||
Assert.assertNotEquals(Subnet.fromString("1.2.3.4/12"), Subnet.fromString("1.2.3.5/12"))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun invalidParsing1() {
|
||||
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129")
|
||||
}
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun invalidParsing2() {
|
||||
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/-99")
|
||||
}
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun invalidParsing3() {
|
||||
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/1/0")
|
||||
@Test
|
||||
fun invalidParsings() {
|
||||
Assert.assertEquals(null, Subnet.fromString("1.2.3.456"))
|
||||
Assert.assertEquals(null, Subnet.fromString("ffff::f0000"))
|
||||
Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129"))
|
||||
Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129"))
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user