Compare commits

..

14 Commits

Author SHA1 Message Date
世界
178d426364
documentation: Bump version 2025-08-18 17:13:45 +08:00
世界
e60c70d262
documentation: Remove outdated icons 2025-08-18 16:06:48 +08:00
世界
b09cb7969b
documentation: Improve local DNS server 2025-08-18 16:06:34 +08:00
世界
7058c2e08a
Use libresolv in local DNS server on darwin 2025-08-18 16:06:34 +08:00
世界
095750bfaf
Use resolved in local DNS server if available 2025-08-18 14:20:04 +08:00
xchacha20-poly1305
4572a808b6
Fix rule set version 2025-08-17 19:28:28 +08:00
世界
234e4bce67
documentation: Add preferred_by route rule item 2025-08-17 19:28:27 +08:00
世界
337b7e0b17
Add preferred_by route rule item 2025-08-17 19:28:27 +08:00
世界
4f2ec1a42f
documentation: Add interface address rule items 2025-08-17 19:28:27 +08:00
世界
ca7fd0c429
Add interface address rule items 2025-08-17 19:28:27 +08:00
neletor
9a32fc1121
Add support for ech retry configs 2025-08-17 19:28:27 +08:00
Zephyruso
4e6e3d2471
Add /dns/flush-clash meta api 2025-08-17 19:28:26 +08:00
世界
f9c973072b
Bump version 2025-08-17 14:51:06 +08:00
世界
acda4ce985
Fix bind_interface not working with auto_redirect 2025-08-17 14:48:01 +08:00
27 changed files with 448 additions and 349 deletions

View File

@ -149,7 +149,7 @@ jobs:
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
if: matrix.os != 'android'
if: matrix.os != 'darwin' && matrix.os != 'android'
run: |
set -xeuo pipefail
mkdir -p dist
@ -165,6 +165,23 @@ jobs:
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build darwin
if: matrix.os == 'darwin'
run: |
set -xeuo pipefail
mkdir -p dist
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
./cmd/sing-box
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GO386: ${{ matrix.go386 }}
GOARM: ${{ matrix.goarm }}
GOMIPS: ${{ matrix.gomips }}
GOMIPS64: ${{ matrix.gomips }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Build Android
if: matrix.os == 'android'
run: |

View File

@ -6,7 +6,7 @@ GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
MAIN = ./cmd/sing-box
PREFIX ?= $(shell go env GOPATH)

View File

@ -20,6 +20,7 @@ type NetworkManager interface {
DefaultOptions() NetworkOptions
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
AutoRedirectOutputMarkFunc() control.Func
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager

View File

@ -56,6 +56,14 @@ func (m *Manager) Start(stage adapter.StartStage) error {
m.started = true
m.stage = stage
if stage == adapter.StartStateStart {
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
}
if m.defaultOutbound == nil {
directOutbound, err := m.defaultOutboundFallback()
if err != nil {
@ -66,14 +74,6 @@ func (m *Manager) Start(stage adapter.StartStage) error {
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 })...))

7
box.go
View File

@ -323,13 +323,14 @@ func New(options Options) (*Box, error) {
option.DirectOutboundOptions{},
)
})
dnsTransportManager.Initialize(common.Must1(
local.NewTransport(
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
return local.NewTransport(
ctx,
logFactory.NewLogger("dns/local"),
"local",
option.LocalDNSServerOptions{},
)))
)
})
if platformInterface != nil {
err = platformInterface.Initialize(networkManager)
if err != nil {

@ -1 +1 @@
Subproject commit 6db8e06e8d6c77648e79e3f93bdd41a4d48cc319
Subproject commit 875f572f641332ac525e4dd716abae798455683a

View File

@ -59,8 +59,8 @@ func init() {
if err != nil {
currentTag = "unknown"
}
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+"-s -w -buildid= -checklinkname=0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
darwinTags = append(darwinTags, "with_dhcp")
@ -106,19 +106,17 @@ func buildAndroid() {
"-libname=box",
}
if !debugEnabled {
sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
args = append(args, sharedFlags...)
} else {
debugFlags[1] = debugFlags[1] + " -checklinkname=0"
args = append(args, debugFlags...)
}
tags := append(sharedTags, memcTags...)
if debugEnabled {
tags = append(tags, debugTags...)
}
if !debugEnabled {
args = append(args, sharedFlags...)
} else {
args = append(args, debugFlags...)
}
args = append(args, "-tags", strings.Join(tags, ","))
args = append(args, "./experimental/libbox")

View File

@ -121,11 +121,16 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
listener.Control = control.Append(listener.Control, bindFunc)
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, defaultOptions.RoutingMark, true))
}
}
if networkManager != nil {
markFunc := networkManager.AutoRedirectOutputMarkFunc()
dialer.Control = control.Append(dialer.Control, markFunc)
listener.Control = control.Append(listener.Control, markFunc)
}
if options.ReuseAddr {
listener.Control = control.Append(listener.Control, control.ReuseAddr())

View File

@ -20,6 +20,10 @@ import (
mDNS "github.com/miekg/dns"
)
func RegisterTransport(registry *dns.TransportRegistry) {
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewTransport)
}
var _ adapter.DNSTransport = (*Transport)(nil)
type Transport struct {
@ -28,10 +32,14 @@ type Transport struct {
logger logger.ContextLogger
hosts *hosts.File
dialer N.Dialer
preferGo bool
resolved ResolvedResolver
}
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
if C.IsDarwin && !options.PreferGo {
return NewResolvTransport(ctx, logger, tag)
}
transportDialer, err := dns.NewLocalDialer(ctx, options)
if err != nil {
return nil, err
@ -42,19 +50,22 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
logger: logger,
hosts: hosts.NewFile(hosts.DefaultPath),
dialer: transportDialer,
preferGo: options.PreferGo,
}, 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 !t.preferGo {
resolvedResolver, err := NewResolvedResolver(t.ctx, t.logger)
if err == nil {
t.resolved = resolvedResolver
} else {
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
err = resolvedResolver.Start()
if err == nil {
t.resolved = resolvedResolver
} else {
t.logger.Warn(E.Cause(err, "initialize resolved resolver"))
}
}
}
}

