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 @@
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
......
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 <algorithm>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <signal.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;
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; \
}
if (!(ProcessImpl_exitValueMutex = env->GetFieldID(ProcessImpl, "exitValueMutex", "Ljava/lang/Object;"))) {
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValueMutex 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"))
}
}
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