diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt index f94d168..ef16df5 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt @@ -22,6 +22,7 @@ import io.nekohasekai.sagernet.plugin.PluginManager import kotlinx.coroutines.* import libcore.BoxInstance import libcore.Libcore +import moe.matsuri.nb4a.net.LocalResolverImpl import moe.matsuri.nb4a.plugin.NekoPluginManager import moe.matsuri.nb4a.proxy.neko.NekoBean import moe.matsuri.nb4a.proxy.neko.NekoJSInterface @@ -111,6 +112,7 @@ abstract class BoxInstance( } } } + Libcore.registerLocalDNSTransport(LocalResolverImpl) loadConfig() } @@ -277,6 +279,8 @@ abstract class BoxInstance( if (::box.isInitialized) { box.close() } + + Libcore.registerLocalDNSTransport(null) } } \ No newline at end of file diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt index 6ff7873..e52f7a7 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt @@ -54,7 +54,7 @@ const val TAG_DNS_IN = "dns-in" const val TAG_DNS_OUT = "dns-out" const val LOCALHOST = "127.0.0.1" -const val LOCAL_DNS_SERVER = "underlying://0.0.0.0" +const val LOCAL_DNS_SERVER = "local" class ConfigBuildResult( var config: String, diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt index f536245..9b409c9 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt @@ -94,17 +94,17 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { val enableDnsRouting = findPreference(Key.ENABLE_DNS_ROUTING)!! val enableFakeDns = findPreference(Key.ENABLE_FAKEDNS)!! - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - DataStore.directDnsUseSystem = false - directDnsUseSystem.remove() - } else { + // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + // DataStore.directDnsUseSystem = false + // directDnsUseSystem.remove() + // } else { directDns.isEnabled = !directDnsUseSystem.isChecked directDnsUseSystem.setOnPreferenceChangeListener { _, newValue -> directDns.isEnabled = !(newValue as Boolean) needReload() true } - } + // } val requireTransproxy = findPreference(Key.REQUIRE_TRANSPROXY)!! val transproxyPort = findPreference(Key.TRANSPROXY_PORT)!! diff --git a/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt b/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt index 8a5a219..a4444a7 100644 --- a/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt +++ b/app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt @@ -3,16 +3,25 @@ package moe.matsuri.nb4a.net import android.net.DnsResolver import android.os.Build import android.os.CancellationSignal +import android.system.ErrnoException +import androidx.annotation.RequiresApi import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.ktx.tryResume import io.nekohasekai.sagernet.ktx.tryResumeWithException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asExecutor import kotlinx.coroutines.runBlocking +import libcore.ExchangeContext +import libcore.LocalDNSTransport +import libcore.LocalResolver import java.net.InetAddress +import java.net.UnknownHostException +import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -object LocalResolverImpl : libcore.LocalResolver { +object LocalResolverImpl : LocalResolver, LocalDNSTransport { + + // old override fun lookupIP(network: String, domain: String): String { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -28,11 +37,13 @@ object LocalResolverImpl : libcore.LocalResolver { continuation.tryResume((answer as Collection).mapNotNull { it?.hostAddress } .joinToString(",")) } + rcode == 0 -> { // fuck AAAA no record // features/dns/client.go continuation.tryResume("") } + else -> { // Need return rcode // proxy/dns/dns.go @@ -77,4 +88,120 @@ object LocalResolverImpl : libcore.LocalResolver { } } + // new local + + private const val RCODE_NXDOMAIN = 3 + + override fun raw(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + } + + @RequiresApi(Build.VERSION_CODES.Q) + override fun exchange(ctx: ExchangeContext, message: ByteArray) { + return runBlocking { + suspendCoroutine { continuation -> + val signal = CancellationSignal() + ctx.onCancel(signal::cancel) + val callback = object : DnsResolver.Callback { + override fun onAnswer(answer: ByteArray, rcode: Int) { + if (rcode == 0) { + ctx.rawSuccess(answer) + } else { + ctx.errorCode(rcode) + } + continuation.resume(Unit) + } + + override fun onError(error: DnsResolver.DnsException) { + when (val cause = error.cause) { + is ErrnoException -> { + ctx.errnoCode(cause.errno) + continuation.resume(Unit) + return + } + } + continuation.tryResumeWithException(error) + } + } + DnsResolver.getInstance().rawQuery( + SagerNet.underlyingNetwork, + message, + DnsResolver.FLAG_NO_RETRY, + Dispatchers.IO.asExecutor(), + signal, + callback + ) + } + } + } + + override fun lookup(ctx: ExchangeContext, network: String, domain: String) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return runBlocking { + suspendCoroutine { continuation -> + val signal = CancellationSignal() + ctx.onCancel(signal::cancel) + val callback = object : DnsResolver.Callback> { + override fun onAnswer(answer: Collection, rcode: Int) { + if (rcode == 0) { + ctx.success((answer as Collection).mapNotNull { it?.hostAddress } + .joinToString("\n")) + } else { + ctx.errorCode(rcode) + } + continuation.resume(Unit) + } + + override fun onError(error: DnsResolver.DnsException) { + when (val cause = error.cause) { + is ErrnoException -> { + ctx.errnoCode(cause.errno) + continuation.resume(Unit) + return + } + } + continuation.tryResumeWithException(error) + } + } + val type = when { + network.endsWith("4") -> DnsResolver.TYPE_A + network.endsWith("6") -> DnsResolver.TYPE_AAAA + else -> null + } + if (type != null) { + DnsResolver.getInstance().query( + SagerNet.underlyingNetwork, + domain, + type, + DnsResolver.FLAG_NO_RETRY, + Dispatchers.IO.asExecutor(), + signal, + callback + ) + } else { + DnsResolver.getInstance().query( + SagerNet.underlyingNetwork, + domain, + DnsResolver.FLAG_NO_RETRY, + Dispatchers.IO.asExecutor(), + signal, + callback + ) + } + } + } + } else { + val underlyingNetwork = + SagerNet.underlyingNetwork ?: error("upstream network not found") + val answer = try { + underlyingNetwork.getAllByName(domain) + } catch (e: UnknownHostException) { + ctx.errorCode(RCODE_NXDOMAIN) + return + } + ctx.success(answer.mapNotNull { it.hostAddress }.joinToString("\n")) + } + } + + } \ No newline at end of file diff --git a/libcore/dns_box.go b/libcore/dns_box.go new file mode 100644 index 0000000..7a730e0 --- /dev/null +++ b/libcore/dns_box.go @@ -0,0 +1,164 @@ +// libbox/dns.go + +package libcore + +import ( + "context" + "net/netip" + "strings" + "syscall" + + dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/task" + + mDNS "github.com/miekg/dns" +) + +type LocalDNSTransport interface { + Raw() bool + Lookup(ctx *ExchangeContext, network string, domain string) error + Exchange(ctx *ExchangeContext, message []byte) error +} + +func RegisterLocalDNSTransport(transport LocalDNSTransport) { + if transport == nil { + dns.RegisterTransport([]string{"local"}, dns.CreateLocalTransport) + } else { + dns.RegisterTransport([]string{"local"}, func(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (dns.Transport, error) { + return &platformLocalDNSTransport{ + iif: transport, + }, nil + }) + } +} + +var _ dns.Transport = (*platformLocalDNSTransport)(nil) + +type platformLocalDNSTransport struct { + iif LocalDNSTransport +} + +func (p *platformLocalDNSTransport) Name() string { + return "local" +} + +func (p *platformLocalDNSTransport) Start() error { + return nil +} + +func (p *platformLocalDNSTransport) Close() error { + return nil +} + +func (p *platformLocalDNSTransport) Raw() bool { + return p.iif.Raw() +} + +func (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { + messageBytes, err := message.Pack() + if err != nil { + return nil, err + } + response := &ExchangeContext{ + context: ctx, + } + var responseMessage *mDNS.Msg + return responseMessage, task.Run(ctx, func() error { + err = p.iif.Exchange(response, messageBytes) + if err != nil { + return err + } + if response.error != nil { + return response.error + } + responseMessage = &response.message + return nil + }) +} + +func (p *platformLocalDNSTransport) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) { + var network string + switch strategy { + case dns.DomainStrategyUseIPv4: + network = "ip4" + case dns.DomainStrategyPreferIPv6: + network = "ip6" + default: + network = "ip" + } + response := &ExchangeContext{ + context: ctx, + } + var responseAddr []netip.Addr + return responseAddr, task.Run(ctx, func() error { + err := p.iif.Lookup(response, network, domain) + if err != nil { + return err + } + if response.error != nil { + return response.error + } + switch strategy { + case dns.DomainStrategyUseIPv4: + responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool { + return it.Is4() + }) + case dns.DomainStrategyPreferIPv6: + responseAddr = common.Filter(response.addresses, func(it netip.Addr) bool { + return it.Is6() + }) + default: + responseAddr = response.addresses + } + /*if len(responseAddr) == 0 { + response.error = dns.RCodeSuccess + }*/ + return nil + }) +} + +type Func interface { + Invoke() error +} + +type ExchangeContext struct { + context context.Context + message mDNS.Msg + addresses []netip.Addr + error error +} + +func (c *ExchangeContext) OnCancel(callback Func) { + go func() { + <-c.context.Done() + callback.Invoke() + }() +} + +func (c *ExchangeContext) Success(result string) { + c.addresses = common.Map(common.Filter(strings.Split(result, "\n"), func(it string) bool { + return !common.IsEmpty(it) + }), func(it string) netip.Addr { + return M.ParseSocksaddrHostPort(it, 0).Unwrap().Addr + }) +} + +func (c *ExchangeContext) RawSuccess(result []byte) { + err := c.message.Unpack(result) + if err != nil { + c.error = E.Cause(err, "parse response") + } +} + +func (c *ExchangeContext) ErrorCode(code int32) { + c.error = dns.RCodeError(code) +} + +func (c *ExchangeContext) ErrnoCode(code int32) { + c.error = syscall.Errno(code) +} diff --git a/libcore/platform_java.go b/libcore/platform_java.go index 94ccff1..8c78280 100644 --- a/libcore/platform_java.go +++ b/libcore/platform_java.go @@ -17,7 +17,7 @@ type LocalResolver interface { var localResolver LocalResolver // Android: passed from java (only when VPNService) func SetLocalResolver(lr LocalResolver) { - localResolver = lr + localResolver = lr // old "underlyig://0.0.0.0" } type BoxPlatformInterface interface {