mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-10-26 21:16:56 +00:00
dns: Add mDNS server
This commit is contained in:
@@ -26,6 +26,7 @@ const (
|
|||||||
DNSTypeHosts = "hosts"
|
DNSTypeHosts = "hosts"
|
||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeDHCP = "dhcp"
|
||||||
|
DNSTypeMDNS = "mdns"
|
||||||
DNSTypeTailscale = "tailscale"
|
DNSTypeTailscale = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build !darwin
|
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -9,10 +7,13 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/mdns"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
@@ -35,11 +36,20 @@ type Transport struct {
|
|||||||
hosts *hosts.File
|
hosts *hosts.File
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
preferGo bool
|
preferGo bool
|
||||||
|
fallback bool
|
||||||
resolved ResolvedResolver
|
resolved ResolvedResolver
|
||||||
|
mdnsTransport adapter.DNSTransport
|
||||||
|
dhcpTransport dhcpTransport
|
||||||
neighborResolver adapter.NeighborResolver
|
neighborResolver adapter.NeighborResolver
|
||||||
neighborSuffixes []string
|
neighborSuffixes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dhcpTransport interface {
|
||||||
|
adapter.DNSTransport
|
||||||
|
Fetch() []M.Socksaddr
|
||||||
|
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
||||||
|
}
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
transportDialer, err := dns.NewLocalDialer(ctx, options)
|
transportDialer, err := dns.NewLocalDialer(ctx, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -68,55 +78,77 @@ func (t *Transport) Start(stage adapter.StartStage) error {
|
|||||||
} else {
|
} else {
|
||||||
t.hosts = defaultHosts
|
t.hosts = defaultHosts
|
||||||
}
|
}
|
||||||
if !t.preferGo {
|
if !t.preferGo && isSystemdResolvedManaged() {
|
||||||
if isSystemdResolvedManaged() {
|
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
|
||||||
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
|
if err == nil {
|
||||||
|
err = resolvedResolver.Start()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = resolvedResolver.Start()
|
t.resolved = resolvedResolver
|
||||||
if err == nil {
|
} else {
|
||||||
t.resolved = resolvedResolver
|
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
|
||||||
} else {
|
|
||||||
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case adapter.StartStateStart:
|
case adapter.StartStateStart:
|
||||||
|
if C.IsDarwin {
|
||||||
|
inboundManager := service.FromContext[adapter.InboundManager](t.ctx)
|
||||||
|
for _, inbound := range inboundManager.Inbounds() {
|
||||||
|
if inbound.Type() == C.TypeTun {
|
||||||
|
t.fallback = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.fallback {
|
||||||
|
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.mdnsTransport = mdns.NewRawTransport(t.TransportAdapter, t.ctx, t.logger)
|
||||||
|
}
|
||||||
router := service.FromContext[adapter.Router](t.ctx)
|
router := service.FromContext[adapter.Router](t.ctx)
|
||||||
if router != nil {
|
if router != nil {
|
||||||
t.neighborResolver = router.NeighborResolver()
|
t.neighborResolver = router.NeighborResolver()
|
||||||
}
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
err := t.dhcpTransport.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.mdnsTransport != nil {
|
||||||
|
err := t.mdnsTransport.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
func (t *Transport) Close() error {
|
||||||
if t.resolved != nil {
|
return common.Close(t.resolved, t.dhcpTransport, t.mdnsTransport)
|
||||||
return t.resolved.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Reset() {
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
t.dhcpTransport.Reset()
|
||||||
|
}
|
||||||
|
if t.mdnsTransport != nil {
|
||||||
|
t.mdnsTransport.Reset()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) PreferredDomain(domain string) bool {
|
func (t *Transport) PreferredDomain(domain string) bool {
|
||||||
if t.hosts != nil && t.resolved == nil {
|
if t.hosts != nil {
|
||||||
if len(t.hosts.Lookup(dns.FqdnToDomain(domain))) > 0 {
|
if len(t.hosts.Lookup(dns.FqdnToDomain(domain))) > 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return t.hasNeighborHost(domain)
|
return t.hasNeighborHost(domain) || mdns.IsLocalDomain(domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
if t.resolved != nil {
|
|
||||||
response := t.lookupNeighbor(message)
|
|
||||||
if response != nil {
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
return t.resolved.Exchange(ctx, message)
|
|
||||||
}
|
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if t.hosts != nil && (question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA) {
|
if t.hosts != nil && (question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA) {
|
||||||
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
||||||
@@ -128,5 +160,23 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
if response != nil {
|
if response != nil {
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
if mdns.IsLocalDomain(question.Name) {
|
||||||
|
if C.IsDarwin {
|
||||||
|
return t.systemExchange(ctx, message)
|
||||||
|
}
|
||||||
|
return t.mdnsTransport.Exchange(ctx, message)
|
||||||
|
}
|
||||||
|
if t.resolved != nil {
|
||||||
|
return t.resolved.Exchange(ctx, message)
|
||||||
|
}
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
servers := t.dhcpTransport.Fetch()
|
||||||
|
if len(servers) > 0 {
|
||||||
|
return t.dhcpTransport.Exchange0(ctx, message, servers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.fallback {
|
||||||
|
return t.systemExchange(ctx, message)
|
||||||
|
}
|
||||||
return t.exchange(ctx, message, question.Name)
|
return t.exchange(ctx, message, question.Name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
|
||||||
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ adapter.DNSTransport = (*Transport)(nil)
|
|
||||||
_ adapter.DNSTransportWithPreferredDomain = (*Transport)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Transport struct {
|
|
||||||
dns.TransportAdapter
|
|
||||||
ctx context.Context
|
|
||||||
logger logger.ContextLogger
|
|
||||||
hosts *hosts.File
|
|
||||||
dialer N.Dialer
|
|
||||||
fallback bool
|
|
||||||
dhcpTransport dhcpTransport
|
|
||||||
neighborResolver adapter.NeighborResolver
|
|
||||||
neighborSuffixes []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type dhcpTransport interface {
|
|
||||||
adapter.DNSTransport
|
|
||||||
Fetch() []M.Socksaddr
|
|
||||||
Exchange0(ctx context.Context, message *mDNS.Msg, servers []M.Socksaddr) (*mDNS.Msg, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
|
||||||
transportDialer, err := dns.NewLocalDialer(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
suffixes, err := buildNeighborMatchers(options.NeighborDomain)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Transport{
|
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
|
||||||
ctx: ctx,
|
|
||||||
logger: logger,
|
|
||||||
dialer: transportDialer,
|
|
||||||
neighborSuffixes: suffixes,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
|
||||||
switch stage {
|
|
||||||
case adapter.StartStateStart:
|
|
||||||
defaultHosts, err := hosts.NewDefault()
|
|
||||||
if err != nil {
|
|
||||||
t.logger.Warn(err)
|
|
||||||
} else {
|
|
||||||
t.hosts = defaultHosts
|
|
||||||
}
|
|
||||||
inboundManager := service.FromContext[adapter.InboundManager](t.ctx)
|
|
||||||
for _, inbound := range inboundManager.Inbounds() {
|
|
||||||
if inbound.Type() == C.TypeTun {
|
|
||||||
t.fallback = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.fallback {
|
|
||||||
t.dhcpTransport = newDHCPTransport(t.TransportAdapter, log.ContextWithOverrideLevel(t.ctx, log.LevelDebug), t.dialer, t.logger)
|
|
||||||
if t.dhcpTransport != nil {
|
|
||||||
err = t.dhcpTransport.Start(stage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
router := service.FromContext[adapter.Router](t.ctx)
|
|
||||||
if router != nil {
|
|
||||||
t.neighborResolver = router.NeighborResolver()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
|
||||||
return common.Close(
|
|
||||||
t.dhcpTransport,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
|
||||||
if t.dhcpTransport != nil {
|
|
||||||
t.dhcpTransport.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) PreferredDomain(domain string) bool {
|
|
||||||
if t.hosts != nil {
|
|
||||||
if len(t.hosts.Lookup(dns.FqdnToDomain(domain))) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return t.hasNeighborHost(domain)
|
|
||||||
}
|
|
||||||
@@ -31,7 +31,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
boxC "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
@@ -78,24 +77,8 @@ func darwinResolverHErrno(name string, hErrno int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if t.hosts != nil && (question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA) {
|
|
||||||
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
return dns.FixedResponse(message.Id, question, addresses, boxC.DefaultDNSTTL), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response := t.lookupNeighbor(message)
|
|
||||||
if response != nil {
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
if t.dhcpTransport != nil {
|
|
||||||
dhcpServers := t.dhcpTransport.Fetch()
|
|
||||||
if len(dhcpServers) > 0 {
|
|
||||||
return t.dhcpTransport.Exchange0(ctx, message, dhcpServers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type resolvResult struct {
|
type resolvResult struct {
|
||||||
response *mDNS.Msg
|
response *mDNS.Msg
|
||||||
err error
|
err error
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build darwin && with_dhcp
|
//go:build with_dhcp
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build darwin && !with_dhcp
|
//go:build !with_dhcp
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build !darwin
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t *Transport) systemExchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build !darwin
|
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -0,0 +1,456 @@
|
|||||||
|
package mdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
|
mDNS "github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mdnsPort = 5353
|
||||||
|
mdnsClassTopBit = 1 << 15
|
||||||
|
mdnsTimeout = time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mdnsGroupIPv4 = net.IPv4(224, 0, 0, 251)
|
||||||
|
mdnsGroupIPv6 = net.ParseIP("ff02::fb")
|
||||||
|
mdnsLocalZones = []string{
|
||||||
|
"local.",
|
||||||
|
"254.169.in-addr.arpa.",
|
||||||
|
"8.e.f.ip6.arpa.",
|
||||||
|
"9.e.f.ip6.arpa.",
|
||||||
|
"a.e.f.ip6.arpa.",
|
||||||
|
"b.e.f.ip6.arpa.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsLocalDomain(name string) bool {
|
||||||
|
canonical := mDNS.CanonicalName(name)
|
||||||
|
return common.Any(mdnsLocalZones, func(zone string) bool {
|
||||||
|
return canonical == zone || strings.HasSuffix(canonical, "."+zone)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterTransport(registry *dns.TransportRegistry) {
|
||||||
|
dns.RegisterTransport[option.MDNSDNSServerOptions](registry, C.DNSTypeMDNS, NewTransport)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ adapter.DNSTransport = (*Transport)(nil)
|
||||||
|
_ adapter.DNSTransportWithPreferredDomain = (*Transport)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Transport struct {
|
||||||
|
dns.TransportAdapter
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
networkManager adapter.NetworkManager
|
||||||
|
interfaceNames badoption.Listable[string]
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.MDNSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeMDNS, tag, options.LocalDNSServerOptions),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
|
interfaceNames: options.Interface,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRawTransport(transportAdapter dns.TransportAdapter, ctx context.Context, logger log.ContextLogger) *Transport {
|
||||||
|
return &Transport{
|
||||||
|
TransportAdapter: transportAdapter,
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) PreferredDomain(domain string) bool {
|
||||||
|
return IsLocalDomain(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
targets, err := t.queryTargets()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "mdns: prepare interfaces")
|
||||||
|
}
|
||||||
|
request := makeQueryMessage(message)
|
||||||
|
rawMessage, err := request.Pack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "mdns: pack request")
|
||||||
|
}
|
||||||
|
deadline, loaded := ctx.Deadline()
|
||||||
|
if !loaded || deadline.IsZero() {
|
||||||
|
deadline = time.Now().Add(mdnsTimeout)
|
||||||
|
}
|
||||||
|
exchangeCtx, cancel := context.WithDeadline(ctx, deadline)
|
||||||
|
defer cancel()
|
||||||
|
results := make(chan exchangeResult, len(targets))
|
||||||
|
var group task.Group
|
||||||
|
for _, target := range targets {
|
||||||
|
group.Append0(func(ctx context.Context) error {
|
||||||
|
response, err := t.exchangeTarget(ctx, target, rawMessage, message.Question[0], deadline)
|
||||||
|
if err != nil || response != nil {
|
||||||
|
results <- exchangeResult{
|
||||||
|
response: response,
|
||||||
|
err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
groupErr := group.Run(exchangeCtx)
|
||||||
|
close(results)
|
||||||
|
response := newResponse(message)
|
||||||
|
seenRecords := make(map[string]bool)
|
||||||
|
var lastErr error
|
||||||
|
for result := range results {
|
||||||
|
if result.err != nil {
|
||||||
|
lastErr = result.err
|
||||||
|
t.logger.TraceContext(ctx, result.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mergeResponse(response, result.response, seenRecords)
|
||||||
|
}
|
||||||
|
if len(response.Answer) > 0 || len(response.Ns) > 0 || len(response.Extra) > 0 {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
if lastErr != nil {
|
||||||
|
return nil, lastErr
|
||||||
|
}
|
||||||
|
if groupErr != nil && ctx.Err() != nil {
|
||||||
|
return nil, groupErr
|
||||||
|
}
|
||||||
|
return nil, E.New("mdns: query timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
type exchangeResult struct {
|
||||||
|
response *mDNS.Msg
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryTarget struct {
|
||||||
|
iface control.Interface
|
||||||
|
family string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) exchangeTarget(ctx context.Context, target queryTarget, rawMessage []byte, question mDNS.Question, deadline time.Time) (*mDNS.Msg, error) {
|
||||||
|
packetConn, destination, err := t.listenPacket(ctx, target)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer packetConn.Close()
|
||||||
|
|
||||||
|
_, err = packetConn.WriteTo(rawMessage, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "mdns: write request on ", target.iface.Name, " ", target.family)
|
||||||
|
}
|
||||||
|
err = packetConn.SetReadDeadline(deadline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "mdns: set deadline on ", target.iface.Name, " ", target.family)
|
||||||
|
}
|
||||||
|
response := newResponseFromQuestion(question)
|
||||||
|
seenRecords := make(map[string]bool)
|
||||||
|
buffer := buf.Get(buf.UDPBufferSize)
|
||||||
|
defer buf.Put(buffer)
|
||||||
|
for {
|
||||||
|
n, source, readErr := packetConn.ReadFrom(buffer)
|
||||||
|
if readErr != nil {
|
||||||
|
if E.IsTimeout(readErr) {
|
||||||
|
if len(response.Answer) > 0 || len(response.Ns) > 0 || len(response.Extra) > 0 {
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, E.Cause(readErr, "mdns: read response on ", target.iface.Name, " ", target.family)
|
||||||
|
}
|
||||||
|
if !validSource(source, target) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var candidate mDNS.Msg
|
||||||
|
err = candidate.Unpack(buffer[:n])
|
||||||
|
if err != nil {
|
||||||
|
t.logger.TraceContext(ctx, "mdns: unpack response: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !validResponse(&candidate, question) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
normalizeResponse(&candidate, question)
|
||||||
|
mergeResponse(response, &candidate, seenRecords)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) listenPacket(ctx context.Context, target queryTarget) (net.PacketConn, net.Addr, error) {
|
||||||
|
var listenConfig net.ListenConfig
|
||||||
|
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(t.networkManager.InterfaceFinder(), target.iface.Name, target.iface.Index))
|
||||||
|
netInterface := target.iface.NetInterface()
|
||||||
|
switch target.family {
|
||||||
|
case "udp4":
|
||||||
|
packetConn, err := listenConfig.ListenPacket(ctx, "udp4", "0.0.0.0:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, E.Cause(err, "mdns: listen on ", target.iface.Name, " udp4")
|
||||||
|
}
|
||||||
|
ipv4Conn := ipv4.NewPacketConn(packetConn)
|
||||||
|
err = ipv4Conn.SetMulticastInterface(&netInterface)
|
||||||
|
if err != nil {
|
||||||
|
packetConn.Close()
|
||||||
|
return nil, nil, E.Cause(err, "mdns: set multicast interface on ", target.iface.Name, " udp4")
|
||||||
|
}
|
||||||
|
_ = ipv4Conn.SetMulticastTTL(255)
|
||||||
|
return packetConn, &net.UDPAddr{IP: mdnsGroupIPv4, Port: mdnsPort}, nil
|
||||||
|
case "udp6":
|
||||||
|
packetConn, err := listenConfig.ListenPacket(ctx, "udp6", "[::]:0")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, E.Cause(err, "mdns: listen on ", target.iface.Name, " udp6")
|
||||||
|
}
|
||||||
|
ipv6Conn := ipv6.NewPacketConn(packetConn)
|
||||||
|
err = ipv6Conn.SetMulticastInterface(&netInterface)
|
||||||
|
if err != nil {
|
||||||
|
packetConn.Close()
|
||||||
|
return nil, nil, E.Cause(err, "mdns: set multicast interface on ", target.iface.Name, " udp6")
|
||||||
|
}
|
||||||
|
_ = ipv6Conn.SetMulticastHopLimit(255)
|
||||||
|
return packetConn, &net.UDPAddr{IP: mdnsGroupIPv6, Port: mdnsPort, Zone: target.iface.Name}, nil
|
||||||
|
default:
|
||||||
|
return nil, nil, E.New("mdns: unknown network: ", target.family)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) queryTargets() ([]queryTarget, error) {
|
||||||
|
interfaces, err := t.fetchInterfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var targets []queryTarget
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
supports4, supports6 := interfaceFamilies(iface)
|
||||||
|
if supports4 {
|
||||||
|
targets = append(targets, queryTarget{
|
||||||
|
iface: iface,
|
||||||
|
family: "udp4",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if supports6 {
|
||||||
|
targets = append(targets, queryTarget{
|
||||||
|
iface: iface,
|
||||||
|
family: "udp6",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(targets) == 0 {
|
||||||
|
return nil, E.New("missing usable mDNS interfaces")
|
||||||
|
}
|
||||||
|
return targets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) fetchInterfaces() ([]control.Interface, error) {
|
||||||
|
finder := t.networkManager.InterfaceFinder()
|
||||||
|
var interfaces []control.Interface
|
||||||
|
if len(t.interfaceNames) > 0 {
|
||||||
|
for _, interfaceName := range t.interfaceNames {
|
||||||
|
iface, err := finder.ByName(interfaceName)
|
||||||
|
if err != nil {
|
||||||
|
t.logger.Warn("mdns: interface ", interfaceName, " not found")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !isUsableInterface(*iface) {
|
||||||
|
t.logger.Warn("mdns: interface ", interfaceName, " is not usable")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
interfaces = append(interfaces, *iface)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
interfaces = common.Filter(finder.Interfaces(), isUsableInterface)
|
||||||
|
}
|
||||||
|
if len(interfaces) == 0 {
|
||||||
|
return nil, E.New("mdns: missing usable interface")
|
||||||
|
}
|
||||||
|
return interfaces, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isUsableInterface(iface control.Interface) bool {
|
||||||
|
return iface.Flags&net.FlagUp != 0 &&
|
||||||
|
iface.Flags&net.FlagMulticast != 0 &&
|
||||||
|
iface.Flags&net.FlagLoopback == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func interfaceFamilies(iface control.Interface) (supports4, supports6 bool) {
|
||||||
|
for _, prefix := range iface.Addresses {
|
||||||
|
addr := prefix.Addr()
|
||||||
|
if addr.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if addr.Is4() {
|
||||||
|
supports4 = true
|
||||||
|
} else if addr.Is6() && !addr.Is4In6() {
|
||||||
|
supports6 = true
|
||||||
|
}
|
||||||
|
if supports4 && supports6 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeQueryMessage(message *mDNS.Msg) *mDNS.Msg {
|
||||||
|
request := &mDNS.Msg{
|
||||||
|
Question: slices.Clone(message.Question),
|
||||||
|
}
|
||||||
|
for i := range request.Question {
|
||||||
|
stripQuestionClass(&request.Question[i])
|
||||||
|
}
|
||||||
|
return request
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResponse(message *mDNS.Msg) *mDNS.Msg {
|
||||||
|
response := newResponseFromQuestion(message.Question[0])
|
||||||
|
response.Id = message.Id
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResponseFromQuestion(question mDNS.Question) *mDNS.Msg {
|
||||||
|
stripQuestionClass(&question)
|
||||||
|
return &mDNS.Msg{
|
||||||
|
MsgHdr: mDNS.MsgHdr{
|
||||||
|
Response: true,
|
||||||
|
Authoritative: true,
|
||||||
|
Rcode: mDNS.RcodeSuccess,
|
||||||
|
},
|
||||||
|
Question: []mDNS.Question{question},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validSource(source net.Addr, target queryTarget) bool {
|
||||||
|
sourceUDP, isUDP := source.(*net.UDPAddr)
|
||||||
|
if !isUDP || sourceUDP.Port != mdnsPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sourceAddr, loaded := netip.AddrFromSlice(sourceUDP.IP)
|
||||||
|
if !loaded {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sourceAddr = sourceAddr.Unmap()
|
||||||
|
if (target.family == "udp4" && !sourceAddr.Is4()) || (target.family == "udp6" && !sourceAddr.Is6()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, prefix := range target.iface.Addresses {
|
||||||
|
if prefix.Contains(sourceAddr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func validResponse(response *mDNS.Msg, question mDNS.Question) bool {
|
||||||
|
if !response.Response ||
|
||||||
|
response.Opcode != mDNS.OpcodeQuery ||
|
||||||
|
response.Rcode != mDNS.RcodeSuccess {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, responseQuestion := range response.Question {
|
||||||
|
if questionMatches(responseQuestion, question) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responseHasMatchingRecord(response, question)
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseHasMatchingRecord(response *mDNS.Msg, question mDNS.Question) bool {
|
||||||
|
for _, recordList := range [][]mDNS.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
if recordMatchesQuestion(record, question) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func questionMatches(left mDNS.Question, right mDNS.Question) bool {
|
||||||
|
stripQuestionClass(&left)
|
||||||
|
stripQuestionClass(&right)
|
||||||
|
return left.Qtype == right.Qtype &&
|
||||||
|
left.Qclass == right.Qclass &&
|
||||||
|
strings.EqualFold(left.Name, right.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordMatchesQuestion(record mDNS.RR, question mDNS.Question) bool {
|
||||||
|
header := record.Header()
|
||||||
|
return strings.EqualFold(header.Name, question.Name) &&
|
||||||
|
(question.Qtype == mDNS.TypeANY ||
|
||||||
|
header.Rrtype == question.Qtype ||
|
||||||
|
header.Rrtype == mDNS.TypeCNAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeResponse(response *mDNS.Msg, question mDNS.Question) {
|
||||||
|
response.Id = 0
|
||||||
|
response.Question = []mDNS.Question{question}
|
||||||
|
for i := range response.Question {
|
||||||
|
stripQuestionClass(&response.Question[i])
|
||||||
|
}
|
||||||
|
for _, recordList := range [][]mDNS.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
|
for _, record := range recordList {
|
||||||
|
stripRecordClass(record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeResponse(destination *mDNS.Msg, source *mDNS.Msg, seenRecords map[string]bool) {
|
||||||
|
appendRecords := func(destinationRecords *[]mDNS.RR, sourceRecords []mDNS.RR) {
|
||||||
|
for _, record := range sourceRecords {
|
||||||
|
key := record.String()
|
||||||
|
if seenRecords[key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenRecords[key] = true
|
||||||
|
*destinationRecords = append(*destinationRecords, record)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appendRecords(&destination.Answer, source.Answer)
|
||||||
|
appendRecords(&destination.Ns, source.Ns)
|
||||||
|
appendRecords(&destination.Extra, source.Extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripQuestionClass(question *mDNS.Question) {
|
||||||
|
question.Qclass &^= mdnsClassTopBit
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripRecordClass(record mDNS.RR) {
|
||||||
|
record.Header().Class &^= mdnsClassTopBit
|
||||||
|
}
|
||||||
@@ -507,11 +507,12 @@ Match source device hostname from DHCP leases.
|
|||||||
|
|
||||||
Match specified DNS servers' preferred domains.
|
Match specified DNS servers' preferred domains.
|
||||||
|
|
||||||
| Type | Match |
|
| Type | Match |
|
||||||
|-------------|-----------------------------------------------------|
|
|-------------|------------------------------------------------------------------------------|
|
||||||
| `hosts` | Match predefined entries and entries in hosts files |
|
| `hosts` | Match predefined entries and entries in hosts files |
|
||||||
| `local` | Match hosts entries and neighbor-resolved hosts |
|
| `local` | Match hosts entries, neighbor-resolved hosts, and mDNS local domains |
|
||||||
| `tailscale` | Match MagicDNS hosts and DNS route suffixes |
|
| `mdns` | Match mDNS local domains (`*.local.` and IPv4/IPv6 link-local reverse zones) |
|
||||||
|
| `tailscale` | Match MagicDNS hosts and DNS route suffixes |
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
|
|||||||
@@ -499,11 +499,12 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配指定 DNS 服务器的首选域名。
|
匹配指定 DNS 服务器的首选域名。
|
||||||
|
|
||||||
| 类型 | 匹配 |
|
| 类型 | 匹配 |
|
||||||
|-------------|--------------------------|
|
|-------------|-------------------------------------------------------------|
|
||||||
| `hosts` | 匹配预定义条目和 hosts 文件中的条目 |
|
| `hosts` | 匹配预定义条目和 hosts 文件中的条目 |
|
||||||
| `local` | 匹配 hosts 中的条目和邻居解析得到的主机名 |
|
| `local` | 匹配 hosts 中的条目、邻居解析得到的主机名以及 mDNS 本地域名 |
|
||||||
| `tailscale` | 匹配 MagicDNS 主机和 DNS 路由后缀 |
|
| `mdns` | 匹配 mDNS 本地域名(`*.local.` 以及 IPv4/IPv6 链路本地反向区域) |
|
||||||
|
| `tailscale` | 匹配 MagicDNS 主机和 DNS 路由后缀 |
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.14.0"
|
||||||
|
|
||||||
|
:material-plus: [mdns](./mdns/)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [type](#type)
|
:material-plus: [type](#type)
|
||||||
@@ -39,6 +43,7 @@ The type of the DNS server.
|
|||||||
| `https` | [HTTPS](./https/) |
|
| `https` | [HTTPS](./https/) |
|
||||||
| `h3` | [HTTP/3](./http3/) |
|
| `h3` | [HTTP/3](./http3/) |
|
||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
|
| `mdns` | [mDNS](./mdns/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
| `resolved` | [Resolved](./resolved/) |
|
| `resolved` | [Resolved](./resolved/) |
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.14.0 中的更改"
|
||||||
|
|
||||||
|
:material-plus: [mdns](./mdns/)
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [type](#type)
|
:material-plus: [type](#type)
|
||||||
@@ -39,6 +43,7 @@ DNS 服务器的类型。
|
|||||||
| `https` | [HTTPS](./https/) |
|
| `https` | [HTTPS](./https/) |
|
||||||
| `h3` | [HTTP/3](./http3/) |
|
| `h3` | [HTTP/3](./http3/) |
|
||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
|
| `mdns` | [mDNS](./mdns/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
| `resolved` | [Resolved](./resolved/) |
|
| `resolved` | [Resolved](./resolved/) |
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "Since sing-box 1.14.0"
|
||||||
|
|
||||||
|
# mDNS
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "mdns",
|
||||||
|
"tag": "",
|
||||||
|
|
||||||
|
"interface": [],
|
||||||
|
|
||||||
|
// Dial Fields
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
|
||||||
|
You usually do not need an explicit `mdns` server in addition to a [Local](./local/) server: the local server already routes queries for `*.local.` and IPv4/IPv6 link-local reverse zones via mDNS on non-Apple platforms and via the system resolver on Apple platforms. Add an explicit `mdns` server only when you want to reference it from [`preferred_by`](../rule/#preferred_by) or use it standalone.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### interface
|
||||||
|
|
||||||
|
List of network interface names to send mDNS queries on.
|
||||||
|
|
||||||
|
When empty, all interfaces that are up, multicast-capable, and non-loopback are used.
|
||||||
|
|
||||||
|
### Dial Fields
|
||||||
|
|
||||||
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
!!! question "自 sing-box 1.14.0 起"
|
||||||
|
|
||||||
|
# mDNS
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "mdns",
|
||||||
|
"tag": "",
|
||||||
|
|
||||||
|
"interface": [],
|
||||||
|
|
||||||
|
// 拨号字段
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
|
||||||
|
通常不需要在 [Local](./local/) 服务器之外再添加显式的 `mdns` 服务器:本地服务器已经会在非 Apple 平台通过 mDNS、在 Apple 平台通过系统解析器来回答 `*.local.` 与 IPv4/IPv6 链路本地反向区域的查询。仅当需要从 [`preferred_by`](../rule/#preferred_by) 引用,或独立使用时,才需要显式添加 `mdns` 服务器。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### interface
|
||||||
|
|
||||||
|
用于发送 mDNS 查询的网络接口名称列表。
|
||||||
|
|
||||||
|
留空时,将使用所有处于 up 状态、支持多播且非环回的接口。
|
||||||
|
|
||||||
|
### 拨号字段
|
||||||
|
|
||||||
|
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。
|
||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/dns/transport/fakeip"
|
"github.com/sagernet/sing-box/dns/transport/fakeip"
|
||||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
"github.com/sagernet/sing-box/dns/transport/hosts"
|
||||||
"github.com/sagernet/sing-box/dns/transport/local"
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/mdns"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/anytls"
|
"github.com/sagernet/sing-box/protocol/anytls"
|
||||||
@@ -118,6 +119,7 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
|||||||
transport.RegisterHTTPS(registry)
|
transport.RegisterHTTPS(registry)
|
||||||
hosts.RegisterTransport(registry)
|
hosts.RegisterTransport(registry)
|
||||||
local.RegisterTransport(registry)
|
local.RegisterTransport(registry)
|
||||||
|
mdns.RegisterTransport(registry)
|
||||||
fakeip.RegisterTransport(registry)
|
fakeip.RegisterTransport(registry)
|
||||||
resolved.RegisterTransport(registry)
|
resolved.RegisterTransport(registry)
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ nav:
|
|||||||
- HTTPS: configuration/dns/server/https.md
|
- HTTPS: configuration/dns/server/https.md
|
||||||
- HTTP3: configuration/dns/server/http3.md
|
- HTTP3: configuration/dns/server/http3.md
|
||||||
- DHCP: configuration/dns/server/dhcp.md
|
- DHCP: configuration/dns/server/dhcp.md
|
||||||
|
- mDNS: configuration/dns/server/mdns.md
|
||||||
- FakeIP: configuration/dns/server/fakeip.md
|
- FakeIP: configuration/dns/server/fakeip.md
|
||||||
- Tailscale: configuration/dns/server/tailscale.md
|
- Tailscale: configuration/dns/server/tailscale.md
|
||||||
- Resolved: configuration/dns/server/resolved.md
|
- Resolved: configuration/dns/server/resolved.md
|
||||||
|
|||||||
@@ -184,3 +184,8 @@ type DHCPDNSServerOptions struct {
|
|||||||
LocalDNSServerOptions
|
LocalDNSServerOptions
|
||||||
Interface string `json:"interface,omitempty"`
|
Interface string `json:"interface,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MDNSDNSServerOptions struct {
|
||||||
|
LocalDNSServerOptions
|
||||||
|
Interface badoption.Listable[string] `json:"interface,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user