View File

@ -1,209 +0,0 @@
package local
import (
"context"
"errors"
"net"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/service"
mDNS "github.com/miekg/dns"
)
func RegisterTransport(registry *dns.TransportRegistry) {
dns.RegisterTransport[option.LocalDNSServerOptions](registry, C.DNSTypeLocal, NewFallbackTransport)
}
type FallbackTransport struct {
adapter.DNSTransport
ctx context.Context
fallback bool
resolver net.Resolver
}
func NewFallbackTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {
transport, err := NewTransport(ctx, logger, tag, options)
if err != nil {
return nil, err
}
platformInterface := service.FromContext[platform.Interface](ctx)
if platformInterface == nil {
return transport, nil
}
return &FallbackTransport{
DNSTransport: transport,
ctx: ctx,
}, nil
}
func (f *FallbackTransport) Start(stage adapter.StartStage) error {
err := f.DNSTransport.Start(stage)
if err != nil {
return err
}
if stage != adapter.StartStatePostStart {
return nil
}
inboundManager := service.FromContext[adapter.InboundManager](f.ctx)
for _, inbound := range inboundManager.Inbounds() {
if inbound.Type() == C.TypeTun {
// platform tun hijacks DNS, so we can only use cgo resolver here
f.fallback = true
break
}
}
return nil
}
func (f *FallbackTransport) Close() error {
return f.DNSTransport.Close()
}
func (f *FallbackTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
if !f.fallback {
return f.DNSTransport.Exchange(ctx, message)
}
question := message.Question[0]
domain := dns.FqdnToDomain(question.Name)
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
var network string
if question.Qtype == mDNS.TypeA {
network = "ip4"
} else {
network = "ip6"
}
addresses, err := f.resolver.LookupNetIP(ctx, network, domain)
if err != nil {
var dnsError *net.DNSError
if errors.As(err, &dnsError) && dnsError.IsNotFound {
return nil, dns.RcodeRefused
}
return nil, err
}
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
} else if question.Qtype == mDNS.TypeNS {
records, err := f.resolver.LookupNS(ctx, domain)
if err != nil {
var dnsError *net.DNSError
if errors.As(err, &dnsError) && dnsError.IsNotFound {
return nil, dns.RcodeRefused
}
return nil, err
}
response := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Rcode: mDNS.RcodeSuccess,
Response: true,
},
Question: []mDNS.Question{question},
}
for _, record := range records {
response.Answer = append(response.Answer, &mDNS.NS{
Hdr: mDNS.RR_Header{
Name: question.Name,
Rrtype: mDNS.TypeNS,
Class: mDNS.ClassINET,
Ttl: C.DefaultDNSTTL,
},
Ns: record.Host,
})
}
return response, nil
} else if question.Qtype == mDNS.TypeCNAME {
cname, err := f.resolver.LookupCNAME(ctx, domain)
if err != nil {
var dnsError *net.DNSError
if errors.As(err, &dnsError) && dnsError.IsNotFound {
return nil, dns.RcodeRefused
}
return nil, err
}
return &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Rcode: mDNS.RcodeSuccess,
Response: true,
},
Question: []mDNS.Question{question},
Answer: []mDNS.RR{
&mDNS.CNAME{
Hdr: mDNS.RR_Header{
Name: question.Name,
Rrtype: mDNS.TypeCNAME,
Class: mDNS.ClassINET,
Ttl: C.DefaultDNSTTL,
},
Target: cname,
},
},
}, nil
} else if question.Qtype == mDNS.TypeTXT {
records, err := f.resolver.LookupTXT(ctx, domain)
if err != nil {
var dnsError *net.DNSError
if errors.As(err, &dnsError) && dnsError.IsNotFound {
return nil, dns.RcodeRefused
}
return nil, err
}
return &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Rcode: mDNS.RcodeSuccess,
Response: true,
},
Question: []mDNS.Question{question},
Answer: []mDNS.RR{
&mDNS.TXT{
Hdr: mDNS.RR_Header{
Name: question.Name,
Rrtype: mDNS.TypeCNAME,
Class: mDNS.ClassINET,
Ttl: C.DefaultDNSTTL,
},
Txt: records,
},
},
}, nil
} else if question.Qtype == mDNS.TypeMX {
records, err := f.resolver.LookupMX(ctx, domain)
if err != nil {
var dnsError *net.DNSError
if errors.As(err, &dnsError) && dnsError.IsNotFound {
return nil, dns.RcodeRefused
}
return nil, err
}
response := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Rcode: mDNS.RcodeSuccess,
Response: true,
},
Question: []mDNS.Question{question},
}
for _, record := range records {
response.Answer = append(response.Answer, &mDNS.MX{
Hdr: mDNS.RR_Header{
Name: question.Name,
Rrtype: mDNS.TypeA,
Class: mDNS.ClassINET,
Ttl: C.DefaultDNSTTL,
},
Preference: record.Pref,
Mx: record.Host,
})
}
return response, nil
} else {
return nil, E.New("only A, AAAA, NS, CNAME, TXT, MX queries are supported on current platform when using TUN, please switch to a fixed DNS server.")
}
}

