mirror of
https://github.com/MatsuriDayo/NekoBoxForAndroid.git
synced 2025-12-19 14:40:06 +08:00
new LocalDNSTransport by sfa
This commit is contained in:
parent
ad0dd1d63f
commit
4438c4f0e5
@ -22,6 +22,7 @@ import io.nekohasekai.sagernet.plugin.PluginManager
|
|||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import libcore.BoxInstance
|
import libcore.BoxInstance
|
||||||
import libcore.Libcore
|
import libcore.Libcore
|
||||||
|
import moe.matsuri.nb4a.net.LocalResolverImpl
|
||||||
import moe.matsuri.nb4a.plugin.NekoPluginManager
|
import moe.matsuri.nb4a.plugin.NekoPluginManager
|
||||||
import moe.matsuri.nb4a.proxy.neko.NekoBean
|
import moe.matsuri.nb4a.proxy.neko.NekoBean
|
||||||
import moe.matsuri.nb4a.proxy.neko.NekoJSInterface
|
import moe.matsuri.nb4a.proxy.neko.NekoJSInterface
|
||||||
@ -111,6 +112,7 @@ abstract class BoxInstance(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Libcore.registerLocalDNSTransport(LocalResolverImpl)
|
||||||
loadConfig()
|
loadConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,6 +279,8 @@ abstract class BoxInstance(
|
|||||||
if (::box.isInitialized) {
|
if (::box.isInitialized) {
|
||||||
box.close()
|
box.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Libcore.registerLocalDNSTransport(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ const val TAG_DNS_IN = "dns-in"
|
|||||||
const val TAG_DNS_OUT = "dns-out"
|
const val TAG_DNS_OUT = "dns-out"
|
||||||
|
|
||||||
const val LOCALHOST = "127.0.0.1"
|
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(
|
class ConfigBuildResult(
|
||||||
var config: String,
|
var config: String,
|
||||||
|
|||||||
@ -94,17 +94,17 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
val enableDnsRouting = findPreference<SwitchPreference>(Key.ENABLE_DNS_ROUTING)!!
|
val enableDnsRouting = findPreference<SwitchPreference>(Key.ENABLE_DNS_ROUTING)!!
|
||||||
val enableFakeDns = findPreference<SwitchPreference>(Key.ENABLE_FAKEDNS)!!
|
val enableFakeDns = findPreference<SwitchPreference>(Key.ENABLE_FAKEDNS)!!
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||||
DataStore.directDnsUseSystem = false
|
// DataStore.directDnsUseSystem = false
|
||||||
directDnsUseSystem.remove()
|
// directDnsUseSystem.remove()
|
||||||
} else {
|
// } else {
|
||||||
directDns.isEnabled = !directDnsUseSystem.isChecked
|
directDns.isEnabled = !directDnsUseSystem.isChecked
|
||||||
directDnsUseSystem.setOnPreferenceChangeListener { _, newValue ->
|
directDnsUseSystem.setOnPreferenceChangeListener { _, newValue ->
|
||||||
directDns.isEnabled = !(newValue as Boolean)
|
directDns.isEnabled = !(newValue as Boolean)
|
||||||
needReload()
|
needReload()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
|
|
||||||
val requireTransproxy = findPreference<SwitchPreference>(Key.REQUIRE_TRANSPROXY)!!
|
val requireTransproxy = findPreference<SwitchPreference>(Key.REQUIRE_TRANSPROXY)!!
|
||||||
val transproxyPort = findPreference<EditTextPreference>(Key.TRANSPROXY_PORT)!!
|
val transproxyPort = findPreference<EditTextPreference>(Key.TRANSPROXY_PORT)!!
|
||||||
|
|||||||
@ -3,16 +3,25 @@ package moe.matsuri.nb4a.net
|
|||||||
import android.net.DnsResolver
|
import android.net.DnsResolver
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.CancellationSignal
|
import android.os.CancellationSignal
|
||||||
|
import android.system.ErrnoException
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import io.nekohasekai.sagernet.SagerNet
|
import io.nekohasekai.sagernet.SagerNet
|
||||||
import io.nekohasekai.sagernet.ktx.tryResume
|
import io.nekohasekai.sagernet.ktx.tryResume
|
||||||
import io.nekohasekai.sagernet.ktx.tryResumeWithException
|
import io.nekohasekai.sagernet.ktx.tryResumeWithException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.asExecutor
|
import kotlinx.coroutines.asExecutor
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import libcore.ExchangeContext
|
||||||
|
import libcore.LocalDNSTransport
|
||||||
|
import libcore.LocalResolver
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
object LocalResolverImpl : libcore.LocalResolver {
|
object LocalResolverImpl : LocalResolver, LocalDNSTransport {
|
||||||
|
|
||||||
|
// old
|
||||||
|
|
||||||
override fun lookupIP(network: String, domain: String): String {
|
override fun lookupIP(network: String, domain: String): String {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
@ -28,11 +37,13 @@ object LocalResolverImpl : libcore.LocalResolver {
|
|||||||
continuation.tryResume((answer as Collection<InetAddress?>).mapNotNull { it?.hostAddress }
|
continuation.tryResume((answer as Collection<InetAddress?>).mapNotNull { it?.hostAddress }
|
||||||
.joinToString(","))
|
.joinToString(","))
|
||||||
}
|
}
|
||||||
|
|
||||||
rcode == 0 -> {
|
rcode == 0 -> {
|
||||||
// fuck AAAA no record
|
// fuck AAAA no record
|
||||||
// features/dns/client.go
|
// features/dns/client.go
|
||||||
continuation.tryResume("")
|
continuation.tryResume("")
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// Need return rcode
|
// Need return rcode
|
||||||
// proxy/dns/dns.go
|
// 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<ByteArray> {
|
||||||
|
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<Collection<InetAddress>> {
|
||||||
|
override fun onAnswer(answer: Collection<InetAddress>, rcode: Int) {
|
||||||
|
if (rcode == 0) {
|
||||||
|
ctx.success((answer as Collection<InetAddress?>).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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
164
libcore/dns_box.go
Normal file
164
libcore/dns_box.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@ type LocalResolver interface {
|
|||||||
var localResolver LocalResolver // Android: passed from java (only when VPNService)
|
var localResolver LocalResolver // Android: passed from java (only when VPNService)
|
||||||
|
|
||||||
func SetLocalResolver(lr LocalResolver) {
|
func SetLocalResolver(lr LocalResolver) {
|
||||||
localResolver = lr
|
localResolver = lr // old "underlyig://0.0.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoxPlatformInterface interface {
|
type BoxPlatformInterface interface {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user