Commit b7520896 authored by Mygod's avatar Mygod

Use re2j to match hosts

parent 2406bdc2
......@@ -65,6 +65,7 @@ dependencies {
api 'com.google.code.gson:gson:2.8.6'
api 'com.google.firebase:firebase-analytics:17.2.1'
api 'com.google.firebase:firebase-config:19.0.4'
api 'com.google.re2j:re2j:1.3'
api 'dnsjava:dnsjava:2.1.9'
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"
api "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion"
......
......@@ -64,6 +64,43 @@ class Acl {
if ((!value.bypass || value.subnets.size() == 0) && value.bypassHostnames.size() == 0 &&
value.proxyHostnames.size() == 0 && value.urls.size() == 0) null else value.toString())
fun save(id: String, acl: Acl) = getFile(id).writeText(acl.toString())
fun <T> parse(reader: Reader, bypassHostnames: (String) -> T, proxyHostnames: (String) -> T,
urls: ((URL) -> T)? = null, defaultBypass: Boolean = false): Pair<Boolean, List<Subnet>> {
var bypass = defaultBypass
val bypassSubnets = mutableListOf<Subnet>()
val proxySubnets = mutableListOf<Subnet>()
var hostnames: ((String) -> T)? = if (defaultBypass) proxyHostnames else bypassHostnames
var subnets: MutableList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets
reader.useLines {
for (line in it) {
val blocks = line.split('#', limit = 2)
val url = networkAclParser.matchEntire(blocks.getOrElse(1) { "" })?.groupValues?.getOrNull(1)
if (url != null) urls!!(URL(url))
when (val input = blocks[0].trim()) {
"[outbound_block_list]" -> {
hostnames = null
subnets = null
}
"[black_list]", "[bypass_list]" -> {
hostnames = bypassHostnames
subnets = bypassSubnets
}
"[white_list]", "[proxy_list]" -> {
hostnames = proxyHostnames
subnets = proxySubnets
}
"[reject_all]", "[bypass_all]" -> bypass = true
"[accept_all]", "[proxy_all]" -> bypass = false
else -> if (subnets != null && input.isNotEmpty()) {
val subnet = Subnet.fromString(input)
if (subnet == null) hostnames!!(input) else subnets!!.add(subnet)
}
}
}
}
return bypass to if (bypass) proxySubnets else bypassSubnets
}
}
private abstract class BaseSorter<T> : SortedList.Callback<T>() {
......@@ -110,39 +147,9 @@ class Acl {
proxyHostnames.clear()
subnets.clear()
urls.clear()
bypass = defaultBypass
val bypassSubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) }
val proxySubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) }
var hostnames: SortedList<String>? = if (defaultBypass) proxyHostnames else bypassHostnames
var subnets: SortedList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets
reader.useLines {
for (line in it) {
val blocks = line.split('#', limit = 2)
val url = networkAclParser.matchEntire(blocks.getOrElse(1) { "" })?.groupValues?.getOrNull(1)
if (url != null) urls.add(URL(url))
when (val input = blocks[0].trim()) {
"[outbound_block_list]" -> {
hostnames = null
subnets = null
}
"[black_list]", "[bypass_list]" -> {
hostnames = bypassHostnames
subnets = bypassSubnets
}
"[white_list]", "[proxy_list]" -> {
hostnames = proxyHostnames
subnets = proxySubnets
}
"[reject_all]", "[bypass_all]" -> bypass = true
"[accept_all]", "[proxy_all]" -> bypass = false
else -> if (subnets != null && input.isNotEmpty()) {
val subnet = Subnet.fromString(input)
if (subnet == null) hostnames!!.add(input) else subnets!!.add(subnet)
}
}
}
}
for (item in (if (bypass) proxySubnets else bypassSubnets).asIterable()) this.subnets.add(item)
val (bypass, subnets) = parse(reader, bypassHostnames::add, proxyHostnames::add, urls::add, defaultBypass)
this.bypass = bypass
for (item in subnets) this.subnets.add(item)
return this
}
......
......@@ -20,24 +20,40 @@
package com.github.shadowsocks.acl
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.net.Subnet
import com.google.re2j.Pattern
import java.net.Inet4Address
import java.net.Inet6Address
class AclMatcher(acl: Acl) {
private val subnetsIpv4 =
acl.subnets.asIterable().filter { it.address is Inet4Address }.map { it to it.address.address }
private val subnetsIpv6 =
acl.subnets.asIterable().filter { it.address is Inet6Address }.map { it to it.address.address }
private val bypassDomains = acl.bypassHostnames.asIterable().map { it.toRegex() }
private val proxyDomains = acl.proxyHostnames.asIterable().map { it.toRegex() }
private val bypass = acl.bypass
class AclMatcher(id: String) {
private val subnetsIpv4: List<Subnet.Immutable>
private val subnetsIpv6: List<Subnet.Immutable>
private val bypassDomains: Pattern?
private val proxyDomains: Pattern?
private val bypass: Boolean
fun shouldBypassIpv4(ip: ByteArray) = bypass xor subnetsIpv4.any { (subnet, sip) -> subnet.matches(sip, ip) }
fun shouldBypassIpv6(ip: ByteArray) = bypass xor subnetsIpv6.any { (subnet, sip) -> subnet.matches(sip, ip) }
init {
val bypassBuilder = StringBuilder()
val proxyBuilder = StringBuilder()
val (bypass, subnets) = Acl.parse(Acl.getFile(id).bufferedReader(), {
if (bypassBuilder.isNotEmpty()) bypassBuilder.append('|')
bypassBuilder.append(it)
}, {
if (proxyBuilder.isNotEmpty()) proxyBuilder.append('|')
proxyBuilder.append(it)
})
subnetsIpv4 = subnets.filter { it.address is Inet4Address }.map { it.toImmutable() }
subnetsIpv6 = subnets.filter { it.address is Inet6Address }.map { it.toImmutable() }
bypassDomains = if (bypassBuilder.isEmpty()) null else Pattern.compile(bypassBuilder.toString())
proxyDomains = if (proxyBuilder.isEmpty()) null else Pattern.compile(proxyBuilder.toString())
this.bypass = bypass
}
fun shouldBypassIpv4(ip: ByteArray) = bypass xor subnetsIpv4.any { it.matches(ip) }
fun shouldBypassIpv6(ip: ByteArray) = bypass xor subnetsIpv6.any { it.matches(ip) }
fun shouldBypass(host: String): Boolean? {
if (bypassDomains.any { it.matches(host) }) return true
if (proxyDomains.any { it.matches(host) }) return false
if (bypassDomains?.matches(host) == true) return true
if (proxyDomains?.matches(host) == false) return false
return null
}
}
......@@ -234,7 +234,7 @@ object BaseService {
fun buildAdditionalArguments(cmd: ArrayList<String>): ArrayList<String> = cmd
suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
suspend fun startProcesses(hosts: HostsFile) {
val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>()
?.isUserUnlocked != false) app else Core.deviceStorage).noBackupFilesDir
val udpFallback = data.udpFallback
......@@ -349,21 +349,21 @@ object BaseService {
val hosts = HostsFile(DataStore.publicStore.getString(Key.hosts) ?: "")
proxy.init(this@Interface, hosts)
data.udpFallback?.init(this@Interface, hosts)
val acl = if (profile.route == Acl.CUSTOM_RULES) try {
if (profile.route == Acl.CUSTOM_RULES) try {
withContext(Dispatchers.IO) {
Acl.customRules.flatten(10, this@Interface::openConnection).also {
Acl.save(Acl.CUSTOM_RULES, it)
}
}.let { lazy { it } }
}
} catch (e: IOException) {
throw ExpectedExceptionWrapper(e)
} else lazy { if (profile.route == Acl.ALL) null else Acl().fromId(profile.route) }
}
data.processes = GuardedProcessPool {
printLog(it)
stopRunner(false, it.readableMessage)
}
startProcesses(hosts, acl)
startProcesses(hosts)
proxy.scheduleUpdate()
data.udpFallback?.scheduleUpdate()
......
......@@ -36,8 +36,8 @@ object LocalDnsService {
private val servers = WeakHashMap<Interface, LocalDnsServer>()
interface Interface : BaseService.Interface {
override suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
super.startProcesses(hosts, acl)
override suspend fun startProcesses(hosts: HostsFile) {
super.startProcesses(hosts)
val profile = data.proxy!!.profile
val dns = try {
URI("dns://${profile.remoteDns}")
......@@ -49,7 +49,7 @@ object LocalDnsService {
DataStore.proxyAddress,
hosts,
!profile.udpdns,
acl.value?.let { AclMatcher(it) }).also {
if (profile.route == Acl.ALL) null else AclMatcher(profile.route)).also {
servers[this] = it
}.start(InetSocketAddress(DataStore.listenAddress, DataStore.portLocalDns))
}
......
......@@ -58,9 +58,9 @@ redsocks {
File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, "-c", "redsocks.conf"))
}
override suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
override suspend fun startProcesses(hosts: HostsFile) {
startRedsocksDaemon()
super.startProcesses(hosts, acl)
super.startProcesses(hosts)
}
override fun onDestroy() {
......
......@@ -144,9 +144,9 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override suspend fun resolver(host: String) = DnsResolverCompat.resolve(DefaultNetworkListener.get(), host)
override suspend fun openConnection(url: URL) = DefaultNetworkListener.get().openConnection(url)
override suspend fun startProcesses(hosts: HostsFile, acl: Lazy<Acl?>) {
override suspend fun startProcesses(hosts: HostsFile) {
worker = ProtectWorker().apply { start() }
super.startProcesses(hosts, acl)
super.startProcesses(hosts)
sendFd(startVpn())
}
......
......@@ -45,11 +45,8 @@ class Subnet(val address: InetAddress, val prefixSize: Int) : Comparable<Subnet>
require(prefixSize in 0..addressLength) { "prefixSize $prefixSize not in 0..$addressLength" }
}
/**
* Optimized version of matches.
* a corresponds to this address and b is the address in question.
*/
fun matches(a: ByteArray, b: ByteArray): Boolean {
class Immutable(private val a: ByteArray, private val prefixSize: Int) {
fun matches(b: ByteArray): Boolean {
if (a.size != b.size) return false
var i = 0
while (i * 8 < prefixSize && i * 8 + 8 <= prefixSize) {
......@@ -60,6 +57,8 @@ class Subnet(val address: InetAddress, val prefixSize: Int) : Comparable<Subnet>
val mask = 256 - (1 shl i * 8 + 8 - prefixSize)
return a[i].toInt() and mask == b[i].toInt() and mask
}
}
fun toImmutable() = Immutable(address.address, prefixSize)
override fun toString(): String =
if (prefixSize == addressLength) address.hostAddress else address.hostAddress + '/' + prefixSize
......
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