View File

@ -0,0 +1,46 @@
//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/log"
"github.com/sagernet/sing/common/logger"
mDNS "github.com/miekg/dns"
)
var _ adapter.DNSTransport = (*ResolvTransport)(nil)
type ResolvTransport struct {
dns.TransportAdapter
ctx context.Context
logger logger.ContextLogger
}
func NewResolvTransport(ctx context.Context, logger log.ContextLogger, tag string) (adapter.DNSTransport, error) {
return &ResolvTransport{
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeLocal, tag, nil),
ctx: ctx,
logger: logger,
}, nil
}
func (t *ResolvTransport) Start(stage adapter.StartStage) error {
return nil
}
func (t *ResolvTransport) Close() error {
return nil
}
func (t *ResolvTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
question := message.Question[0]
return doBlockingWithCtx(ctx, func() (*mDNS.Msg, error) {
return cgoResSearch(question.Name, int(question.Qtype), int(question.Qclass))
})
}

View File

@ -0,0 +1,170 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin
package local
import (
"context"
"errors"
"runtime"
"syscall"
"unsafe"
_ "unsafe"
E "github.com/sagernet/sing/common/exceptions"
mDNS "github.com/miekg/dns"
)
type (
_C_char = byte
_C_int = int32
_C_uchar = byte
_C_ushort = uint16
_C_uint = uint32
_C_ulong = uint64
_C_struct___res_state = ResState
_C_struct_sockaddr = syscall.RawSockaddr
)
func _C_free(p unsafe.Pointer) { runtime.KeepAlive(p) }
func _C_malloc(n uintptr) unsafe.Pointer {
if n <= 0 {
n = 1
}
return unsafe.Pointer(&make([]byte, n)[0])
}
const (
MAXNS = 3
MAXDNSRCH = 6
)
type ResState struct {
Retrans _C_int
Retry _C_int
Options _C_ulong
Nscount _C_int
Nsaddrlist [MAXNS]_C_struct_sockaddr
Id _C_ushort
Dnsrch [MAXDNSRCH + 1]*_C_char
Defname [256]_C_char
Pfcode _C_ulong
Ndots _C_uint
Nsort _C_uint
stub [128]byte
}
//go:linkname ResNinit internal/syscall/unix.ResNinit
func ResNinit(state *_C_struct___res_state) error
//go:linkname ResNsearch internal/syscall/unix.ResNsearch
func ResNsearch(state *_C_struct___res_state, dname *byte, class, typ int, ans *byte, anslen int) (int, error)
//go:linkname ResNclose internal/syscall/unix.ResNclose
func ResNclose(state *_C_struct___res_state)
//go:linkname GoString internal/syscall/unix.GoString
func GoString(p *byte) string
// doBlockingWithCtx executes a blocking function in a separate goroutine when the provided
// context is cancellable. It is intended for use with calls that don't support context
// cancellation (cgo, syscalls). blocking func may still be running after this function finishes.
// For the duration of the execution of the blocking function, the thread is 'acquired' using [acquireThread],
// blocking might not be executed when the context gets canceled early.
func doBlockingWithCtx[T any](ctx context.Context, blocking func() (T, error)) (T, error) {
if err := acquireThread(ctx); err != nil {
var zero T
return zero, err
}
if ctx.Done() == nil {
defer releaseThread()
return blocking()
}
type result struct {
res T
err error
}
res := make(chan result, 1)
go func() {
defer releaseThread()
var r result
r.res, r.err = blocking()
res <- r
}()
select {
case r := <-res:
return r.res, r.err
case <-ctx.Done():
var zero T
return zero, ctx.Err()
}
}
//go:linkname acquireThread net.acquireThread
func acquireThread(ctx context.Context) error
//go:linkname releaseThread net.releaseThread
func releaseThread()
func cgoResSearch(hostname string, rtype, class int) (*mDNS.Msg, error) {
resStateSize := unsafe.Sizeof(_C_struct___res_state{})
var state *_C_struct___res_state
if resStateSize > 0 {
mem := _C_malloc(resStateSize)
defer _C_free(mem)
memSlice := unsafe.Slice((*byte)(mem), resStateSize)
clear(memSlice)
state = (*_C_struct___res_state)(unsafe.Pointer(&memSlice[0]))
}
if err := ResNinit(state); err != nil {
return nil, errors.New("res_ninit failure: " + err.Error())
}
defer ResNclose(state)
bufSize := maxDNSPacketSize
buf := (*_C_uchar)(_C_malloc(uintptr(bufSize)))
defer _C_free(unsafe.Pointer(buf))
s, err := syscall.BytePtrFromString(hostname)
if err != nil {
return nil, err
}
var size int
for {
size, _ = ResNsearch(state, s, class, rtype, buf, bufSize)
if size <= bufSize || size > 0xffff {
break
}
// Allocate a bigger buffer to fit the entire msg.
_C_free(unsafe.Pointer(buf))
bufSize = size
buf = (*_C_uchar)(_C_malloc(uintptr(bufSize)))
}
var msg mDNS.Msg
if size == -1 {
// macOS's libresolv seems to directly return -1 for responses that are not success responses but are exchanged.
// However, we still need the response, so we fall back to parsing the entire buffer.
err = msg.Unpack(unsafe.Slice(buf, bufSize))
if err != nil {
return nil, E.New("res_nsearch failure")
}
} else {
err = msg.Unpack(unsafe.Slice(buf, size))
if err != nil {
return nil, err
}
}
return &msg, nil
}

