Skip to content

Commit d4fb2d7

Browse files
committed
Use resolved in local DNS server if available
1 parent eeede11 commit d4fb2d7

File tree

5 files changed

+215
-8
lines changed

5 files changed

+215
-8
lines changed

dns/transport/local/local.go

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/sagernet/sing-box/option"
1414
"github.com/sagernet/sing/common/buf"
1515
E "github.com/sagernet/sing/common/exceptions"
16+
"github.com/sagernet/sing/common/logger"
1617
M "github.com/sagernet/sing/common/metadata"
1718
N "github.com/sagernet/sing/common/network"
1819

@@ -23,9 +24,11 @@ var _ adapter.DNSTransport = (*Transport)(nil)
2324

2425
type Transport struct {
2526
dns.TransportAdapter
26-
ctx context.Context
27-
hosts *hosts.File
28-
dialer N.Dialer
27+
ctx context.Context
28+
logger logger.ContextLogger
29+
hosts *hosts.File
30+
dialer N.Dialer
31+
resolved ResolvedResolver
2932
}
3033

3134
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
@@ -36,20 +39,42 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
3639
return &Transport{
3740
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
3841
ctx: ctx,
42+
logger: logger,
3943
hosts: hosts.NewFile(hosts.DefaultPath),
4044
dialer: transportDialer,
4145
}, nil
4246
}
4347

4448
func (t *Transport) Start(stage adapter.StartStage) error {
49+
switch stage {
50+
case adapter.StartStateInitialize:
51+
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
52+
if err == nil {
53+
err = resolvedResolver.Start()
54+
if err == nil {
55+
t.resolved = resolvedResolver
56+
} else {
57+
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
58+
}
59+
}
60+
}
4561
return nil
4662
}
4763

4864
func (t *Transport) Close() error {
65+
if t.resolved != nil {
66+
return t.resolved.Close()
67+
}
4968
return nil
5069
}
5170

5271
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
72+
if t.resolved != nil {
73+
resolverObject := t.resolved.Object()
74+
if resolverObject != nil {
75+
return t.resolved.Exchange(resolverObject, ctx, message)
76+
}
77+
}
5378
question := message.Question[0]
5479
domain := dns.FqdnToDomain(question.Name)
5580
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {

dns/transport/local/local_fallback.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,22 @@ func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag str
3333
if err != nil {
3434
return nil, err
3535
}
36+
platformInterface := service.FromContext[platform.Interface](ctx)
37+
if platformInterface == nil {
38+
return transport, nil
39+
}
3640
return &FallbackTransport{
3741
DNSTransport: transport,
3842
ctx: ctx,
3943
}, nil
4044
}
4145

