Commit b7520896 authored by Mygod's avatar Mygod

Use re2j to match hosts

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