View File

@ -0,0 +1,15 @@
//go:build !darwin
package local
import (
"context"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
)
func NewResolvTransport(ctx context.Context, logger log.ContextLogger, tag string) (adapter.DNSTransport, error) {
return nil, os.ErrInvalid
}

View File

@ -0,0 +1,72 @@
package local
import (
"context"
"net/netip"
"syscall"
"time"
"unsafe"
E "github.com/sagernet/sing/common/exceptions"
"github.com/miekg/dns"
)
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
resStateSize := unsafe.Sizeof(_C_struct___res_state{})
var state *_C_struct___res_state
if resStateSize > 0 {
mem := _C_malloc(resStateSize)
defer _C_free(mem)
memSlice := unsafe.Slice((*byte)(mem), resStateSize)
clear(memSlice)
state = (*_C_struct___res_state)(unsafe.Pointer(&memSlice[0]))
}
if err := ResNinit(state); err != nil {
return &dnsConfig{
servers: defaultNS,
search: dnsDefaultSearch(),
ndots: 1,
timeout: 5 * time.Second,
attempts: 2,
err: E.Cause(err, "libresolv initialization failed"),
}
}
defer ResNclose(state)
conf := &dnsConfig{
ndots: 1,
timeout: 5 * time.Second,
attempts: int(state.Retry),
}
for i := 0; i < int(state.Nscount); i++ {
addr := parseRawSockaddr(&state.Nsaddrlist[i])
if addr.IsValid() {
conf.servers = append(conf.servers, addr.String())
}
}
for i := 0; ; i++ {
search := state.Dnsrch[i]
if search == nil {
break
}
name := dns.Fqdn(GoString(search))
if name == "" {
continue
}
conf.search = append(conf.search, name)
}
return conf
}
func parseRawSockaddr(rawSockaddr *syscall.RawSockaddr) netip.Addr {
switch rawSockaddr.Family {
case syscall.AF_INET:
sa := (*syscall.RawSockaddrInet4)(unsafe.Pointer(rawSockaddr))
return netip.AddrFrom4(sa.Addr)
case syscall.AF_INET6:
sa := (*syscall.RawSockaddrInet6)(unsafe.Pointer(rawSockaddr))
return netip.AddrFrom16(sa.Addr)
default:
return netip.Addr{}
}
}

