mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-10 13:24:08 +08:00
Compare commits
10 Commits
a4c600d94a
...
9e0059cc2b
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9e0059cc2b | ||
![]() |
eeede11fc1 | ||
![]() |
9886c6a325 | ||
![]() |
0e32e3c1c9 | ||
![]() |
86fee69c3f | ||
![]() |
e536e6f4d0 | ||
![]() |
90804bdccb | ||
![]() |
d041736875 | ||
![]() |
831644cea2 | ||
![]() |
354ece2bdf |
@ -30,7 +30,7 @@ type Manager struct {
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
dependByTag map[string][]string
|
||||
defaultOutbound adapter.Outbound
|
||||
defaultOutboundFallback adapter.Outbound
|
||||
defaultOutboundFallback func() (adapter.Outbound, error)
|
||||
}
|
||||
|
||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||
@ -44,7 +44,7 @@ func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||
func (m *Manager) Initialize(defaultOutboundFallback func() (adapter.Outbound, error)) {
|
||||
m.defaultOutboundFallback = defaultOutboundFallback
|
||||
}
|
||||
|
||||
@ -55,18 +55,31 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
if stage == adapter.StartStateStart {
|
||||
if m.defaultOutbound == nil {
|
||||
directOutbound, err := m.defaultOutboundFallback()
|
||||
if err != nil {
|
||||
m.access.Unlock()
|
||||
return E.Cause(err, "create direct outbound for fallback")
|
||||
}
|
||||
m.outbounds = append(m.outbounds, directOutbound)
|
||||
m.outboundByTag[directOutbound.Tag()] = directOutbound
|
||||
m.defaultOutbound = directOutbound
|
||||
}
|
||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||
if !loaded {
|
||||
m.access.Unlock()
|
||||
return E.New("default outbound not found: ", m.defaultTag)
|
||||
}
|
||||
m.defaultOutbound = defaultEndpoint
|
||||
}
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||
} else {
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
for _, outbound := range outbounds {
|
||||
err := adapter.LegacyStart(outbound, stage)
|
||||
if err != nil {
|
||||
@ -187,11 +200,7 @@ func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||
func (m *Manager) Default() adapter.Outbound {
|
||||
m.access.RLock()
|
||||
defer m.access.RUnlock()
|
||||
if m.defaultOutbound != nil {
|
||||
return m.defaultOutbound
|
||||
} else {
|
||||
return m.defaultOutboundFallback
|
||||
}
|
||||
return m.defaultOutbound
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
|
8
box.go
8
box.go
@ -314,15 +314,15 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||
}
|
||||
}
|
||||
outboundManager.Initialize(common.Must1(
|
||||
direct.NewOutbound(
|
||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||
return direct.NewOutbound(
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger("outbound/direct"),
|
||||
"direct",
|
||||
option.DirectOutboundOptions{},
|
||||
),
|
||||
))
|
||||
)
|
||||
})
|
||||
dnsTransportManager.Initialize(common.Must1(
|
||||
local.NewTransport(
|
||||
ctx,
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
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"
|
||||
|
||||
@ -23,9 +24,11 @@ var _ adapter.DNSTransport = (*Transport)(nil)
|
||||
|
||||
type Transport struct {
|
||||
dns.TransportAdapter
|
||||
ctx context.Context
|
||||
hosts *hosts.File
|
||||
dialer N.Dialer
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
hosts *hosts.File
|
||||
dialer N.Dialer
|
||||
resolved ResolvedResolver
|
||||
}
|
||||
|
||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
|
||||
@ -36,20 +39,39 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
||||
return &Transport{
|
||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||
dialer: transportDialer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||
switch stage {
|
||||
case adapter.StartStateInitialize:
|
||||
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
|
||||
if err == nil {
|
||||
err = resolvedResolver.Start()
|
||||
if err == nil {
|
||||
t.resolved = resolvedResolver
|
||||
} else {
|
||||
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Transport) Close() error {
|
||||
if t.resolved != nil {
|
||||
return t.resolved.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
if t.resolved != nil && t.resolved.Available() {
|
||||
return t.resolved.Exchange(ctx, message)
|
||||
}
|
||||
question := message.Question[0]
|
||||
domain := dns.FqdnToDomain(question.Name)
|
||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||
|
@ -33,6 +33,10 @@ func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag str
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
if platformInterface == nil {
|
||||
return transport, nil
|
||||
}
|
||||
return &FallbackTransport{
|
||||
DNSTransport: transport,
|
||||
ctx: ctx,
|
||||
@ -40,11 +44,11 @@ func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag str
|
||||
}
|
||||
|
||||
func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
err := f.DNSTransport.Start(stage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
platformInterface := service.FromContext[platform.Interface](f.ctx)
|
||||
if platformInterface == nil {
|
||||
if stage != adapter.StartStatePostStart {
|
||||
return nil
|
||||
}
|
||||
inboundManager := service.FromContext[adapter.InboundManager](f.ctx)
|
||||
@ -59,7 +63,7 @@ func (f *FallbackTransport) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
|
||||
func (f *FallbackTransport) Close() error {
|
||||
return nil
|
||||
return f.DNSTransport.Close()
|
||||
}
|
||||
|
||||
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
|
14
dns/transport/local/local_resolved.go
Normal file
14
dns/transport/local/local_resolved.go
Normal file
@ -0,0 +1,14 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type ResolvedResolver interface {
|
||||
Start() error
|
||||
Close() error
|
||||
Available() bool
|
||||
Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
|
||||
}
|
154
dns/transport/local/local_resolved_linux.go
Normal file
154
dns/transport/local/local_resolved_linux.go
Normal file
@ -0,0 +1,154 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/service/resolved"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
mDNS "github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type DBusResolvedResolver struct {
|
||||
logger logger.ContextLogger
|
||||
interfaceMonitor tun.DefaultInterfaceMonitor
|
||||
systemBus *dbus.Conn
|
||||
resoledObject atomic.TypedValue[dbus.BusObject]
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
||||
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
|
||||
if interfaceMonitor == nil {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
systemBus, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DBusResolvedResolver{
|
||||
logger: logger,
|
||||
interfaceMonitor: interfaceMonitor,
|
||||
systemBus: systemBus,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *DBusResolvedResolver) Start() error {
|
||||
t.updateStatus()
|
||||
err := t.systemBus.BusObject().AddMatchSignal(
|
||||
"org.freedesktop.DBus",
|
||||
"NameOwnerChanged",
|
||||
dbus.WithMatchSender("org.freedesktop.DBus"),
|
||||
dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"),
|
||||
).Err
|
||||
if err != nil {
|
||||
return E.Cause(err, "configure resolved restart listener")
|
||||
}
|
||||
go t.loopUpdateStatus()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DBusResolvedResolver) Close() error {
|
||||
t.closeOnce.Do(func() {
|
||||
if t.systemBus != nil {
|
||||
_ = t.systemBus.Close()
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *DBusResolvedResolver) Available() bool {
|
||||
return t.resoledObject.Load() == nil
|
||||
}
|
||||
|
||||
func (t *DBusResolvedResolver) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
defaultInterface := t.interfaceMonitor.DefaultInterface()
|
||||
if defaultInterface == nil {
|
||||
return nil, E.New("missing default interface")
|
||||
}
|
||||
resolvedObject := t.resoledObject.Load()
|
||||
if resolvedObject == nil {
|
||||
return nil, E.New("DBus interface for resolved is not available")
|
||||
}
|
||||
question := message.Question[0]
|
||||
call := resolvedObject.CallWithContext(
|
||||
ctx,
|
||||
"org.freedesktop.resolve1.Manager.ResolveRecord",
|
||||
0,
|
||||
int32(defaultInterface.Index),
|
||||
question.Name,
|
||||
question.Qclass,
|
||||
question.Qtype,
|
||||
uint64(0),
|
||||
)
|
||||
if call.Err != nil {
|
||||
return nil, E.Cause(call.Err, " resolve record via resolved")
|
||||
}
|
||||
var (
|
||||
records []resolved.ResourceRecord
|
||||
outflags uint64
|
||||
)
|
||||
err := call.Store(&records, &outflags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Response: true,
|
||||
Authoritative: true,
|
||||
RecursionDesired: true,
|
||||
RecursionAvailable: true,
|
||||
Rcode: mDNS.RcodeSuccess,
|
||||
},
|
||||
Question: []mDNS.Question{question},
|
||||
}
|
||||
for _, record := range records {
|
||||
var rr mDNS.RR
|
||||
rr, _, err = mDNS.UnpackRR(record.Data, 0)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "unpack resource record")
|
||||
}
|
||||
response.Answer = append(response.Answer, rr)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (t *DBusResolvedResolver) loopUpdateStatus() {
|
||||
signalChan := make(chan *dbus.Signal, 1)
|
||||
t.systemBus.Signal(signalChan)
|
||||
for signal := range signalChan {
|
||||
var restarted bool
|
||||
if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" {
|
||||
if len(signal.Body) != 3 || signal.Body[2].(string) == "" {
|
||||
continue
|
||||
} else {
|
||||
restarted = true
|
||||
}
|
||||
}
|
||||
if restarted {
|
||||
t.updateStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DBusResolvedResolver) updateStatus() {
|
||||
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
|
||||
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err
|
||||
if err != nil {
|
||||
if t.resoledObject.Swap(nil) != nil {
|
||||
t.logger.Debug("systemd-resolved service is gone")
|
||||
}
|
||||
return
|
||||
}
|
||||
t.resoledObject.Store(dbusObject)
|
||||
t.logger.Debug("using systemd-resolved service as resolver")
|
||||
}
|
14
dns/transport/local/local_resolved_stub.go
Normal file
14
dns/transport/local/local_resolved_stub.go
Normal file
@ -0,0 +1,14 @@
|
||||
//go:build !linux
|
||||
|
||||
package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
@ -182,9 +182,9 @@ func (t *resolve1Manager) logRequest(sender dbus.Sender, message ...any) context
|
||||
} else if metadata.ProcessInfo.UserId != 0 {
|
||||
prefix = F.ToString("uid:", metadata.ProcessInfo.UserId)
|
||||
}
|
||||
t.logger.InfoContext(ctx, "(", prefix, ") ", F.ToString(message...))
|
||||
t.logger.InfoContext(ctx, "(", prefix, ") ", strings.Join(F.MapToString(message), " "))
|
||||
} else {
|
||||
t.logger.InfoContext(ctx, F.ToString(message...))
|
||||
t.logger.InfoContext(ctx, strings.Join(F.MapToString(message), " "))
|
||||
}
|
||||
return adapter.WithContext(ctx, &metadata)
|
||||
}
|
||||
@ -280,7 +280,10 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami
|
||||
},
|
||||
}
|
||||
ctx := t.logRequest(sender, "ResolveAddress ", link.iif.Name, familyToString(family), addr, flags)
|
||||
response, lookupErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{})
|
||||
var metadata adapter.InboundContext
|
||||
metadata.InboundType = t.Type()
|
||||
metadata.Inbound = t.Tag()
|
||||
response, lookupErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
|
||||
if lookupErr != nil {
|
||||
err = wrapError(err)
|
||||
return
|
||||
@ -301,7 +304,7 @@ func (t *resolve1Manager) ResolveAddress(sender dbus.Sender, ifIndex int32, fami
|
||||
return
|
||||
}
|
||||
|
||||
func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, family int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {
|
||||
func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, hostname string, qClass uint16, qType uint16, flags uint64) (records []ResourceRecord, outflags uint64, err *dbus.Error) {
|
||||
t.linkAccess.Lock()
|
||||
link, err := t.getLink(ifIndex)
|
||||
if err != nil {
|
||||
@ -320,8 +323,11 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, famil
|
||||
},
|
||||
},
|
||||
}
|
||||
ctx := t.logRequest(sender, "ResolveRecord ", link.iif.Name, familyToString(family), hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)
|
||||
response, exchangeErr := t.dnsRouter.Exchange(ctx, request, adapter.DNSQueryOptions{})
|
||||
ctx := t.logRequest(sender, "ResolveRecord", link.iif.Name, hostname, mDNS.Class(qClass), mDNS.Type(qType), flags)
|
||||
var metadata adapter.InboundContext
|
||||
metadata.InboundType = t.Type()
|
||||
metadata.Inbound = t.Tag()
|
||||
response, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), request, adapter.DNSQueryOptions{})
|
||||
if exchangeErr != nil {
|
||||
err = wrapError(exchangeErr)
|
||||
return
|
||||
@ -341,6 +347,7 @@ func (t *resolve1Manager) ResolveRecord(sender dbus.Sender, ifIndex int32, famil
|
||||
err = wrapError(unpackErr)
|
||||
}
|
||||
record.Data = data
|
||||
records = append(records, record)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -380,8 +387,10 @@ func (t *resolve1Manager) ResolveService(sender dbus.Sender, ifIndex int32, host
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
srvResponse, exchangeErr := t.dnsRouter.Exchange(ctx, srvRequest, adapter.DNSQueryOptions{})
|
||||
var metadata adapter.InboundContext
|
||||
metadata.InboundType = t.Type()
|
||||
metadata.Inbound = t.Tag()
|
||||
srvResponse, exchangeErr := t.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), srvRequest, adapter.DNSQueryOptions{})
|
||||
if exchangeErr != nil {
|
||||
err = wrapError(exchangeErr)
|
||||
return
|
||||
|
@ -91,11 +91,6 @@ func (i *Service) Start(stage adapter.StartStage) error {
|
||||
return E.New("multiple resolved service are not supported")
|
||||
}
|
||||
}
|
||||
case adapter.StartStateStart:
|
||||
err := i.listener.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
systemBus, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -117,6 +112,11 @@ func (i *Service) Start(stage adapter.StartStage) error {
|
||||
return E.New("unknown request name reply: ", reply)
|
||||
}
|
||||
i.networkUpdateCallback = i.network.NetworkMonitor().RegisterCallback(i.onNetworkUpdate)
|
||||
case adapter.StartStateStart:
|
||||
err := i.listener.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -167,6 +167,8 @@ func (i *Service) exchangePacket0(ctx context.Context, buffer *buf.Buffer, oob [
|
||||
}
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Source = source
|
||||
metadata.InboundType = i.Type()
|
||||
metadata.Inbound = i.Tag()
|
||||
response, err := i.dnsRouter.Exchange(adapter.WithContext(ctx, &metadata), &message, adapter.DNSQueryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
|
Loading…
x
Reference in New Issue
Block a user