Unverified Commit a5fc2369 authored by Mygod's avatar Mygod Committed by GitHub

Merge pull request #4 from Mygod/re2

Use re2
parents 89988ec3 874c8400
...@@ -30,3 +30,6 @@ ...@@ -30,3 +30,6 @@
[submodule "core/src/main/jni/libev"] [submodule "core/src/main/jni/libev"]
path = core/src/main/jni/libev path = core/src/main/jni/libev
url = https://git.lighttpd.net/libev.git url = https://git.lighttpd.net/libev.git
[submodule "core/src/main/jni/re2"]
path = core/src/main/jni/re2
url = https://github.com/google/re2.git
...@@ -20,10 +20,7 @@ ...@@ -20,10 +20,7 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.Application import android.app.*
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
...@@ -62,6 +59,7 @@ object Core { ...@@ -62,6 +59,7 @@ object Core {
lateinit var app: Application lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent lateinit var configureIntent: (Context) -> PendingIntent
val activity by lazy { app.getSystemService<ActivityManager>()!! }
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! } val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) } val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) } val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
......
...@@ -21,16 +21,39 @@ ...@@ -21,16 +21,39 @@
package com.github.shadowsocks.acl package com.github.shadowsocks.acl
import android.util.Log import android.util.Log
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.Subnet import com.github.shadowsocks.net.Subnet
import java.net.Inet4Address import java.net.Inet4Address
import java.net.Inet6Address import java.net.Inet6Address
import kotlin.system.measureNanoTime import kotlin.system.measureNanoTime
class AclMatcher { class AclMatcher : AutoCloseable {
companion object {
init {
System.loadLibrary("jni-helper")
}
@JvmStatic private external fun init(): Long
@JvmStatic private external fun close(handle: Long)
@JvmStatic private external fun addBypassDomain(handle: Long, regex: String): Boolean
@JvmStatic private external fun addProxyDomain(handle: Long, regex: String): Boolean
@JvmStatic private external fun build(handle: Long, memoryLimit: Long): String?
@JvmStatic private external fun matchHost(handle: Long, host: String): Int
}
class Re2Exception(message: String) : IllegalArgumentException(message)
private var handle = 0L
override fun close() {
if (handle != 0L) {
close(handle)
handle = 0L
}
}
private var subnetsIpv4 = emptyList<Subnet.Immutable>() private var subnetsIpv4 = emptyList<Subnet.Immutable>()
private var subnetsIpv6 = emptyList<Subnet.Immutable>() private var subnetsIpv6 = emptyList<Subnet.Immutable>()
private val bypassDomains = mutableListOf<Regex>()
private val proxyDomains = mutableListOf<Regex>()
private var bypass = false private var bypass = false
suspend fun init(id: String) { suspend fun init(id: String) {
...@@ -44,12 +67,15 @@ class AclMatcher { ...@@ -44,12 +67,15 @@ class AclMatcher {
current = next current = next
} }
}.toList() }.toList()
check(handle == 0L)
handle = init()
val time = measureNanoTime { val time = measureNanoTime {
val (bypass, subnets) = Acl.parse(Acl.getFile(id).bufferedReader(), { val (bypass, subnets) = Acl.parse(Acl.getFile(id).bufferedReader(), {
// bypassDomains.add(it.toRegex()) check(addBypassDomain(handle, it))
}, { }, {
if (it.startsWith("(?:^|\\.)googleapis")) proxyDomains.add(it.toRegex()) check(addProxyDomain(handle, it))
}) })
build(handle, if (Core.activity.isLowRamDevice) 8 shl 20 else 64 shl 20)?.also { throw Re2Exception(it) }
subnetsIpv4 = subnets.asSequence().filter { it.address is Inet4Address }.dedup() subnetsIpv4 = subnets.asSequence().filter { it.address is Inet4Address }.dedup()
subnetsIpv6 = subnets.asSequence().filter { it.address is Inet6Address }.dedup() subnetsIpv6 = subnets.asSequence().filter { it.address is Inet6Address }.dedup()
this.bypass = bypass this.bypass = bypass
...@@ -64,9 +90,10 @@ class AclMatcher { ...@@ -64,9 +90,10 @@ class AclMatcher {
fun shouldBypassIpv4(ip: ByteArray) = bypass xor quickMatches(subnetsIpv4, ip) fun shouldBypassIpv4(ip: ByteArray) = bypass xor quickMatches(subnetsIpv4, ip)
fun shouldBypassIpv6(ip: ByteArray) = bypass xor quickMatches(subnetsIpv6, ip) fun shouldBypassIpv6(ip: ByteArray) = bypass xor quickMatches(subnetsIpv6, ip)
fun shouldBypass(host: String): Boolean? { fun shouldBypass(host: String) = when (val e = matchHost(handle, host)) {
if (bypassDomains.any { it.matches(host) }) return true 0 -> null
if (proxyDomains.any { it.matches(host) }) return false 1 -> true
return null 2 -> false
else -> error("matchHost -> $e")
} }
} }
...@@ -22,7 +22,6 @@ package com.github.shadowsocks.bg ...@@ -22,7 +22,6 @@ package com.github.shadowsocks.bg
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.ActivityManager
import android.net.DnsResolver import android.net.DnsResolver
import android.net.Network import android.net.Network
import android.os.Build import android.os.Build
...@@ -30,9 +29,7 @@ import android.os.CancellationSignal ...@@ -30,9 +29,7 @@ import android.os.CancellationSignal
import android.system.ErrnoException import android.system.ErrnoException
import android.system.Os import android.system.Os
import android.system.OsConstants import android.system.OsConstants
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.utils.int import com.github.shadowsocks.utils.int
import com.github.shadowsocks.utils.parseNumericAddress import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog import com.github.shadowsocks.utils.printLog
...@@ -119,7 +116,7 @@ sealed class DnsResolverCompat { ...@@ -119,7 +116,7 @@ sealed class DnsResolverCompat {
* See also: https://issuetracker.google.com/issues/133874590 * See also: https://issuetracker.google.com/issues/133874590
*/ */
private val unboundedIO by lazy { private val unboundedIO by lazy {
if (app.getSystemService<ActivityManager>()!!.isLowRamDevice) Dispatchers.IO if (Core.activity.isLowRamDevice) Dispatchers.IO
else Executors.newCachedThreadPool().asCoroutineDispatcher() else Executors.newCachedThreadPool().asCoroutineDispatcher()
} }
......
...@@ -48,9 +48,10 @@ object LocalDnsService { ...@@ -48,9 +48,10 @@ object LocalDnsService {
Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port), Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port),
DataStore.proxyAddress, DataStore.proxyAddress,
hosts, hosts,
!profile.udpdns) { !profile.udpdns,
if (profile.route == Acl.ALL) null else AclMatcher().apply { init(profile.route) } if (profile.route == Acl.ALL) null else object {
}.also { suspend fun createAcl() = AclMatcher().apply { init(profile.route) }
}::createAcl).also {
servers[this] = it servers[this] = it
}.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns)) }.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns))
} }
......
...@@ -51,7 +51,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -51,7 +51,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
* Forward UDP queries to TCP. * Forward UDP queries to TCP.
*/ */
private val tcp: Boolean = false, private val tcp: Boolean = false,
private val aclSpawn: suspend () -> AclMatcher? = { null }) : CoroutineScope { aclSpawn: (suspend () -> AclMatcher)? = null) : CoroutineScope {
companion object { companion object {
private const val TAG = "LocalDnsServer" private const val TAG = "LocalDnsServer"
private const val TIMEOUT = 10_000L private const val TIMEOUT = 10_000L
...@@ -84,7 +84,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -84,7 +84,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, t -> override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, t ->
if (t is IOException) Crashlytics.log(Log.WARN, TAG, t.message) else printLog(t) if (t is IOException) Crashlytics.log(Log.WARN, TAG, t.message) else printLog(t)
} }
private val acl = async { aclSpawn() } private val acl = aclSpawn?.let { async { it() } }
suspend fun start(listen: SocketAddress) = DatagramChannel.open().run { suspend fun start(listen: SocketAddress) = DatagramChannel.open().run {
configureBlocking(false) configureBlocking(false)
...@@ -129,7 +129,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -129,7 +129,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
remote.cancel() remote.cancel()
return@supervisorScope cookDnsResponse(request, hostsResults) return@supervisorScope cookDnsResponse(request, hostsResults)
} }
val acl = acl.await() ?: return@supervisorScope remote.await() val acl = acl?.await() ?: return@supervisorScope remote.await()
val useLocal = when (acl.shouldBypass(host)) { val useLocal = when (acl.shouldBypass(host)) {
true -> true.also { remote.cancel() } true -> true.also { remote.cancel() }
false -> return@supervisorScope remote.await() false -> return@supervisorScope remote.await()
...@@ -204,5 +204,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -204,5 +204,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
cancel() cancel()
monitor.close(scope) monitor.close(scope)
coroutineContext[Job]!!.also { job -> scope.launch { job.join() } } coroutineContext[Job]!!.also { job -> scope.launch { job.join() } }
acl?.also { scope.launch { it.await().close() } }
} }
} }
...@@ -278,6 +278,39 @@ LOCAL_LDLIBS := -llog ...@@ -278,6 +278,39 @@ LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_EXECUTABLE) include $(BUILD_SHARED_EXECUTABLE)
########################################################
## jni-helper
########################################################
include $(CLEAR_VARS)
LOCAL_MODULE:= jni-helper
LOCAL_C_INCLUDES := $(LOCAL_PATH)/re2
LOCAL_CFLAGS := -std=c++17
LOCAL_SRC_FILES := jni-helper.cpp \
$(LOCAL_PATH)/re2/re2/bitstate.cc \
$(LOCAL_PATH)/re2/re2/compile.cc \
$(LOCAL_PATH)/re2/re2/dfa.cc \
$(LOCAL_PATH)/re2/re2/nfa.cc \
$(LOCAL_PATH)/re2/re2/onepass.cc \
$(LOCAL_PATH)/re2/re2/parse.cc \
$(LOCAL_PATH)/re2/re2/perl_groups.cc \
$(LOCAL_PATH)/re2/re2/prog.cc \
$(LOCAL_PATH)/re2/re2/re2.cc \
$(LOCAL_PATH)/re2/re2/regexp.cc \
$(LOCAL_PATH)/re2/re2/simplify.cc \
$(LOCAL_PATH)/re2/re2/stringpiece.cc \
$(LOCAL_PATH)/re2/re2/tostring.cc \
$(LOCAL_PATH)/re2/re2/unicode_casefold.cc \
$(LOCAL_PATH)/re2/re2/unicode_groups.cc \
$(LOCAL_PATH)/re2/util/rune.cc \
$(LOCAL_PATH)/re2/util/strutil.cc
include $(BUILD_SHARED_LIBRARY)
######################################################## ########################################################
## tun2socks ## tun2socks
######################################################## ########################################################
......
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* 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/>. *
* *
*******************************************************************************/
#include <sstream>
#include "jni.h"
#include "re2/re2.h"
using namespace std;
struct AclMatcher {
stringstream bypassDomainsBuilder, proxyDomainsBuilder;
RE2 *bypassDomains, *proxyDomains;
~AclMatcher() {
if (bypassDomains) delete bypassDomains;
if (proxyDomains) delete proxyDomains;
}
};
bool addDomain(JNIEnv *env, stringstream &domains, jstring regex) {
const char *regexChars = env->GetStringUTFChars(regex, nullptr);
if (regexChars == nullptr) return false;
if (domains.rdbuf()->in_avail()) domains << '|';
domains << regexChars;
env->ReleaseStringUTFChars(regex, regexChars);
return true;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
extern "C" {
JNIEXPORT jlong JNICALL Java_com_github_shadowsocks_acl_AclMatcher_init(JNIEnv *env, jclass clazz) {
return reinterpret_cast<jlong>(new AclMatcher());
}
JNIEXPORT void JNICALL
Java_com_github_shadowsocks_acl_AclMatcher_close(JNIEnv *env, jclass clazz, jlong handle) {
delete reinterpret_cast<AclMatcher *>(handle);
}
JNIEXPORT jboolean JNICALL
Java_com_github_shadowsocks_acl_AclMatcher_addBypassDomain(JNIEnv *env, jclass clazz, jlong handle,
jstring regex) {
return static_cast<jboolean>(handle &&
::addDomain(env, reinterpret_cast<AclMatcher *>(handle)->bypassDomainsBuilder, regex));
}
JNIEXPORT jboolean JNICALL
Java_com_github_shadowsocks_acl_AclMatcher_addProxyDomain(JNIEnv *env, jclass clazz, jlong handle,
jstring regex) {
return static_cast<jboolean>(handle &&
::addDomain(env, reinterpret_cast<AclMatcher *>(handle)->proxyDomainsBuilder, regex));
}
JNIEXPORT jstring JNICALL
Java_com_github_shadowsocks_acl_AclMatcher_build(JNIEnv *env, jclass clazz, jlong handle,
jlong memory_limit) {
if (!handle) return env->NewStringUTF("AclMatcher closed");
auto matcher = reinterpret_cast<AclMatcher *>(handle);
if (matcher->bypassDomains || matcher->proxyDomains) return env->NewStringUTF("already built");
RE2::Options options;
options.set_max_mem(memory_limit);
options.set_never_capture(true);
matcher->bypassDomains = new RE2(matcher->bypassDomainsBuilder.str(), options);
matcher->bypassDomainsBuilder.clear();
if (!matcher->bypassDomains->ok()) return env->NewStringUTF(matcher->bypassDomains->error().c_str());
matcher->proxyDomains = new RE2(matcher->proxyDomainsBuilder.str(), options);
matcher->proxyDomainsBuilder.clear();
if (!matcher->proxyDomains->ok()) return env->NewStringUTF(matcher->proxyDomains->error().c_str());
return nullptr;
}
JNIEXPORT jint JNICALL
Java_com_github_shadowsocks_acl_AclMatcher_matchHost(JNIEnv *env, jclass clazz, jlong handle,
jstring host) {
if (!handle) return -1;
auto matcher = reinterpret_cast<const AclMatcher *>(handle);
const char *hostChars = env->GetStringUTFChars(host, nullptr);
jint result = 0;
if (RE2::FullMatch(hostChars, *matcher->bypassDomains)) result = 1;
else if (RE2::FullMatch(hostChars, *matcher->proxyDomains)) result = 2;
env->ReleaseStringUTFChars(host, hostChars);
return result;
}
}
Subproject commit bb8e777557ddbdeabdedea4f23613c5021ffd7b1
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