Compare commits

..

4 Commits

Author SHA1 Message Date
世界
1f1ba549fd Add cloudflared inbound support 2026-04-08 14:39:41 +08:00
世界
813b634d08 Bump version 2026-04-06 23:09:11 +08:00
hdrover
d9b435fb62 Fix naive inbound padding bytes 2026-04-06 22:33:11 +08:00
世界
354b4b040e sing: Fix vectorised readv iovec length calculation
This does not seem to affect any actual paths in the sing-box.
2026-04-01 16:16:58 +08:00
153 changed files with 1232 additions and 12715 deletions

View File

@@ -1 +1 @@
ea7cd33752aed62603775af3df946c1b83f4b0b3
2fef65f9dba90ddb89a87d00a6eb6165487c10c1

View File

@@ -1,21 +0,0 @@
package certificate
type Adapter struct {
providerType string
providerTag string
}
func NewAdapter(providerType string, providerTag string) Adapter {
return Adapter{
providerType: providerType,
providerTag: providerTag,
}
}
func (a *Adapter) Type() string {
return a.providerType
}
func (a *Adapter) Tag() string {
return a.providerTag
}

View File

@@ -1,158 +0,0 @@
package certificate
import (
"context"
"os"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
var _ adapter.CertificateProviderManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.CertificateProviderRegistry
access sync.Mutex
started bool
stage adapter.StartStage
providers []adapter.CertificateProviderService
providerByTag map[string]adapter.CertificateProviderService
}
func NewManager(logger log.ContextLogger, registry adapter.CertificateProviderRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
providerByTag: make(map[string]adapter.CertificateProviderService),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
providers := m.providers
m.access.Unlock()
for _, provider := range providers {
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
providers := m.providers
m.providers = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, provider := range providers {
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, provider.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return err
}
func (m *Manager) CertificateProviders() []adapter.CertificateProviderService {
m.access.Lock()
defer m.access.Unlock()
return m.providers
}
func (m *Manager) Get(tag string) (adapter.CertificateProviderService, bool) {
m.access.Lock()
provider, found := m.providerByTag[tag]
m.access.Unlock()
return provider, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
provider, found := m.providerByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.providerByTag, tag)
index := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
return it == provider
})
if index == -1 {
panic("invalid certificate provider index")
}
m.providers = append(m.providers[:index], m.providers[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return provider.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error {
provider, err := m.registry.Create(ctx, logger, tag, providerType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsProvider, loaded := m.providerByTag[tag]; loaded {
if m.started {
err = existsProvider.Close()
if err != nil {
return E.Cause(err, "close certificate-provider/", existsProvider.Type(), "[", existsProvider.Tag(), "]")
}
}
existsIndex := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
return it == existsProvider
})
if existsIndex == -1 {
panic("invalid certificate provider index")
}
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
}
m.providers = append(m.providers, provider)
m.providerByTag[tag] = provider
return nil
}

View File

@@ -1,72 +0,0 @@
package certificate
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.CertificateProviderService, error)
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
registry.register(providerType, func() any {
return new(Options)
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.CertificateProviderService, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.CertificateProviderRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.CertificateProviderService, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(providerType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[providerType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (adapter.CertificateProviderService, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[providerType]
if !loaded {
return nil, E.New("certificate provider type not found: " + providerType)
}
return constructor(ctx, logger, tag, options)
}
func (m *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[providerType] = optionsConstructor
m.constructor[providerType] = constructor
}

View File

@@ -1,38 +0,0 @@
package adapter
import (
"context"
"crypto/tls"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type CertificateProvider interface {
GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
}
type ACMECertificateProvider interface {
CertificateProvider
GetACMENextProtos() []string
}
type CertificateProviderService interface {
Lifecycle
Type() string
Tag() string
CertificateProvider
}
type CertificateProviderRegistry interface {
option.CertificateProviderOptionsRegistry
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (CertificateProviderService, error)
}
type CertificateProviderManager interface {
Lifecycle
CertificateProviders() []CertificateProviderService
Get(tag string) (CertificateProviderService, bool)
Remove(tag string) error
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error
}

View File

@@ -25,8 +25,8 @@ type DNSRouter interface {
type DNSClient interface {
Start()
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error)
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error)
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
ClearCache()
}
@@ -72,6 +72,11 @@ type DNSTransport interface {
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
}
type LegacyDNSTransport interface {
LegacyStrategy() C.DomainStrategy
LegacyClientSubnet() netip.Prefix
}
type DNSTransportRegistry interface {
option.DNSTransportOptionsRegistry
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)

View File

@@ -2,7 +2,6 @@ package adapter
import (
"context"
"net"
"net/netip"
"time"
@@ -10,8 +9,6 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
"github.com/miekg/dns"
)
type Inbound interface {
@@ -81,16 +78,12 @@ type InboundContext struct {
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DestinationAddresses []netip.Addr
DNSResponse *dns.Msg
DestinationAddressMatchFromResponse bool
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *ConnectionOwner
SourceMACAddress net.HardwareAddr
SourceHostname string
QueryType uint16
FakeIP bool
DestinationAddresses []netip.Addr
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *ConnectionOwner
QueryType uint16
FakeIP bool
// rule cache
@@ -99,9 +92,10 @@ type InboundContext struct {
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
DidMatch bool
IgnoreDestinationIPCIDRMatch bool
}
func (c *InboundContext) ResetRuleCache() {
@@ -118,39 +112,6 @@ func (c *InboundContext) ResetRuleMatchCache() {
c.DidMatch = false
}
func (c *InboundContext) DNSResponseAddressesForMatch() []netip.Addr {
return DNSResponseAddresses(c.DNSResponse)
}
func DNSResponseAddresses(response *dns.Msg) []netip.Addr {
if response == nil || response.Rcode != dns.RcodeSuccess {
return nil
}
addresses := make([]netip.Addr, 0, len(response.Answer))
for _, rawRecord := range response.Answer {
switch record := rawRecord.(type) {
case *dns.A:
addresses = append(addresses, M.AddrFromIP(record.A))
case *dns.AAAA:
addresses = append(addresses, M.AddrFromIP(record.AAAA))
case *dns.HTTPS:
for _, value := range record.SVCB.Value {
switch hint := value.(type) {
case *dns.SVCBIPv4Hint:
for _, ip := range hint.Hint {
addresses = append(addresses, M.AddrFromIP(ip).Unmap())
}
case *dns.SVCBIPv6Hint:
for _, ip := range hint.Hint {
addresses = append(addresses, M.AddrFromIP(ip))
}
}
}
}
}
return addresses
}
type inboundContextKey struct{}
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {

View File

@@ -1,45 +0,0 @@
package adapter
import (
"net"
"net/netip"
"testing"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
func TestDNSResponseAddressesUnmapsHTTPSIPv4Hints(t *testing.T) {
t.Parallel()
ipv4Hint := net.ParseIP("1.1.1.1")
require.NotNil(t, ipv4Hint)
response := &dns.Msg{
MsgHdr: dns.MsgHdr{
Response: true,
Rcode: dns.RcodeSuccess,
},
Answer: []dns.RR{
&dns.HTTPS{
SVCB: dns.SVCB{
Hdr: dns.RR_Header{
Name: dns.Fqdn("example.com"),
Rrtype: dns.TypeHTTPS,
Class: dns.ClassINET,
Ttl: 60,
},
Priority: 1,
Target: ".",
Value: []dns.SVCBKeyValue{
&dns.SVCBIPv4Hint{Hint: []net.IP{ipv4Hint}},
},
},
},
},
}
addresses := DNSResponseAddresses(response)
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
require.True(t, addresses[0].Is4())
}

View File

@@ -1,23 +0,0 @@
package adapter
import (
"net"
"net/netip"
)
type NeighborEntry struct {
Address netip.Addr
MACAddress net.HardwareAddr
Hostname string
}
type NeighborResolver interface {
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
LookupHostname(address netip.Addr) (string, bool)
Start() error
Close() error
}
type NeighborUpdateListener interface {
UpdateNeighborTable(entries []NeighborEntry)
}

View File

@@ -36,10 +36,6 @@ type PlatformInterface interface {
UsePlatformNotification() bool
SendNotification(notification *Notification) error
UsePlatformNeighborResolver() bool
StartNeighborMonitor(listener NeighborUpdateListener) error
CloseNeighborMonitor(listener NeighborUpdateListener) error
}
type FindConnectionOwnerRequest struct {

View File

@@ -26,8 +26,6 @@ type Router interface {
RuleSet(tag string) (RuleSet, bool)
Rules() []Rule
NeedFindProcess() bool
NeedFindNeighbor() bool
NeighborResolver() NeighborResolver
AppendTracker(tracker ConnectionTracker)
ResetNetwork()
}
@@ -66,14 +64,10 @@ type RuleSet interface {
type RuleSetUpdateCallback func(it RuleSet)
// Rule-set metadata only exposes headless-rule capabilities that outer routers
// need before evaluating nested matches. Headless rules do not support
// ip_version, so there is intentionally no ContainsIPVersionRule flag here.
type RuleSetMetadata struct {
ContainsProcessRule bool
ContainsWIFIRule bool
ContainsIPCIDRRule bool
ContainsDNSQueryTypeRule bool
ContainsProcessRule bool
ContainsWIFIRule bool
ContainsIPCIDRRule bool
}
type HTTPStartContext struct {
ctx context.Context

View File

@@ -2,8 +2,6 @@ package adapter
import (
C "github.com/sagernet/sing-box/constant"
"github.com/miekg/dns"
)
type HeadlessRule interface {
@@ -20,9 +18,8 @@ type Rule interface {
type DNSRule interface {
Rule
LegacyPreMatch(metadata *InboundContext) bool
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
MatchAddressLimit(metadata *InboundContext) bool
}
type RuleAction interface {

125
box.go
View File

@@ -9,7 +9,6 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
boxCertificate "github.com/sagernet/sing-box/adapter/certificate"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
@@ -38,21 +37,20 @@ import (
var _ adapter.SimpleLifecycle = (*Box)(nil)
type Box struct {
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
service *boxService.Manager
certificateProvider *boxCertificate.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
internalService []adapter.LifecycleService
done chan struct{}
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
service *boxService.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
internalService []adapter.LifecycleService
done chan struct{}
}
type Options struct {
@@ -68,7 +66,6 @@ func Context(
endpointRegistry adapter.EndpointRegistry,
dnsTransportRegistry adapter.DNSTransportRegistry,
serviceRegistry adapter.ServiceRegistry,
certificateProviderRegistry adapter.CertificateProviderRegistry,
) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
@@ -93,10 +90,6 @@ func Context(
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
}
if service.FromContext[adapter.CertificateProviderRegistry](ctx) == nil {
ctx = service.ContextWith[option.CertificateProviderOptionsRegistry](ctx, certificateProviderRegistry)
ctx = service.ContextWith[adapter.CertificateProviderRegistry](ctx, certificateProviderRegistry)
}
return ctx
}
@@ -113,7 +106,6 @@ func New(options Options) (*Box, error) {
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
certificateProviderRegistry := service.FromContext[adapter.CertificateProviderRegistry](ctx)
if endpointRegistry == nil {
return nil, E.New("missing endpoint registry in context")
@@ -130,9 +122,6 @@ func New(options Options) (*Box, error) {
if serviceRegistry == nil {
return nil, E.New("missing service registry in context")
}
if certificateProviderRegistry == nil {
return nil, E.New("missing certificate provider registry in context")
}
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
@@ -190,13 +179,11 @@ func New(options Options) (*Box, error) {
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
certificateProviderManager := boxCertificate.NewManager(logFactory.NewLogger("certificate-provider"), certificateProviderRegistry)
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
@@ -285,24 +272,6 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
}
for i, serviceOptions := range options.Services {
var tag string
if serviceOptions.Tag != "" {
tag = serviceOptions.Tag
} else {
tag = F.ToString(i)
}
err = serviceManager.Create(
ctx,
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
tag,
serviceOptions.Type,
serviceOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize service[", i, "]")
}
}
for i, outboundOptions := range options.Outbounds {
var tag string
if outboundOptions.Tag != "" {
@@ -329,22 +298,22 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize outbound[", i, "]")
}
}
for i, certificateProviderOptions := range options.CertificateProviders {
for i, serviceOptions := range options.Services {
var tag string
if certificateProviderOptions.Tag != "" {
tag = certificateProviderOptions.Tag
if serviceOptions.Tag != "" {
tag = serviceOptions.Tag
} else {
tag = F.ToString(i)
}
err = certificateProviderManager.Create(
err = serviceManager.Create(
ctx,
logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")),
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
tag,
certificateProviderOptions.Type,
certificateProviderOptions.Options,
serviceOptions.Type,
serviceOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize certificate provider[", i, "]")
return nil, E.Cause(err, "initialize service[", i, "]")
}
}
outboundManager.Initialize(func() (adapter.Outbound, error) {
@@ -414,21 +383,20 @@ func New(options Options) (*Box, error) {
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
}
return &Box{
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
service: serviceManager,
certificateProvider: certificateProviderManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
internalService: internalServices,
done: make(chan struct{}),
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
service: serviceManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
internalService: internalServices,
done: make(chan struct{}),
}, nil
}
@@ -482,11 +450,11 @@ func (s *Box) preStart() error {
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service, s.certificateProvider)
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection, s.router, s.dnsRouter)
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
if err != nil {
return err
}
@@ -502,19 +470,11 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint)
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.certificateProvider)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.service)
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.endpoint, s.certificateProvider, s.inbound, s.service)
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
@@ -522,7 +482,7 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.endpoint, s.certificateProvider, s.inbound, s.service)
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
if err != nil {
return err
}
@@ -546,9 +506,8 @@ func (s *Box) Close() error {
service adapter.Lifecycle
}{
{"service", s.service},
{"inbound", s.inbound},
{"certificate-provider", s.certificateProvider},
{"endpoint", s.endpoint},
{"inbound", s.inbound},
{"outbound", s.outbound},
{"router", s.router},
{"connection", s.connection},

View File

@@ -38,6 +38,37 @@ func (w *acmeWrapper) Close() error {
return nil
}
type acmeLogWriter struct {
logger logger.Logger
}
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
logLine := strings.ReplaceAll(string(p), " ", ": ")
switch {
case strings.HasPrefix(logLine, "error: "):
w.logger.Error(logLine[7:])
case strings.HasPrefix(logLine, "warn: "):
w.logger.Warn(logLine[6:])
case strings.HasPrefix(logLine, "info: "):
w.logger.Info(logLine[6:])
case strings.HasPrefix(logLine, "debug: "):
w.logger.Debug(logLine[7:])
default:
w.logger.Debug(logLine)
}
return len(p), nil
}
func (w *acmeLogWriter) Sync() error {
return nil
}
func encoderConfig() zapcore.EncoderConfig {
config := zap.NewProductionEncoderConfig()
config.TimeKey = zapcore.OmitKey
return config
}
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
var acmeServer string
switch options.Provider {
@@ -60,8 +91,8 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
storage = certmagic.Default.Storage
}
zapLogger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(ACMEEncoderConfig()),
&ACMELogWriter{Logger: logger},
zapcore.NewConsoleEncoder(encoderConfig()),
&acmeLogWriter{logger: logger},
zap.DebugLevel,
))
config := &certmagic.Config{
@@ -127,7 +158,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
} else {
tlsConfig = &tls.Config{
GetCertificate: config.GetCertificate,
NextProtos: []string{C.ACMETLS1Protocol},
NextProtos: []string{ACMETLS1Protocol},
}
}
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil

View File

@@ -1,3 +1,3 @@
package constant
package tls
const ACMETLS1Protocol = "acme-tls/1"

View File

@@ -1,41 +0,0 @@
package tls
import (
"strings"
"github.com/sagernet/sing/common/logger"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type ACMELogWriter struct {
Logger logger.Logger
}
func (w *ACMELogWriter) Write(p []byte) (n int, err error) {
logLine := strings.ReplaceAll(string(p), " ", ": ")
switch {
case strings.HasPrefix(logLine, "error: "):
w.Logger.Error(logLine[7:])
case strings.HasPrefix(logLine, "warn: "):
w.Logger.Warn(logLine[6:])
case strings.HasPrefix(logLine, "info: "):
w.Logger.Info(logLine[6:])
case strings.HasPrefix(logLine, "debug: "):
w.Logger.Debug(logLine[7:])
default:
w.Logger.Debug(logLine)
}
return len(p), nil
}
func (w *ACMELogWriter) Sync() error {
return nil
}
func ACMEEncoderConfig() zapcore.EncoderConfig {
config := zap.NewProductionEncoderConfig()
config.TimeKey = zapcore.OmitKey
return config
}

View File

@@ -32,10 +32,6 @@ type RealityServerConfig struct {
func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
var tlsConfig utls.RealityConfig
if options.CertificateProvider != nil {
return nil, E.New("certificate_provider is unavailable in reality")
}
//nolint:staticcheck
if options.ACME != nil && len(options.ACME.Domain) > 0 {
return nil, E.New("acme is unavailable in reality")
}

View File

@@ -13,87 +13,19 @@ import (
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service"
)
var errInsecureUnused = E.New("tls: insecure unused")
type managedCertificateProvider interface {
adapter.CertificateProvider
adapter.SimpleLifecycle
}
type sharedCertificateProvider struct {
tag string
manager adapter.CertificateProviderManager
provider adapter.CertificateProviderService
}
func (p *sharedCertificateProvider) Start() error {
provider, found := p.manager.Get(p.tag)
if !found {
return E.New("certificate provider not found: ", p.tag)
}
p.provider = provider
return nil
}
func (p *sharedCertificateProvider) Close() error {
return nil
}
func (p *sharedCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
return p.provider.GetCertificate(hello)
}
func (p *sharedCertificateProvider) GetACMENextProtos() []string {
return getACMENextProtos(p.provider)
}
type inlineCertificateProvider struct {
provider adapter.CertificateProviderService
}
func (p *inlineCertificateProvider) Start() error {
for _, stage := range adapter.ListStartStages {
err := adapter.LegacyStart(p.provider, stage)
if err != nil {
return err
}
}
return nil
}
func (p *inlineCertificateProvider) Close() error {
return p.provider.Close()
}
func (p *inlineCertificateProvider) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
return p.provider.GetCertificate(hello)
}
func (p *inlineCertificateProvider) GetACMENextProtos() []string {
return getACMENextProtos(p.provider)
}
func getACMENextProtos(provider adapter.CertificateProvider) []string {
if acmeProvider, isACME := provider.(adapter.ACMECertificateProvider); isACME {
return acmeProvider.GetACMENextProtos()
}
return nil
}
type STDServerConfig struct {
access sync.RWMutex
config *tls.Config
logger log.Logger
certificateProvider managedCertificateProvider
acmeService adapter.SimpleLifecycle
certificate []byte
key []byte
@@ -121,17 +53,18 @@ func (c *STDServerConfig) SetServerName(serverName string) {
func (c *STDServerConfig) NextProtos() []string {
c.access.RLock()
defer c.access.RUnlock()
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
return c.config.NextProtos[1:]
} else {
return c.config.NextProtos
}
return c.config.NextProtos
}
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
c.access.Lock()
defer c.access.Unlock()
config := c.config.Clone()
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
} else {
config.NextProtos = nextProto
@@ -139,18 +72,6 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
c.config = config
}
func (c *STDServerConfig) hasACMEALPN() bool {
if c.acmeService != nil {
return true
}
if c.certificateProvider != nil {
if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME {
return len(acmeProvider.GetACMENextProtos()) > 0
}
}
return false
}
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
return c.config, nil
}
@@ -170,39 +91,15 @@ func (c *STDServerConfig) Clone() Config {
}
func (c *STDServerConfig) Start() error {
if c.certificateProvider != nil {
err := c.certificateProvider.Start()
if err != nil {
return err
}
if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME {
nextProtos := acmeProvider.GetACMENextProtos()
if len(nextProtos) > 0 {
c.access.Lock()
config := c.config.Clone()
mergedNextProtos := append([]string{}, nextProtos...)
for _, nextProto := range config.NextProtos {
if !common.Contains(mergedNextProtos, nextProto) {
mergedNextProtos = append(mergedNextProtos, nextProto)
}
}
config.NextProtos = mergedNextProtos
c.config = config
c.access.Unlock()
}
}
}
if c.acmeService != nil {
err := c.acmeService.Start()
return c.acmeService.Start()
} else {
err := c.startWatcher()
if err != nil {
return err
c.logger.Warn("create fsnotify watcher: ", err)
}
return nil
}
err := c.startWatcher()
if err != nil {
c.logger.Warn("create fsnotify watcher: ", err)
}
return nil
}
func (c *STDServerConfig) startWatcher() error {
@@ -306,34 +203,23 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
}
func (c *STDServerConfig) Close() error {
return common.Close(c.certificateProvider, c.acmeService, c.watcher)
if c.acmeService != nil {
return c.acmeService.Close()
}
if c.watcher != nil {
return c.watcher.Close()
}
return nil
}
func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
if !options.Enabled {
return nil, nil
}
//nolint:staticcheck
if options.CertificateProvider != nil && options.ACME != nil {
return nil, E.New("certificate_provider and acme are mutually exclusive")
}
var tlsConfig *tls.Config
var certificateProvider managedCertificateProvider
var acmeService adapter.SimpleLifecycle
var err error
if options.CertificateProvider != nil {
certificateProvider, err = newCertificateProvider(ctx, logger, options.CertificateProvider)
if err != nil {
return nil, err
}
tlsConfig = &tls.Config{
GetCertificate: certificateProvider.GetCertificate,
}
if options.Insecure {
return nil, errInsecureUnused
}
} else if options.ACME != nil && len(options.ACME.Domain) > 0 { //nolint:staticcheck
deprecated.Report(ctx, deprecated.OptionInlineACME)
if options.ACME != nil && len(options.ACME.Domain) > 0 {
//nolint:staticcheck
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
if err != nil {
@@ -386,7 +272,7 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
certificate []byte
key []byte
)
if certificateProvider == nil && acmeService == nil {
if acmeService == nil {
if len(options.Certificate) > 0 {
certificate = []byte(strings.Join(options.Certificate, "\n"))
} else if options.CertificatePath != "" {
@@ -474,7 +360,6 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
serverConfig := &STDServerConfig{
config: tlsConfig,
logger: logger,
certificateProvider: certificateProvider,
acmeService: acmeService,
certificate: certificate,
key: key,
@@ -484,8 +369,8 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
echKeyPath: echKeyPath,
}
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
serverConfig.access.RLock()
defer serverConfig.access.RUnlock()
serverConfig.access.Lock()
defer serverConfig.access.Unlock()
return serverConfig.config, nil
}
var config ServerConfig = serverConfig
@@ -502,27 +387,3 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
}
return config, nil
}
func newCertificateProvider(ctx context.Context, logger log.ContextLogger, options *option.CertificateProviderOptions) (managedCertificateProvider, error) {
if options.IsShared() {
manager := service.FromContext[adapter.CertificateProviderManager](ctx)
if manager == nil {
return nil, E.New("missing certificate provider manager in context")
}
return &sharedCertificateProvider{
tag: options.Tag,
manager: manager,
}, nil
}
registry := service.FromContext[adapter.CertificateProviderRegistry](ctx)
if registry == nil {
return nil, E.New("missing certificate provider registry in context")
}
provider, err := registry.Create(ctx, logger, "", options.Type, options.Options)
if err != nil {
return nil, E.Cause(err, "create inline certificate provider")
}
return &inlineCertificateProvider{
provider: provider,
}, nil
}

View File

@@ -15,18 +15,19 @@ const (
)
const (
DNSTypeLegacy = "legacy"
DNSTypeUDP = "udp"
DNSTypeTCP = "tcp"
DNSTypeTLS = "tls"
DNSTypeHTTPS = "https"
DNSTypeQUIC = "quic"
DNSTypeHTTP3 = "h3"
DNSTypeLocal = "local"
DNSTypeHosts = "hosts"
DNSTypeFakeIP = "fakeip"
DNSTypeDHCP = "dhcp"
DNSTypeTailscale = "tailscale"
DNSTypeLegacy = "legacy"
DNSTypeLegacyRcode = "legacy_rcode"
DNSTypeUDP = "udp"
DNSTypeTCP = "tcp"
DNSTypeTLS = "tls"
DNSTypeHTTPS = "https"
DNSTypeQUIC = "quic"
DNSTypeHTTP3 = "h3"
DNSTypeLocal = "local"
DNSTypeHosts = "hosts"
DNSTypeFakeIP = "fakeip"
DNSTypeDHCP = "dhcp"
DNSTypeTailscale = "tailscale"
)
const (

View File

@@ -1,38 +1,37 @@
package constant
const (
TypeTun = "tun"
TypeRedirect = "redirect"
TypeTProxy = "tproxy"
TypeDirect = "direct"
TypeBlock = "block"
TypeDNS = "dns"
TypeSOCKS = "socks"
TypeHTTP = "http"
TypeMixed = "mixed"
TypeShadowsocks = "shadowsocks"
TypeVMess = "vmess"
TypeTrojan = "trojan"
TypeNaive = "naive"
TypeWireGuard = "wireguard"
TypeHysteria = "hysteria"
TypeTor = "tor"
TypeSSH = "ssh"
TypeShadowTLS = "shadowtls"
TypeAnyTLS = "anytls"
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2"
TypeTailscale = "tailscale"
TypeDERP = "derp"
TypeResolved = "resolved"
TypeSSMAPI = "ssm-api"
TypeCCM = "ccm"
TypeOCM = "ocm"
TypeOOMKiller = "oom-killer"
TypeACME = "acme"
TypeCloudflareOriginCA = "cloudflare-origin-ca"
TypeTun = "tun"
TypeRedirect = "redirect"
TypeTProxy = "tproxy"
TypeDirect = "direct"
TypeBlock = "block"
TypeDNS = "dns"
TypeSOCKS = "socks"
TypeHTTP = "http"
TypeMixed = "mixed"
TypeShadowsocks = "shadowsocks"
TypeVMess = "vmess"
TypeTrojan = "trojan"
TypeNaive = "naive"
TypeWireGuard = "wireguard"
TypeHysteria = "hysteria"
TypeTor = "tor"
TypeSSH = "ssh"
TypeShadowTLS = "shadowtls"
TypeAnyTLS = "anytls"
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2"
TypeTailscale = "tailscale"
TypeCloudflared = "cloudflared"
TypeDERP = "derp"
TypeResolved = "resolved"
TypeSSMAPI = "ssm-api"
TypeCCM = "ccm"
TypeOCM = "ocm"
TypeOOMKiller = "oom-killer"
)
const (
@@ -90,6 +89,8 @@ func ProxyDisplayName(proxyType string) string {
return "AnyTLS"
case TypeTailscale:
return "Tailscale"
case TypeCloudflared:
return "Cloudflared"
case TypeSelector:
return "Selector"
case TypeURLTest:

View File

@@ -29,7 +29,6 @@ const (
const (
RuleActionTypeRoute = "route"
RuleActionTypeRouteOptions = "route-options"
RuleActionTypeEvaluate = "evaluate"
RuleActionTypeDirect = "direct"
RuleActionTypeBypass = "bypass"
RuleActionTypeReject = "reject"

View File

@@ -5,6 +5,7 @@ import (
"errors"
"net"
"net/netip"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -13,6 +14,7 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/task"
"github.com/sagernet/sing/contrab/freelru"
"github.com/sagernet/sing/contrab/maphash"
@@ -107,7 +109,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
return 0, false
}
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) {
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
if len(message.Question) == 0 {
if c.logger != nil {
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
@@ -237,10 +239,13 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
if responseChecker != nil {
var rejected bool
// TODO: add accept_any rule and support to check response instead of addresses
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
rejected = true
} else if len(response.Answer) == 0 {
rejected = !responseChecker(nil)
} else {
rejected = !responseChecker(response)
rejected = !responseChecker(MessageToAddresses(response))
}
if rejected {
if !disableCache && c.rdrc != nil {
@@ -310,7 +315,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
return response, nil
}
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
domain = FqdnToDomain(domain)
dnsName := dns.Fqdn(domain)
var strategy C.DomainStrategy
@@ -395,7 +400,7 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
}
}
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
question := dns.Question{
Name: name,
Qtype: qType,
@@ -510,7 +515,25 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
}
func MessageToAddresses(response *dns.Msg) []netip.Addr {
return adapter.DNSResponseAddresses(response)
if response == nil || response.Rcode != dns.RcodeSuccess {
return nil
}
addresses := make([]netip.Addr, 0, len(response.Answer))
for _, rawAnswer := range response.Answer {
switch answer := rawAnswer.(type) {
case *dns.A:
addresses = append(addresses, M.AddrFromIP(answer.A))
case *dns.AAAA:
addresses = append(addresses, M.AddrFromIP(answer.AAAA))
case *dns.HTTPS:
for _, value := range answer.SVCB.Value {
if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {
addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...)
}
}
}
}
return addresses
}
func wrapError(err error) error {

View File

@@ -1,111 +0,0 @@
package dns
import (
"context"
"net/netip"
"testing"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json/badoption"
mDNS "github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
func TestReproLookupWithRulesUsesRequestStrategy(t *testing.T) {
t.Parallel()
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
var qTypes []uint16
router := newTestRouter(t, nil, &fakeDNSTransportManager{
defaultTransport: defaultTransport,
transports: map[string]adapter.DNSTransport{
"default": defaultTransport,
},
}, &fakeDNSClient{
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
qTypes = append(qTypes, message.Question[0].Qtype)
if message.Question[0].Qtype == mDNS.TypeA {
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
}
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
},
})
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
Strategy: C.DomainStrategyIPv4Only,
})
require.NoError(t, err)
require.Equal(t, []uint16{mDNS.TypeA}, qTypes)
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
}
func TestReproLogicalMatchResponseIPCIDR(t *testing.T) {
t.Parallel()
transportManager := &fakeDNSTransportManager{
defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
transports: map[string]adapter.DNSTransport{
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
"selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP},
"default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
},
}
client := &fakeDNSClient{
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
switch transport.Tag() {
case "upstream":
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
case "selected":
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil
default:
return nil, E.New("unexpected transport")
}
},
}
rules := []option.DNSRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
Domain: badoption.Listable[string]{"example.com"},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeEvaluate,
RouteOptions: option.DNSRouteActionOptions{Server: "upstream"},
},
},
},
{
Type: C.RuleTypeLogical,
LogicalOptions: option.LogicalDNSRule{
RawLogicalDNSRule: option.RawLogicalDNSRule{
Mode: C.LogicalTypeOr,
Rules: []option.DNSRule{{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
RawDefaultDNSRule: option.RawDefaultDNSRule{
MatchResponse: true,
IPCIDR: badoption.Listable[string]{"1.1.1.0/24"},
},
},
}},
},
DNSRuleAction: option.DNSRuleAction{
Action: C.RuleActionTypeRoute,
RouteOptions: option.DNSRouteActionOptions{Server: "selected"},
},
},
},
}
router := newTestRouter(t, rules, transportManager, client)
response, err := router.Exchange(context.Background(), &mDNS.Msg{
Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)},
}, adapter.DNSQueryOptions{})
require.NoError(t, err)
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,21 @@
package dns
import (
"net/netip"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
)
var _ adapter.LegacyDNSTransport = (*TransportAdapter)(nil)
type TransportAdapter struct {
transportType string
transportTag string
dependencies []string
strategy C.DomainStrategy
clientSubnet netip.Prefix
}
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
@@ -27,6 +35,8 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
transportType: transportType,
transportTag: transportTag,
dependencies: dependencies,
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
clientSubnet: localOptions.LegacyClientSubnet,
}
}
@@ -35,10 +45,15 @@ func NewTransportAdapterWithRemoteOptions(transportType string, transportTag str
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
}
if remoteOptions.LegacyAddressResolver != "" {
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
}
return TransportAdapter{
transportType: transportType,
transportTag: transportTag,
dependencies: dependencies,
strategy: C.DomainStrategy(remoteOptions.LegacyStrategy),
clientSubnet: remoteOptions.LegacyClientSubnet,
}
}
@@ -53,3 +68,11 @@ func (a *TransportAdapter) Tag() string {
func (a *TransportAdapter) Dependencies() []string {
return a.dependencies
}
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
return a.strategy
}
func (a *TransportAdapter) LegacyClientSubnet() netip.Prefix {
return a.clientSubnet
}

View File

@@ -2,25 +2,104 @@ package dns
import (
"context"
"net"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
DirectResolver: true,
})
if options.LegacyDefaultDialer {
return dialer.NewDefaultOutbound(ctx), nil
} else {
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
DirectResolver: true,
LegacyDNSDialer: options.Legacy,
})
}
}
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain(),
DirectResolver: true,
})
if options.LegacyDefaultDialer {
transportDialer := dialer.NewDefaultOutbound(ctx)
if options.LegacyAddressResolver != "" {
transport := service.FromContext[adapter.DNSTransportManager](ctx)
resolverTransport, loaded := transport.Transport(options.LegacyAddressResolver)
if !loaded {
return nil, E.New("address resolver not found: ", options.LegacyAddressResolver)
}
transportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay))
} else if options.ServerIsDomain() {
return nil, E.New("missing address resolver for server: ", options.Server)
}
return transportDialer, nil
} else {
return dialer.NewWithOptions(dialer.Options{
Context: ctx,
Options: options.DialerOptions,
RemoteIsDomain: options.ServerIsDomain(),
DirectResolver: true,
LegacyDNSDialer: options.Legacy,
})
}
}
type legacyTransportDialer struct {
dialer N.Dialer
dnsRouter adapter.DNSRouter
transport adapter.DNSTransport
strategy C.DomainStrategy
fallbackDelay time.Duration
}
func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {
return &legacyTransportDialer{
dialer,
dnsRouter,
transport,
strategy,
fallbackDelay,
}
}
func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if destination.IsIP() {
return d.dialer.DialContext(ctx, network, destination)
}
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
Transport: d.transport,
Strategy: d.strategy,
})
if err != nil {
return nil, err
}
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
}
func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if destination.IsIP() {
return d.dialer.ListenPacket(ctx, destination)
}
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
Transport: d.transport,
Strategy: d.strategy,
})
if err != nil {
return nil, err
}
conn, _, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
return conn, err
}
func (d *legacyTransportDialer) Upstream() any {
return d.dialer
}