View File

@ -1,55 +0,0 @@
//go:build darwin && cgo
package local
/*
#include <stdlib.h>
#include <stdio.h>
#include <resolv.h>
#include <arpa/inet.h>
*/
import "C"
import (
"context"
"time"
E "github.com/sagernet/sing/common/exceptions"
"github.com/miekg/dns"
)
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
var state C.struct___res_state
if C.res_ninit(&state) != 0 {
return &dnsConfig{
servers: defaultNS,
search: dnsDefaultSearch(),
ndots: 1,
timeout: 5 * time.Second,
attempts: 2,
err: E.New("libresolv initialization failed"),
}
}
conf := &dnsConfig{
ndots: 1,
timeout: 5 * time.Second,
attempts: int(state.retry),
}
for i := 0; i < int(state.nscount); i++ {
ns := state.nsaddr_list[i]
addr := C.inet_ntoa(ns.sin_addr)
if addr == nil {
continue
}
conf.servers = append(conf.servers, C.GoString(addr))
}
for i := 0; ; i++ {
search := state.dnsrch[i]
if search == nil {
break
}
conf.search = append(conf.search, dns.Fqdn(C.GoString(search)))
}
return conf
}

View File

@ -1,4 +1,4 @@
//go:build !windows && !(darwin && cgo)
//go:build !windows && !darwin
package local