4246
func (f *FallbackTransport) Start(stage adapter.StartStage) error {
43-
if stage != adapter.StartStateStart {
44-
return nil
47+
err := f.DNSTransport.Start(stage)
48+
if err != nil {
49+
return err
4550
}
46-
platformInterface := service.FromContext[platform.Interface](f.ctx)
47-
if platformInterface == nil {
51+
if stage != adapter.StartStatePostStart {
4852
return nil
4953
}
5054
inboundManager := service.FromContext[adapter.InboundManager](f.ctx)
@@ -59,7 +63,7 @@ func (f *FallbackTransport) Start(stage adapter.StartStage) error {
5963
}
6064

6165
func (f *FallbackTransport) Close() error {
62-
return nil
66+
return f.DNSTransport.Close()
6367
}
6468

6569
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package local
2+
3+
import (
4+
"context"
5+
6+
mDNS "github.com/miekg/dns"
7+
)
8+
9+
type ResolvedResolver interface {
10+
Start() error
11+
Close() error
12+
Object() any
13+
Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error)
14+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package local
2+
3+
import (
4+
"context"
5+
"os"
6+
"sync"
7+
8+
"github.com/sagernet/sing-box/adapter"
9+
"github.com/sagernet/sing-box/service/resolved"
10+
"github.com/sagernet/sing-tun"
11+
"github.com/sagernet/sing/common/atomic"
12+
E "github.com/sagernet/sing/common/exceptions"
13+
"github.com/sagernet/sing/common/logger"
14+
"github.com/sagernet/sing/service"
15+
16+
"github.com/godbus/dbus/v5"
17+
mDNS "github.com/miekg/dns"
18+
)
19+
20+
type DBusResolvedResolver struct {
21+
logger logger.ContextLogger
22+
interfaceMonitor tun.DefaultInterfaceMonitor
23+
systemBus *dbus.Conn
24+
resoledObject atomic.TypedValue[dbus.BusObject]
25+
closeOnce sync.Once
26+
}
27+
28+
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
29+
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
30+
if interfaceMonitor == nil {
31+
return nil, os.ErrInvalid
32+
}
33+
systemBus, err := dbus.SystemBus()
34+
if err != nil {
35+
return nil, err
36+
}
37+
return &DBusResolvedResolver{
38+
logger: logger,
39+
interfaceMonitor: interfaceMonitor,
40+
systemBus: systemBus,
41+
}, nil
42+
}
43+
44+
func (t *DBusResolvedResolver) Start() error {
45+
t.updateStatus()
46+
err := t.systemBus.BusObject().AddMatchSignal(
47+
"org.freedesktop.DBus",
48+
"NameOwnerChanged",
49+
dbus.WithMatchSender("org.freedesktop.DBus"),
50+
dbus.WithMatchArg(0, "org.freedesktop.resolve1.Manager"),
51+
).Err
52+
if err != nil {
53+
return E.Cause(err, "configure resolved restart listener")
54+
}
55+
go t.loopUpdateStatus()
56+
return nil
57+
}
58+
59+
func (t *DBusResolvedResolver) Close() error {
60+
t.closeOnce.Do(func() {
61+
if t.systemBus != nil {
62+
_ = t.systemBus.Close()
63+
}
64+
})
65+
return nil
66+
}
67+
68+
func (t *DBusResolvedResolver) Object() any {
69+
return t.resoledObject.Load()
70+
}
71+
72+
func (t *DBusResolvedResolver) Exchange(object any, ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
73+
defaultInterface := t.interfaceMonitor.DefaultInterface()
74+
if defaultInterface == nil {
75+
return nil, E.New("missing default interface")
76+
}
77+
question := message.Question[0]
78+
call := object.(*dbus.Object).CallWithContext(
79+
ctx,
80+
"org.freedesktop.resolve1.Manager.ResolveRecord",
81+
0,
82+
int32(defaultInterface.Index),
83+
question.Name,
84+
question.Qclass,
85+
question.Qtype,
86+
uint64(0),
87+
)
88+
if call.Err != nil {
89+
return nil, E.Cause(call.Err, " resolve record via resolved")
90+
}
91+
var (
92+
records []resolved.ResourceRecord
93+
outflags uint64
94+
)
95+
err := call.Store(&records, &outflags)
96+
if err != nil {
97+
return nil, err
98+
}
99+
response := &mDNS.Msg{
100+
MsgHdr: mDNS.MsgHdr{
101+
Id: message.Id,
102+
Response: true,
103+
Authoritative: true,
104+
RecursionDesired: true,
105+
RecursionAvailable: true,
106+
Rcode: mDNS.RcodeSuccess,
107+
},
108+
Question: []mDNS.Question{question},
109+
}
110+
for _, record := range records {
111+
var rr mDNS.RR
112+
rr, _, err = mDNS.UnpackRR(record.Data, 0)
113+
if err != nil {
114+
return nil, E.Cause(err, "unpack resource record")
115+
}
116+
response.Answer = append(response.Answer, rr)
117+
}
118+
return response, nil
119+
}
120+
121+
func (t *DBusResolvedResolver) loopUpdateStatus() {
122+
signalChan := make(chan *dbus.Signal, 1)
123+
t.systemBus.Signal(signalChan)
124+
for signal := range signalChan {
125+
var restarted bool
126+
if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" {
127+
if len(signal.Body) != 3 || signal.Body[2].(string) == "" {
128+
continue
129+
} else {
130+
restarted = true
131+
}
132+
}
133+
if restarted {
134+
t.updateStatus()
135+
}
136+
}
137+
}
138+
139+
func (t *DBusResolvedResolver) updateStatus() {
140+
dbusObject := t.systemBus.Object("org.freedesktop.resolve1", "/org/freedesktop/resolve1")
141+
err := dbusObject.Call("org.freedesktop.DBus.Peer.Ping", 0).Err
142+
if err != nil {
143+
if t.resoledObject.Swap(nil) != nil {
144+
t.logger.Debug("systemd-resolved service is gone")
145+
}
146+
return
147+
}
148+
t.resoledObject.Store(dbusObject)
149+
t.logger.Debug("using systemd-resolved service as resolver")
150+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build !linux
2+
3+
package local
4+
5+
import (
6+
"context"
7+
"os"
8+
9+
"github.com/sagernet/sing/common/logger"
10+
)
11+
12+
func NewResolvedResolver(ctx context.Context, logger logger.ContextLogger) (ResolvedResolver, error) {
13+
return nil, os.ErrInvalid
14+
}

0 commit comments

Comments
 (0)