View File

@@ -2,16 +2,7 @@
icon: material/alert-decagram
---
#### 1.14.0-alpha.8
* Add BBR profile and hop interval randomization for Hysteria2 **1**
* Fixes and improvements
**1**:
See [Hysteria2 Inbound](/configuration/inbound/hysteria2/#bbr_profile) and [Hysteria2 Outbound](/configuration/outbound/hysteria2/#bbr_profile).
#### 1.14.0-alpha.8
#### 1.13.6
* Fixes and improvements
@@ -19,33 +10,10 @@ See [Hysteria2 Inbound](/configuration/inbound/hysteria2/#bbr_profile) and [Hyst
* Fixes and improvements
#### 1.14.0-alpha.7
* Fixes and improvements
#### 1.13.4
* Fixes and improvements
#### 1.14.0-alpha.4
* Refactor ACME support to certificate provider system **1**
* Add Cloudflare Origin CA certificate provider **2**
* Add Tailscale certificate provider **3**
* Fixes and improvements
**1**:
See [Certificate Provider](/configuration/shared/certificate-provider/) and [Migration](/migration/#migrate-inline-acme-to-certificate-provider).
**2**:
See [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca).
**3**:
See [Tailscale](/configuration/shared/certificate-provider/tailscale).
#### 1.13.3
* Add OpenWrt and Alpine APK packages to release **1**
@@ -70,59 +38,6 @@ from [SagerNet/go](https://github.com/SagerNet/go).
See [OCM](/configuration/service/ocm).
#### 1.12.24
* Fixes and improvements
#### 1.14.0-alpha.2
* Add OpenWrt and Alpine APK packages to release **1**
* Backport to macOS 10.13 High Sierra **2**
* OCM service: Add WebSocket support for Responses API **3**
* Fixes and improvements
**1**:
Alpine APK files use `linux` in the filename to distinguish from OpenWrt APKs which use the `openwrt` prefix:
- OpenWrt: `sing-box_{version}_openwrt_{architecture}.apk`
- Alpine: `sing-box_{version}_linux_{architecture}.apk`
**2**:
Legacy macOS binaries (with `-legacy-macos-10.13` suffix) now support
macOS 10.13 High Sierra, built using Go 1.25 with patches
from [SagerNet/go](https://github.com/SagerNet/go).
**3**:
See [OCM](/configuration/service/ocm).
#### 1.14.0-alpha.1
* Add `source_mac_address` and `source_hostname` rule items **1**
* Add `include_mac_address` and `exclude_mac_address` TUN options **2**
* Update NaiveProxy to 145.0.7632.159 **3**
* Fixes and improvements
**1**:
New rule items for matching LAN devices by MAC address and hostname via neighbor resolution.
Supported on Linux, macOS, or in graphical clients on Android and macOS.
See [Route Rule](/configuration/route/rule/#source_mac_address), [DNS Rule](/configuration/dns/rule/#source_mac_address) and [Neighbor Resolution](/configuration/shared/neighbor/).
**2**:
Limit or exclude devices from TUN routing by MAC address.
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
See [TUN](/configuration/inbound/tun/#include_mac_address).
**3**:
This is not an official update from NaiveProxy. Instead, it's a Chromium codebase update maintained by Project S.
#### 1.13.2
* Fixes and improvements

View File

@@ -1,10 +1,10 @@
---
icon: material/note-remove
icon: material/delete-clock
---
!!! failure "Removed in sing-box 1.14.0"
!!! failure "Deprecated in sing-box 1.12.0"
Legacy fake-ip configuration is deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
### Structure

View File

@@ -1,10 +1,10 @@
---
icon: material/note-remove
icon: material/delete-clock
---
!!! failure "已在 sing-box 1.14.0 移除"
!!! failure "已在 sing-box 1.12.0 废弃"
旧的 fake-ip 配置已在 sing-box 1.12.0 废弃且在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
旧的 fake-ip 配置已废弃且在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
### 结构

View File

@@ -39,7 +39,7 @@ icon: material/alert-decagram
|----------|---------------------------------|
| `server` | List of [DNS Server](./server/) |
| `rules` | List of [DNS Rule](./rule/) |
| `fakeip` | :material-note-remove: [FakeIP](./fakeip/) |
| `fakeip` | [FakeIP](./fakeip/) |
#### final
@@ -88,4 +88,4 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overridden by `servers.[].client_subnet` or `rules.[].client_subnet`.
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.

View File

@@ -88,6 +88,6 @@ LRU 缓存容量。
可以被 `servers.[].client_subnet``rules.[].client_subnet` 覆盖。
#### fakeip :material-note-remove:
#### fakeip
[FakeIP](./fakeip/) 设置。

View File

@@ -2,18 +2,6 @@
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [match_response](#match_response)
:material-plus: [response_rcode](#response_rcode)
:material-plus: [response_answer](#response_answer)
:material-plus: [response_ns](#response_ns)
:material-plus: [response_extra](#response_extra)
:material-plus: [source_mac_address](#source_mac_address)
:material-plus: [source_hostname](#source_hostname)
:material-delete-clock: [ip_accept_any](#ip_accept_any)
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [interface_address](#interface_address)
@@ -101,6 +89,12 @@ icon: material/alert-decagram
"192.168.0.1"
],
"source_ip_is_private": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"ip_accept_any": false,
"source_port": [
12345
],
@@ -155,12 +149,6 @@ icon: material/alert-decagram
"default_interface_address": [
"2000::/3"
],
"source_mac_address": [
"00:11:22:33:44:55"
],
"source_hostname": [
"my-device"
],
"wifi_ssid": [
"My WIFI"
],
@@ -172,16 +160,7 @@ icon: material/alert-decagram
"geosite-cn"
],
"rule_set_ip_cidr_match_source": false,
"match_response": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"response_rcode": "",
"response_answer": [],
"response_ns": [],
"response_extra": [],
"rule_set_ip_cidr_accept_empty": false,
"invert": false,
"outbound": [
"direct"
@@ -190,9 +169,7 @@ icon: material/alert-decagram
"server": "local",
// Deprecated
"ip_accept_any": false,
"rule_set_ip_cidr_accept_empty": false,
"rule_set_ipcidr_match_source": false,
"geosite": [
"cn"
@@ -431,26 +408,6 @@ Matches network interface (same values as `network_type`) address.
Match default interface address.
#### source_mac_address
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device MAC address.
#### source_hostname
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device hostname from DHCP leases.
#### wifi_ssid
!!! quote ""
@@ -489,17 +446,6 @@ Make `ip_cidr` rule items in rule-sets match the source IP.
Make `ip_cidr` rule items in rule-sets match the source IP.
#### match_response
!!! question "Since sing-box 1.14.0"
Enable response-based matching. When enabled, this rule matches against DNS response data
(set by a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action)
instead of only matching the original query.
Required for `response_rcode`, `response_answer`, `response_ns`, `response_extra` fields.
Also required for `ip_cidr` and `ip_is_private` when `legacyDNSMode` is disabled.
#### invert
Invert match result.
@@ -544,15 +490,6 @@ See [DNS Rule Actions](../rule_action/) for details.
Moved to [DNS Rule Action](../rule_action#route).
### Legacy DNS Mode
`legacyDNSMode` is an internal compatibility mode that is automatically detected from your DNS rule
configuration. It is disabled when any rule uses features introduced in sing-box 1.14.0 such as
`evaluate`, `match_response`, response fields (`response_rcode`, `response_answer`, etc.),
`query_type`, or `ip_version`. When disabled, `ip_cidr` and `ip_is_private` require `match_response`
to be set, and deprecated fields like `strategy`, `ip_accept_any`, and `rule_set_ip_cidr_accept_empty`
are no longer accepted.
### Address Filter Fields
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
@@ -579,69 +516,24 @@ Match GeoIP with query response.
Match IP CIDR with query response.
When `legacyDNSMode` is disabled, `match_response` must be set to `true`.
#### ip_is_private
!!! question "Since sing-box 1.9.0"
Match private IP with query response.
When `legacyDNSMode` is disabled, `match_response` must be set to `true`.
#### rule_set_ip_cidr_accept_empty
!!! question "Since sing-box 1.10.0"
!!! failure "Deprecated in sing-box 1.14.0"
`rule_set_ip_cidr_accept_empty` is deprecated and will be removed in sing-box 1.16.0.
Only supported in `legacyDNSMode`.
Make `ip_cidr` rules in rule-sets accept empty query response.
#### ip_accept_any
!!! question "Since sing-box 1.12.0"
!!! failure "Deprecated in sing-box 1.14.0"
`ip_accept_any` is deprecated and will be removed in sing-box 1.16.0.
Only supported in `legacyDNSMode`. Use `match_response` with response items instead.
Match any IP with query response.
### Response Fields
!!! question "Since sing-box 1.14.0"
Match fields for DNS response data. Require `match_response` to be set to `true`
and a preceding rule with [`evaluate`](/configuration/dns/rule_action/#evaluate) action to populate the response.
#### response_rcode
Match DNS response code.
Accepted values are the same as in the [predefined action rcode](/configuration/dns/rule_action/#rcode).
#### response_answer
Match DNS answer records.
Record format is the same as in [predefined action answer](/configuration/dns/rule_action/#answer).
#### response_ns
Match DNS name server records.
Record format is the same as in [predefined action ns](/configuration/dns/rule_action/#ns).
#### response_extra
Match DNS extra records.
Record format is the same as in [predefined action extra](/configuration/dns/rule_action/#extra).
### Logical Fields
#### type

View File

@@ -2,18 +2,6 @@
icon: material/alert-decagram
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [match_response](#match_response)
:material-plus: [response_rcode](#response_rcode)
:material-plus: [response_answer](#response_answer)
:material-plus: [response_ns](#response_ns)
:material-plus: [response_extra](#response_extra)
:material-plus: [source_mac_address](#source_mac_address)
:material-plus: [source_hostname](#source_hostname)
:material-delete-clock: [ip_accept_any](#ip_accept_any)
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [interface_address](#interface_address)
@@ -101,6 +89,12 @@ icon: material/alert-decagram
"192.168.0.1"
],
"source_ip_is_private": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"ip_accept_any": false,
"source_port": [
12345
],
@@ -155,12 +149,6 @@ icon: material/alert-decagram
"default_interface_address": [
"2000::/3"
],
"source_mac_address": [
"00:11:22:33:44:55"
],
"source_hostname": [
"my-device"
],
"wifi_ssid": [
"My WIFI"
],
@@ -172,16 +160,7 @@ icon: material/alert-decagram
"geosite-cn"
],
"rule_set_ip_cidr_match_source": false,
"match_response": false,
"ip_cidr": [
"10.0.0.0/24",
"192.168.0.1"
],
"ip_is_private": false,
"response_rcode": "",
"response_answer": [],
"response_ns": [],
"response_extra": [],
"rule_set_ip_cidr_accept_empty": false,
"invert": false,
"outbound": [
"direct"
@@ -190,9 +169,6 @@ icon: material/alert-decagram
"server": "local",
// 已弃用
"ip_accept_any": false,
"rule_set_ip_cidr_accept_empty": false,
"rule_set_ipcidr_match_source": false,
"geosite": [
"cn"
@@ -431,26 +407,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
匹配默认接口地址。
#### source_mac_address
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux、macOS或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备 MAC 地址。
#### source_hostname
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux、macOS或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备从 DHCP 租约获取的主机名。
#### wifi_ssid
!!! quote ""
@@ -489,15 +445,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
使规则集中的 `ip_cidr` 规则匹配源 IP。
#### match_response
!!! question "自 sing-box 1.14.0 起"
启用响应匹配。启用后,此规则将匹配 DNS 响应数据(由前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作设置),而不仅是匹配原始查询。
`response_rcode``response_answer``response_ns``response_extra` 字段需要此选项。
`legacyDNSMode` 未启用时,`ip_cidr``ip_is_private` 也需要此选项。
#### invert
反选匹配结果。
@@ -542,14 +489,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
已移动到 [DNS 规则动作](../rule_action#route).
### Legacy DNS Mode
`legacyDNSMode` 是一种内部兼容模式,会根据 DNS 规则配置自动检测。
当任何规则使用了 sing-box 1.14.0 引入的特性(如 `evaluate``match_response`
响应字段(`response_rcode``response_answer` 等)、`query_type``ip_version`)时,
该模式将被自动禁用。禁用后,`ip_cidr``ip_is_private` 需要设置 `match_response`
且已废弃的字段(如 `strategy``ip_accept_any``rule_set_ip_cidr_accept_empty`)将不再被接受。
### 地址筛选字段
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
@@ -577,69 +516,24 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
与查询响应匹配 IP CIDR。
`legacyDNSMode` 未启用时,`match_response` 必须设为 `true`
#### ip_is_private
!!! question "自 sing-box 1.9.0 起"
与查询响应匹配非公开 IP。
`legacyDNSMode` 未启用时,`match_response` 必须设为 `true`
#### ip_accept_any
!!! question "自 sing-box 1.12.0 起"
匹配任意 IP。
#### rule_set_ip_cidr_accept_empty
!!! question "自 sing-box 1.10.0 起"
!!! failure "已在 sing-box 1.14.0 废弃"
`rule_set_ip_cidr_accept_empty` 已废弃且将在 sing-box 1.16.0 中被移除。
仅在 `legacyDNSMode` 中可用。
使规则集中的 `ip_cidr` 规则接受空查询响应。
#### ip_accept_any
!!! question "自 sing-box 1.12.0 起"
!!! failure "已在 sing-box 1.14.0 废弃"
`ip_accept_any` 已废弃且将在 sing-box 1.16.0 中被移除。
仅在 `legacyDNSMode` 中可用。请使用 `match_response` 和响应项替代。
匹配任意 IP。
### 响应字段
!!! question "自 sing-box 1.14.0 起"
DNS 响应数据的匹配字段。需要将 `match_response` 设为 `true`
且需要前序规则使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作来填充响应。
#### response_rcode
匹配 DNS 响应码。
接受的值与 [predefined 动作 rcode](/zh/configuration/dns/rule_action/#rcode) 中相同。
#### response_answer
匹配 DNS 应答记录。
记录格式与 [predefined 动作 answer](/zh/configuration/dns/rule_action/#answer) 中相同。
#### response_ns
匹配 DNS 名称服务器记录。
记录格式与 [predefined 动作 ns](/zh/configuration/dns/rule_action/#ns) 中相同。
#### response_extra
匹配 DNS 额外记录。
记录格式与 [predefined 动作 extra](/zh/configuration/dns/rule_action/#extra) 中相同。
### 逻辑字段
#### type

View File

@@ -2,11 +2,6 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [evaluate](#evaluate)
:material-delete-clock: [strategy](#strategy)
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [strategy](#strategy)
@@ -39,11 +34,7 @@ Tag of target server.
!!! question "Since sing-box 1.12.0"
!!! failure "Deprecated in sing-box 1.14.0"
`strategy` is deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0.
Set domain strategy for this query. Only supported when `legacyDNSMode` is active.
Set domain strategy for this query.
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
@@ -61,49 +52,7 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will override `dns.client_subnet`.
### evaluate
!!! question "Since sing-box 1.14.0"
```json
{
"action": "evaluate",
"server": "",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
`evaluate` sends a DNS query to the specified server and saves the response for subsequent rules
to match against using [`match_response`](/configuration/dns/rule/#match_response) and response fields.
Unlike `route`, it does **not** terminate rule evaluation.
Only allowed on top-level DNS rules (not inside logical sub-rules).
#### server
==Required==
Tag of target server.
#### disable_cache
Disable cache and save cache in this query.
#### rewrite_ttl
Rewrite TTL in DNS responses.
#### client_subnet
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will override `dns.client_subnet`.
Will overrides `dns.client_subnet`.
### route-options

View File

@@ -2,11 +2,6 @@
icon: material/new-box
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [evaluate](#evaluate)
:material-delete-clock: [strategy](#strategy)
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [strategy](#strategy)
@@ -39,11 +34,7 @@ icon: material/new-box
!!! question "自 sing-box 1.12.0 起"
!!! failure "已在 sing-box 1.14.0 废弃"
`strategy` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除。
为此查询设置域名策略。仅在 `legacyDNSMode` 启用时可用。
为此查询设置域名策略。
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`
@@ -63,46 +54,6 @@ icon: material/new-box
将覆盖 `dns.client_subnet`.
### evaluate
!!! question "自 sing-box 1.14.0 起"
```json
{
"action": "evaluate",
"server": "",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
`evaluate` 向指定服务器发送 DNS 查询并保存响应,供后续规则通过 [`match_response`](/zh/configuration/dns/rule/#match_response) 和响应字段进行匹配。与 `route` 不同,它**不会**终止规则评估。
仅允许在顶层 DNS 规则中使用(不可在逻辑子规则内部使用)。
#### server
==必填==
目标 DNS 服务器的标签。
#### disable_cache
在此查询中禁用缓存。
#### rewrite_ttl
重写 DNS 回应中的 TTL。
#### client_subnet
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
将覆盖 `dns.client_subnet`.
### route-options
```json
@@ -133,7 +84,7 @@ icon: material/new-box
- `default`: 返回 REFUSED。
- `drop`: 丢弃请求。
默认使用 `default`
默认使用 `defualt`
#### no_drop

View File

@@ -29,7 +29,7 @@ The type of the DNS server.
| Type | Format |
|-----------------|---------------------------|
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
| empty (default) | [Legacy](./legacy/) |
| `local` | [Local](./local/) |
| `hosts` | [Hosts](./hosts/) |
| `tcp` | [TCP](./tcp/) |

View File

@@ -29,7 +29,7 @@ DNS 服务器的类型。
| 类型 | 格式 |
|-----------------|---------------------------|
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
| empty (default) | [Legacy](./legacy/) |
| `local` | [Local](./local/) |
| `hosts` | [Hosts](./hosts/) |
| `tcp` | [TCP](./tcp/) |

View File

@@ -1,10 +1,10 @@
---
icon: material/note-remove
icon: material/delete-clock
---
!!! failure "Removed in sing-box 1.14.0"
!!! failure "Deprecated in sing-box 1.12.0"
Legacy DNS servers are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
!!! quote "Changes in sing-box 1.9.0"
@@ -108,6 +108,6 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Can be overridden by `rules.[].client_subnet`.
Can be overrides by `rules.[].client_subnet`.
Will override `dns.client_subnet`.
Will overrides `dns.client_subnet`.

View File

@@ -1,10 +1,10 @@
---
icon: material/note-remove
icon: material/delete-clock
---
!!! failure "已在 sing-box 1.14.0 移除"
!!! failure "Deprecated in sing-box 1.12.0"
旧的 DNS 服务器配置已在 sing-box 1.12.0 废弃且在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
旧的 DNS 服务器配置已废弃且在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
!!! quote "sing-box 1.9.0 中的更改"

View File

@@ -2,10 +2,6 @@
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [bbr_profile](#bbr_profile)
!!! quote "Changes in sing-box 1.11.0"
:material-alert: [masquerade](#masquerade)
@@ -35,7 +31,6 @@ icon: material/alert-decagram
"ignore_client_bandwidth": false,
"tls": {},
"masquerade": "", // or {}
"bbr_profile": "",
"brutal_debug": false
}
```
@@ -146,14 +141,6 @@ Fixed response headers.
Fixed response content.
#### bbr_profile
!!! question "Since sing-box 1.14.0"
BBR congestion control algorithm profile, one of `conservative` `standard` `aggressive`.
`standard` is used by default.
#### brutal_debug
Enable debug information logging for Hysteria Brutal CC.

View File

@@ -2,10 +2,6 @@
icon: material/alert-decagram
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [bbr_profile](#bbr_profile)
!!! quote "sing-box 1.11.0 中的更改"
:material-alert: [masquerade](#masquerade)
@@ -35,7 +31,6 @@ icon: material/alert-decagram
"ignore_client_bandwidth": false,
"tls": {},
"masquerade": "", // 或 {}
"bbr_profile": "",
"brutal_debug": false
}
```
@@ -143,14 +138,6 @@ HTTP3 服务器认证失败时的行为 (对象配置)。
固定响应内容。
#### bbr_profile
!!! question "自 sing-box 1.14.0 起"
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`
默认使用 `standard`
#### brutal_debug
启用 Hysteria Brutal CC 的调试信息日志记录。

View File

@@ -4,7 +4,7 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [include_mac_address](#include_mac_address)
:material-plus: [include_mac_address](#include_mac_address)
:material-plus: [exclude_mac_address](#exclude_mac_address)
!!! quote "Changes in sing-box 1.13.3"
@@ -134,12 +134,6 @@ icon: material/new-box
"exclude_package": [
"com.android.captiveportallogin"
],
"include_mac_address": [
"00:11:22:33:44:55"
],
"exclude_mac_address": [
"66:77:88:99:aa:bb"
],
"platform": {
"http_proxy": {
"enabled": false,
@@ -566,30 +560,6 @@ Limit android packages in route.
Exclude android packages in route.
#### include_mac_address
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
Limit MAC addresses in route. Not limited by default.
Conflict with `exclude_mac_address`.
#### exclude_mac_address
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux with `auto_route` and `auto_redirect` enabled.
Exclude MAC addresses in route.
Conflict with `include_mac_address`.
#### platform
Platform-specific settings, provided by client applications.

View File

@@ -2,11 +2,6 @@
icon: material/new-box
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [include_mac_address](#include_mac_address)
:material-plus: [exclude_mac_address](#exclude_mac_address)
!!! quote "sing-box 1.13.3 中的更改"
:material-alert: [strict_route](#strict_route)
@@ -135,12 +130,6 @@ icon: material/new-box
"exclude_package": [
"com.android.captiveportallogin"
],
"include_mac_address": [
"00:11:22:33:44:55"
],
"exclude_mac_address": [
"66:77:88:99:aa:bb"
],
"platform": {
"http_proxy": {
"enabled": false,
@@ -554,30 +543,6 @@ TCP/IP 栈。
排除路由的 Android 应用包名。
#### include_mac_address
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux且需要 `auto_route``auto_redirect` 已启用。
限制被路由的 MAC 地址。默认不限制。
`exclude_mac_address` 冲突。
#### exclude_mac_address
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux且需要 `auto_route``auto_redirect` 已启用。
排除路由的 MAC 地址。
`include_mac_address` 冲突。
#### platform
平台特定的设置,由客户端应用提供。

View File

@@ -1,6 +1,7 @@
# Introduction
sing-box uses JSON for configuration files.
### Structure
```json
@@ -9,7 +10,6 @@ sing-box uses JSON for configuration files.
"dns": {},
"ntp": {},
"certificate": {},
"certificate_providers": [],
"endpoints": [],
"inbounds": [],
"outbounds": [],
@@ -27,7 +27,6 @@ sing-box uses JSON for configuration files.
| `dns` | [DNS](./dns/) |
| `ntp` | [NTP](./ntp/) |
| `certificate` | [Certificate](./certificate/) |
| `certificate_providers` | [Certificate Provider](./shared/certificate-provider/) |
| `endpoints` | [Endpoint](./endpoint/) |
| `inbounds` | [Inbound](./inbound/) |
| `outbounds` | [Outbound](./outbound/) |
@@ -51,4 +50,4 @@ sing-box format -w -c config.json -D config_directory
```bash
sing-box merge output.json -c config.json -D config_directory
```
```

View File

@@ -1,6 +1,7 @@
# 引言
sing-box 使用 JSON 作为配置文件格式。
### 结构
```json
@@ -9,7 +10,6 @@ sing-box 使用 JSON 作为配置文件格式。
"dns": {},
"ntp": {},
"certificate": {},
"certificate_providers": [],
"endpoints": [],
"inbounds": [],
"outbounds": [],
@@ -27,7 +27,6 @@ sing-box 使用 JSON 作为配置文件格式。
| `dns` | [DNS](./dns/) |
| `ntp` | [NTP](./ntp/) |
| `certificate` | [证书](./certificate/) |
| `certificate_providers` | [证书提供者](./shared/certificate-provider/) |
| `endpoints` | [端点](./endpoint/) |
| `inbounds` | [入站](./inbound/) |
| `outbounds` | [出站](./outbound/) |
@@ -51,4 +50,4 @@ sing-box format -w -c config.json -D config_directory
```bash
sing-box merge output.json -c config.json -D config_directory
```
```

View File

@@ -1,8 +1,3 @@
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [hop_interval_max](#hop_interval_max)
:material-plus: [bbr_profile](#bbr_profile)
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [server_ports](#server_ports)
@@ -14,14 +9,13 @@
{
"type": "hysteria2",
"tag": "hy2-out",
"server": "127.0.0.1",
"server_port": 1080,
"server_ports": [
"2080:3000"
],
"hop_interval": "",
"hop_interval_max": "",
"up_mbps": 100,
"down_mbps": 100,
"obfs": {
@@ -31,9 +25,8 @@
"password": "goofy_ahh_password",
"network": "tcp",
"tls": {},
"bbr_profile": "",
"brutal_debug": false,
... // Dial Fields
}
```
@@ -82,14 +75,6 @@ Port hopping interval.
`30s` is used by default.
#### hop_interval_max
!!! question "Since sing-box 1.14.0"
Maximum port hopping interval, used for randomization.
If set, the actual hop interval will be randomly chosen between `hop_interval` and `hop_interval_max`.
#### up_mbps, down_mbps
Max bandwidth, in Mbps.
@@ -124,14 +109,6 @@ Both is enabled by default.
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
#### bbr_profile
!!! question "Since sing-box 1.14.0"
BBR congestion control algorithm profile, one of `conservative` `standard` `aggressive`.
`standard` is used by default.
#### brutal_debug
Enable debug information logging for Hysteria Brutal CC.

View File

@@ -1,8 +1,3 @@
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [hop_interval_max](#hop_interval_max)
:material-plus: [bbr_profile](#bbr_profile)
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [server_ports](#server_ports)
@@ -21,7 +16,6 @@
"2080:3000"
],
"hop_interval": "",
"hop_interval_max": "",
"up_mbps": 100,
"down_mbps": 100,
"obfs": {
@@ -31,9 +25,8 @@
"password": "goofy_ahh_password",
"network": "tcp",
"tls": {},
"bbr_profile": "",
"brutal_debug": false,
... // 拨号字段
}
```
@@ -80,14 +73,6 @@
默认使用 `30s`
#### hop_interval_max
!!! question "自 sing-box 1.14.0 起"
最大端口跳跃间隔,用于随机化。
如果设置,实际跳跃间隔将在 `hop_interval``hop_interval_max` 之间随机选择。
#### up_mbps, down_mbps
最大带宽。
@@ -122,14 +107,6 @@ QUIC 流量混淆器密码.
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。
#### bbr_profile
!!! question "自 sing-box 1.14.0 起"
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`
默认使用 `standard`
#### brutal_debug
启用 Hysteria Brutal CC 的调试信息日志记录。

View File

@@ -4,11 +4,6 @@ icon: material/alert-decagram
# Route
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [find_neighbor](#find_neighbor)
:material-plus: [dhcp_lease_files](#dhcp_lease_files)
!!! quote "Changes in sing-box 1.12.0"
:material-plus: [default_domain_resolver](#default_domain_resolver)
@@ -40,9 +35,6 @@ icon: material/alert-decagram
"override_android_vpn": false,
"default_interface": "",
"default_mark": 0,
"find_process": false,
"find_neighbor": false,
"dhcp_lease_files": [],
"default_domain_resolver": "", // or {}
"default_network_strategy": "",
"default_network_type": [],
@@ -115,45 +107,13 @@ Set routing mark by default.
Takes no effect if `outbound.routing_mark` is set.
#### find_process
!!! quote ""
Only supported on Linux, Windows, and macOS.
Enable process search for logging when no `process_name`, `process_path`, `package_name`, `user` or `user_id` rules exist.
#### find_neighbor
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux and macOS.
Enable neighbor resolution for logging when no `source_mac_address` or `source_hostname` rules exist.
See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
#### dhcp_lease_files
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux and macOS.
Custom DHCP lease file paths for hostname and MAC address resolution.
Automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea) if empty.
#### default_domain_resolver
!!! question "Since sing-box 1.12.0"
See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.
Can be overridden by `outbound.domain_resolver`.
Can be overrides by `outbound.domain_resolver`.
#### default_network_strategy
@@ -163,7 +123,7 @@ See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.
Can be overridden by `outbound.network_strategy`.
Can be overrides by `outbound.network_strategy`.
Conflicts with `default_interface`.

View File

@@ -4,11 +4,6 @@ icon: material/alert-decagram
# 路由
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [find_neighbor](#find_neighbor)
:material-plus: [dhcp_lease_files](#dhcp_lease_files)
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [default_domain_resolver](#default_domain_resolver)
@@ -42,9 +37,6 @@ icon: material/alert-decagram
"override_android_vpn": false,
"default_interface": "",
"default_mark": 0,
"find_process": false,
"find_neighbor": false,
"dhcp_lease_files": [],
"default_network_strategy": "",
"default_fallback_delay": ""
}
@@ -114,38 +106,6 @@ icon: material/alert-decagram
如果设置了 `outbound.routing_mark` 设置,则不生效。
#### find_process
!!! quote ""
仅支持 Linux、Windows 和 macOS。
在没有 `process_name``process_path``package_name``user``user_id` 规则时启用进程搜索以输出日志。
#### find_neighbor
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux 和 macOS。
在没有 `source_mac_address``source_hostname` 规则时启用邻居解析以输出日志。
参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
#### dhcp_lease_files
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux 和 macOS。
用于主机名和 MAC 地址解析的自定义 DHCP 租约文件路径。
为空时自动从常见 DHCP 服务器dnsmasq、odhcpd、ISC dhcpd、Kea检测。
#### default_domain_resolver
!!! question "自 sing-box 1.12.0 起"

View File

@@ -2,11 +2,6 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [source_mac_address](#source_mac_address)
:material-plus: [source_hostname](#source_hostname)
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [interface_address](#interface_address)
@@ -164,12 +159,6 @@ icon: material/new-box
"tailscale",
"wireguard"
],
"source_mac_address": [
"00:11:22:33:44:55"
],
"source_hostname": [
"my-device"
],
"rule_set": [
"geoip-cn",
"geosite-cn"
@@ -460,26 +449,6 @@ Match specified outbounds' preferred routes.
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
| `wireguard` | Match peers's allowed IPs |
#### source_mac_address
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device MAC address.
#### source_hostname
!!! question "Since sing-box 1.14.0"
!!! quote ""
Only supported on Linux, macOS, or in graphical clients on Android and macOS. See [Neighbor Resolution](/configuration/shared/neighbor/) for setup.
Match source device hostname from DHCP leases.
#### rule_set
!!! question "Since sing-box 1.8.0"

View File

@@ -2,11 +2,6 @@
icon: material/new-box
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [source_mac_address](#source_mac_address)
:material-plus: [source_hostname](#source_hostname)
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [interface_address](#interface_address)
@@ -162,12 +157,6 @@ icon: material/new-box
"tailscale",
"wireguard"
],
"source_mac_address": [
"00:11:22:33:44:55"
],
"source_hostname": [
"my-device"
],
"rule_set": [
"geoip-cn",
"geosite-cn"
@@ -458,26 +447,6 @@ icon: material/new-box
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
| `wireguard` | 匹配对端的 allowed IPs |
#### source_mac_address
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux、macOS或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备 MAC 地址。
#### source_hostname
!!! question "自 sing-box 1.14.0 起"
!!! quote ""
仅支持 Linux、macOS或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
匹配源设备从 DHCP 租约获取的主机名。
#### rule_set
!!! question "自 sing-box 1.8.0 起"

View File

@@ -316,4 +316,4 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will override `dns.client_subnet`.
Will overrides `dns.client_subnet`.

View File

@@ -1,150 +0,0 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [account_key](#account_key)
:material-plus: [key_type](#key_type)
:material-plus: [detour](#detour)
# ACME
!!! quote ""
`with_acme` build tag required.
### Structure
```json
{
"type": "acme",
"tag": "",
"domain": [],
"data_directory": "",
"default_server_name": "",
"email": "",
"provider": "",
"account_key": "",
"disable_http_challenge": false,
"disable_tls_alpn_challenge": false,
"alternative_http_port": 0,
"alternative_tls_port": 0,
"external_account": {
"key_id": "",
"mac_key": ""
},
"dns01_challenge": {},
"key_type": "",
"detour": ""
}
```
### Fields
#### domain
==Required==
List of domains.
#### data_directory
The directory to store ACME data.
`$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic` will be used if empty.
#### default_server_name
Server name to use when choosing a certificate if the ClientHello's ServerName field is empty.
#### email
The email address to use when creating or selecting an existing ACME server account.
#### provider
The ACME CA provider to use.
| Value | Provider |
|-------------------------|---------------|
| `letsencrypt (default)` | Let's Encrypt |
| `zerossl` | ZeroSSL |
| `https://...` | Custom |
When `provider` is `zerossl`, sing-box will automatically request ZeroSSL EAB credentials if `email` is set and
`external_account` is empty.
When `provider` is `zerossl`, at least one of `external_account`, `email`, or `account_key` is required.
#### account_key
!!! question "Since sing-box 1.14.0"
The PEM-encoded private key of an existing ACME account.
#### disable_http_challenge
Disable all HTTP challenges.
#### disable_tls_alpn_challenge
Disable all TLS-ALPN challenges
#### alternative_http_port
The alternate port to use for the ACME HTTP challenge; if non-empty, this port will be used instead of 80 to spin up a
listener for the HTTP challenge.
#### alternative_tls_port
The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to
succeed.
#### external_account
EAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known
by the CA.
External account bindings are used to associate an ACME account with an existing account in a non-ACME system, such as
a CA customer database.
To enable ACME account binding, the CA operating the ACME server needs to provide the ACME client with a MAC key and a
key identifier, using some mechanism outside of ACME. §7.3.4
#### external_account.key_id
The key identifier.
#### external_account.mac_key
The MAC key.
#### dns01_challenge
ACME DNS01 challenge field. If configured, other challenge methods will be disabled.
See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details.
#### key_type
!!! question "Since sing-box 1.14.0"
The private key type to generate for new certificates.
| Value | Type |
|------------|---------|
| `ed25519` | Ed25519 |
| `p256` | P-256 |
| `p384` | P-384 |
| `rsa2048` | RSA |
| `rsa4096` | RSA |
#### detour
!!! question "Since sing-box 1.14.0"
The tag of the upstream outbound.
All provider HTTP requests will use this outbound.

View File

@@ -1,145 +0,0 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [account_key](#account_key)
:material-plus: [key_type](#key_type)
:material-plus: [detour](#detour)
# ACME
!!! quote ""
需要 `with_acme` 构建标签。
### 结构
```json
{
"type": "acme",
"tag": "",
"domain": [],
"data_directory": "",
"default_server_name": "",
"email": "",
"provider": "",
"account_key": "",
"disable_http_challenge": false,
"disable_tls_alpn_challenge": false,
"alternative_http_port": 0,
"alternative_tls_port": 0,
"external_account": {
"key_id": "",
"mac_key": ""
},
"dns01_challenge": {},
"key_type": "",
"detour": ""
}
```
### 字段
#### domain
==必填==
域名列表。
#### data_directory
ACME 数据存储目录。
如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`
#### default_server_name
如果 ClientHello 的 ServerName 字段为空,则选择证书时要使用的服务器名称。
#### email
创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。
#### provider
要使用的 ACME CA 提供商。
| 值 | 提供商 |
|--------------------|---------------|
| `letsencrypt (默认)` | Let's Encrypt |
| `zerossl` | ZeroSSL |
| `https://...` | 自定义 |
`provider``zerossl` 时,如果设置了 `email` 且未设置 `external_account`
sing-box 会自动向 ZeroSSL 请求 EAB 凭据。
`provider``zerossl` 时,必须至少设置 `external_account``email``account_key` 之一。
#### account_key
!!! question "自 sing-box 1.14.0 起"
现有 ACME 帐户的 PEM 编码私钥。
#### disable_http_challenge
禁用所有 HTTP 质询。
#### disable_tls_alpn_challenge
禁用所有 TLS-ALPN 质询。
#### alternative_http_port
用于 ACME HTTP 质询的备用端口;如果非空,将使用此端口而不是 80 来启动 HTTP 质询的侦听器。
#### alternative_tls_port
用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。
#### external_account
EAB外部帐户绑定包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。
外部帐户绑定用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。
为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4
#### external_account.key_id
密钥标识符。
#### external_account.mac_key
MAC 密钥。
#### dns01_challenge
ACME DNS01 质询字段。如果配置,将禁用其他质询方法。
参阅 [DNS01 质询字段](/zh/configuration/shared/dns01_challenge/)。
#### key_type
!!! question "自 sing-box 1.14.0 起"
为新证书生成的私钥类型。
| 值 | 类型 |
|-----------|----------|
| `ed25519` | Ed25519 |
| `p256` | P-256 |
| `p384` | P-384 |
| `rsa2048` | RSA |
| `rsa4096` | RSA |
#### detour
!!! question "自 sing-box 1.14.0 起"
上游出站的标签。
所有提供者 HTTP 请求将使用此出站。

View File

@@ -1,82 +0,0 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.14.0"
# Cloudflare Origin CA
### Structure
```json
{
"type": "cloudflare-origin-ca",
"tag": "",
"domain": [],
"data_directory": "",
"api_token": "",
"origin_ca_key": "",
"request_type": "",
"requested_validity": 0,
"detour": ""
}
```
### Fields
#### domain
==Required==
List of domain names or wildcard domain names to include in the certificate.
#### data_directory
Root directory used to store the issued certificate, private key, and metadata.
If empty, sing-box uses the same default data directory as the ACME certificate provider:
`$XDG_DATA_HOME/certmagic` or `$HOME/.local/share/certmagic`.
#### api_token
Cloudflare API token used to create the certificate.
Get or create one in [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens).
Requires the `Zone / SSL and Certificates / Edit` permission.
Conflict with `origin_ca_key`.
#### origin_ca_key
Cloudflare Origin CA Key.
Get it in [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens).
Conflict with `api_token`.
#### request_type
The signature type to request from Cloudflare.
| Value | Type |
|----------------------|-------------|
| `origin-rsa` | RSA |
| `origin-ecc` | ECDSA P-256 |
`origin-rsa` is used if empty.
#### requested_validity
The requested certificate validity in days.
Available values: `7`, `30`, `90`, `365`, `730`, `1095`, `5475`.
`5475` days (15 years) is used if empty.
#### detour
The tag of the upstream outbound.
All provider HTTP requests will use this outbound.

View File

@@ -1,82 +0,0 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.14.0 起"
# Cloudflare Origin CA
### 结构
```json
{
"type": "cloudflare-origin-ca",
"tag": "",
"domain": [],
"data_directory": "",
"api_token": "",
"origin_ca_key": "",
"request_type": "",
"requested_validity": 0,
"detour": ""
}
```
### 字段
#### domain
==必填==
要写入证书的域名或通配符域名列表。
#### data_directory
保存签发证书、私钥和元数据的根目录。
如果为空sing-box 会使用与 ACME 证书提供者相同的默认数据目录:
`$XDG_DATA_HOME/certmagic``$HOME/.local/share/certmagic`
#### api_token
用于创建证书的 Cloudflare API Token。
可在 [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens) 获取或创建。
需要 `Zone / SSL and Certificates / Edit` 权限。
`origin_ca_key` 冲突。
#### origin_ca_key
Cloudflare Origin CA Key。
可在 [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens) 获取。
`api_token` 冲突。
#### request_type
向 Cloudflare 请求的签名类型。
| 值 | 类型 |
|----------------------|-------------|
| `origin-rsa` | RSA |
| `origin-ecc` | ECDSA P-256 |
如果为空,使用 `origin-rsa`
#### requested_validity
请求的证书有效期,单位为天。
可用值:`7``30``90``365``730``1095``5475`
如果为空,使用 `5475`15 年)。
#### detour
上游出站的标签。
所有提供者 HTTP 请求将使用此出站。

View File

@@ -1,32 +0,0 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.14.0"
# Certificate Provider
### Structure
```json
{
"certificate_providers": [
{
"type": "",
"tag": ""
}
]
}
```
### Fields
| Type | Format |
|--------|------------------|
| `acme` | [ACME](/configuration/shared/certificate-provider/acme) |
| `tailscale` | [Tailscale](/configuration/shared/certificate-provider/tailscale) |
| `cloudflare-origin-ca` | [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca) |
#### tag
The tag of the certificate provider.

View File

@@ -1,32 +0,0 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.14.0 起"
# 证书提供者
### 结构
```json
{
"certificate_providers": [
{
"type": "",
"tag": ""
}
]
}
```
### 字段
| 类型 | 格式 |
|--------|------------------|
| `acme` | [ACME](/zh/configuration/shared/certificate-provider/acme) |
| `tailscale` | [Tailscale](/zh/configuration/shared/certificate-provider/tailscale) |
| `cloudflare-origin-ca` | [Cloudflare Origin CA](/zh/configuration/shared/certificate-provider/cloudflare-origin-ca) |
#### tag
证书提供者的标签。

View File

@@ -1,27 +0,0 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.14.0"
# Tailscale
### Structure
```json
{
"type": "tailscale",
"tag": "ts-cert",
"endpoint": "ts-ep"
}
```
### Fields
#### endpoint
==Required==
The tag of the [Tailscale endpoint](/configuration/endpoint/tailscale/) to reuse.
[MagicDNS and HTTPS](https://tailscale.com/kb/1153/enabling-https) must be enabled in the Tailscale admin console.

View File

@@ -1,27 +0,0 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.14.0 起"
# Tailscale
### 结构
```json
{
"type": "tailscale",
"tag": "ts-cert",
"endpoint": "ts-ep"
}
```
### 字段
#### endpoint
==必填==
要复用的 [Tailscale 端点](/zh/configuration/endpoint/tailscale/) 的标签。
必须在 Tailscale 管理控制台中启用 [MagicDNS 和 HTTPS](https://tailscale.com/kb/1153/enabling-https)。

View File

@@ -2,14 +2,6 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [ttl](#ttl)
:material-plus: [propagation_delay](#propagation_delay)
:material-plus: [propagation_timeout](#propagation_timeout)
:material-plus: [resolvers](#resolvers)
:material-plus: [override_domain](#override_domain)
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [alidns.security_token](#security_token)
@@ -20,57 +12,12 @@ icon: material/new-box
```json
{
"ttl": "",
"propagation_delay": "",
"propagation_timeout": "",
"resolvers": [],
"override_domain": "",
"provider": "",
... // Provider Fields
}
```
### Fields
#### ttl
!!! question "Since sing-box 1.14.0"
The TTL of the temporary TXT record used for the DNS challenge.
#### propagation_delay
!!! question "Since sing-box 1.14.0"
How long to wait after creating the challenge record before starting propagation checks.
#### propagation_timeout
!!! question "Since sing-box 1.14.0"
The maximum time to wait for the challenge record to propagate.
Set to `-1` to disable propagation checks.
#### resolvers
!!! question "Since sing-box 1.14.0"
Preferred DNS resolvers to use for DNS propagation checks.
#### override_domain
!!! question "Since sing-box 1.14.0"
Override the domain name used for the DNS challenge record.
Useful when `_acme-challenge` is delegated to a different zone.
#### provider
The DNS provider. See below for provider-specific fields.
### Provider Fields
#### Alibaba Cloud DNS

View File

@@ -2,14 +2,6 @@
icon: material/new-box
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [ttl](#ttl)
:material-plus: [propagation_delay](#propagation_delay)
:material-plus: [propagation_timeout](#propagation_timeout)
:material-plus: [resolvers](#resolvers)
:material-plus: [override_domain](#override_domain)
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [alidns.security_token](#security_token)
@@ -20,57 +12,12 @@ icon: material/new-box
```json
{
"ttl": "",
"propagation_delay": "",
"propagation_timeout": "",
"resolvers": [],
"override_domain": "",
"provider": "",
... // 提供商字段
}
```
### 字段
#### ttl
!!! question "自 sing-box 1.14.0 起"
DNS 质询临时 TXT 记录的 TTL。
#### propagation_delay
!!! question "自 sing-box 1.14.0 起"
创建质询记录后,在开始传播检查前要等待的时间。
#### propagation_timeout
!!! question "自 sing-box 1.14.0 起"
等待质询记录传播完成的最长时间。
设为 `-1` 可禁用传播检查。
#### resolvers
!!! question "自 sing-box 1.14.0 起"
进行 DNS 传播检查时优先使用的 DNS 解析器。
#### override_domain
!!! question "自 sing-box 1.14.0 起"
覆盖 DNS 质询记录使用的域名。
适用于将 `_acme-challenge` 委托到其他 zone 的场景。
#### provider
DNS 提供商。提供商专有字段见下文。
### 提供商字段
#### Alibaba Cloud DNS

View File

@@ -1,49 +0,0 @@
---
icon: material/lan
---
# Neighbor Resolution
Match LAN devices by MAC address and hostname using
[`source_mac_address`](/configuration/route/rule/#source_mac_address) and
[`source_hostname`](/configuration/route/rule/#source_hostname) rule items.
Neighbor resolution is automatically enabled when these rule items exist.
Use [`route.find_neighbor`](/configuration/route/#find_neighbor) to force enable it for logging without rules.
## Linux
Works natively. No special setup required.
Hostname resolution requires DHCP lease files,
automatically detected from common DHCP servers (dnsmasq, odhcpd, ISC dhcpd, Kea).
Custom paths can be set via [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files).
## Android
!!! quote ""
Only supported in graphical clients.
Requires Android 11 or above and ROOT.
Must use [VPNHotspot](https://github.com/Mygod/VPNHotspot) to share the VPN connection.
ROM built-in features like "Use VPN for connected devices" can share VPN
but cannot provide MAC address or hostname information.
Set **IP Masquerade Mode** to **None** in VPNHotspot settings.
Only route/DNS rules are supported. TUN include/exclude routes are not supported.
### Hostname Visibility
Hostname is only visible in sing-box if it is visible in VPNHotspot.
For Apple devices, change **Private Wi-Fi Address** from **Rotating** to **Fixed** in the Wi-Fi settings
of the connected network. Non-Apple devices are always visible.
## macOS
Requires the standalone version (macOS system extension).
The App Store version can share the VPN as a hotspot but does not support MAC address or hostname reading.
See [VPN Hotspot](/manual/misc/vpn-hotspot/#macos) for Internet Sharing setup.

View File

@@ -1,49 +0,0 @@
---
icon: material/lan
---
# 邻居解析
通过
[`source_mac_address`](/configuration/route/rule/#source_mac_address) 和
[`source_hostname`](/configuration/route/rule/#source_hostname) 规则项匹配局域网设备的 MAC 地址和主机名。
当这些规则项存在时,邻居解析自动启用。
使用 [`route.find_neighbor`](/configuration/route/#find_neighbor) 可在没有规则时强制启用以输出日志。
## Linux
原生支持,无需特殊设置。
主机名解析需要 DHCP 租约文件,
自动从常见 DHCP 服务器dnsmasq、odhcpd、ISC dhcpd、Kea检测。
可通过 [`route.dhcp_lease_files`](/configuration/route/#dhcp_lease_files) 设置自定义路径。
## Android
!!! quote ""
仅在图形客户端中支持。
需要 Android 11 或以上版本和 ROOT。
必须使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot) 共享 VPN 连接。
ROM 自带的「通过 VPN 共享连接」等功能可以共享 VPN
但无法提供 MAC 地址或主机名信息。
在 VPNHotspot 设置中将 **IP 遮掩模式** 设为 **无**
仅支持路由/DNS 规则。不支持 TUN 的 include/exclude 路由。
### 设备可见性
MAC 地址和主机名仅在 VPNHotspot 中可见时 sing-box 才能读取。
对于 Apple 设备,需要在所连接网络的 Wi-Fi 设置中将**私有无线局域网地址**从**轮替**改为**固定**。
非 Apple 设备始终可见。
## macOS
需要独立版本macOS 系统扩展)。
App Store 版本可以共享 VPN 热点但不支持 MAC 地址或主机名读取。
参阅 [VPN 热点](/manual/misc/vpn-hotspot/#macos) 了解互联网共享设置。

View File

@@ -2,11 +2,6 @@
icon: material/new-box
---
!!! quote "Changes in sing-box 1.14.0"
:material-plus: [certificate_provider](#certificate_provider)
:material-delete-clock: [acme](#acme-fields)
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [kernel_tx](#kernel_tx)
@@ -54,10 +49,6 @@ icon: material/new-box
"key_path": "",
"kernel_tx": false,
"kernel_rx": false,
"certificate_provider": "",
// Deprecated
"acme": {
"domain": [],
"data_directory": "",
@@ -417,18 +408,6 @@ Enable kernel TLS transmit support.
Enable kernel TLS receive support.
#### certificate_provider
!!! question "Since sing-box 1.14.0"
==Server only==
A string or an object.
When string, the tag of a shared [Certificate Provider](/configuration/shared/certificate-provider/).
When object, an inline certificate provider. See [Certificate Provider](/configuration/shared/certificate-provider/) for available types and fields.
## Custom TLS support
!!! info "QUIC support"
@@ -490,7 +469,7 @@ The ECH key and configuration can be generated by `sing-box generate ech-keypair
!!! failure "Deprecated in sing-box 1.12.0"
`pq_signature_schemes_enabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0.
ECH support has been migrated to use stdlib in sing-box 1.12.0, which does not come with support for PQ signature schemes, so `pq_signature_schemes_enabled` has been deprecated and no longer works.
Enable support for post-quantum peer certificate signature schemes.
@@ -498,7 +477,7 @@ Enable support for post-quantum peer certificate signature schemes.
!!! failure "Deprecated in sing-box 1.12.0"
`dynamic_record_sizing_disabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0.
`dynamic_record_sizing_disabled` has nothing to do with ECH, was added by mistake, has been deprecated and no longer works.
Disables adaptive sizing of TLS records.
@@ -587,10 +566,6 @@ Fragment TLS handshake into multiple TLS records to bypass firewalls.
### ACME Fields
!!! failure "Deprecated in sing-box 1.14.0"
Inline ACME options are deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0, check [Migration](/migration/#migrate-inline-acme-to-certificate-provider).
#### domain
List of domain.
@@ -702,4 +677,4 @@ A hexadecimal string with zero to eight digits.
The maximum time difference between the server and the client.
Check disabled if empty.
Check disabled if empty.

View File

@@ -2,11 +2,6 @@
icon: material/new-box
---
!!! quote "sing-box 1.14.0 中的更改"
:material-plus: [certificate_provider](#certificate_provider)
:material-delete-clock: [acme](#acme-字段)
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [kernel_tx](#kernel_tx)
@@ -54,10 +49,6 @@ icon: material/new-box
"key_path": "",
"kernel_tx": false,
"kernel_rx": false,
"certificate_provider": "",
// 废弃的
"acme": {
"domain": [],
"data_directory": "",
@@ -416,18 +407,6 @@ echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/
启用内核 TLS 接收支持。
#### certificate_provider
!!! question "自 sing-box 1.14.0 起"
==仅服务器==
字符串或对象。
为字符串时,共享[证书提供者](/zh/configuration/shared/certificate-provider/)的标签。
为对象时,内联的证书提供者。可用类型和字段参阅[证书提供者](/zh/configuration/shared/certificate-provider/)。
## 自定义 TLS 支持
!!! info "QUIC 支持"
@@ -486,7 +465,7 @@ ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
!!! failure "已在 sing-box 1.12.0 废弃"
`pq_signature_schemes_enabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除
ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支持后量子对等证书签名方案,因此 `pq_signature_schemes_enabled` 已被弃用且不再工作
启用对后量子对等证书签名方案的支持。
@@ -494,7 +473,7 @@ ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
!!! failure "已在 sing-box 1.12.0 废弃"
`dynamic_record_sizing_disabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除
`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作
禁用 TLS 记录的自适应大小调整。
@@ -582,10 +561,6 @@ ECH 配置路径PEM 格式。
### ACME 字段
!!! failure "已在 sing-box 1.14.0 废弃"
内联 ACME 选项已在 sing-box 1.14.0 废弃且将在 sing-box 1.16.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移内联-acme-到证书提供者)。
#### domain
域名列表。

View File

@@ -4,46 +4,6 @@ icon: material/delete-alert
# Deprecated Feature List
## 1.14.0
#### Inline ACME options in TLS
Inline ACME options (`tls.acme`) are deprecated
and can be replaced by the ACME certificate provider,
check [Migration](../migration/#migrate-inline-acme-to-certificate-provider).
Old fields will be removed in sing-box 1.16.0.
#### `strategy` in DNS rule actions
`strategy` in DNS rule actions is deprecated
and only supported in `legacyDNSMode`.
Old fields will be removed in sing-box 1.16.0.
#### `ip_accept_any` in DNS rules
`ip_accept_any` in DNS rules is deprecated
and only supported in `legacyDNSMode`.
Use `match_response` with response items instead.
Old fields will be removed in sing-box 1.16.0.
#### `rule_set_ip_cidr_accept_empty` in DNS rules
`rule_set_ip_cidr_accept_empty` in DNS rules is deprecated
and only supported in `legacyDNSMode`.
Old fields will be removed in sing-box 1.16.0.
#### Legacy address filter DNS rule items
Legacy address filter DNS rule items (`ip_cidr`, `ip_is_private` without `match_response`)
are deprecated and only supported in `legacyDNSMode`.
Use `match_response` with the `evaluate` action instead.
Old behavior will be removed in sing-box 1.16.0.
## 1.12.0
#### Legacy DNS server formats
@@ -51,7 +11,7 @@ Old behavior will be removed in sing-box 1.16.0.
DNS servers are refactored,
check [Migration](../migration/#migrate-to-new-dns-servers).
Old formats were removed in sing-box 1.14.0.
Compatibility for old formats will be removed in sing-box 1.14.0.
#### `outbound` DNS rule item
@@ -68,7 +28,7 @@ so `pq_signature_schemes_enabled` has been deprecated and no longer works.
Also, `dynamic_record_sizing_disabled` has nothing to do with ECH,
was added by mistake, has been deprecated and no longer works.
These fields were removed in sing-box 1.13.0.
These fields will be removed in sing-box 1.13.0.
## 1.11.0
@@ -78,7 +38,7 @@ Legacy special outbounds (`block` / `dns`) are deprecated
and can be replaced by rule actions,
check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions).
Old fields were removed in sing-box 1.13.0.
Old fields will be removed in sing-box 1.13.0.
#### Legacy inbound fields
@@ -86,7 +46,7 @@ Legacy inbound fields `inbound.<sniff/domain_strategy/...>` are deprecated
and can be replaced by rule actions,
check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
Old fields were removed in sing-box 1.13.0.
Old fields will be removed in sing-box 1.13.0.
#### Destination override fields in direct outbound
@@ -94,20 +54,18 @@ Destination override fields (`override_address` / `override_port`) in direct out
and can be replaced by rule actions,
check [Migration](../migration/#migrate-destination-override-fields-to-route-options).
Old fields were removed in sing-box 1.13.0.
#### WireGuard outbound
WireGuard outbound is deprecated and can be replaced by endpoint,
check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint).
Old outbound was removed in sing-box 1.13.0.
Old outbound will be removed in sing-box 1.13.0.
#### GSO option in TUN
GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN.
Old fields were removed in sing-box 1.13.0.
Old fields will be removed in sing-box 1.13.0.
## 1.10.0
@@ -117,12 +75,12 @@ Old fields were removed in sing-box 1.13.0.
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
Old fields were removed in sing-box 1.12.0.
Old fields will be removed in sing-box 1.12.0.
#### Match source rule items are renamed
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
`rule_set_ip_cidr_match_source` and were removed in sing-box 1.11.0.
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
#### Drop support for go1.18 and go1.19
@@ -137,7 +95,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o
#### GeoIP
GeoIP is deprecated and was removed in sing-box 1.12.0.
GeoIP is deprecated and will be removed in sing-box 1.12.0.
The maxmind GeoIP National Database, as an IP classification database,
is not entirely suitable for traffic bypassing,
@@ -148,7 +106,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets).
#### Geosite
Geosite is deprecated and was removed in sing-box 1.12.0.
Geosite is deprecated and will be removed in sing-box 1.12.0.
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.

View File

@@ -4,54 +4,12 @@ icon: material/delete-alert
# 废弃功能列表
## 1.14.0
#### TLS 中的内联 ACME 选项
TLS 中的内联 ACME 选项(`tls.acme`)已废弃,
且可以通过 ACME 证书提供者替代,
参阅 [迁移指南](/zh/migration/#迁移内联-acme-到证书提供者)。
旧字段将在 sing-box 1.16.0 中被移除。
#### DNS 规则动作中的 `strategy`
DNS 规则动作中的 `strategy` 已废弃,
且仅在 `legacyDNSMode` 中可用。
旧字段将在 sing-box 1.16.0 中被移除。
#### DNS 规则中的 `ip_accept_any`
DNS 规则中的 `ip_accept_any` 已废弃,
且仅在 `legacyDNSMode` 中可用。
请使用 `match_response` 和响应项替代。
旧字段将在 sing-box 1.16.0 中被移除。
#### DNS 规则中的 `rule_set_ip_cidr_accept_empty`
DNS 规则中的 `rule_set_ip_cidr_accept_empty` 已废弃,
且仅在 `legacyDNSMode` 中可用。
旧字段将在 sing-box 1.16.0 中被移除。
#### 旧的地址筛选 DNS 规则项
旧的地址筛选 DNS 规则项(不使用 `match_response``ip_cidr``ip_is_private`)已废弃,
且仅在 `legacyDNSMode` 中可用。
请使用 `match_response``evaluate` 动作替代。
旧行为将在 sing-box 1.16.0 中被移除。
## 1.12.0
#### 旧的 DNS 服务器格式
DNS 服务器已重构,
参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式).
旧格式在 sing-box 1.14.0 中被移除。
旧格式的兼容性将在 sing-box 1.14.0 中被移除。
#### `outbound` DNS 规则项
@@ -66,7 +24,7 @@ ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支
另外,`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。
相关字段在 sing-box 1.13.0 中被移除。
相关字段在 sing-box 1.13.0 中被移除。
## 1.11.0
@@ -75,41 +33,41 @@ ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支
旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代,
参阅 [迁移指南](/zh/migration/#迁移旧的特殊出站到规则动作)。
旧字段在 sing-box 1.13.0 中被移除。
旧字段在 sing-box 1.13.0 中被移除。
#### 旧的入站字段
旧的入站字段(`inbound.<sniff/domain_strategy/...>`)已废弃且可以通过规则动作替代,
参阅 [迁移指南](/zh/migration/#迁移旧的入站字段到规则动作)。
旧字段在 sing-box 1.13.0 中被移除。
旧字段在 sing-box 1.13.0 中被移除。
#### direct 出站中的目标地址覆盖字段
direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代,
参阅 [迁移指南](/zh/migration/#迁移-direct-出站中的目标地址覆盖字段到路由字段)。
旧字段在 sing-box 1.13.0 中被移除。
旧字段在 sing-box 1.13.0 中被移除。
#### WireGuard 出站
WireGuard 出站已废弃且可以通过端点替代,
参阅 [迁移指南](/zh/migration/#迁移-wireguard-出站到端点)。
旧出站在 sing-box 1.13.0 中被移除。
旧出站在 sing-box 1.13.0 中被移除。
#### TUN 的 GSO 字段
GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用。
旧字段在 sing-box 1.13.0 中被移除。
旧字段在 sing-box 1.13.0 中被移除。
## 1.10.0
#### Match source 规则项已重命名
`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为
`rule_set_ip_cidr_match_source`在 sing-box 1.11.0 中被移除。
`rule_set_ip_cidr_match_source`在 sing-box 1.11.0 中被移除。
#### TUN 地址字段已合并
@@ -117,7 +75,7 @@ GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用
`inet4_route_address``inet6_route_address` 已合并为 `route_address`
`inet4_route_exclude_address``inet6_route_exclude_address` 已合并为 `route_exclude_address`
旧字段在 sing-box 1.12.0 中被移除。
旧字段在 sing-box 1.11.0 中被移除。
#### 移除对 go1.18 和 go1.19 的支持
@@ -132,7 +90,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `
#### GeoIP
GeoIP 已废弃且在 sing-box 1.12.0 中被移除。
GeoIP 已废弃且在 sing-box 1.12.0 中被移除。
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
且现有的实现均存在内存使用大与管理困难的问题。
@@ -142,7 +100,7 @@ sing-box 1.8.0 引入了[规则集](/zh/configuration/rule-set/)
#### Geosite
Geosite 已废弃且在 sing-box 1.12.0 中被移除。
Geosite 已废弃且在 sing-box 1.12.0 中被移除。
Geosite即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
存在着包括缺少维护、规则不准确和管理困难内的大量问题。

View File

@@ -2,83 +2,6 @@
icon: material/arrange-bring-forward
---
## 1.14.0
### Migrate inline ACME to certificate provider
Inline ACME options in TLS are deprecated and can be replaced by certificate providers.
Most `tls.acme` fields can be moved into the ACME certificate provider unchanged.
See [ACME](/configuration/shared/certificate-provider/acme/) for fields newly added in sing-box 1.14.0.
!!! info "References"
[TLS](/configuration/shared/tls/#certificate_provider) /
[Certificate Provider](/configuration/shared/certificate-provider/)
=== ":material-card-remove: Deprecated"
```json
{
"inbounds": [
{
"type": "trojan",
"tls": {
"enabled": true,
"acme": {
"domain": ["example.com"],
"email": "admin@example.com"
}
}
}
]
}
```
=== ":material-card-multiple: Inline"
```json
{
"inbounds": [
{
"type": "trojan",
"tls": {
"enabled": true,
"certificate_provider": {
"type": "acme",
"domain": ["example.com"],
"email": "admin@example.com"
}
}
}
]
}
```
=== ":material-card-multiple: Shared"
```json
{
"certificate_providers": [
{
"type": "acme",
"tag": "my-cert",
"domain": ["example.com"],
"email": "admin@example.com"
}
],
"inbounds": [
{
"type": "trojan",
"tls": {
"enabled": true,
"certificate_provider": "my-cert"
}
}
]
}
```
## 1.12.0
### Migrate to new DNS server formats

View File

@@ -2,83 +2,6 @@
icon: material/arrange-bring-forward
---
## 1.14.0
### 迁移内联 ACME 到证书提供者
TLS 中的内联 ACME 选项已废弃,且可以被证书提供者替代。
`tls.acme` 的大多数字段都可以原样迁移到 ACME 证书提供者中。
sing-box 1.14.0 新增字段参阅 [ACME](/zh/configuration/shared/certificate-provider/acme/) 页面。
!!! info "参考"
[TLS](/zh/configuration/shared/tls/#certificate_provider) /
[证书提供者](/zh/configuration/shared/certificate-provider/)
=== ":material-card-remove: 弃用的"
```json
{
"inbounds": [
{
"type": "trojan",
"tls": {
"enabled": true,
"acme": {
"domain": ["example.com"],
"email": "admin@example.com"
}
}
}
]
}
```
=== ":material-card-multiple: 内联"
```json
{
"inbounds": [
{
"type": "trojan",
"tls": {
"enabled": true,
"certificate_provider": {
"type": "acme",
"domain": ["example.com"],
"email": "admin@example.com"
}
}
}
]
}
```
=== ":material-card-multiple: 共享"
```json
{
"certificate_providers": [
{
"type": "acme",
"tag": "my-cert",
"domain": ["example.com"],
"email": "admin@example.com"
}
],
"inbounds": [
{
"type": "trojan",
"tls": {
"enabled": true,
"certificate_provider": "my-cert"
}
}
]
}
```
## 1.12.0
### 迁移到新的 DNS 服务器格式

View File

@@ -57,6 +57,24 @@ func (n Note) MessageWithLink() string {
}
}
var OptionLegacyDNSTransport = Note{
Name: "legacy-dns-transport",
Description: "legacy DNS servers",
DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.14.0",
EnvName: "LEGACY_DNS_SERVERS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
}
var OptionLegacyDNSFakeIPOptions = Note{
Name: "legacy-dns-fakeip-options",
Description: "legacy DNS fakeip options",
DeprecatedVersion: "1.12.0",
ScheduledVersion: "1.14.0",
EnvName: "LEGACY_DNS_FAKEIP_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats",
}
var OptionOutboundDNSRuleItem = Note{
Name: "outbound-dns-rule-item",
Description: "outbound DNS rule item",
@@ -84,58 +102,10 @@ var OptionLegacyDomainStrategyOptions = Note{
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-domain-strategy-options",
}
var OptionInlineACME = Note{
Name: "inline-acme-options",
Description: "inline ACME options in TLS",
DeprecatedVersion: "1.14.0",
ScheduledVersion: "1.16.0",
EnvName: "INLINE_ACME_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-inline-acme-to-certificate-provider",
}
var OptionIPAcceptAny = Note{
Name: "dns-rule-ip-accept-any",
Description: "`ip_accept_any` in DNS rules",
DeprecatedVersion: "1.14.0",
ScheduledVersion: "1.16.0",
EnvName: "DNS_RULE_IP_ACCEPT_ANY",
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule/",
}
var OptionRuleSetIPCIDRAcceptEmpty = Note{
Name: "dns-rule-rule-set-ip-cidr-accept-empty",
Description: "`rule_set_ip_cidr_accept_empty` in DNS rules",
DeprecatedVersion: "1.14.0",
ScheduledVersion: "1.16.0",
EnvName: "DNS_RULE_RULE_SET_IP_CIDR_ACCEPT_EMPTY",
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule/",
}
var OptionLegacyDNSAddressFilter = Note{
Name: "legacy-dns-address-filter",
Description: "legacy address filter DNS rule items",
DeprecatedVersion: "1.14.0",
ScheduledVersion: "1.16.0",
EnvName: "LEGACY_DNS_ADDRESS_FILTER",
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule/",
}
var OptionLegacyDNSRuleStrategy = Note{
Name: "legacy-dns-rule-strategy",
Description: "`strategy` in DNS rule actions",
DeprecatedVersion: "1.14.0",
ScheduledVersion: "1.16.0",
EnvName: "LEGACY_DNS_RULE_STRATEGY",
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule_action/",
}
var Options = []Note{
OptionLegacyDNSTransport,
OptionLegacyDNSFakeIPOptions,
OptionOutboundDNSRuleItem,
OptionMissingDomainResolver,
OptionLegacyDomainStrategyOptions,
OptionInlineACME,
OptionIPAcceptAny,
OptionRuleSetIPCIDRAcceptEmpty,
OptionLegacyDNSAddressFilter,
OptionLegacyDNSRuleStrategy,
}

View File

@@ -33,7 +33,7 @@ func baseContext(platformInterface PlatformInterface) context.Context {
}
ctx := context.Background()
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry(), include.CertificateProviderRegistry())
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
}
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
@@ -144,18 +144,6 @@ func (s *platformInterfaceStub) SendNotification(notification *adapter.Notificat
return nil
}
func (s *platformInterfaceStub) UsePlatformNeighborResolver() bool {
return false
}
func (s *platformInterfaceStub) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
return os.ErrInvalid
}
func (s *platformInterfaceStub) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
return nil
}
func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {
return false
}

View File

@@ -1,53 +0,0 @@
package libbox
import (
"net"
"net/netip"
)
type NeighborEntry struct {
Address string
MacAddress string
Hostname string
}
type NeighborEntryIterator interface {
Next() *NeighborEntry
HasNext() bool
}
type NeighborSubscription struct {
done chan struct{}
}
func (s *NeighborSubscription) Close() {
close(s.done)
}
func tableToIterator(table map[netip.Addr]net.HardwareAddr) NeighborEntryIterator {
entries := make([]*NeighborEntry, 0, len(table))
for address, mac := range table {
entries = append(entries, &NeighborEntry{
Address: address.String(),
MacAddress: mac.String(),
})
}
return &neighborEntryIterator{entries}
}
type neighborEntryIterator struct {
entries []*NeighborEntry
}
func (i *neighborEntryIterator) HasNext() bool {
return len(i.entries) > 0
}
func (i *neighborEntryIterator) Next() *NeighborEntry {
if len(i.entries) == 0 {
return nil
}
entry := i.entries[0]
i.entries = i.entries[1:]
return entry
}

View File

@@ -1,123 +0,0 @@
//go:build darwin
package libbox
import (
"net"
"net/netip"
"os"
"slices"
"time"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
xroute "golang.org/x/net/route"
"golang.org/x/sys/unix"
)
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
entries, err := route.ReadNeighborEntries()
if err != nil {
return nil, E.Cause(err, "initial neighbor dump")
}
table := make(map[netip.Addr]net.HardwareAddr)
for _, entry := range entries {
table[entry.Address] = entry.MACAddress
}
listener.UpdateNeighborTable(tableToIterator(table))
routeSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
if err != nil {
return nil, E.Cause(err, "open route socket")
}
err = unix.SetNonblock(routeSocket, true)
if err != nil {
unix.Close(routeSocket)
return nil, E.Cause(err, "set route socket nonblock")
}
subscription := &NeighborSubscription{
done: make(chan struct{}),
}
go subscription.loop(listener, routeSocket, table)
return subscription, nil
}
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, routeSocket int, table map[netip.Addr]net.HardwareAddr) {
routeSocketFile := os.NewFile(uintptr(routeSocket), "route")
defer routeSocketFile.Close()
buffer := buf.NewPacket()
defer buffer.Release()
for {
select {
case <-s.done:
return
default:
}
tv := unix.NsecToTimeval(int64(3 * time.Second))
_ = unix.SetsockoptTimeval(routeSocket, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
n, err := routeSocketFile.Read(buffer.FreeBytes())
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
continue
}
select {
case <-s.done:
return
default:
}
continue
}
messages, err := xroute.ParseRIB(xroute.RIBTypeRoute, buffer.FreeBytes()[:n])
if err != nil {
continue
}
changed := false
for _, message := range messages {
routeMessage, isRouteMessage := message.(*xroute.RouteMessage)
if !isRouteMessage {
continue
}
if routeMessage.Flags&unix.RTF_LLINFO == 0 {
continue
}
address, mac, isDelete, ok := route.ParseRouteNeighborMessage(routeMessage)
if !ok {
continue
}
if isDelete {
if _, exists := table[address]; exists {
delete(table, address)
changed = true
}
} else {
existing, exists := table[address]
if !exists || !slices.Equal(existing, mac) {
table[address] = mac
changed = true
}
}
}
if changed {
listener.UpdateNeighborTable(tableToIterator(table))
}
}
}
func ReadBootpdLeases() NeighborEntryIterator {
leaseIPToMAC, ipToHostname, macToHostname := route.ReloadLeaseFiles([]string{"/var/db/dhcpd_leases"})
entries := make([]*NeighborEntry, 0, len(leaseIPToMAC))
for address, mac := range leaseIPToMAC {
entry := &NeighborEntry{
Address: address.String(),
MacAddress: mac.String(),
}
hostname, found := ipToHostname[address]
if !found {
hostname = macToHostname[mac.String()]
}
entry.Hostname = hostname
entries = append(entries, entry)
}
return &neighborEntryIterator{entries}
}

View File

@@ -1,88 +0,0 @@
//go:build linux
package libbox
import (
"net"
"net/netip"
"slices"
"time"
"github.com/sagernet/sing-box/route"
E "github.com/sagernet/sing/common/exceptions"
"github.com/mdlayher/netlink"
"golang.org/x/sys/unix"
)
func SubscribeNeighborTable(listener NeighborUpdateListener) (*NeighborSubscription, error) {
entries, err := route.ReadNeighborEntries()
if err != nil {
return nil, E.Cause(err, "initial neighbor dump")
}
table := make(map[netip.Addr]net.HardwareAddr)
for _, entry := range entries {
table[entry.Address] = entry.MACAddress
}
listener.UpdateNeighborTable(tableToIterator(table))
connection, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
Groups: 1 << (unix.RTNLGRP_NEIGH - 1),
})
if err != nil {
return nil, E.Cause(err, "subscribe neighbor updates")
}
subscription := &NeighborSubscription{
done: make(chan struct{}),
}
go subscription.loop(listener, connection, table)
return subscription, nil
}
func (s *NeighborSubscription) loop(listener NeighborUpdateListener, connection *netlink.Conn, table map[netip.Addr]net.HardwareAddr) {
defer connection.Close()
for {
select {
case <-s.done:
return
default:
}
err := connection.SetReadDeadline(time.Now().Add(3 * time.Second))
if err != nil {
return
}
messages, err := connection.Receive()
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
continue
}
select {
case <-s.done:
return
default:
}
continue
}
changed := false
for _, message := range messages {
address, mac, isDelete, ok := route.ParseNeighborMessage(message)
if !ok {
continue
}
if isDelete {
if _, exists := table[address]; exists {
delete(table, address)
changed = true
}
} else {
existing, exists := table[address]
if !exists || !slices.Equal(existing, mac) {
table[address] = mac
changed = true
}
}
}
if changed {
listener.UpdateNeighborTable(tableToIterator(table))
}
}
}

View File

@@ -1,9 +0,0 @@
//go:build !linux && !darwin
package libbox
import "os"
func SubscribeNeighborTable(_ NeighborUpdateListener) (*NeighborSubscription, error) {
return nil, os.ErrInvalid
}

View File

@@ -21,13 +21,6 @@ type PlatformInterface interface {
SystemCertificates() StringIterator
ClearDNSCache()
SendNotification(notification *Notification) error
StartNeighborMonitor(listener NeighborUpdateListener) error
CloseNeighborMonitor(listener NeighborUpdateListener) error
RegisterMyInterface(name string)
}
type NeighborUpdateListener interface {
UpdateNeighborTable(entries NeighborEntryIterator)
}
type ConnectionOwner struct {

View File

@@ -78,7 +78,6 @@ func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformO
}
options.FileDescriptor = dupFd
w.myTunName = options.Name
w.iif.RegisterMyInterface(options.Name)
return tun.New(*options)
}
@@ -221,46 +220,6 @@ func (w *platformInterfaceWrapper) SendNotification(notification *adapter.Notifi
return w.iif.SendNotification((*Notification)(notification))
}
func (w *platformInterfaceWrapper) UsePlatformNeighborResolver() bool {
return true
}
func (w *platformInterfaceWrapper) StartNeighborMonitor(listener adapter.NeighborUpdateListener) error {
return w.iif.StartNeighborMonitor(&neighborUpdateListenerWrapper{listener: listener})
}
func (w *platformInterfaceWrapper) CloseNeighborMonitor(listener adapter.NeighborUpdateListener) error {
return w.iif.CloseNeighborMonitor(nil)
}
type neighborUpdateListenerWrapper struct {
listener adapter.NeighborUpdateListener
}
func (w *neighborUpdateListenerWrapper) UpdateNeighborTable(entries NeighborEntryIterator) {
var result []adapter.NeighborEntry
for entries.HasNext() {
entry := entries.Next()
if entry == nil {
continue
}
address, err := netip.ParseAddr(entry.Address)
if err != nil {
continue
}
macAddress, err := net.ParseMAC(entry.MacAddress)
if err != nil {
continue
}
result = append(result, adapter.NeighborEntry{
Address: address,
MACAddress: macAddress,
Hostname: entry.Hostname,
})
}
w.listener.UpdateNeighborTable(result)
}
func AvailablePort(startPort int32) (int32, error) {
for port := int(startPort); ; port++ {
if port > 65535 {

23
go.mod
View File

@@ -6,7 +6,6 @@ require (
github.com/anthropics/anthropic-sdk-go v1.26.0
github.com/anytls/sing-anytls v0.0.11
github.com/caddyserver/certmagic v0.25.2
github.com/caddyserver/zerossl v0.1.5
github.com/coder/websocket v1.8.14
github.com/cretz/bine v0.2.0
github.com/database64128/tfo-go/v2 v2.3.2
@@ -15,14 +14,11 @@ require (
github.com/godbus/dbus/v5 v5.2.2
github.com/gofrs/uuid/v5 v5.4.0
github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91
github.com/jsimonetti/rtnetlink v1.4.0
github.com/keybase/go-keychain v0.0.1
github.com/libdns/acmedns v0.5.0
github.com/libdns/alidns v1.0.6
github.com/libdns/cloudflare v0.2.2
github.com/libdns/libdns v1.1.1
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mdlayher/netlink v1.9.0
github.com/metacubex/utls v1.8.4
github.com/mholt/acmez/v3 v3.1.6
github.com/miekg/dns v1.1.72
@@ -31,19 +27,20 @@ require (
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cors v1.2.1
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc
github.com/sagernet/cronet-go v0.0.0-20260309102448-2fef65f9dba9
github.com/sagernet/cronet-go/all v0.0.0-20260309102448-2fef65f9dba9
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.12
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4
github.com/sagernet/sing v0.8.3
github.com/sagernet/sing v0.8.4
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa
github.com/sagernet/sing-mux v0.3.4
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212
github.com/sagernet/sing-quic v0.6.1
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
github.com/sagernet/smux v1.5.50-sing-box-mod.1
github.com/sagernet/tailscale v1.92.4-sing-box-1.13-mod.7
@@ -71,8 +68,10 @@ require (
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/caddyserver/zerossl v0.1.5 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/coreos/go-oidc/v3 v3.17.0 // indirect
github.com/database64128/netx-go v0.1.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
@@ -82,6 +81,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gaissmai/bart v0.18.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
@@ -95,10 +95,14 @@ require (
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/libdns/libdns v1.1.1 // indirect
github.com/mdlayher/netlink v1.9.0 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pires/go-proxyproto v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -165,4 +169,5 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
zombiezen.com/go/capnproto2 v2.18.2+incompatible // indirect
)

32
go.sum
View File

@@ -28,6 +28,8 @@ github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
@@ -110,6 +112,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU=
github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk=
github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U=
@@ -142,6 +146,8 @@ github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xx
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
@@ -162,10 +168,10 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc h1:YK7PwJT0irRAEui9ASdXSxcE2BOVQipWMF/A1Ogt+7c=
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc h1:EJPHOqk23IuBsTjXK9OXqkNxPbKOBWKRmviQoCcriAs=
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc/go.mod h1:8aty0RW96DrJSMWXO6bRPMBJEjuqq5JWiOIi4bCRzFA=
github.com/sagernet/cronet-go v0.0.0-20260309102448-2fef65f9dba9 h1:xq5Yr10jXEppD3cnGjE3WENaB6D0YsZu6KptZ8d3054=
github.com/sagernet/cronet-go v0.0.0-20260309102448-2fef65f9dba9/go.mod h1:hwFHBEjjthyEquDULbr4c4ucMedp8Drb6Jvm2kt/0Bw=
github.com/sagernet/cronet-go/all v0.0.0-20260309102448-2fef65f9dba9 h1:uxQyy6Y/boOuecVA66tf79JgtoRGfeDJcfYZZLKVA5E=
github.com/sagernet/cronet-go/all v0.0.0-20260309102448-2fef65f9dba9/go.mod h1:Xm6cCvs0/twozC1JYNq0sVlOVmcSGzV7YON1XGcD97w=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9 h1:Qi0IKBpoPP3qZqIXuOKMsT2dv+l/MLWMyBHDMLRw2EA=
github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20260309101654-0cbdcfddded9/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw=
github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20260309101654-0cbdcfddded9 h1:p+wCMjOhj46SpSD/AJeTGgkCcbyA76FyH631XZatyU8=
@@ -236,20 +242,22 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
github.com/sagernet/sing v0.8.3 h1:zGMy9M1deBPEew9pCYIUHKeE+/lDQ5A2CBqjBjjzqkA=
github.com/sagernet/sing v0.8.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.8.4 h1:Fj+jlY3F8vhcRfz/G/P3Dwcs5wqnmyNPT7u1RVVmjFI=
github.com/sagernet/sing v0.8.4/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa h1:165HiOfgfofJIirEp1NGSmsoJAi+++WhR29IhtAu4A4=
github.com/sagernet/sing-cloudflared v0.0.0-20260407120610-7715dc2523fa/go.mod h1:bH2NKX+NpDTY1Zkxfboxw6MXB/ZywaNLmrDJYgKMJ2Y=
github.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=
github.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212 h1:7mFOUqy+DyOj7qKGd1X54UMXbnbJiiMileK/tn17xYc=
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
github.com/sagernet/sing-quic v0.6.1 h1:lx0tcm99wIA1RkyvILNzRSsMy1k7TTQYIhx71E/WBlw=
github.com/sagernet/sing-quic v0.6.1/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d h1:vi0j6301f6H8t2GYgAC2PA2AdnGdMwkP34B4+N03Qt4=
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6 h1:HV2I7DicF5Ar8v6F55f03W5FviBB7jgvLhJSDwbFvbk=
github.com/sagernet/sing-tun v0.8.7-0.20260402180740-11f6e77ec6c6/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
@@ -294,6 +302,8 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
@@ -401,3 +411,5 @@ lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
zombiezen.com/go/capnproto2 v2.18.2+incompatible h1:v3BD1zbruvffn7zjJUU5Pn8nZAB11bhZSQC4W+YnnKo=
zombiezen.com/go/capnproto2 v2.18.2+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=

View File

@@ -1,12 +0,0 @@
//go:build with_acme
package include
import (
"github.com/sagernet/sing-box/adapter/certificate"
"github.com/sagernet/sing-box/service/acme"
)
func registerACMECertificateProvider(registry *certificate.Registry) {
acme.RegisterCertificateProvider(registry)
}

View File

@@ -1,20 +0,0 @@
//go:build !with_acme
package include
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/certificate"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func registerACMECertificateProvider(registry *certificate.Registry) {
certificate.Register[option.ACMECertificateProviderOptions](registry, C.TypeACME, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ACMECertificateProviderOptions) (adapter.CertificateProviderService, error) {
return nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
})
}

12
include/cloudflared.go Normal file
View File

@@ -0,0 +1,12 @@
//go:build with_cloudflared
package include
import (
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/protocol/cloudflare"
)
func registerCloudflaredInbound(registry *inbound.Registry) {
cloudflare.RegisterInbound(registry)
}

View File

@@ -0,0 +1,20 @@
//go:build !with_cloudflared
package include
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func registerCloudflaredInbound(registry *inbound.Registry) {
inbound.Register[option.CloudflaredInboundOptions](registry, C.TypeCloudflared, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.CloudflaredInboundOptions) (adapter.Inbound, error) {
return nil, E.New(`Cloudflared is not included in this build, rebuild with -tags with_cloudflared`)
})
}

View File

@@ -5,7 +5,6 @@ import (
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/certificate"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
@@ -35,14 +34,13 @@ import (
"github.com/sagernet/sing-box/protocol/tun"
"github.com/sagernet/sing-box/protocol/vless"
"github.com/sagernet/sing-box/protocol/vmess"
originca "github.com/sagernet/sing-box/service/origin_ca"
"github.com/sagernet/sing-box/service/resolved"
"github.com/sagernet/sing-box/service/ssmapi"
E "github.com/sagernet/sing/common/exceptions"
)
func Context(ctx context.Context) context.Context {
return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry(), CertificateProviderRegistry())
return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry())
}
func InboundRegistry() *inbound.Registry {
@@ -66,6 +64,7 @@ func InboundRegistry() *inbound.Registry {
anytls.RegisterInbound(registry)
registerQUICInbounds(registry)
registerCloudflaredInbound(registry)
registerStubForRemovedInbounds(registry)
return registry
@@ -141,16 +140,6 @@ func ServiceRegistry() *service.Registry {
return registry
}
func CertificateProviderRegistry() *certificate.Registry {
registry := certificate.NewRegistry()
registerACMECertificateProvider(registry)
registerTailscaleCertificateProvider(registry)
originca.RegisterCertificateProvider(registry)
return registry
}
func registerStubForRemovedInbounds(registry *inbound.Registry) {
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")

View File

@@ -3,7 +3,6 @@
package include
import (
"github.com/sagernet/sing-box/adapter/certificate"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/service"
"github.com/sagernet/sing-box/dns"
@@ -19,10 +18,6 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) {
tailscale.RegistryTransport(registry)
}
func registerTailscaleCertificateProvider(registry *certificate.Registry) {
tailscale.RegisterCertificateProvider(registry)
}
func registerDERPService(registry *service.Registry) {
derp.Register(registry)
}

View File

@@ -6,7 +6,6 @@ import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/certificate"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/service"
C "github.com/sagernet/sing-box/constant"
@@ -28,12 +27,6 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) {
})
}
func registerTailscaleCertificateProvider(registry *certificate.Registry) {
certificate.Register[option.TailscaleCertificateProviderOptions](registry, C.TypeTailscale, func(ctx context.Context, logger log.ContextLogger, tag string, options option.TailscaleCertificateProviderOptions) (adapter.CertificateProviderService, error) {
return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)
})
}
func registerDERPService(registry *service.Registry) {
service.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) {
return nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`)

View File

@@ -122,11 +122,6 @@ nav:
- Listen Fields: configuration/shared/listen.md
- Dial Fields: configuration/shared/dial.md
- TLS: configuration/shared/tls.md
- Certificate Provider:
- configuration/shared/certificate-provider/index.md
- ACME: configuration/shared/certificate-provider/acme.md
- Tailscale: configuration/shared/certificate-provider/tailscale.md
- Cloudflare Origin CA: configuration/shared/certificate-provider/cloudflare-origin-ca.md
- DNS01 Challenge Fields: configuration/shared/dns01_challenge.md
- Pre-match: configuration/shared/pre-match.md
- Multiplex: configuration/shared/multiplex.md
@@ -134,7 +129,6 @@ nav:
- UDP over TCP: configuration/shared/udp-over-tcp.md
- TCP Brutal: configuration/shared/tcp-brutal.md
- Wi-Fi State: configuration/shared/wifi-state.md
- Neighbor Resolution: configuration/shared/neighbor.md
- Endpoint:
- configuration/endpoint/index.md
- WireGuard: configuration/endpoint/wireguard.md
@@ -278,7 +272,6 @@ plugins:
Shared: 通用
Listen Fields: 监听字段
Dial Fields: 拨号字段
Certificate Provider Fields: 证书提供者字段
DNS01 Challenge Fields: DNS01 验证字段
Multiplex: 多路复用
V2Ray Transport: V2Ray 传输层
@@ -287,7 +280,6 @@ plugins:
Endpoint: 端点
Inbound: 入站
Outbound: 出站
Certificate Provider: 证书提供者
Manual: 手册
reconfigure_material: true

View File

@@ -1,106 +0,0 @@
package option
import (
"strings"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
)
type ACMECertificateProviderOptions struct {
Domain badoption.Listable[string] `json:"domain,omitempty"`
DataDirectory string `json:"data_directory,omitempty"`
DefaultServerName string `json:"default_server_name,omitempty"`
Email string `json:"email,omitempty"`
Provider string `json:"provider,omitempty"`
AccountKey string `json:"account_key,omitempty"`
DisableHTTPChallenge bool `json:"disable_http_challenge,omitempty"`
DisableTLSALPNChallenge bool `json:"disable_tls_alpn_challenge,omitempty"`
AlternativeHTTPPort uint16 `json:"alternative_http_port,omitempty"`
AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"`
ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"`
DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,omitempty"`
KeyType ACMEKeyType `json:"key_type,omitempty"`
Detour string `json:"detour,omitempty"`
}
type _ACMEProviderDNS01ChallengeOptions struct {
TTL badoption.Duration `json:"ttl,omitempty"`
PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"`
PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"`
Resolvers badoption.Listable[string] `json:"resolvers,omitempty"`
OverrideDomain string `json:"override_domain,omitempty"`
Provider string `json:"provider,omitempty"`
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
}
type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions
func (o ACMEProviderDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {
var v any
switch o.Provider {
case C.DNSProviderAliDNS:
v = o.AliDNSOptions
case C.DNSProviderCloudflare:
v = o.CloudflareOptions
case C.DNSProviderACMEDNS:
v = o.ACMEDNSOptions
case "":
return nil, E.New("missing provider type")
default:
return nil, E.New("unknown provider type: ", o.Provider)
}
return badjson.MarshallObjects((_ACMEProviderDNS01ChallengeOptions)(o), v)
}
func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o))
if err != nil {
return err
}
var v any
switch o.Provider {
case C.DNSProviderAliDNS:
v = &o.AliDNSOptions
case C.DNSProviderCloudflare:
v = &o.CloudflareOptions
case C.DNSProviderACMEDNS:
v = &o.ACMEDNSOptions
case "":
return E.New("missing provider type")
default:
return E.New("unknown provider type: ", o.Provider)
}
return badjson.UnmarshallExcluded(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o), v)
}
type ACMEKeyType string
const (
ACMEKeyTypeED25519 = ACMEKeyType("ed25519")
ACMEKeyTypeP256 = ACMEKeyType("p256")
ACMEKeyTypeP384 = ACMEKeyType("p384")
ACMEKeyTypeRSA2048 = ACMEKeyType("rsa2048")
ACMEKeyTypeRSA4096 = ACMEKeyType("rsa4096")
)
func (t *ACMEKeyType) UnmarshalJSON(data []byte) error {
var value string
err := json.Unmarshal(data, &value)
if err != nil {
return err
}
value = strings.ToLower(value)
switch ACMEKeyType(value) {
case "", ACMEKeyTypeED25519, ACMEKeyTypeP256, ACMEKeyTypeP384, ACMEKeyTypeRSA2048, ACMEKeyTypeRSA4096:
*t = ACMEKeyType(value)
default:
return E.New("unknown ACME key type: ", value)
}
return nil
}

View File

@@ -1,100 +0,0 @@
package option
import (
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/service"
)
type CertificateProviderOptionsRegistry interface {
CreateOptions(providerType string) (any, bool)
}
type _CertificateProvider struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
Options any `json:"-"`
}
type CertificateProvider _CertificateProvider
func (h *CertificateProvider) MarshalJSONContext(ctx context.Context) ([]byte, error) {
return badjson.MarshallObjectsContext(ctx, (*_CertificateProvider)(h), h.Options)
}
func (h *CertificateProvider) UnmarshalJSONContext(ctx context.Context, content []byte) error {
err := json.UnmarshalContext(ctx, content, (*_CertificateProvider)(h))
if err != nil {
return err
}
registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
if registry == nil {
return E.New("missing certificate provider options registry in context")
}
options, loaded := registry.CreateOptions(h.Type)
if !loaded {
return E.New("unknown certificate provider type: ", h.Type)
}
err = badjson.UnmarshallExcludedContext(ctx, content, (*_CertificateProvider)(h), options)
if err != nil {
return err
}
h.Options = options
return nil
}
type CertificateProviderOptions struct {
Tag string `json:"-"`
Type string `json:"-"`
Options any `json:"-"`
}
type _CertificateProviderInline struct {
Type string `json:"type"`
}
func (o *CertificateProviderOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
if o.Tag != "" {
return json.Marshal(o.Tag)
}
return badjson.MarshallObjectsContext(ctx, _CertificateProviderInline{Type: o.Type}, o.Options)
}
func (o *CertificateProviderOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
if len(content) == 0 {
return E.New("empty certificate_provider value")
}
if content[0] == '"' {
return json.UnmarshalContext(ctx, content, &o.Tag)
}
var inline _CertificateProviderInline
err := json.UnmarshalContext(ctx, content, &inline)
if err != nil {
return err
}
o.Type = inline.Type
if o.Type == "" {
return E.New("missing certificate provider type")
}
registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
if registry == nil {
return E.New("missing certificate provider options registry in context")
}
options, loaded := registry.CreateOptions(o.Type)
if !loaded {
return E.New("unknown certificate provider type: ", o.Type)
}
err = badjson.UnmarshallExcludedContext(ctx, content, &inline, options)
if err != nil {
return err
}
o.Options = options
return nil
}
func (o *CertificateProviderOptions) IsShared() bool {
return o.Tag != ""
}

16
option/cloudflared.go Normal file
View File

@@ -0,0 +1,16 @@
package option
import "github.com/sagernet/sing/common/json/badoption"
type CloudflaredInboundOptions struct {
Token string `json:"token,omitempty"`
HAConnections int `json:"ha_connections,omitempty"`
Protocol string `json:"protocol,omitempty"`
PostQuantum bool `json:"post_quantum,omitempty"`
ControlDialer DialerOptions `json:"control_dialer,omitempty"`
TunnelDialer DialerOptions `json:"tunnel_dialer,omitempty"`
EdgeIPVersion int `json:"edge_ip_version,omitempty"`
DatagramVersion string `json:"datagram_version,omitempty"`
GracePeriod *badoption.Duration `json:"grace_period,omitempty"`
Region string `json:"region,omitempty"`
}

View File

@@ -3,14 +3,19 @@ package option
import (
"context"
"net/netip"
"net/url"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/json/badoption"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/service"
"github.com/miekg/dns"
)
type RawDNSOptions struct {
@@ -21,29 +26,80 @@ type RawDNSOptions struct {
DNSClientOptions
}
type DNSOptions struct {
RawDNSOptions
type LegacyDNSOptions struct {
FakeIP *LegacyDNSFakeIPOptions `json:"fakeip,omitempty"`
}
const (
legacyDNSFakeIPRemovedMessage = "legacy DNS fakeip options are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats"
legacyDNSServerRemovedMessage = "legacy DNS server formats are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, checkout migration: https://sing-box.sagernet.org/migration/#migrate-to-new-dns-server-formats"
)
type DNSOptions struct {
RawDNSOptions
LegacyDNSOptions
}
type removedLegacyDNSOptions struct {
FakeIP json.RawMessage `json:"fakeip,omitempty"`
type contextKeyDontUpgrade struct{}
func ContextWithDontUpgrade(ctx context.Context) context.Context {
return context.WithValue(ctx, (*contextKeyDontUpgrade)(nil), true)
}
func dontUpgradeFromContext(ctx context.Context) bool {
return ctx.Value((*contextKeyDontUpgrade)(nil)) == true
}
func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
var legacyOptions removedLegacyDNSOptions
err := json.UnmarshalContext(ctx, content, &legacyOptions)
err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions)
if err != nil {
return err
}
if len(legacyOptions.FakeIP) != 0 {
return E.New(legacyDNSFakeIPRemovedMessage)
dontUpgrade := dontUpgradeFromContext(ctx)
legacyOptions := o.LegacyDNSOptions
if !dontUpgrade {
if o.FakeIP != nil && o.FakeIP.Enabled {
deprecated.Report(ctx, deprecated.OptionLegacyDNSFakeIPOptions)
ctx = context.WithValue(ctx, (*LegacyDNSFakeIPOptions)(nil), o.FakeIP)
}
o.LegacyDNSOptions = LegacyDNSOptions{}
}
return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
err = badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
if err != nil {
return err
}
if !dontUpgrade {
rcodeMap := make(map[string]int)
o.Servers = common.Filter(o.Servers, func(it DNSServerOptions) bool {
if it.Type == C.DNSTypeLegacyRcode {
rcodeMap[it.Tag] = it.Options.(int)
return false
}
return true
})
if len(rcodeMap) > 0 {
for i := 0; i < len(o.Rules); i++ {
rewriteRcode(rcodeMap, &o.Rules[i])
}
}
}
return nil
}
func rewriteRcode(rcodeMap map[string]int, rule *DNSRule) {
switch rule.Type {
case C.RuleTypeDefault:
rewriteRcodeAction(rcodeMap, &rule.DefaultOptions.DNSRuleAction)
case C.RuleTypeLogical:
rewriteRcodeAction(rcodeMap, &rule.LogicalOptions.DNSRuleAction)
}
}
func rewriteRcodeAction(rcodeMap map[string]int, ruleAction *DNSRuleAction) {
if ruleAction.Action != C.RuleActionTypeRoute {
return
}
rcode, loaded := rcodeMap[ruleAction.RouteOptions.Server]
if !loaded {
return
}
ruleAction.Action = C.RuleActionTypePredefined
ruleAction.PredefinedOptions.Rcode = common.Ptr(DNSRCode(rcode))
}
type DNSClientOptions struct {
@@ -55,6 +111,12 @@ type DNSClientOptions struct {
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
}
type LegacyDNSFakeIPOptions struct {
Enabled bool `json:"enabled,omitempty"`
Inet4Range *badoption.Prefix `json:"inet4_range,omitempty"`
Inet6Range *badoption.Prefix `json:"inet6_range,omitempty"`
}
type DNSTransportOptionsRegistry interface {
CreateOptions(transportType string) (any, bool)
}
@@ -67,6 +129,10 @@ type _DNSServerOptions struct {
type DNSServerOptions _DNSServerOptions
func (o *DNSServerOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
switch o.Type {
case C.DNSTypeLegacy:
o.Type = ""
}
return badjson.MarshallObjectsContext(ctx, (*_DNSServerOptions)(o), o.Options)
}
@@ -82,7 +148,9 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b
var options any
switch o.Type {
case "", C.DNSTypeLegacy:
return E.New(legacyDNSServerRemovedMessage)
o.Type = C.DNSTypeLegacy
options = new(LegacyDNSServerOptions)
deprecated.Report(ctx, deprecated.OptionLegacyDNSTransport)
default:
var loaded bool
options, loaded = registry.CreateOptions(o.Type)
@@ -95,6 +163,169 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b
return err
}
o.Options = options
if o.Type == C.DNSTypeLegacy && !dontUpgradeFromContext(ctx) {
err = o.Upgrade(ctx)
if err != nil {
return err
}
}
return nil
}
func (o *DNSServerOptions) Upgrade(ctx context.Context) error {
if o.Type != C.DNSTypeLegacy {
return nil
}
options := o.Options.(*LegacyDNSServerOptions)
serverURL, _ := url.Parse(options.Address)
var serverType string
if serverURL != nil && serverURL.Scheme != "" {
serverType = serverURL.Scheme
} else {
switch options.Address {
case "local", "fakeip":
serverType = options.Address
default:
serverType = C.DNSTypeUDP
}
}
remoteOptions := RemoteDNSServerOptions{
RawLocalDNSServerOptions: RawLocalDNSServerOptions{
DialerOptions: DialerOptions{
Detour: options.Detour,
DomainResolver: &DomainResolveOptions{
Server: options.AddressResolver,
Strategy: options.AddressStrategy,
},
FallbackDelay: options.AddressFallbackDelay,
},
Legacy: true,
LegacyStrategy: options.Strategy,
LegacyDefaultDialer: options.Detour == "",
LegacyClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
},
LegacyAddressResolver: options.AddressResolver,
LegacyAddressStrategy: options.AddressStrategy,
LegacyAddressFallbackDelay: options.AddressFallbackDelay,
}
switch serverType {
case C.DNSTypeLocal:
o.Type = C.DNSTypeLocal
o.Options = &LocalDNSServerOptions{
RawLocalDNSServerOptions: remoteOptions.RawLocalDNSServerOptions,
}
case C.DNSTypeUDP:
o.Type = C.DNSTypeUDP
o.Options = &remoteOptions
var serverAddr M.Socksaddr
if serverURL == nil || serverURL.Scheme == "" {
serverAddr = M.ParseSocksaddr(options.Address)
} else {
serverAddr = M.ParseSocksaddr(serverURL.Host)
}
if !serverAddr.IsValid() {
return E.New("invalid server address")
}
remoteOptions.Server = serverAddr.AddrString()
if serverAddr.Port != 0 && serverAddr.Port != 53 {
remoteOptions.ServerPort = serverAddr.Port
}
case C.DNSTypeTCP:
o.Type = C.DNSTypeTCP
o.Options = &remoteOptions
if serverURL == nil {
return E.New("invalid server address")
}
serverAddr := M.ParseSocksaddr(serverURL.Host)
if !serverAddr.IsValid() {
return E.New("invalid server address")
}
remoteOptions.Server = serverAddr.AddrString()
if serverAddr.Port != 0 && serverAddr.Port != 53 {
remoteOptions.ServerPort = serverAddr.Port
}
case C.DNSTypeTLS, C.DNSTypeQUIC:
o.Type = serverType
if serverURL == nil {
return E.New("invalid server address")
}
serverAddr := M.ParseSocksaddr(serverURL.Host)
if !serverAddr.IsValid() {
return E.New("invalid server address")
}
remoteOptions.Server = serverAddr.AddrString()
if serverAddr.Port != 0 && serverAddr.Port != 853 {
remoteOptions.ServerPort = serverAddr.Port
}
o.Options = &RemoteTLSDNSServerOptions{
RemoteDNSServerOptions: remoteOptions,
}
case C.DNSTypeHTTPS, C.DNSTypeHTTP3:
o.Type = serverType
httpsOptions := RemoteHTTPSDNSServerOptions{
RemoteTLSDNSServerOptions: RemoteTLSDNSServerOptions{
RemoteDNSServerOptions: remoteOptions,
},
}
o.Options = &httpsOptions
if serverURL == nil {
return E.New("invalid server address")
}
serverAddr := M.ParseSocksaddr(serverURL.Host)
if !serverAddr.IsValid() {
return E.New("invalid server address")
}
httpsOptions.Server = serverAddr.AddrString()
if serverAddr.Port != 0 && serverAddr.Port != 443 {
httpsOptions.ServerPort = serverAddr.Port
}
if serverURL.Path != "/dns-query" {
httpsOptions.Path = serverURL.Path
}
case "rcode":
var rcode int
if serverURL == nil {
return E.New("invalid server address")
}
switch serverURL.Host {
case "success":
rcode = dns.RcodeSuccess
case "format_error":
rcode = dns.RcodeFormatError
case "server_failure":
rcode = dns.RcodeServerFailure
case "name_error":
rcode = dns.RcodeNameError
case "not_implemented":
rcode = dns.RcodeNotImplemented
case "refused":
rcode = dns.RcodeRefused
default:
return E.New("unknown rcode: ", serverURL.Host)
}
o.Type = C.DNSTypeLegacyRcode
o.Options = rcode
case C.DNSTypeDHCP:
o.Type = C.DNSTypeDHCP
dhcpOptions := DHCPDNSServerOptions{}
if serverURL == nil {
return E.New("invalid server address")
}
if serverURL.Host != "" && serverURL.Host != "auto" {
dhcpOptions.Interface = serverURL.Host
}
o.Options = &dhcpOptions
case C.DNSTypeFakeIP:
o.Type = C.DNSTypeFakeIP
fakeipOptions := FakeIPDNSServerOptions{}
if legacyOptions, loaded := ctx.Value((*LegacyDNSFakeIPOptions)(nil)).(*LegacyDNSFakeIPOptions); loaded {
fakeipOptions.Inet4Range = legacyOptions.Inet4Range
fakeipOptions.Inet6Range = legacyOptions.Inet6Range
}
o.Options = &fakeipOptions
default:
return E.New("unsupported DNS server scheme: ", serverType)
}
return nil
}
@@ -119,6 +350,16 @@ func (o *DNSServerAddressOptions) ReplaceServerOptions(options ServerOptions) {
*o = DNSServerAddressOptions(options)
}
type LegacyDNSServerOptions struct {
Address string `json:"address"`
AddressResolver string `json:"address_resolver,omitempty"`
AddressStrategy DomainStrategy `json:"address_strategy,omitempty"`
AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"`
Detour string `json:"detour,omitempty"`
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
}
type HostsDNSServerOptions struct {
Path badoption.Listable[string] `json:"path,omitempty"`
Predefined *badjson.TypedMap[string, badoption.Listable[netip.Addr]] `json:"predefined,omitempty"`
@@ -126,6 +367,10 @@ type HostsDNSServerOptions struct {
type RawLocalDNSServerOptions struct {
DialerOptions
Legacy bool `json:"-"`
LegacyStrategy DomainStrategy `json:"-"`
LegacyDefaultDialer bool `json:"-"`
LegacyClientSubnet netip.Prefix `json:"-"`
}
type LocalDNSServerOptions struct {
@@ -136,6 +381,9 @@ type LocalDNSServerOptions struct {
type RemoteDNSServerOptions struct {
RawLocalDNSServerOptions
DNSServerAddressOptions
LegacyAddressResolver string `json:"-"`
LegacyAddressStrategy DomainStrategy `json:"-"`
LegacyAddressFallbackDelay badoption.Duration `json:"-"`
}
type RemoteTLSDNSServerOptions struct {

View File

@@ -2,7 +2,6 @@ package option
import (
"encoding/base64"
"strings"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
@@ -12,8 +11,6 @@ import (
"github.com/miekg/dns"
)
const defaultDNSRecordTTL uint32 = 3600
type DNSRCode int
func (r DNSRCode) MarshalJSON() ([]byte, error) {
@@ -79,13 +76,10 @@ func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error {
if err == nil {
return o.unmarshalBase64(binary)
}
record, err := parseDNSRecord(stringValue)
record, err := dns.NewRR(stringValue)
if err != nil {
return err
}
if record == nil {
return E.New("empty DNS record")
}
if a, isA := record.(*dns.A); isA {
a.A = M.AddrFromIP(a.A).Unmap().AsSlice()
}
@@ -93,16 +87,6 @@ func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error {
return nil
}
func parseDNSRecord(stringValue string) (dns.RR, error) {
if len(stringValue) > 0 && stringValue[len(stringValue)-1] != '\n' {
stringValue += "\n"
}
parser := dns.NewZoneParser(strings.NewReader(stringValue), "", "")
parser.SetDefaultTTL(defaultDNSRecordTTL)
record, _ := parser.Next()
return record, parser.Err()
}
func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error {
record, _, err := dns.UnpackRR(binary, 0)
if err != nil {
@@ -116,10 +100,3 @@ func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error {
func (o DNSRecordOptions) Build() dns.RR {
return o.RR
}
func (o DNSRecordOptions) Match(record dns.RR) bool {
if o.RR == nil || record == nil {
return false
}
return dns.IsDuplicate(o.RR, record)
}

View File

@@ -1,52 +0,0 @@
package option
import (
"testing"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
func mustRecordOptions(t *testing.T, record string) DNSRecordOptions {
t.Helper()
var value DNSRecordOptions
require.NoError(t, value.UnmarshalJSON([]byte(`"`+record+`"`)))
return value
}
func TestDNSRecordOptionsUnmarshalJSONAcceptsFullyQualifiedNames(t *testing.T) {
t.Parallel()
for _, record := range []string{
"example.com. A 1.1.1.1",
"www.example.com. IN CNAME example.com.",
} {
value := mustRecordOptions(t, record)
require.NotNil(t, value.RR)
}
}
func TestDNSRecordOptionsUnmarshalJSONRejectsRelativeNames(t *testing.T) {
t.Parallel()
for _, record := range []string{
"@ IN A 1.1.1.1",
"www IN CNAME example.com.",
"example.com. IN CNAME @",
"example.com. IN CNAME www",
} {
var value DNSRecordOptions
err := value.UnmarshalJSON([]byte(`"` + record + `"`))
require.Error(t, err)
}
}
func TestDNSRecordOptionsMatchIgnoresTTL(t *testing.T) {
t.Parallel()
expected := mustRecordOptions(t, "example.com. 600 IN A 1.1.1.1")
record, err := dns.NewRR("example.com. 60 IN A 1.1.1.1")
require.NoError(t, err)
require.True(t, expected.Match(record))
}

View File

@@ -1,91 +0,0 @@
package option
import (
"context"
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/service"
"github.com/stretchr/testify/require"
)
type stubDNSTransportOptionsRegistry struct{}
func (stubDNSTransportOptionsRegistry) CreateOptions(transportType string) (any, bool) {
switch transportType {
case C.DNSTypeUDP:
return new(RemoteDNSServerOptions), true
case C.DNSTypeFakeIP:
return new(FakeIPDNSServerOptions), true
default:
return nil, false
}
}
func TestDNSOptionsRejectsLegacyFakeIPOptions(t *testing.T) {
t.Parallel()
ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{})
var options DNSOptions
err := json.UnmarshalContext(ctx, []byte(`{
"fakeip": {
"enabled": true,
"inet4_range": "198.18.0.0/15"
}
}`), &options)
require.EqualError(t, err, legacyDNSFakeIPRemovedMessage)
}
func TestDNSServerOptionsRejectsLegacyFormats(t *testing.T) {
t.Parallel()
ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{})
testCases := []string{
`{"address":"1.1.1.1"}`,
`{"type":"legacy","address":"1.1.1.1"}`,
}
for _, content := range testCases {
var options DNSServerOptions
err := json.UnmarshalContext(ctx, []byte(content), &options)
require.EqualError(t, err, legacyDNSServerRemovedMessage)
}
}
func TestDNSOptionsAcceptsTypedServers(t *testing.T) {
t.Parallel()
ctx := service.ContextWith[DNSTransportOptionsRegistry](context.Background(), stubDNSTransportOptionsRegistry{})
var options DNSOptions
err := json.UnmarshalContext(ctx, []byte(`{
"servers": [
{"type": "udp", "tag": "default", "server": "1.1.1.1"},
{"type": "fakeip", "tag": "fake", "inet4_range": "198.18.0.0/15"}
]
}`), &options)
require.NoError(t, err)
require.Len(t, options.Servers, 2)
require.Equal(t, C.DNSTypeUDP, options.Servers[0].Type)
require.Equal(t, "1.1.1.1", options.Servers[0].Options.(*RemoteDNSServerOptions).Server)
require.Equal(t, C.DNSTypeFakeIP, options.Servers[1].Type)
}
func TestDNSRuleActionEvaluateRoundTrip(t *testing.T) {
t.Parallel()
action := DNSRuleAction{
Action: C.RuleActionTypeEvaluate,
RouteOptions: DNSRouteActionOptions{
Server: "default",
},
}
content, err := json.Marshal(action)
require.NoError(t, err)
var decoded DNSRuleAction
err = json.UnmarshalContext(context.Background(), content, &decoded)
require.NoError(t, err)
require.Equal(t, action, decoded)
}

View File

@@ -19,7 +19,6 @@ type Hysteria2InboundOptions struct {
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth,omitempty"`
InboundTLSOptionsContainer
Masquerade *Hysteria2Masquerade `json:"masquerade,omitempty"`
BBRProfile string `json:"bbr_profile,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"`
}
@@ -113,15 +112,13 @@ type Hysteria2MasqueradeString struct {
type Hysteria2OutboundOptions struct {
DialerOptions
ServerOptions
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
HopIntervalMax badoption.Duration `json:"hop_interval_max,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
ServerPorts badoption.Listable[string] `json:"server_ports,omitempty"`
HopInterval badoption.Duration `json:"hop_interval,omitempty"`
UpMbps int `json:"up_mbps,omitempty"`
DownMbps int `json:"down_mbps,omitempty"`
Obfs *Hysteria2Obfs `json:"obfs,omitempty"`
Password string `json:"password,omitempty"`
Network NetworkList `json:"network,omitempty"`
OutboundTLSOptionsContainer
BBRProfile string `json:"bbr_profile,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"`
BrutalDebug bool `json:"brutal_debug,omitempty"`
}

View File

@@ -10,19 +10,18 @@ import (
)
type _Options struct {
RawMessage json.RawMessage `json:"-"`
Schema string `json:"$schema,omitempty"`
Log *LogOptions `json:"log,omitempty"`
DNS *DNSOptions `json:"dns,omitempty"`
NTP *NTPOptions `json:"ntp,omitempty"`
Certificate *CertificateOptions `json:"certificate,omitempty"`
CertificateProviders []CertificateProvider `json:"certificate_providers,omitempty"`
Endpoints []Endpoint `json:"endpoints,omitempty"`
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"`
Services []Service `json:"services,omitempty"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
RawMessage json.RawMessage `json:"-"`
Schema string `json:"$schema,omitempty"`
Log *LogOptions `json:"log,omitempty"`
DNS *DNSOptions `json:"dns,omitempty"`
NTP *NTPOptions `json:"ntp,omitempty"`
Certificate *CertificateOptions `json:"certificate,omitempty"`
Endpoints []Endpoint `json:"endpoints,omitempty"`
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"`
Services []Service `json:"services,omitempty"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
}
type Options _Options
@@ -57,25 +56,6 @@ func checkOptions(options *Options) error {
if err != nil {
return err
}
err = checkCertificateProviders(options.CertificateProviders)
if err != nil {
return err
}
return nil
}
func checkCertificateProviders(providers []CertificateProvider) error {
seen := make(map[string]bool)
for i, provider := range providers {
tag := provider.Tag
if tag == "" {
tag = F.ToString(i)
}
if seen[tag] {
return E.New("duplicate certificate provider tag: ", tag)
}
seen[tag] = true
}
return nil
}

View File

@@ -1,76 +0,0 @@
package option
import (
"strings"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badoption"
)
type CloudflareOriginCACertificateProviderOptions struct {
Domain badoption.Listable[string] `json:"domain,omitempty"`
DataDirectory string `json:"data_directory,omitempty"`
APIToken string `json:"api_token,omitempty"`
OriginCAKey string `json:"origin_ca_key,omitempty"`
RequestType CloudflareOriginCARequestType `json:"request_type,omitempty"`
RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"`
Detour string `json:"detour,omitempty"`
}
type CloudflareOriginCARequestType string
const (
CloudflareOriginCARequestTypeOriginRSA = CloudflareOriginCARequestType("origin-rsa")
CloudflareOriginCARequestTypeOriginECC = CloudflareOriginCARequestType("origin-ecc")
)
func (t *CloudflareOriginCARequestType) UnmarshalJSON(data []byte) error {
var value string
err := json.Unmarshal(data, &value)
if err != nil {
return err
}
value = strings.ToLower(value)
switch CloudflareOriginCARequestType(value) {
case "", CloudflareOriginCARequestTypeOriginRSA, CloudflareOriginCARequestTypeOriginECC:
*t = CloudflareOriginCARequestType(value)
default:
return E.New("unsupported Cloudflare Origin CA request type: ", value)
}
return nil
}
type CloudflareOriginCARequestValidity uint16
const (
CloudflareOriginCARequestValidity7 = CloudflareOriginCARequestValidity(7)
CloudflareOriginCARequestValidity30 = CloudflareOriginCARequestValidity(30)
CloudflareOriginCARequestValidity90 = CloudflareOriginCARequestValidity(90)
CloudflareOriginCARequestValidity365 = CloudflareOriginCARequestValidity(365)
CloudflareOriginCARequestValidity730 = CloudflareOriginCARequestValidity(730)
CloudflareOriginCARequestValidity1095 = CloudflareOriginCARequestValidity(1095)
CloudflareOriginCARequestValidity5475 = CloudflareOriginCARequestValidity(5475)
)
func (v *CloudflareOriginCARequestValidity) UnmarshalJSON(data []byte) error {
var value uint16
err := json.Unmarshal(data, &value)
if err != nil {
return err
}
switch CloudflareOriginCARequestValidity(value) {
case 0,
CloudflareOriginCARequestValidity7,
CloudflareOriginCARequestValidity30,
CloudflareOriginCARequestValidity90,
CloudflareOriginCARequestValidity365,
CloudflareOriginCARequestValidity730,
CloudflareOriginCARequestValidity1095,
CloudflareOriginCARequestValidity5475:
*v = CloudflareOriginCARequestValidity(value)
default:
return E.New("unsupported Cloudflare Origin CA requested validity: ", value)
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More