View File

@ -30,7 +30,7 @@ type TransportManager struct {
transportByTag map[string]adapter.DNSTransport
dependByTag map[string][]string
defaultTransport adapter.DNSTransport
defaultTransportFallback adapter.DNSTransport
defaultTransportFallback func() (adapter.DNSTransport, error)
fakeIPTransport adapter.FakeIPTransport
}
@ -45,7 +45,7 @@ func NewTransportManager(logger logger.ContextLogger, registry adapter.DNSTransp
}
}
func (m *TransportManager) Initialize(defaultTransportFallback adapter.DNSTransport) {
func (m *TransportManager) Initialize(defaultTransportFallback func() (adapter.DNSTransport, error)) {
m.defaultTransportFallback = defaultTransportFallback
}
@ -56,14 +56,27 @@ func (m *TransportManager) Start(stage adapter.StartStage) error {
}
m.started = true
m.stage = stage
transports := m.transports
m.access.Unlock()
if stage == adapter.StartStateStart {
if m.defaultTag != "" && m.defaultTransport == nil {
m.access.Unlock()
return E.New("default DNS server not found: ", m.defaultTag)
}
return m.startTransports(m.transports)
if m.defaultTransport == nil {
defaultTransport, err := m.defaultTransportFallback()
if err != nil {
m.access.Unlock()
return E.Cause(err, "default DNS server fallback")
}
m.transports = append(m.transports, defaultTransport)
m.transportByTag[defaultTransport.Tag()] = defaultTransport
m.defaultTransport = defaultTransport
}
transports := m.transports
m.access.Unlock()
return m.startTransports(transports)
} else {
transports := m.transports
m.access.Unlock()
for _, outbound := range transports {
err := adapter.LegacyStart(outbound, stage)
if err != nil {
@ -172,11 +185,7 @@ func (m *TransportManager) Transport(tag string) (adapter.DNSTransport, bool) {
func (m *TransportManager) Default() adapter.DNSTransport {
m.access.RLock()
defer m.access.RUnlock()
if m.defaultTransport != nil {
return m.defaultTransport
} else {
return m.defaultTransportFallback
}
return m.defaultTransport
}
func (m *TransportManager) FakeIP() adapter.FakeIPTransport {

View File

@ -2,6 +2,18 @@
icon: material/alert-decagram
---
#### 1.13.0-alpha.3
* Improve `local` DNS server **1**
* Fixes and improvements
**1**:
On Apple platforms, Windows, and Linux (when using systemd-resolved),
`local` DNS server now works with Tun inbound which overrides system DNS servers.
See [Local DNS Server](/configuration/dns/server/local/).
#### 1.13.0-alpha.2
* Add `preferred_by` rule item **1**

View File

@ -2,6 +2,10 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [prefer_go](#prefer_go)
!!! question "Since sing-box 1.12.0"
# Local
@ -15,6 +19,7 @@ icon: material/new-box
{
"type": "local",
"tag": "",
"prefer_go": false
// Dial Fields
}
@ -24,10 +29,31 @@ icon: material/new-box
```
!!! info "Difference from legacy local server"
* The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.
* The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
### Fields
#### prefer_go
!!! question "Since sing-box 1.13.0"
When enabled, `local` DNS server will resolve DNS by dialing itself whenever possible.
Specifically, it disables following behaviors which was added as features in sing-box 1.13.0:
* On Apple platforms: Use `libresolv` for resolution, as it is the only one that works properly with NetworkExtension
that overrides DNS servers (DHCP is also possible but is not considered).
* On Linux: Resolve through `systemd-resolvd`'s DBus interface when available.
As a sole exception, it cannot disable the following behavior:
In the Android graphical client, the `local` DNS server will always resolve DNS through the platform interface,
as there is no other way to obtain upstream DNS servers.
On devices running Android versions lower than 10, this interface can only resolve IP queries.
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.11.0"
# Endpoint

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.11.0 起"
# 端点

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.11.0"
### Structure

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.11.0 起"
### 结构

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [server_ports](#server_ports)

View File

@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [server_ports](#server_ports)

View File

@ -190,7 +190,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
}
}
remoteOptions := RemoteDNSServerOptions{
LocalDNSServerOptions: LocalDNSServerOptions{
RawLocalDNSServerOptions: RawLocalDNSServerOptions{
DialerOptions: DialerOptions{
Detour: options.Detour,
DomainResolver: &DomainResolveOptions{
@ -211,7 +211,7 @@ func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
switch serverType {
case C.DNSTypeLocal:
o.Type = C.DNSTypeLocal
o.Options = &remoteOptions.LocalDNSServerOptions
o.Options = &remoteOptions.RawLocalDNSServerOptions
case C.DNSTypeUDP:
o.Type = C.DNSTypeUDP
o.Options = &remoteOptions
@ -363,7 +363,7 @@ type HostsDNSServerOptions struct {
Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
}
type LocalDNSServerOptions struct {
type RawLocalDNSServerOptions struct {
DialerOptions
Legacy bool `json:"-"`
LegacyStrategy DomainStrategy `json:"-"`
@ -371,8 +371,13 @@ type LocalDNSServerOptions struct {
LegacyClientSubnet netip.Prefix `json:"-"`
}
type LocalDNSServerOptions struct {
RawLocalDNSServerOptions
PreferGo bool `json:"prefer_go,omitempty"`
}
type RemoteDNSServerOptions struct {
LocalDNSServerOptions
RawLocalDNSServerOptions
DNSServerAddressOptions
LegacyAddressResolver string `json:"-"`
LegacyAddressStrategy DomainStrategy `json:"-"`

View File

@ -312,7 +312,7 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
if r.interfaceMonitor == nil {
return nil
}
bindFunc := control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
return control.BindToInterfaceFunc(r.interfaceFinder, func(network string, address string) (interfaceName string, interfaceIndex int, err error) {
remoteAddr := M.ParseSocksaddr(address).Addr
if remoteAddr.IsValid() {
iif, err := r.interfaceFinder.ByAddr(remoteAddr)
@ -326,16 +326,6 @@ func (r *NetworkManager) AutoDetectInterfaceFunc() control.Func {
}
return defaultInterface.Name, defaultInterface.Index, nil
})
return func(network, address string, conn syscall.RawConn) error {
err := bindFunc(network, address, conn)
if err != nil {
return err
}
if r.autoRedirectOutputMark > 0 {
return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)
}
return nil
}
}
}
@ -366,6 +356,15 @@ func (r *NetworkManager) AutoRedirectOutputMark() uint32 {
return r.autoRedirectOutputMark
}
func (r *NetworkManager) AutoRedirectOutputMarkFunc() control.Func {
return func(network, address string, conn syscall.RawConn) error {
if r.autoRedirectOutputMark == 0 {
return nil
}
return control.RoutingMark(r.autoRedirectOutputMark)(network, address, conn)
}
}
func (r *NetworkManager) NetworkMonitor() tun.NetworkUpdateMonitor {
return r.networkMonitor
}