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 @@
[submodule "core/src/main/jni/libev"]
path = core/src/main/jni/libev
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 @@
package com.github.shadowsocks
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.*
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
......@@ -62,6 +59,7 @@ object Core {
lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent
val activity by lazy { app.getSystemService<ActivityManager>()!! }
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
......
......@@ -21,16 +21,39 @@
package com.github.shadowsocks.acl
import android.util.Log
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.Subnet
import java.net.Inet4Address
import java.net.Inet6Address
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 subnetsIpv6 = emptyList<Subnet.Immutable>()
private val bypassDomains = mutableListOf<Regex>()
private val proxyDomains = mutableListOf<Regex>()
private var bypass = false
suspend fun init(id: String) {
......@@ -44,12 +67,15 @@ class AclMatcher {
current = next
}
}.toList()
check(handle == 0L)
handle = init()
val time = measureNanoTime {
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()
subnetsIpv6 = subnets.asSequence().filter { it.address is Inet6Address }.dedup()
this.bypass = bypass
......@@ -64,9 +90,10 @@ class AclMatcher {
fun shouldBypassIpv4(ip: ByteArray) = bypass xor quickMatches(subnetsIpv4, ip)
fun shouldBypassIpv6(ip: ByteArray) = bypass xor quickMatches(subnetsIpv6, ip)
fun shouldBypass(host: String): Boolean? {
if (bypassDomains.any { it.matches(host) }) return true
if (proxyDomains.any { it.matches(host) }) return false
return null
fun shouldBypass(host: String) = when (val e = matchHost(handle, host)) {
0 -> null
1 -> true
2 -> false
else -> error("matchHost -> $e")
}
}
......@@ -22,7 +22,6 @@ package com.github.shadowsocks.bg
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.ActivityManager
import android.net.DnsResolver
import android.net.Network
import android.os.Build
......@@ -30,9 +29,7 @@ import android.os.CancellationSignal
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.utils.int
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog
......@@ -119,7 +116,7 @@ sealed class DnsResolverCompat {
* See also: https://issuetracker.google.com/issues/133874590
*/
private val unboundedIO by lazy {
if (app.getSystemService<ActivityManager>()!!.isLowRamDevice) Dispatchers.IO
if (Core.activity.isLowRamDevice) Dispatchers.IO
else Executors.newCachedThreadPool().asCoroutineDispatcher()
}
......
......@@ -48,9 +48,10 @@ object LocalDnsService {
Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port),
DataStore.proxyAddress,
hosts,
!profile.udpdns) {
if (profile.route == Acl.ALL) null else AclMatcher().apply { init(profile.route) }
}.also {
!profile.udpdns,
if (profile.route == Acl.ALL) null else object {
suspend fun createAcl() = AclMatcher().apply { init(profile.route) }
}::createAcl).also {
servers[this] = it
}.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns))
}
......
......@@ -51,7 +51,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
* Forward UDP queries to TCP.
*/
private val tcp: Boolean = false,
private val aclSpawn: suspend () -> AclMatcher? = { null }) : CoroutineScope {
aclSpawn: (suspend () -> AclMatcher)? = null) : CoroutineScope {
companion object {
private const val TAG = "LocalDnsServer"
private const val TIMEOUT = 10_000L
......@@ -84,7 +84,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
override val coroutineContext = SupervisorJob() + CoroutineExceptionHandler { _, 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 {
configureBlocking(false)
......@@ -129,7 +129,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
remote.cancel()
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)) {
true -> true.also { remote.cancel() }
false -> return@supervisorScope remote.await()
......@@ -204,5 +204,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
cancel()
monitor.close(scope)
coroutineContext[Job]!!.also { job -> scope.launch { job.join() } }
acl?.also { scope.launch { it.await().close() } }
}
}
......@@ -278,6 +278,39 @@ LOCAL_LDLIBS := -llog
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
########################################################
......
/*******************************************************************************
* *
* 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