1
0
mirror of https://github.com/NekoX-Dev/NekoX.git synced 2024-12-12 11:39:49 +01:00

Update DnsFactory

This commit is contained in:
arm64v8a 2021-11-21 23:08:09 +08:00
parent 657f4bd66c
commit 762a7b43be
4 changed files with 186 additions and 149 deletions

View File

@ -5,9 +5,9 @@ import cn.hutool.http.HttpResponse
import cn.hutool.http.HttpUtil
import kotlinx.coroutines.*
import org.telegram.messenger.FileLog
import tw.nekomimi.nekogram.utils.DnsFactory
import tw.nekomimi.nekogram.utils.ProxyUtil.parseProxies
import tw.nekomimi.nkmr.NekomuraUtil
import java.util.ArrayList
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.resume
@ -15,77 +15,26 @@ import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
fun loadProxiesPublic(urls: List<String>, exceptions: MutableMap<String, Exception>): List<String> {
val urlsDoH = ArrayList<String>()
val urlsOld = ArrayList<String>()
for (url in urls) {
if (url.startsWith("doh://")) {
urlsDoH.add(url)
} else {
urlsOld.add(url)
}
}
// Try DoH first ( github.com is often blocked
try {
return runBlocking {
var jobs = ArrayList<Job>()
var content = DnsFactory.getTxts("nachonekodayo.sekai.icu").joinToString()
var a: List<String> = suspendCoroutine {
val ret = AtomicBoolean()
val cl = AtomicInteger(urlsDoH.size)
for (url in urlsDoH) {
jobs.add(launch(Dispatchers.IO) {
try {
val para = "?name=nekogramx-public-proxy-v1.seyana.moe&type=TXT"
val reqURL = url.replace("doh://", "https://", false) + para
val req = HttpUtil.createGet(reqURL)
req.addHeaders(mapOf("accept" to "application/dns-json"))
req.timeout(10 * 1000)
var content = req.execute().body()
content = content.replace("\\\"", "", false)
content = content.replace(" ", "", false)
val proxiesString = NekomuraUtil.getSubString(content, "#NekoXStart#", "#NekoXEnd#")
if (proxiesString.equals(content)) {
throw Exception("DoH get public proxy: Not found")
}
val proxies = parseProxies(proxiesString)
if (proxies.count() == 0) {
throw Exception("DoH get public proxy: Empty")
}
if (ret.getAndSet(true)) return@launch
// Log.e("NekoPublicProxy", reqURL)
it.resume(proxies)
} catch (e: Exception) {
// Log.e("NekoPublicProxy", e.stackTraceToString())
FileLog.d(url)
FileLog.e(e.stackTraceToString())
exceptions[url] = e
if (cl.decrementAndGet() == 0) {
it.resumeWithException(e)
}
}
})
}
}
// Quit when the first success
for (job in jobs) {
// TODO cannot cancel Hutool HTTPRequest now...
job.cancel()
}
a
val proxiesString = NekomuraUtil.getSubString(content, "#NekoXStart#", "#NekoXEnd#")
if (proxiesString.equals(content)) {
throw Exception("DoH get public proxy: Not found")
}
val proxies = parseProxies(proxiesString)
if (proxies.count() == 0) {
throw Exception("DoH get public proxy: Empty")
}
return proxies
} catch (e: Exception) {
// Try Other Urls
return loadProxies(urlsOld, exceptions)
FileLog.e(e.stackTraceToString())
}
// Try Other Urls
return loadProxies(urls, exceptions)
}
@ -129,7 +78,7 @@ fun loadProxies(urls: List<String>, exceptions: MutableMap<String, Exception>):
if (url.contains("https://api.github.com")) {
content = content.replace("\\n", "", false)
content = String(Base64.decode(content, Base64.DEFAULT))
content = String(Base64.decode(content, Base64.NO_PADDING))
}
val proxies = parseProxies(content)

View File

@ -27,12 +27,6 @@ object SubManager {
enable = public?.enable ?: true
urls = listOf(
"doh://1.1.1.1/dns-query", // Domain is hardcoded in ProxyLoads.kt
"doh://1.0.0.1/dns-query",
"doh://101.101.101.101/dns-query",
"doh://8.8.8.8/resolve",
"doh://8.8.4.4/resolve",
"doh://[2606:4700:4700::1111]/dns-query",
"https://nekox.pages.dev/proxy_list_pro", // Note: NO DoH apply to here and neko.services now.
"https://github.com/NekoX-Dev/ProxyList/blob/master/proxy_list_pro@js-file-line\">@<",
"https://api.github.com/repos/NekoX-Dev/ProxyList/contents/proxy_list_pro?ref=master@\"content\": \"@\"",

View File

@ -128,8 +128,12 @@ public class ProxyHandler implements Runnable {
for (int i = 1; target == null && i < 4; i++) {
target = mapper.get(server.substring(0, server.length() - i));
}
if (target == null) {
FileLog.e("No route for ip " + server);
if (target == null || target.equals(-1)) {
// Too many logs
if (!mapper.containsKey(server)) {
mapper.put(server, -1);
FileLog.e("No route for ip " + server);
}
close();
return;
}

View File

@ -1,31 +1,56 @@
package tw.nekomimi.nekogram.utils
import android.util.Log
import cn.hutool.http.Header
import cn.hutool.http.HttpUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.telegram.messenger.FileLog
import org.telegram.tgnet.ConnectionsManager
import org.xbill.DNS.*
import tw.nekomimi.nkmr.NekomuraConfig
import java.net.InetAddress
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import kotlin.collections.ArrayList
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
object DnsFactory {
fun providers() = if (NekomuraConfig.customDoH.String().isNotBlank()) arrayOf(NekomuraConfig.customDoH.String())
else if (Locale.getDefault().country == "CN") arrayOf(
// 1.1.1.1 / 8.8.8.8 seems blocked
"https://101.101.101.101/dns-query", //Taiwan
"https://9.9.9.9/dns-query", // Anycast Japan
"https://doh.dns.sb/dns-query", // Japan
) else arrayOf(
"https://mozilla.cloudflare-dns.com/dns-query",
"https://dns.google/dns-query",
else arrayOf(
// behaviour: try all concurrently and stop when the first result returns.
"https://1.1.1.1/dns-query",
"https://1.0.0.1/dns-query",
"https://8.8.8.8/resolve",
"https://101.101.101.101/dns-query",
"https://9.9.9.9/dns-query",
"https://185.222.222.222/dns-query",
"https://45.11.45.11/dns-query",
"https://[2606:4700:4700::1111]/dns-query",
)
val cache = Cache()
class CustomException(message: String) : Exception(message)
//cancel blocking
private fun cancel(client: OkHttpClient) {
for (call in client.dispatcher.queuedCalls()) {
call.cancel()
}
for (call in client.dispatcher.runningCalls()) {
call.cancel()
}
}
@JvmStatic
@JvmOverloads
fun lookup(domain: String, fallback: Boolean = false): List<InetAddress> {
@ -37,7 +62,6 @@ object DnsFactory {
ConnectionsManager.getIpStrategy()
val noFallback = !ConnectionsManager.hasIpv4 || !ConnectionsManager.hasIpv6
val type = if (noFallback) {
if (ConnectionsManager.hasIpv4) Type.A else Type.AAAA
} else if (NekomuraConfig.useIPv6.Bool() xor !fallback) Type.A else Type.AAAA
@ -47,49 +71,86 @@ object DnsFactory {
val message = Message.newQuery(Record.newRecord(name, type, dc)).toWire()
var sr = cache.lookupRecords(name, type, dc)
if (!sr.isSuccessful) for (provider in providers()) {
FileLog.d("Provider $provider")
try {
val response = HttpUtil.createPost(provider)
.contentType("application/dns-message")
.header(Header.ACCEPT, "application/dns-message")
.body(message)
.setConnectionTimeout(5000)
.execute()
if (!response.isOk) continue
val result = Message(response.bodyBytes())
val rcode = result.header.rcode
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN && rcode != Rcode.NXRRSET) continue
cache.addMessage(result)
sr = cache.lookupRecords(name, type, dc)
if (sr == null) sr = cache.lookupRecords(name, type, dc)
break
} catch (e: Exception) {
fun sr2Ret(sr: SetResponse?): List<InetAddress>? {
if (sr == null) return null
if (sr.isSuccessful) {
val records = ArrayList<Record>()
for (set in sr.answers()) {
records.addAll(set.rrs(true))
}
val addresses = records.map {
(it as? ARecord)?.address ?: (it as AAAARecord).address
}
FileLog.d(addresses.toString())
return addresses
}
}
if (sr.isSuccessful) {
val records = ArrayList<Record>()
for (set in sr.answers()) {
records.addAll(set.rrs(true))
FileLog.d("DNS Result $domain: $sr")
if (sr.isCNAME) {
FileLog.d("DNS CNAME: origin:$domain, CNAME ${sr.cname.target.toString(true)}")
return lookup(sr.cname.target.toString(true), false)
}
val addresses = records.map {
(it as? ARecord)?.address ?: (it as AAAARecord).address
if ((sr.isNXRRSET && !noFallback && !fallback)) {
return lookup(domain, true)
}
FileLog.d(addresses.toString())
return addresses
return null
}
FileLog.d("DNS Result $domain: $sr")
val cachedSr = sr2Ret(sr)
if (cachedSr != null) return cachedSr
if (sr.isCNAME) {
FileLog.d("DNS CNAME: origin:$domain, CNAME ${sr.cname.target.toString(true)}")
return lookup(sr.cname.target.toString(true), false)
}
var counterAll = AtomicInteger(0)
var counterGood = AtomicInteger(0)
if ((sr.isNXRRSET && !noFallback && !fallback)) {
return lookup(domain, true)
val ret = runBlocking {
val client = OkHttpClient()
val ret: List<InetAddress>? = suspendCoroutine {
for (provider in providers()) {
launch(Dispatchers.IO) {
try {
val request = Request.Builder().url(provider)
.header("accept", "application/dns-message")
.post(message.toRequestBody("application/dns-message".toMediaTypeOrNull()))
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw CustomException("$provider not successful")
}
val result = Message(response.body!!.bytes())
val rcode = result.header.rcode
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN && rcode != Rcode.NXRRSET) {
throw CustomException("$provider dns error")
}
val ret = sr2Ret(cache.addMessage(result))
if (ret != null && counterGood.incrementAndGet() == 1) {
it.resume(ret)
}
} catch (e: Exception) {
if (e is CustomException) FileLog.e(e)
}
if (counterAll.incrementAndGet() == providers().size && counterGood.get() == 0) {
it.resume(null)
}
}
}
launch {
delay(5000L)
cancel(client)
}
}
cancel(client)
ret
}
if (ret != null) return ret
}
FileLog.d("Try system dns to resolve $domain")
@ -115,41 +176,70 @@ object DnsFactory {
val message = Message.newQuery(Record.newRecord(name, type, dc)).toWire()
var sr = cache.lookupRecords(name, type, dc)
if (!sr.isSuccessful) for (provider in providers()) {
FileLog.d("Provider $provider")
try {
val response = HttpUtil.createPost(provider)
.contentType("application/dns-message")
.header(Header.ACCEPT, "application/dns-message")
.body(message)
.setConnectionTimeout(5000)
.execute()
if (!response.isOk) continue
val result = Message(response.bodyBytes())
val rcode = result.header.rcode
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN && rcode != Rcode.NXRRSET) continue
cache.addMessage(result)
sr = cache.lookupRecords(name, type, dc)
if (sr == null) sr = cache.lookupRecords(name, type, dc)
break
} catch (e: Exception) {
FileLog.e(e)
fun sr2Ret(sr: SetResponse?): List<String>? {
if (sr != null && sr.isSuccessful) {
val txts = ArrayList<String>().apply {
sr.answers().forEach { rRset -> rRset.rrs(true).filterIsInstance<TXTRecord>().forEach { addAll(it.strings) } }
}
FileLog.d(sr.toString())
FileLog.d(txts.toString())
return txts
}
return null
}
if (sr.isSuccessful) {
val txts = ArrayList<String>().apply {
sr.answers().forEach { rRset -> rRset.rrs(true).filterIsInstance<TXTRecord>().forEach { addAll(it.strings) } }
val cachedSr = sr2Ret(sr)
if (cachedSr != null) return cachedSr
var counterAll = AtomicInteger(0)
var counterGood = AtomicInteger(0)
return runBlocking {
val client = OkHttpClient()
val ret: List<String> = suspendCoroutine {
for (provider in providers()) {
launch(Dispatchers.IO) {
try {
val request = Request.Builder().url(provider)
.header("accept", "application/dns-message")
.post(message.toRequestBody("application/dns-message".toMediaTypeOrNull()))
.build()
val response = client.newCall(request).execute()
if (!response.isSuccessful) {
throw CustomException("$provider not successful")
}
val result = Message(response.body!!.bytes())
val rcode = result.header.rcode
if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN && rcode != Rcode.NXRRSET) {
throw CustomException("$provider dns error")
}
val ret = sr2Ret(cache.addMessage(result))
if (ret != null && counterGood.incrementAndGet() == 1) {
// first OK
it.resume(ret)
}
} catch (e: Exception) {
if (e is CustomException) FileLog.e(e)
}
if (counterAll.incrementAndGet() == providers().size && counterGood.get() == 0) {
//all failed
it.resume(listOf())
}
}
}
launch {
delay(5000L)
cancel(client)
}
}
FileLog.d(txts.toString())
cancel(client)
ret
}
FileLog.d(sr.toString())
return listOf()
}
}