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:
parent
657f4bd66c
commit
762a7b43be
@ -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)
|
||||
|
@ -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\": \"@\"",
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user