new LocalDNSTransport by sfa

This commit is contained in:
arm64v8a 2023-07-01 18:10:35 +09:00
parent ad0dd1d63f
commit 4438c4f0e5
6 changed files with 303 additions and 8 deletions

View File

@ -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)
}
}

View File

@ -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,

View File

@ -94,17 +94,17 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
val enableDnsRouting = findPreference<SwitchPreference>(Key.ENABLE_DNS_ROUTING)!!
val enableFakeDns = findPreference<SwitchPreference>(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<SwitchPreference>(Key.REQUIRE_TRANSPROXY)!!
val transproxyPort = findPreference<EditTextPreference>(Key.TRANSPROXY_PORT)!!

View File

@ -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<InetAddress?>).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<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
View 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)
}

View File

@ -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 {