mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-11 17:47:20 +10:00
Compare commits
41 Commits
2853fc5c61
...
6a351be73a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a351be73a | ||
|
|
1913376113 | ||
|
|
b05f58b469 | ||
|
|
baf1da892b | ||
|
|
f3c8fe59ac | ||
|
|
117422db68 | ||
|
|
5b32dbf57f | ||
|
|
d103fc2aea | ||
|
|
532f350637 | ||
|
|
d82e7cd4b6 | ||
|
|
f628519333 | ||
|
|
7def08b5a1 | ||
|
|
8ba8ad5f0c | ||
|
|
07f2fd65b2 | ||
|
|
31c707f8e8 | ||
|
|
3549c02b8c | ||
|
|
e5aaf782c6 | ||
|
|
5b08ae150f | ||
|
|
704482bb4a | ||
|
|
5436192ada | ||
|
|
d2f005aea3 | ||
|
|
dc9b2089ea | ||
|
|
b16b6f8b18 | ||
|
|
ab414f20f5 | ||
|
|
f8cbe27b39 | ||
|
|
2544d26664 | ||
|
|
bcaba94c61 | ||
|
|
ebf8a213b6 | ||
|
|
ab323e0eb9 | ||
|
|
2132e68d3a | ||
|
|
47742abe93 | ||
|
|
77e51035bd | ||
|
|
eeb5dead2a | ||
|
|
45339d101b | ||
|
|
04c0490992 | ||
|
|
7ffdc48b49 | ||
|
|
e15bdf11eb | ||
|
|
e3bcb06c3e | ||
|
|
84d2280960 | ||
|
|
4fd2532b0a | ||
|
|
02ccde6c71 |
@@ -4,6 +4,7 @@
|
||||
--license GPL-3.0-or-later
|
||||
--description "The universal proxy platform."
|
||||
--url "https://sing-box.sagernet.org/"
|
||||
--vendor SagerNet
|
||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||
--no-deb-generate-changes
|
||||
|
||||
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
||||
2fef65f9dba90ddb89a87d00a6eb6165487c10c1
|
||||
ea7cd33752aed62603775af3df946c1b83f4b0b3
|
||||
|
||||
21
adapter/certificate/adapter.go
Normal file
21
adapter/certificate/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
||||
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
|
||||
}
|
||||
158
adapter/certificate/manager.go
Normal file
158
adapter/certificate/manager.go
Normal file
@@ -0,0 +1,158 @@
|
||||
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
|
||||
}
|
||||
72
adapter/certificate/registry.go
Normal file
72
adapter/certificate/registry.go
Normal file
@@ -0,0 +1,72 @@
|
||||
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
|
||||
}
|
||||
38
adapter/certificate_provider.go
Normal file
38
adapter/certificate_provider.go
Normal file
@@ -0,0 +1,38 @@
|
||||
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
|
||||
}
|
||||
@@ -25,8 +25,8 @@ type DNSRouter interface {
|
||||
|
||||
type DNSClient interface {
|
||||
Start()
|
||||
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)
|
||||
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)
|
||||
ClearCache()
|
||||
}
|
||||
|
||||
@@ -72,11 +72,6 @@ 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)
|
||||
|
||||
@@ -2,6 +2,7 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
@@ -9,6 +10,8 @@ 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 {
|
||||
@@ -78,12 +81,16 @@ type InboundContext struct {
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
DestinationAddresses []netip.Addr
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
DestinationAddresses []netip.Addr
|
||||
DNSResponse *dns.Msg
|
||||
DestinationAddressMatchFromResponse bool
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *ConnectionOwner
|
||||
SourceMACAddress net.HardwareAddr
|
||||
SourceHostname string
|
||||
QueryType uint16
|
||||
FakeIP bool
|
||||
|
||||
// rule cache
|
||||
|
||||
@@ -112,6 +119,46 @@ func (c *InboundContext) ResetRuleMatchCache() {
|
||||
c.DidMatch = false
|
||||
}
|
||||
|
||||
func (c *InboundContext) DestinationAddressesForMatch() []netip.Addr {
|
||||
if c.DestinationAddressMatchFromResponse {
|
||||
return DNSResponseAddresses(c.DNSResponse)
|
||||
}
|
||||
return c.DestinationAddresses
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
45
adapter/inbound_test.go
Normal file
45
adapter/inbound_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
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())
|
||||
}
|
||||
23
adapter/neighbor.go
Normal file
23
adapter/neighbor.go
Normal file
@@ -0,0 +1,23 @@
|
||||
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)
|
||||
}
|
||||
@@ -36,6 +36,10 @@ type PlatformInterface interface {
|
||||
|
||||
UsePlatformNotification() bool
|
||||
SendNotification(notification *Notification) error
|
||||
|
||||
UsePlatformNeighborResolver() bool
|
||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
||||
}
|
||||
|
||||
type FindConnectionOwnerRequest struct {
|
||||
|
||||
@@ -26,6 +26,8 @@ type Router interface {
|
||||
RuleSet(tag string) (RuleSet, bool)
|
||||
Rules() []Rule
|
||||
NeedFindProcess() bool
|
||||
NeedFindNeighbor() bool
|
||||
NeighborResolver() NeighborResolver
|
||||
AppendTracker(tracker ConnectionTracker)
|
||||
ResetNetwork()
|
||||
}
|
||||
@@ -64,10 +66,14 @@ 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
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
ContainsDNSQueryTypeRule bool
|
||||
}
|
||||
type HTTPStartContext struct {
|
||||
ctx context.Context
|
||||
|
||||
@@ -2,6 +2,8 @@ package adapter
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type HeadlessRule interface {
|
||||
@@ -18,8 +20,9 @@ type Rule interface {
|
||||
|
||||
type DNSRule interface {
|
||||
Rule
|
||||
LegacyPreMatch(metadata *InboundContext) bool
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
|
||||
}
|
||||
|
||||
type RuleAction interface {
|
||||
|
||||
125
box.go
125
box.go
@@ -9,6 +9,7 @@ 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"
|
||||
@@ -37,20 +38,21 @@ 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
|
||||
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
|
||||
certificateProvider *boxCertificate.Manager
|
||||
dnsTransport *dns.TransportManager
|
||||
dnsRouter *dns.Router
|
||||
connection *route.ConnectionManager
|
||||
router *route.Router
|
||||
internalService []adapter.LifecycleService
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@@ -66,6 +68,7 @@ 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 {
|
||||
@@ -90,6 +93,10 @@ 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
|
||||
}
|
||||
|
||||
@@ -106,6 +113,7 @@ 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")
|
||||
@@ -122,6 +130,9 @@ 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)
|
||||
@@ -179,11 +190,13 @@ 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)
|
||||
@@ -272,6 +285,24 @@ 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 != "" {
|
||||
@@ -298,22 +329,22 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||
}
|
||||
}
|
||||
for i, serviceOptions := range options.Services {
|
||||
for i, certificateProviderOptions := range options.CertificateProviders {
|
||||
var tag string
|
||||
if serviceOptions.Tag != "" {
|
||||
tag = serviceOptions.Tag
|
||||
if certificateProviderOptions.Tag != "" {
|
||||
tag = certificateProviderOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
err = serviceManager.Create(
|
||||
err = certificateProviderManager.Create(
|
||||
ctx,
|
||||
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||
logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
serviceOptions.Type,
|
||||
serviceOptions.Options,
|
||||
certificateProviderOptions.Type,
|
||||
certificateProviderOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||
return nil, E.Cause(err, "initialize certificate provider[", i, "]")
|
||||
}
|
||||
}
|
||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||
@@ -383,20 +414,21 @@ 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,
|
||||
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,
|
||||
certificateProvider: certificateProviderManager,
|
||||
dnsRouter: dnsRouter,
|
||||
connection: connectionManager,
|
||||
router: router,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
internalService: internalServices,
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -450,11 +482,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)
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection, s.router, s.dnsRouter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -470,11 +502,19 @@ func (s *Box) start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint)
|
||||
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.inbound, s.endpoint, s.service)
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -482,7 +522,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.inbound, s.endpoint, s.service)
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -506,8 +546,9 @@ func (s *Box) Close() error {
|
||||
service adapter.Lifecycle
|
||||
}{
|
||||
{"service", s.service},
|
||||
{"endpoint", s.endpoint},
|
||||
{"inbound", s.inbound},
|
||||
{"certificate-provider", s.certificateProvider},
|
||||
{"endpoint", s.endpoint},
|
||||
{"outbound", s.outbound},
|
||||
{"router", s.router},
|
||||
{"connection", s.connection},
|
||||
|
||||
Submodule clients/android updated: 834a5f7df0...cba1cc3ce0
Submodule clients/apple updated: 6b790c7a80...ffbf405b52
@@ -38,37 +38,6 @@ 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 {
|
||||
@@ -91,8 +60,8 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
||||
storage = certmagic.Default.Storage
|
||||
}
|
||||
zapLogger := zap.New(zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(encoderConfig()),
|
||||
&acmeLogWriter{logger: logger},
|
||||
zapcore.NewConsoleEncoder(ACMEEncoderConfig()),
|
||||
&ACMELogWriter{Logger: logger},
|
||||
zap.DebugLevel,
|
||||
))
|
||||
config := &certmagic.Config{
|
||||
@@ -158,7 +127,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
||||
} else {
|
||||
tlsConfig = &tls.Config{
|
||||
GetCertificate: config.GetCertificate,
|
||||
NextProtos: []string{ACMETLS1Protocol},
|
||||
NextProtos: []string{C.ACMETLS1Protocol},
|
||||
}
|
||||
}
|
||||
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
||||
|
||||
41
common/tls/acme_logger.go
Normal file
41
common/tls/acme_logger.go
Normal file
@@ -0,0 +1,41 @@
|
||||
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
|
||||
}
|
||||
@@ -32,6 +32,10 @@ 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")
|
||||
}
|
||||
|
||||
@@ -13,19 +13,87 @@ 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
|
||||
@@ -53,18 +121,17 @@ func (c *STDServerConfig) SetServerName(serverName string) {
|
||||
func (c *STDServerConfig) NextProtos() []string {
|
||||
c.access.RLock()
|
||||
defer c.access.RUnlock()
|
||||
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.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.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
|
||||
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||
} else {
|
||||
config.NextProtos = nextProto
|
||||
@@ -72,6 +139,18 @@ 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
|
||||
}
|
||||
@@ -91,15 +170,39 @@ func (c *STDServerConfig) Clone() Config {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Start() error {
|
||||
if c.acmeService != nil {
|
||||
return c.acmeService.Start()
|
||||
} else {
|
||||
err := c.startWatcher()
|
||||
if c.certificateProvider != nil {
|
||||
err := c.certificateProvider.Start()
|
||||
if err != nil {
|
||||
c.logger.Warn("create fsnotify watcher: ", err)
|
||||
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()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if c.acmeService != nil {
|
||||
err := c.acmeService.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := c.startWatcher()
|
||||
if err != nil {
|
||||
c.logger.Warn("create fsnotify watcher: ", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) startWatcher() error {
|
||||
@@ -203,23 +306,34 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Close() error {
|
||||
if c.acmeService != nil {
|
||||
return c.acmeService.Close()
|
||||
}
|
||||
if c.watcher != nil {
|
||||
return c.watcher.Close()
|
||||
}
|
||||
return nil
|
||||
return common.Close(c.certificateProvider, c.acmeService, c.watcher)
|
||||
}
|
||||
|
||||
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.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
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)
|
||||
//nolint:staticcheck
|
||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
||||
if err != nil {
|
||||
@@ -272,7 +386,7 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
||||
certificate []byte
|
||||
key []byte
|
||||
)
|
||||
if acmeService == nil {
|
||||
if certificateProvider == nil && acmeService == nil {
|
||||
if len(options.Certificate) > 0 {
|
||||
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||
} else if options.CertificatePath != "" {
|
||||
@@ -360,6 +474,7 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
||||
serverConfig := &STDServerConfig{
|
||||
config: tlsConfig,
|
||||
logger: logger,
|
||||
certificateProvider: certificateProvider,
|
||||
acmeService: acmeService,
|
||||
certificate: certificate,
|
||||
key: key,
|
||||
@@ -369,8 +484,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.Lock()
|
||||
defer serverConfig.access.Unlock()
|
||||
serverConfig.access.RLock()
|
||||
defer serverConfig.access.RUnlock()
|
||||
return serverConfig.config, nil
|
||||
}
|
||||
var config ServerConfig = serverConfig
|
||||
@@ -387,3 +502,27 @@ 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
|
||||
}
|
||||
|
||||
@@ -15,19 +15,18 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
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"
|
||||
DNSTypeLegacy = "legacy"
|
||||
DNSTypeUDP = "udp"
|
||||
DNSTypeTCP = "tcp"
|
||||
DNSTypeTLS = "tls"
|
||||
DNSTypeHTTPS = "https"
|
||||
DNSTypeQUIC = "quic"
|
||||
DNSTypeHTTP3 = "h3"
|
||||
DNSTypeLocal = "local"
|
||||
DNSTypeHosts = "hosts"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeDHCP = "dhcp"
|
||||
DNSTypeTailscale = "tailscale"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -29,6 +29,7 @@ const (
|
||||
const (
|
||||
RuleActionTypeRoute = "route"
|
||||
RuleActionTypeRouteOptions = "route-options"
|
||||
RuleActionTypeEvaluate = "evaluate"
|
||||
RuleActionTypeDirect = "direct"
|
||||
RuleActionTypeBypass = "bypass"
|
||||
RuleActionTypeReject = "reject"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package tls
|
||||
package constant
|
||||
|
||||
const ACMETLS1Protocol = "acme-tls/1"
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -14,7 +13,6 @@ 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"
|
||||
@@ -109,7 +107,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(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
|
||||
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) {
|
||||
if len(message.Question) == 0 {
|
||||
if c.logger != nil {
|
||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||
@@ -239,13 +237,10 @@ 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(MessageToAddresses(response))
|
||||
rejected = !responseChecker(response)
|
||||
}
|
||||
if rejected {
|
||||
if !disableCache && c.rdrc != nil {
|
||||
@@ -315,7 +310,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(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
||||
domain = FqdnToDomain(domain)
|
||||
dnsName := dns.Fqdn(domain)
|
||||
var strategy C.DomainStrategy
|
||||
@@ -400,7 +395,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(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||
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) {
|
||||
question := dns.Question{
|
||||
Name: name,
|
||||
Qtype: qType,
|
||||
@@ -515,25 +510,7 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
||||
}
|
||||
|
||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||
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
|
||||
return adapter.DNSResponseAddresses(response)
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
|
||||
111
dns/repro_test.go
Normal file
111
dns/repro_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"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
|
||||
},
|
||||
})
|
||||
|
||||
addrs, 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")}, addrs)
|
||||
}
|
||||
|
||||
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, errors.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))
|
||||
}
|
||||
803
dns/router.go
803
dns/router.go
@@ -5,11 +5,13 @@ import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"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/experimental/deprecated"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
R "github.com/sagernet/sing-box/route/rule"
|
||||
@@ -19,6 +21,8 @@ import (
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/task"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/contrab/freelru"
|
||||
"github.com/sagernet/sing/contrab/maphash"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -28,16 +32,29 @@ import (
|
||||
|
||||
var _ adapter.DNSRouter = (*Router)(nil)
|
||||
|
||||
type dnsRuleSetCallback struct {
|
||||
ruleSet adapter.RuleSet
|
||||
element *list.Element[adapter.RuleSetUpdateCallback]
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
transport adapter.DNSTransportManager
|
||||
outbound adapter.OutboundManager
|
||||
client adapter.DNSClient
|
||||
rules []adapter.DNSRule
|
||||
defaultDomainStrategy C.DomainStrategy
|
||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||
platformInterface adapter.PlatformInterface
|
||||
ctx context.Context
|
||||
logger logger.ContextLogger
|
||||
transport adapter.DNSTransportManager
|
||||
outbound adapter.OutboundManager
|
||||
client adapter.DNSClient
|
||||
rawRules []option.DNSRule
|
||||
rules []adapter.DNSRule
|
||||
defaultDomainStrategy C.DomainStrategy
|
||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||
platformInterface adapter.PlatformInterface
|
||||
legacyDNSMode bool
|
||||
rulesAccess sync.RWMutex
|
||||
closing bool
|
||||
ruleSetCallbacks []dnsRuleSetCallback
|
||||
runtimeRuleError error
|
||||
addressFilterDeprecatedReported bool
|
||||
ruleStrategyDeprecatedReported bool
|
||||
}
|
||||
|
||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
||||
@@ -46,6 +63,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
logger: logFactory.NewLogger("dns"),
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||
rawRules: make([]option.DNSRule, 0, len(options.Rules)),
|
||||
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
||||
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
||||
}
|
||||
@@ -74,13 +92,12 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
||||
}
|
||||
|
||||
func (r *Router) Initialize(rules []option.DNSRule) error {
|
||||
for i, ruleOptions := range rules {
|
||||
dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse dns rule[", i, "]")
|
||||
}
|
||||
r.rules = append(r.rules, dnsRule)
|
||||
r.rawRules = append(r.rawRules[:0], rules...)
|
||||
newRules, _, err := r.buildRules(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
closeRules(newRules)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -92,13 +109,17 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||
r.client.Start()
|
||||
monitor.Finish()
|
||||
|
||||
for i, rule := range r.rules {
|
||||
monitor.Start("initialize DNS rule[", i, "]")
|
||||
err := rule.Start()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize DNS rule[", i, "]")
|
||||
}
|
||||
monitor.Start("initialize DNS rules")
|
||||
err := r.rebuildRules(true)
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
monitor.Start("register DNS rule-set callbacks")
|
||||
err = r.registerRuleSetCallbacks()
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -106,8 +127,19 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
||||
|
||||
func (r *Router) Close() error {
|
||||
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||
r.rulesAccess.Lock()
|
||||
r.closing = true
|
||||
callbacks := r.ruleSetCallbacks
|
||||
r.ruleSetCallbacks = nil
|
||||
runtimeRules := r.rules
|
||||
r.rules = nil
|
||||
r.runtimeRuleError = nil
|
||||
for _, callback := range callbacks {
|
||||
callback.ruleSet.UnregisterCallback(callback.element)
|
||||
}
|
||||
r.rulesAccess.Unlock()
|
||||
var err error
|
||||
for i, rule := range r.rules {
|
||||
for i, rule := range runtimeRules {
|
||||
monitor.Start("close dns rule[", i, "]")
|
||||
err = E.Append(err, rule.Close(), func(err error) error {
|
||||
return E.Cause(err, "close dns rule[", i, "]")
|
||||
@@ -117,6 +149,153 @@ func (r *Router) Close() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Router) rebuildRules(startRules bool) error {
|
||||
if r.isClosing() {
|
||||
return nil
|
||||
}
|
||||
newRules, legacyDNSMode, err := r.buildRules(startRules)
|
||||
if err != nil {
|
||||
if r.isClosing() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
shouldReportAddressFilterDeprecated := startRules &&
|
||||
legacyDNSMode &&
|
||||
!r.addressFilterDeprecatedReported &&
|
||||
common.Any(newRules, func(rule adapter.DNSRule) bool { return rule.WithAddressLimit() })
|
||||
shouldReportRuleStrategyDeprecated := startRules &&
|
||||
legacyDNSMode &&
|
||||
!r.ruleStrategyDeprecatedReported &&
|
||||
hasDNSRuleActionStrategy(r.rawRules)
|
||||
r.rulesAccess.Lock()
|
||||
if r.closing {
|
||||
r.rulesAccess.Unlock()
|
||||
closeRules(newRules)
|
||||
return nil
|
||||
}
|
||||
oldRules := r.rules
|
||||
r.rules = newRules
|
||||
r.legacyDNSMode = legacyDNSMode
|
||||
r.runtimeRuleError = nil
|
||||
if shouldReportAddressFilterDeprecated {
|
||||
r.addressFilterDeprecatedReported = true
|
||||
}
|
||||
if shouldReportRuleStrategyDeprecated {
|
||||
r.ruleStrategyDeprecatedReported = true
|
||||
}
|
||||
r.rulesAccess.Unlock()
|
||||
closeRules(oldRules)
|
||||
if shouldReportAddressFilterDeprecated {
|
||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSAddressFilter)
|
||||
}
|
||||
if shouldReportRuleStrategyDeprecated {
|
||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSRuleStrategy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) isClosing() bool {
|
||||
r.rulesAccess.RLock()
|
||||
defer r.rulesAccess.RUnlock()
|
||||
return r.closing
|
||||
}
|
||||
|
||||
func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, error) {
|
||||
for i, ruleOptions := range r.rawRules {
|
||||
err := R.ValidateNoNestedDNSRuleActions(ruleOptions)
|
||||
if err != nil {
|
||||
return nil, false, E.Cause(err, "parse dns rule[", i, "]")
|
||||
}
|
||||
}
|
||||
router := service.FromContext[adapter.Router](r.ctx)
|
||||
legacyDNSMode, err := resolveLegacyDNSMode(router, r.rawRules)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !legacyDNSMode {
|
||||
err = validateLegacyDNSModeDisabledRules(r.rawRules)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
newRules := make([]adapter.DNSRule, 0, len(r.rawRules))
|
||||
for i, ruleOptions := range r.rawRules {
|
||||
dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true, legacyDNSMode)
|
||||
if err != nil {
|
||||
closeRules(newRules)
|
||||
return nil, false, E.Cause(err, "parse dns rule[", i, "]")
|
||||
}
|
||||
newRules = append(newRules, dnsRule)
|
||||
}
|
||||
if startRules {
|
||||
for i, rule := range newRules {
|
||||
err := rule.Start()
|
||||
if err != nil {
|
||||
closeRules(newRules)
|
||||
return nil, false, E.Cause(err, "initialize DNS rule[", i, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
return newRules, legacyDNSMode, nil
|
||||
}
|
||||
|
||||
func closeRules(rules []adapter.DNSRule) {
|
||||
for _, rule := range rules {
|
||||
_ = rule.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) registerRuleSetCallbacks() error {
|
||||
tags := referencedDNSRuleSetTags(r.rawRules)
|
||||
if len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
r.rulesAccess.RLock()
|
||||
if len(r.ruleSetCallbacks) > 0 {
|
||||
r.rulesAccess.RUnlock()
|
||||
return nil
|
||||
}
|
||||
r.rulesAccess.RUnlock()
|
||||
router := service.FromContext[adapter.Router](r.ctx)
|
||||
if router == nil {
|
||||
return E.New("router service not found")
|
||||
}
|
||||
callbacks := make([]dnsRuleSetCallback, 0, len(tags))
|
||||
for _, tag := range tags {
|
||||
ruleSet, loaded := router.RuleSet(tag)
|
||||
if !loaded {
|
||||
for _, callback := range callbacks {
|
||||
callback.ruleSet.UnregisterCallback(callback.element)
|
||||
}
|
||||
return E.New("rule-set not found: ", tag)
|
||||
}
|
||||
element := ruleSet.RegisterCallback(func(adapter.RuleSet) {
|
||||
err := r.rebuildRules(true)
|
||||
if err != nil {
|
||||
r.rulesAccess.Lock()
|
||||
r.runtimeRuleError = err
|
||||
r.rulesAccess.Unlock()
|
||||
r.logger.Error(E.Cause(err, "rebuild DNS rules after rule-set update"))
|
||||
}
|
||||
})
|
||||
callbacks = append(callbacks, dnsRuleSetCallback{
|
||||
ruleSet: ruleSet,
|
||||
element: element,
|
||||
})
|
||||
}
|
||||
r.rulesAccess.Lock()
|
||||
if len(r.ruleSetCallbacks) == 0 {
|
||||
r.ruleSetCallbacks = callbacks
|
||||
callbacks = nil
|
||||
}
|
||||
r.rulesAccess.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback.ruleSet.UnregisterCallback(callback.element)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
@@ -132,16 +311,12 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||
continue
|
||||
}
|
||||
metadata.ResetRuleCache()
|
||||
if currentRule.Match(metadata) {
|
||||
displayRuleIndex := currentRuleIndex
|
||||
if displayRuleIndex != -1 {
|
||||
displayRuleIndex += displayRuleIndex + 1
|
||||
}
|
||||
ruleDescription := currentRule.String()
|
||||
if ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
if currentRule.LegacyPreMatch(metadata) {
|
||||
if ruleDescription := currentRule.String(); ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
|
||||
}
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionDNSRoute:
|
||||
@@ -166,14 +341,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||
if action.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = action.ClientSubnet
|
||||
}
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
return transport, currentRule, currentRuleIndex
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
if action.Strategy != C.DomainStrategyAsIS {
|
||||
@@ -196,15 +363,279 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||
}
|
||||
}
|
||||
transport := r.transport.Default()
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
return transport, nil, -1
|
||||
}
|
||||
|
||||
func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOptions R.RuleActionDNSRouteOptions) {
|
||||
if routeOptions.DisableCache {
|
||||
options.DisableCache = true
|
||||
}
|
||||
if routeOptions.RewriteTTL != nil {
|
||||
options.RewriteTTL = routeOptions.RewriteTTL
|
||||
}
|
||||
if routeOptions.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = routeOptions.ClientSubnet
|
||||
}
|
||||
}
|
||||
|
||||
type dnsRouteStatus uint8
|
||||
|
||||
const (
|
||||
dnsRouteStatusMissing dnsRouteStatus = iota
|
||||
dnsRouteStatusSkipped
|
||||
dnsRouteStatusResolved
|
||||
)
|
||||
|
||||
func (r *Router) resolveDNSRoute(action *R.RuleActionDNSRoute, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, dnsRouteStatus) {
|
||||
transport, loaded := r.transport.Transport(action.Server)
|
||||
if !loaded {
|
||||
return nil, dnsRouteStatusMissing
|
||||
}
|
||||
isFakeIP := transport.Type() == C.DNSTypeFakeIP
|
||||
if isFakeIP && !allowFakeIP {
|
||||
return transport, dnsRouteStatusSkipped
|
||||
}
|
||||
r.applyDNSRouteOptions(options, action.RuleActionDNSRouteOptions)
|
||||
if isFakeIP {
|
||||
options.DisableCache = true
|
||||
}
|
||||
return transport, dnsRouteStatusResolved
|
||||
}
|
||||
|
||||
func (r *Router) logRuleMatch(ctx context.Context, ruleIndex int, currentRule adapter.DNSRule) {
|
||||
if ruleDescription := currentRule.String(); ruleDescription != "" {
|
||||
r.logger.DebugContext(ctx, "match[", ruleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||
} else {
|
||||
r.logger.DebugContext(ctx, "match[", ruleIndex, "] => ", currentRule.Action())
|
||||
}
|
||||
}
|
||||
|
||||
type exchangeWithRulesResult struct {
|
||||
response *mDNS.Msg
|
||||
transport adapter.DNSTransport
|
||||
rejectAction *R.RuleActionReject
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *Router) exchangeWithRules(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) exchangeWithRulesResult {
|
||||
metadata := adapter.ContextFrom(ctx)
|
||||
if metadata == nil {
|
||||
panic("no context")
|
||||
}
|
||||
effectiveOptions := options
|
||||
var savedResponse *mDNS.Msg
|
||||
for currentRuleIndex, currentRule := range r.rules {
|
||||
metadata.ResetRuleCache()
|
||||
metadata.DNSResponse = savedResponse
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
if !currentRule.Match(metadata) {
|
||||
continue
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
r.logRuleMatch(ctx, currentRuleIndex, currentRule)
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
r.applyDNSRouteOptions(&effectiveOptions, *action)
|
||||
case *R.RuleActionEvaluate:
|
||||
queryOptions := effectiveOptions
|
||||
transport, status := r.resolveDNSRoute(&R.RuleActionDNSRoute{
|
||||
Server: action.Server,
|
||||
RuleActionDNSRouteOptions: action.RuleActionDNSRouteOptions,
|
||||
}, allowFakeIP, &queryOptions)
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
savedResponse = nil
|
||||
continue
|
||||
case dnsRouteStatusSkipped:
|
||||
continue
|
||||
}
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
if err != nil {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||
savedResponse = nil
|
||||
continue
|
||||
}
|
||||
savedResponse = response
|
||||
case *R.RuleActionDNSRoute:
|
||||
queryOptions := effectiveOptions
|
||||
transport, status := r.resolveDNSRoute(action, allowFakeIP, &queryOptions)
|
||||
switch status {
|
||||
case dnsRouteStatusMissing:
|
||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
||||
continue
|
||||
case dnsRouteStatusSkipped:
|
||||
continue
|
||||
}
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return exchangeWithRulesResult{
|
||||
response: response,
|
||||
transport: transport,
|
||||
err: err,
|
||||
}
|
||||
case *R.RuleActionReject:
|
||||
switch action.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
return exchangeWithRulesResult{
|
||||
response: &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: message.Id,
|
||||
Rcode: mDNS.RcodeRefused,
|
||||
Response: true,
|
||||
},
|
||||
Question: []mDNS.Question{message.Question[0]},
|
||||
},
|
||||
rejectAction: action,
|
||||
}
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return exchangeWithRulesResult{
|
||||
rejectAction: action,
|
||||
err: tun.ErrDrop,
|
||||
}
|
||||
}
|
||||
case *R.RuleActionPredefined:
|
||||
return exchangeWithRulesResult{
|
||||
response: action.Response(message),
|
||||
}
|
||||
}
|
||||
}
|
||||
return transport, nil, -1
|
||||
queryOptions := effectiveOptions
|
||||
transport := r.transport.Default()
|
||||
exchangeOptions := queryOptions
|
||||
if exchangeOptions.Strategy == C.DomainStrategyAsIS {
|
||||
exchangeOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err := r.client.Exchange(adapter.OverrideContext(ctx), transport, message, exchangeOptions, nil)
|
||||
return exchangeWithRulesResult{
|
||||
response: response,
|
||||
transport: transport,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
type lookupWithRulesResponse struct {
|
||||
addresses []netip.Addr
|
||||
}
|
||||
|
||||
func (r *Router) resolveLookupStrategy(options adapter.DNSQueryOptions) C.DomainStrategy {
|
||||
if options.LookupStrategy != C.DomainStrategyAsIS {
|
||||
return options.LookupStrategy
|
||||
}
|
||||
if options.Strategy != C.DomainStrategyAsIS {
|
||||
return options.Strategy
|
||||
}
|
||||
return r.defaultDomainStrategy
|
||||
}
|
||||
|
||||
func lookupStrategyAllowsQueryType(strategy C.DomainStrategy, qType uint16) bool {
|
||||
switch strategy {
|
||||
case C.DomainStrategyIPv4Only:
|
||||
return qType == mDNS.TypeA
|
||||
case C.DomainStrategyIPv6Only:
|
||||
return qType == mDNS.TypeAAAA
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func withLookupQueryMetadata(ctx context.Context, qType uint16) context.Context {
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.QueryType = qType
|
||||
metadata.IPVersion = 0
|
||||
switch qType {
|
||||
case mDNS.TypeA:
|
||||
metadata.IPVersion = 4
|
||||
case mDNS.TypeAAAA:
|
||||
metadata.IPVersion = 6
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func filterAddressesByQueryType(addresses []netip.Addr, qType uint16) []netip.Addr {
|
||||
switch qType {
|
||||
case mDNS.TypeA:
|
||||
return common.Filter(addresses, func(address netip.Addr) bool {
|
||||
return address.Is4()
|
||||
})
|
||||
case mDNS.TypeAAAA:
|
||||
return common.Filter(addresses, func(address netip.Addr) bool {
|
||||
return address.Is6()
|
||||
})
|
||||
default:
|
||||
return addresses
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) lookupWithRules(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
strategy := r.resolveLookupStrategy(options)
|
||||
lookupOptions := options
|
||||
if strategy != C.DomainStrategyAsIS {
|
||||
lookupOptions.Strategy = strategy
|
||||
}
|
||||
if strategy == C.DomainStrategyIPv4Only {
|
||||
response, err := r.lookupWithRulesType(ctx, domain, mDNS.TypeA, lookupOptions)
|
||||
return response.addresses, err
|
||||
}
|
||||
if strategy == C.DomainStrategyIPv6Only {
|
||||
response, err := r.lookupWithRulesType(ctx, domain, mDNS.TypeAAAA, lookupOptions)
|
||||
return response.addresses, err
|
||||
}
|
||||
var (
|
||||
response4 lookupWithRulesResponse
|
||||
response6 lookupWithRulesResponse
|
||||
)
|
||||
var group task.Group
|
||||
group.Append("exchange4", func(ctx context.Context) error {
|
||||
result, err := r.lookupWithRulesType(ctx, domain, mDNS.TypeA, lookupOptions)
|
||||
response4 = result
|
||||
return err
|
||||
})
|
||||
group.Append("exchange6", func(ctx context.Context) error {
|
||||
result, err := r.lookupWithRulesType(ctx, domain, mDNS.TypeAAAA, lookupOptions)
|
||||
response6 = result
|
||||
return err
|
||||
})
|
||||
err := group.Run(ctx)
|
||||
if len(response4.addresses) == 0 && len(response6.addresses) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return sortAddresses(response4.addresses, response6.addresses, strategy), nil
|
||||
}
|
||||
|
||||
func (r *Router) lookupWithRulesType(ctx context.Context, domain string, qType uint16, options adapter.DNSQueryOptions) (lookupWithRulesResponse, error) {
|
||||
request := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
RecursionDesired: true,
|
||||
},
|
||||
Question: []mDNS.Question{{
|
||||
Name: mDNS.Fqdn(FqdnToDomain(domain)),
|
||||
Qtype: qType,
|
||||
Qclass: mDNS.ClassINET,
|
||||
}},
|
||||
}
|
||||
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), request, options, false)
|
||||
result := lookupWithRulesResponse{}
|
||||
if exchangeResult.rejectAction != nil {
|
||||
return result, exchangeResult.rejectAction.Error(ctx)
|
||||
}
|
||||
if exchangeResult.err != nil {
|
||||
return result, exchangeResult.err
|
||||
}
|
||||
if exchangeResult.response.Rcode != mDNS.RcodeSuccess {
|
||||
return result, RcodeError(exchangeResult.response.Rcode)
|
||||
}
|
||||
if !lookupStrategyAllowsQueryType(r.resolveLookupStrategy(options), qType) {
|
||||
return result, nil
|
||||
}
|
||||
result.addresses = filterAddressesByQueryType(MessageToAddresses(exchangeResult.response), qType)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
|
||||
@@ -220,6 +651,11 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
}
|
||||
return &responseMessage, nil
|
||||
}
|
||||
r.rulesAccess.RLock()
|
||||
defer r.rulesAccess.RUnlock()
|
||||
if r.runtimeRuleError != nil {
|
||||
return nil, r.runtimeRuleError
|
||||
}
|
||||
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
||||
var (
|
||||
response *mDNS.Msg
|
||||
@@ -230,6 +666,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
ctx, metadata = adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
metadata.QueryType = message.Question[0].Qtype
|
||||
metadata.DNSResponse = nil
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
switch metadata.QueryType {
|
||||
case mDNS.TypeA:
|
||||
metadata.IPVersion = 4
|
||||
@@ -239,18 +677,13 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||
if options.Transport != nil {
|
||||
transport = options.Transport
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||
} else if !r.legacyDNSMode {
|
||||
exchangeResult := r.exchangeWithRules(ctx, message, options, true)
|
||||
response, transport, err = exchangeResult.response, exchangeResult.transport, exchangeResult.err
|
||||
} else {
|
||||
var (
|
||||
rule adapter.DNSRule
|
||||
@@ -325,6 +758,11 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
}
|
||||
|
||||
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||
r.rulesAccess.RLock()
|
||||
defer r.rulesAccess.RUnlock()
|
||||
if r.runtimeRuleError != nil {
|
||||
return nil, r.runtimeRuleError
|
||||
}
|
||||
var (
|
||||
responseAddrs []netip.Addr
|
||||
err error
|
||||
@@ -338,6 +776,8 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||
} else if errors.Is(err, ErrResponseRejected) {
|
||||
r.logger.DebugContext(ctx, "response rejected for ", domain)
|
||||
} else if R.IsRejected(err) {
|
||||
r.logger.DebugContext(ctx, "lookup rejected for ", domain)
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||
}
|
||||
@@ -350,20 +790,16 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
metadata.Domain = FqdnToDomain(domain)
|
||||
metadata.DNSResponse = nil
|
||||
metadata.DestinationAddressMatchFromResponse = false
|
||||
if options.Transport != nil {
|
||||
transport := options.Transport
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = legacyTransport.LegacyStrategy()
|
||||
}
|
||||
if !options.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil)
|
||||
} else if !r.legacyDNSMode {
|
||||
responseAddrs, err = r.lookupWithRules(ctx, domain, options)
|
||||
} else {
|
||||
var (
|
||||
transport adapter.DNSTransport
|
||||
@@ -425,15 +861,15 @@ func isAddressQuery(message *mDNS.Msg) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(responseAddrs []netip.Addr) bool {
|
||||
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(response *mDNS.Msg) bool {
|
||||
if rule == nil || !rule.WithAddressLimit() {
|
||||
return nil
|
||||
}
|
||||
responseMetadata := *metadata
|
||||
return func(responseAddrs []netip.Addr) bool {
|
||||
return func(response *mDNS.Msg) bool {
|
||||
checkMetadata := responseMetadata
|
||||
checkMetadata.DestinationAddresses = responseAddrs
|
||||
return rule.MatchAddressLimit(&checkMetadata)
|
||||
checkMetadata.DNSResponse = response
|
||||
return rule.MatchAddressLimit(&checkMetadata, response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,3 +894,238 @@ func (r *Router) ResetNetwork() {
|
||||
transport.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule option.DefaultDNSRule) bool {
|
||||
if rule.IPAcceptAny || rule.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck
|
||||
return true
|
||||
}
|
||||
return !rule.MatchResponse && (len(rule.IPCIDR) > 0 || rule.IPIsPrivate)
|
||||
}
|
||||
|
||||
func hasResponseMatchFields(rule option.DefaultDNSRule) bool {
|
||||
return rule.ResponseRcode != nil ||
|
||||
len(rule.ResponseAnswer) > 0 ||
|
||||
len(rule.ResponseNs) > 0 ||
|
||||
len(rule.ResponseExtra) > 0
|
||||
}
|
||||
|
||||
func defaultRuleDisablesLegacyDNSMode(rule option.DefaultDNSRule) bool {
|
||||
return rule.MatchResponse ||
|
||||
hasResponseMatchFields(rule) ||
|
||||
rule.Action == C.RuleActionTypeEvaluate ||
|
||||
rule.IPVersion > 0 ||
|
||||
len(rule.QueryType) > 0
|
||||
}
|
||||
|
||||
func resolveLegacyDNSMode(router adapter.Router, rules []option.DNSRule) (bool, error) {
|
||||
legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, err := dnsRuleModeRequirements(router, rules)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if legacyDNSModeDisabled && needsLegacyDNSModeFromStrategy {
|
||||
return false, E.New("DNS rule action strategy is only supported in legacyDNSMode")
|
||||
}
|
||||
if legacyDNSModeDisabled {
|
||||
return false, nil
|
||||
}
|
||||
return needsLegacyDNSMode, nil
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirements(router adapter.Router, rules []option.DNSRule) (bool, bool, bool, error) {
|
||||
var legacyDNSModeDisabled bool
|
||||
var needsLegacyDNSMode bool
|
||||
var needsLegacyDNSModeFromStrategy bool
|
||||
for i, rule := range rules {
|
||||
ruleLegacyDNSModeDisabled, ruleNeedsLegacyDNSMode, ruleNeedsLegacyDNSModeFromStrategy, err := dnsRuleModeRequirementsInRule(router, rule)
|
||||
if err != nil {
|
||||
return false, false, false, E.Cause(err, "dns rule[", i, "]")
|
||||
}
|
||||
legacyDNSModeDisabled = legacyDNSModeDisabled || ruleLegacyDNSModeDisabled
|
||||
needsLegacyDNSMode = needsLegacyDNSMode || ruleNeedsLegacyDNSMode
|
||||
needsLegacyDNSModeFromStrategy = needsLegacyDNSModeFromStrategy || ruleNeedsLegacyDNSModeFromStrategy
|
||||
}
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule) (bool, bool, bool, error) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return dnsRuleModeRequirementsInDefaultRule(router, rule.DefaultOptions)
|
||||
case C.RuleTypeLogical:
|
||||
legacyDNSModeDisabled := dnsRuleActionType(rule) == C.RuleActionTypeEvaluate
|
||||
needsLegacyDNSModeFromStrategy := dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction)
|
||||
needsLegacyDNSMode := needsLegacyDNSModeFromStrategy
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
subLegacyDNSModeDisabled, subNeedsLegacyDNSMode, subNeedsLegacyDNSModeFromStrategy, err := dnsRuleModeRequirementsInRule(router, subRule)
|
||||
if err != nil {
|
||||
return false, false, false, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
legacyDNSModeDisabled = legacyDNSModeDisabled || subLegacyDNSModeDisabled
|
||||
needsLegacyDNSMode = needsLegacyDNSMode || subNeedsLegacyDNSMode
|
||||
needsLegacyDNSModeFromStrategy = needsLegacyDNSModeFromStrategy || subNeedsLegacyDNSModeFromStrategy
|
||||
}
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
default:
|
||||
return false, false, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule) (bool, bool, bool, error) {
|
||||
legacyDNSModeDisabled := defaultRuleDisablesLegacyDNSMode(rule)
|
||||
needsLegacyDNSModeFromStrategy := dnsRuleActionHasStrategy(rule.DNSRuleAction)
|
||||
needsLegacyDNSMode := defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule) || needsLegacyDNSModeFromStrategy
|
||||
if len(rule.RuleSet) == 0 {
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
}
|
||||
if router == nil {
|
||||
return false, false, false, E.New("router service not found")
|
||||
}
|
||||
for _, tag := range rule.RuleSet {
|
||||
ruleSet, loaded := router.RuleSet(tag)
|
||||
if !loaded {
|
||||
return false, false, false, E.New("rule-set not found: ", tag)
|
||||
}
|
||||
metadata := ruleSet.Metadata()
|
||||
// Rule sets are built from headless rules, so query_type is the only
|
||||
// per-query DNS predicate they can contribute here. ip_version is not a
|
||||
// headless-rule item and is therefore intentionally absent from metadata.
|
||||
legacyDNSModeDisabled = legacyDNSModeDisabled || metadata.ContainsDNSQueryTypeRule
|
||||
if !rule.RuleSetIPCIDRMatchSource && metadata.ContainsIPCIDRRule {
|
||||
needsLegacyDNSMode = true
|
||||
}
|
||||
}
|
||||
return legacyDNSModeDisabled, needsLegacyDNSMode, needsLegacyDNSModeFromStrategy, nil
|
||||
}
|
||||
|
||||
func referencedDNSRuleSetTags(rules []option.DNSRule) []string {
|
||||
tagMap := make(map[string]bool)
|
||||
var walkRule func(rule option.DNSRule)
|
||||
walkRule = func(rule option.DNSRule) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
for _, tag := range rule.DefaultOptions.RuleSet {
|
||||
tagMap[tag] = true
|
||||
}
|
||||
case C.RuleTypeLogical:
|
||||
for _, subRule := range rule.LogicalOptions.Rules {
|
||||
walkRule(subRule)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, rule := range rules {
|
||||
walkRule(rule)
|
||||
}
|
||||
tags := make([]string, 0, len(tagMap))
|
||||
for tag := range tagMap {
|
||||
if tag != "" {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func validateLegacyDNSModeDisabledRules(rules []option.DNSRule) error {
|
||||
for i, rule := range rules {
|
||||
consumesResponse, err := validateLegacyDNSModeDisabledRuleTree(rule)
|
||||
if err != nil {
|
||||
return E.Cause(err, "validate dns rule[", i, "]")
|
||||
}
|
||||
action := dnsRuleActionType(rule)
|
||||
if action == C.RuleActionTypeEvaluate && consumesResponse {
|
||||
return E.New("dns rule[", i, "]: evaluate rule cannot consume response state")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateLegacyDNSModeDisabledRuleTree(rule option.DNSRule) (bool, error) {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return validateLegacyDNSModeDisabledDefaultRule(rule.DefaultOptions)
|
||||
case C.RuleTypeLogical:
|
||||
var consumesResponse bool
|
||||
for i, subRule := range rule.LogicalOptions.Rules {
|
||||
subConsumesResponse, err := validateLegacyDNSModeDisabledRuleTree(subRule)
|
||||
if err != nil {
|
||||
return false, E.Cause(err, "sub rule[", i, "]")
|
||||
}
|
||||
consumesResponse = consumesResponse || subConsumesResponse
|
||||
}
|
||||
return consumesResponse, nil
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func validateLegacyDNSModeDisabledDefaultRule(rule option.DefaultDNSRule) (bool, error) {
|
||||
hasResponseRecords := hasResponseMatchFields(rule)
|
||||
if hasResponseRecords && !rule.MatchResponse {
|
||||
return false, E.New("response_* items require match_response")
|
||||
}
|
||||
if (len(rule.IPCIDR) > 0 || rule.IPIsPrivate) && !rule.MatchResponse {
|
||||
return false, E.New("ip_cidr and ip_is_private require match_response when legacyDNSMode is disabled")
|
||||
}
|
||||
// Intentionally do not reject rule_set here. A referenced rule set may mix
|
||||
// destination-IP predicates with pre-response predicates such as domain items.
|
||||
// When match_response is false, those destination-IP branches fail closed during
|
||||
// pre-response evaluation instead of consuming DNS response state, while sibling
|
||||
// non-response branches remain matchable.
|
||||
if rule.IPAcceptAny { //nolint:staticcheck
|
||||
return false, E.New("ip_accept_any is removed when legacyDNSMode is disabled, use ip_cidr with match_response")
|
||||
}
|
||||
if rule.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck
|
||||
return false, E.New("rule_set_ip_cidr_accept_empty is removed when legacyDNSMode is disabled")
|
||||
}
|
||||
return rule.MatchResponse, nil
|
||||
}
|
||||
|
||||
func hasDNSRuleActionStrategy(rules []option.DNSRule) bool {
|
||||
for _, rule := range rules {
|
||||
if dnsRuleHasActionStrategy(rule) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func dnsRuleHasActionStrategy(rule option.DNSRule) bool {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
return dnsRuleActionHasStrategy(rule.DefaultOptions.DNSRuleAction)
|
||||
case C.RuleTypeLogical:
|
||||
if dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction) {
|
||||
return true
|
||||
}
|
||||
return hasDNSRuleActionStrategy(rule.LogicalOptions.Rules)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleActionHasStrategy(action option.DNSRuleAction) bool {
|
||||
switch action.Action {
|
||||
case "", C.RuleActionTypeRoute, C.RuleActionTypeEvaluate:
|
||||
return C.DomainStrategy(action.RouteOptions.Strategy) != C.DomainStrategyAsIS
|
||||
case C.RuleActionTypeRouteOptions:
|
||||
return C.DomainStrategy(action.RouteOptionsOptions.Strategy) != C.DomainStrategyAsIS
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func dnsRuleActionType(rule option.DNSRule) string {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
if rule.DefaultOptions.Action == "" {
|
||||
return C.RuleActionTypeRoute
|
||||
}
|
||||
return rule.DefaultOptions.Action
|
||||
case C.RuleTypeLogical:
|
||||
if rule.LogicalOptions.Action == "" {
|
||||
return C.RuleActionTypeRoute
|
||||
}
|
||||
return rule.LogicalOptions.Action
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
2019
dns/router_test.go
Normal file
2019
dns/router_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,13 @@
|
||||
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 {
|
||||
@@ -35,8 +27,6 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
||||
transportType: transportType,
|
||||
transportTag: transportTag,
|
||||
dependencies: dependencies,
|
||||
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||
clientSubnet: localOptions.LegacyClientSubnet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,15 +35,10 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +53,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -2,104 +2,25 @@ 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) {
|
||||
if options.LegacyDefaultDialer {
|
||||
return dialer.NewDefaultOutbound(ctx), nil
|
||||
} else {
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
DirectResolver: true,
|
||||
LegacyDNSDialer: options.Legacy,
|
||||
})
|
||||
}
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
DirectResolver: true,
|
||||
})
|
||||
}
|
||||
|
||||
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||
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,
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
RemoteIsDomain: options.ServerIsDomain(),
|
||||
DirectResolver: true,
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,10 +2,50 @@
|
||||
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
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.5
|
||||
|
||||
* 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**
|
||||
@@ -30,6 +70,59 @@ 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
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
!!! failure "Removed in sing-box 1.14.0"
|
||||
|
||||
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
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).
|
||||
|
||||
### Structure
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
!!! failure "已在 sing-box 1.14.0 移除"
|
||||
|
||||
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
旧的 fake-ip 配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
|
||||
### 结构
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ icon: material/alert-decagram
|
||||
|----------|---------------------------------|
|
||||
| `server` | List of [DNS Server](./server/) |
|
||||
| `rules` | List of [DNS Rule](./rule/) |
|
||||
| `fakeip` | [FakeIP](./fakeip/) |
|
||||
| `fakeip` | :material-note-remove: [FakeIP](./fakeip/) |
|
||||
|
||||
#### final
|
||||
|
||||
|
||||
@@ -88,6 +88,6 @@ LRU 缓存容量。
|
||||
|
||||
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
||||
|
||||
#### fakeip
|
||||
#### fakeip :material-note-remove:
|
||||
|
||||
[FakeIP](./fakeip/) 设置。
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
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)
|
||||
@@ -89,12 +101,6 @@ 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
|
||||
],
|
||||
@@ -149,6 +155,12 @@ 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"
|
||||
],
|
||||
@@ -160,7 +172,16 @@ icon: material/alert-decagram
|
||||
"geosite-cn"
|
||||
],
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": 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": [],
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
@@ -169,7 +190,9 @@ 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"
|
||||
@@ -408,6 +431,26 @@ 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 ""
|
||||
@@ -446,6 +489,17 @@ 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.
|
||||
@@ -516,24 +570,69 @@ 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
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
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)
|
||||
@@ -89,12 +101,6 @@ 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
|
||||
],
|
||||
@@ -149,6 +155,12 @@ 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"
|
||||
],
|
||||
@@ -160,7 +172,16 @@ icon: material/alert-decagram
|
||||
"geosite-cn"
|
||||
],
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": 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": [],
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
@@ -169,6 +190,9 @@ 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"
|
||||
@@ -407,6 +431,26 @@ 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 ""
|
||||
@@ -445,6 +489,15 @@ 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
|
||||
|
||||
反选匹配结果。
|
||||
@@ -516,24 +569,69 @@ 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 起"
|
||||
|
||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||
|
||||
`ip_accept_any` 已废弃且将在 sing-box 1.16.0 中被移除。
|
||||
仅在 `legacyDNSMode` 中可用。请使用 `match_response` 和响应项替代。
|
||||
|
||||
匹配任意 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` 规则接受空查询响应。
|
||||
|
||||
### 响应字段
|
||||
|
||||
!!! 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
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
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)
|
||||
@@ -34,7 +39,11 @@ Tag of target server.
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Set domain strategy for this query.
|
||||
!!! warning
|
||||
|
||||
`strategy` is deprecated and only supported in `legacyDNSMode`.
|
||||
|
||||
Set domain strategy for this query in `legacyDNSMode`.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
@@ -54,6 +63,48 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au
|
||||
|
||||
Will overrides `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 overrides `dns.client_subnet`.
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
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)
|
||||
@@ -34,7 +39,11 @@ icon: material/new-box
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
为此查询设置域名策略。
|
||||
!!! warning
|
||||
|
||||
`strategy` 已废弃,且仅在 `legacyDNSMode` 中可用。
|
||||
|
||||
在 `legacyDNSMode` 中为此查询设置域名策略。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
@@ -54,6 +63,46 @@ 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
|
||||
|
||||
@@ -29,7 +29,7 @@ The type of the DNS server.
|
||||
|
||||
| Type | Format |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
|
||||
@@ -29,7 +29,7 @@ DNS 服务器的类型。
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-----------------|---------------------------|
|
||||
| empty (default) | [Legacy](./legacy/) |
|
||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
||||
| `local` | [Local](./local/) |
|
||||
| `hosts` | [Hosts](./hosts/) |
|
||||
| `tcp` | [TCP](./tcp/) |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
!!! failure "Removed in sing-box 1.14.0"
|
||||
|
||||
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
Legacy DNS servers is deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
!!! failure "已在 sing-box 1.14.0 移除"
|
||||
|
||||
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
旧的 DNS 服务器配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
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)
|
||||
@@ -31,6 +35,7 @@ icon: material/alert-decagram
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "", // or {}
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -141,6 +146,14 @@ 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.
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
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)
|
||||
@@ -31,6 +35,7 @@ icon: material/alert-decagram
|
||||
"ignore_client_bandwidth": false,
|
||||
"tls": {},
|
||||
"masquerade": "", // 或 {}
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false
|
||||
}
|
||||
```
|
||||
@@ -138,6 +143,14 @@ HTTP3 服务器认证失败时的行为 (对象配置)。
|
||||
|
||||
固定响应内容。
|
||||
|
||||
#### bbr_profile
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
||||
|
||||
默认使用 `standard`。
|
||||
|
||||
#### brutal_debug
|
||||
|
||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||
|
||||
@@ -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,6 +134,12 @@ 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,
|
||||
@@ -560,6 +566,30 @@ 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.
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
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)
|
||||
@@ -130,6 +135,12 @@ 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,
|
||||
@@ -543,6 +554,30 @@ 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
|
||||
|
||||
平台特定的设置,由客户端应用提供。
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Introduction
|
||||
|
||||
sing-box uses JSON for configuration files.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -10,6 +9,7 @@ sing-box uses JSON for configuration files.
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"certificate": {},
|
||||
"certificate_providers": [],
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
@@ -27,6 +27,7 @@ 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/) |
|
||||
@@ -50,4 +51,4 @@ sing-box format -w -c config.json -D config_directory
|
||||
|
||||
```bash
|
||||
sing-box merge output.json -c config.json -D config_directory
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# 引言
|
||||
|
||||
sing-box 使用 JSON 作为配置文件格式。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
@@ -10,6 +9,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"certificate": {},
|
||||
"certificate_providers": [],
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
@@ -27,6 +27,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
| `dns` | [DNS](./dns/) |
|
||||
| `ntp` | [NTP](./ntp/) |
|
||||
| `certificate` | [证书](./certificate/) |
|
||||
| `certificate_providers` | [证书提供者](./shared/certificate-provider/) |
|
||||
| `endpoints` | [端点](./endpoint/) |
|
||||
| `inbounds` | [入站](./inbound/) |
|
||||
| `outbounds` | [出站](./outbound/) |
|
||||
@@ -50,4 +51,4 @@ sing-box format -w -c config.json -D config_directory
|
||||
|
||||
```bash
|
||||
sing-box merge output.json -c config.json -D config_directory
|
||||
```
|
||||
```
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
!!! 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)
|
||||
@@ -9,13 +14,14 @@
|
||||
{
|
||||
"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": {
|
||||
@@ -25,8 +31,9 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false,
|
||||
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
@@ -75,6 +82,14 @@ 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.
|
||||
@@ -109,6 +124,14 @@ 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.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
!!! 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)
|
||||
@@ -16,6 +21,7 @@
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"hop_interval_max": "",
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"obfs": {
|
||||
@@ -25,8 +31,9 @@
|
||||
"password": "goofy_ahh_password",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"bbr_profile": "",
|
||||
"brutal_debug": false,
|
||||
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
@@ -73,6 +80,14 @@
|
||||
|
||||
默认使用 `30s`。
|
||||
|
||||
#### hop_interval_max
|
||||
|
||||
!!! question "自 sing-box 1.14.0 起"
|
||||
|
||||
最大端口跳跃间隔,用于随机化。
|
||||
|
||||
如果设置,实际跳跃间隔将在 `hop_interval` 和 `hop_interval_max` 之间随机选择。
|
||||
|
||||
#### up_mbps, down_mbps
|
||||
|
||||
最大带宽。
|
||||
@@ -107,6 +122,14 @@ 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 的调试信息日志记录。
|
||||
|
||||
@@ -4,6 +4,11 @@ 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)
|
||||
@@ -35,6 +40,9 @@ 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": [],
|
||||
@@ -107,6 +115,38 @@ 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"
|
||||
|
||||
@@ -4,6 +4,11 @@ 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)
|
||||
@@ -37,6 +42,9 @@ 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": ""
|
||||
}
|
||||
@@ -106,6 +114,38 @@ 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 起"
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
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)
|
||||
@@ -159,6 +164,12 @@ 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"
|
||||
@@ -449,6 +460,26 @@ 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"
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
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)
|
||||
@@ -157,6 +162,12 @@ 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"
|
||||
@@ -447,6 +458,26 @@ 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 起"
|
||||
|
||||
150
docs/configuration/shared/certificate-provider/acme.md
Normal file
150
docs/configuration/shared/certificate-provider/acme.md
Normal file
@@ -0,0 +1,150 @@
|
||||
---
|
||||
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.
|
||||
145
docs/configuration/shared/certificate-provider/acme.zh.md
Normal file
145
docs/configuration/shared/certificate-provider/acme.zh.md
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
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 请求将使用此出站。
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
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 请求将使用此出站。
|
||||
32
docs/configuration/shared/certificate-provider/index.md
Normal file
32
docs/configuration/shared/certificate-provider/index.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
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.
|
||||
32
docs/configuration/shared/certificate-provider/index.zh.md
Normal file
32
docs/configuration/shared/certificate-provider/index.zh.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
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
|
||||
|
||||
证书提供者的标签。
|
||||
27
docs/configuration/shared/certificate-provider/tailscale.md
Normal file
27
docs/configuration/shared/certificate-provider/tailscale.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
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)。
|
||||
@@ -2,6 +2,14 @@
|
||||
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)
|
||||
@@ -12,12 +20,57 @@ 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
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
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)
|
||||
@@ -12,12 +20,57 @@ 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
|
||||
|
||||
49
docs/configuration/shared/neighbor.md
Normal file
49
docs/configuration/shared/neighbor.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
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.
|
||||
49
docs/configuration/shared/neighbor.zh.md
Normal file
49
docs/configuration/shared/neighbor.zh.md
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
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) 了解互联网共享设置。
|
||||
@@ -2,6 +2,11 @@
|
||||
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)
|
||||
@@ -49,6 +54,10 @@ icon: material/new-box
|
||||
"key_path": "",
|
||||
"kernel_tx": false,
|
||||
"kernel_rx": false,
|
||||
"certificate_provider": "",
|
||||
|
||||
// Deprecated
|
||||
|
||||
"acme": {
|
||||
"domain": [],
|
||||
"data_directory": "",
|
||||
@@ -408,6 +417,18 @@ 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"
|
||||
@@ -469,7 +490,7 @@ The ECH key and configuration can be generated by `sing-box generate ech-keypair
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.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.
|
||||
`pq_signature_schemes_enabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0.
|
||||
|
||||
Enable support for post-quantum peer certificate signature schemes.
|
||||
|
||||
@@ -477,7 +498,7 @@ Enable support for post-quantum peer certificate signature schemes.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
`dynamic_record_sizing_disabled` has nothing to do with ECH, was added by mistake, has been deprecated and no longer works.
|
||||
`dynamic_record_sizing_disabled` is deprecated in sing-box 1.12.0 and removed in sing-box 1.13.0.
|
||||
|
||||
Disables adaptive sizing of TLS records.
|
||||
|
||||
@@ -566,6 +587,10 @@ 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.
|
||||
@@ -677,4 +702,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.
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
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)
|
||||
@@ -49,6 +54,10 @@ icon: material/new-box
|
||||
"key_path": "",
|
||||
"kernel_tx": false,
|
||||
"kernel_rx": false,
|
||||
"certificate_provider": "",
|
||||
|
||||
// 废弃的
|
||||
|
||||
"acme": {
|
||||
"domain": [],
|
||||
"data_directory": "",
|
||||
@@ -407,6 +416,18 @@ 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 支持"
|
||||
@@ -465,7 +486,7 @@ ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
|
||||
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
|
||||
ECH 支持已在 sing-box 1.12.0 迁移至使用标准库,但标准库不支持后量子对等证书签名方案,因此 `pq_signature_schemes_enabled` 已被弃用且不再工作。
|
||||
`pq_signature_schemes_enabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除。
|
||||
|
||||
启用对后量子对等证书签名方案的支持。
|
||||
|
||||
@@ -473,7 +494,7 @@ ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
|
||||
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
|
||||
`dynamic_record_sizing_disabled` 与 ECH 无关,是错误添加的,现已弃用且不再工作。
|
||||
`dynamic_record_sizing_disabled` 已在 sing-box 1.12.0 废弃且已在 sing-box 1.13.0 中被移除。
|
||||
|
||||
禁用 TLS 记录的自适应大小调整。
|
||||
|
||||
@@ -561,6 +582,10 @@ ECH 配置路径,PEM 格式。
|
||||
|
||||
### ACME 字段
|
||||
|
||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
||||
|
||||
内联 ACME 选项已在 sing-box 1.14.0 废弃且将在 sing-box 1.16.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移内联-acme-到证书提供者)。
|
||||
|
||||
#### domain
|
||||
|
||||
域名列表。
|
||||
|
||||
@@ -4,6 +4,46 @@ 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
|
||||
@@ -11,7 +51,7 @@ icon: material/delete-alert
|
||||
DNS servers are refactored,
|
||||
check [Migration](../migration/#migrate-to-new-dns-servers).
|
||||
|
||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||
Old formats were removed in sing-box 1.14.0.
|
||||
|
||||
#### `outbound` DNS rule item
|
||||
|
||||
@@ -28,7 +68,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 will be removed in sing-box 1.13.0.
|
||||
These fields were removed in sing-box 1.13.0.
|
||||
|
||||
## 1.11.0
|
||||
|
||||
@@ -38,7 +78,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 will be removed in sing-box 1.13.0.
|
||||
Old fields were removed in sing-box 1.13.0.
|
||||
|
||||
#### Legacy inbound fields
|
||||
|
||||
@@ -46,7 +86,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 will be removed in sing-box 1.13.0.
|
||||
Old fields were removed in sing-box 1.13.0.
|
||||
|
||||
#### Destination override fields in direct outbound
|
||||
|
||||
@@ -54,18 +94,20 @@ 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 will be removed in sing-box 1.13.0.
|
||||
Old outbound was 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 will be removed in sing-box 1.13.0.
|
||||
Old fields were removed in sing-box 1.13.0.
|
||||
|
||||
## 1.10.0
|
||||
|
||||
@@ -75,12 +117,12 @@ Old fields will be 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 will be removed in sing-box 1.12.0.
|
||||
Old fields were 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 will be remove in sing-box 1.11.0.
|
||||
`rule_set_ip_cidr_match_source` and were removed in sing-box 1.11.0.
|
||||
|
||||
#### Drop support for go1.18 and go1.19
|
||||
|
||||
@@ -95,7 +137,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o
|
||||
|
||||
#### GeoIP
|
||||
|
||||
GeoIP is deprecated and will be removed in sing-box 1.12.0.
|
||||
GeoIP is deprecated and was removed in sing-box 1.12.0.
|
||||
|
||||
The maxmind GeoIP National Database, as an IP classification database,
|
||||
is not entirely suitable for traffic bypassing,
|
||||
@@ -106,7 +148,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||
|
||||
#### Geosite
|
||||
|
||||
Geosite is deprecated and will be removed in sing-box 1.12.0.
|
||||
Geosite is deprecated and was 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.
|
||||
|
||||
@@ -4,12 +4,54 @@ 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 规则项
|
||||
|
||||
@@ -24,7 +66,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
|
||||
|
||||
@@ -33,41 +75,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 地址字段已合并
|
||||
|
||||
@@ -75,7 +117,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.11.0 中被移除。
|
||||
旧字段已在 sing-box 1.12.0 中被移除。
|
||||
|
||||
#### 移除对 go1.18 和 go1.19 的支持
|
||||
|
||||
@@ -90,7 +132,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `
|
||||
|
||||
#### GeoIP
|
||||
|
||||
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。
|
||||
GeoIP 已废弃且已在 sing-box 1.12.0 中被移除。
|
||||
|
||||
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
|
||||
且现有的实现均存在内存使用大与管理困难的问题。
|
||||
@@ -100,7 +142,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 项目,作为早期流量绕过解决方案,
|
||||
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
|
||||
|
||||
@@ -2,6 +2,83 @@
|
||||
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
|
||||
|
||||
@@ -2,6 +2,83 @@
|
||||
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 服务器格式
|
||||
|
||||
@@ -57,24 +57,6 @@ 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",
|
||||
@@ -102,10 +84,54 @@ 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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
MigrationLink: "https://sing-box.sagernet.org/configuration/dns/rule_action/",
|
||||
}
|
||||
|
||||
var Options = []Note{
|
||||
OptionLegacyDNSTransport,
|
||||
OptionLegacyDNSFakeIPOptions,
|
||||
OptionOutboundDNSRuleItem,
|
||||
OptionMissingDomainResolver,
|
||||
OptionLegacyDomainStrategyOptions,
|
||||
OptionInlineACME,
|
||||
OptionIPAcceptAny,
|
||||
OptionRuleSetIPCIDRAcceptEmpty,
|
||||
OptionLegacyDNSAddressFilter,
|
||||
OptionLegacyDNSRuleStrategy,
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
return box.Context(ctx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry(), include.CertificateProviderRegistry())
|
||||
}
|
||||
|
||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||
@@ -144,6 +144,18 @@ 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
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ type HTTPRequest interface {
|
||||
type HTTPResponse interface {
|
||||
GetContent() (*StringBox, error)
|
||||
WriteTo(path string) error
|
||||
WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error
|
||||
}
|
||||
|
||||
type HTTPResponseWriteToProgressHandler interface {
|
||||
Update(progress int64, total int64)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -239,3 +244,31 @@ func (h *httpResponse) WriteTo(path string) error {
|
||||
defer file.Close()
|
||||
return common.Error(bufio.Copy(file, h.Body))
|
||||
}
|
||||
|
||||
func (h *httpResponse) WriteToWithProgress(path string, handler HTTPResponseWriteToProgressHandler) error {
|
||||
defer h.Body.Close()
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return common.Error(bufio.Copy(&progressWriter{
|
||||
writer: file,
|
||||
handler: handler,
|
||||
total: h.ContentLength,
|
||||
}, h.Body))
|
||||
}
|
||||
|
||||
type progressWriter struct {
|
||||
writer io.Writer
|
||||
handler HTTPResponseWriteToProgressHandler
|
||||
total int64
|
||||
written int64
|
||||
}
|
||||
|
||||
func (w *progressWriter) Write(p []byte) (int, error) {
|
||||
n, err := w.writer.Write(p)
|
||||
w.written += int64(n)
|
||||
w.handler.Update(w.written, w.total)
|
||||
return n, err
|
||||
}
|
||||
|
||||
53
experimental/libbox/neighbor.go
Normal file
53
experimental/libbox/neighbor.go
Normal file
@@ -0,0 +1,53 @@
|
||||
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
|
||||
}
|
||||
123
experimental/libbox/neighbor_darwin.go
Normal file
123
experimental/libbox/neighbor_darwin.go
Normal file
@@ -0,0 +1,123 @@
|
||||
//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}
|
||||
}
|
||||
88
experimental/libbox/neighbor_linux.go
Normal file
88
experimental/libbox/neighbor_linux.go
Normal file
@@ -0,0 +1,88 @@
|
||||
//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))
|
||||
}
|
||||
}
|
||||
}
|
||||
9
experimental/libbox/neighbor_stub.go
Normal file
9
experimental/libbox/neighbor_stub.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !linux && !darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import "os"
|
||||
|
||||
func SubscribeNeighborTable(_ NeighborUpdateListener) (*NeighborSubscription, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
@@ -21,6 +21,13 @@ 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 {
|
||||
|
||||
@@ -78,6 +78,7 @@ func (w *platformInterfaceWrapper) OpenInterface(options *tun.Options, platformO
|
||||
}
|
||||
options.FileDescriptor = dupFd
|
||||
w.myTunName = options.Name
|
||||
w.iif.RegisterMyInterface(options.Name)
|
||||
return tun.New(*options)
|
||||
}
|
||||
|
||||
@@ -220,6 +221,46 @@ 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 {
|
||||
|
||||
18
go.mod
18
go.mod
@@ -6,6 +6,7 @@ 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
|
||||
@@ -14,11 +15,14 @@ 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
|
||||
@@ -27,19 +31,19 @@ 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-20260309102448-2fef65f9dba9
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260309102448-2fef65f9dba9
|
||||
github.com/sagernet/cronet-go v0.0.0-20260309100020-c128886ff3fc
|
||||
github.com/sagernet/cronet-go/all v0.0.0-20260309100020-c128886ff3fc
|
||||
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-0.20260315153529-ed51f65fbfde
|
||||
github.com/sagernet/sing v0.8.3
|
||||
github.com/sagernet/sing-mux v0.3.4
|
||||
github.com/sagernet/sing-quic v0.6.0
|
||||
github.com/sagernet/sing-quic v0.6.2-0.20260330152607-bf674c163212
|
||||
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.6
|
||||
github.com/sagernet/sing-tun v0.8.7-0.20260323120017-8eb4e8acfc2d
|
||||
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
|
||||
@@ -67,7 +71,6 @@ 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/database64128/netx-go v0.1.1 // indirect
|
||||
@@ -92,11 +95,8 @@ 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/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
|
||||
20
go.sum
20
go.sum
@@ -162,10 +162,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-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 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/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 +236,20 @@ 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-0.20260315153529-ed51f65fbfde h1:RNQzlpnsXIuu1HGts/fIzJ1PR7RhrzaNlU52MDyiX1c=
|
||||
github.com/sagernet/sing v0.8.3-0.20260315153529-ed51f65fbfde/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
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-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.0 h1:dhrFnP45wgVKEOT1EvtsToxdzRnHIDIAgj6WHV9pLyM=
|
||||
github.com/sagernet/sing-quic v0.6.0/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
||||
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-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.6 h1:NydXFikSXhiKqhahHKtuZ90HQPZFzlOFVRONmkr4C7I=
|
||||
github.com/sagernet/sing-tun v0.8.6/go.mod h1:pLCo4o+LacXEzz0bhwhJkKBjLlKOGPBNOAZ97ZVZWzs=
|
||||
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-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=
|
||||
|
||||
12
include/acme.go
Normal file
12
include/acme.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//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)
|
||||
}
|
||||
20
include/acme_stub.go
Normal file
20
include/acme_stub.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//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`)
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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"
|
||||
@@ -34,13 +35,14 @@ 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())
|
||||
return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry(), CertificateProviderRegistry())
|
||||
}
|
||||
|
||||
func InboundRegistry() *inbound.Registry {
|
||||
@@ -139,6 +141,16 @@ 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")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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"
|
||||
@@ -18,6 +19,10 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
||||
tailscale.RegistryTransport(registry)
|
||||
}
|
||||
|
||||
func registerTailscaleCertificateProvider(registry *certificate.Registry) {
|
||||
tailscale.RegisterCertificateProvider(registry)
|
||||
}
|
||||
|
||||
func registerDERPService(registry *service.Registry) {
|
||||
derp.Register(registry)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ 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"
|
||||
@@ -27,6 +28,12 @@ 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`)
|
||||
|
||||
@@ -122,6 +122,11 @@ 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
|
||||
@@ -129,6 +134,7 @@ 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
|
||||
@@ -272,6 +278,7 @@ plugins:
|
||||
Shared: 通用
|
||||
Listen Fields: 监听字段
|
||||
Dial Fields: 拨号字段
|
||||
Certificate Provider Fields: 证书提供者字段
|
||||
DNS01 Challenge Fields: DNS01 验证字段
|
||||
Multiplex: 多路复用
|
||||
V2Ray Transport: V2Ray 传输层
|
||||
@@ -280,6 +287,7 @@ plugins:
|
||||
Endpoint: 端点
|
||||
Inbound: 入站
|
||||
Outbound: 出站
|
||||
Certificate Provider: 证书提供者
|
||||
|
||||
Manual: 手册
|
||||
reconfigure_material: true
|
||||
|
||||
106
option/acme.go
Normal file
106
option/acme.go
Normal file
@@ -0,0 +1,106 @@
|
||||
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
|
||||
}
|
||||
100
option/certificate_provider.go
Normal file
100
option/certificate_provider.go
Normal file
@@ -0,0 +1,100 @@
|
||||
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 != ""
|
||||
}
|
||||
272
option/dns.go
272
option/dns.go
@@ -3,19 +3,14 @@ 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 {
|
||||
@@ -26,80 +21,29 @@ type RawDNSOptions struct {
|
||||
DNSClientOptions
|
||||
}
|
||||
|
||||
type LegacyDNSOptions struct {
|
||||
FakeIP *LegacyDNSFakeIPOptions `json:"fakeip,omitempty"`
|
||||
}
|
||||
|
||||
type DNSOptions struct {
|
||||
RawDNSOptions
|
||||
LegacyDNSOptions
|
||||
}
|
||||
|
||||
type contextKeyDontUpgrade struct{}
|
||||
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"
|
||||
)
|
||||
|
||||
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
|
||||
type removedLegacyDNSOptions struct {
|
||||
FakeIP json.RawMessage `json:"fakeip,omitempty"`
|
||||
}
|
||||
|
||||
func (o *DNSOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
||||
err := json.UnmarshalContext(ctx, content, &o.LegacyDNSOptions)
|
||||
var legacyOptions removedLegacyDNSOptions
|
||||
err := json.UnmarshalContext(ctx, content, &legacyOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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{}
|
||||
if len(legacyOptions.FakeIP) != 0 {
|
||||
return E.New(legacyDNSFakeIPRemovedMessage)
|
||||
}
|
||||
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))
|
||||
return badjson.UnmarshallExcludedContext(ctx, content, legacyOptions, &o.RawDNSOptions)
|
||||
}
|
||||
|
||||
type DNSClientOptions struct {
|
||||
@@ -111,12 +55,6 @@ 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)
|
||||
}
|
||||
@@ -129,10 +67,6 @@ 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)
|
||||
}
|
||||
|
||||
@@ -148,9 +82,7 @@ func (o *DNSServerOptions) UnmarshalJSONContext(ctx context.Context, content []b
|
||||
var options any
|
||||
switch o.Type {
|
||||
case "", C.DNSTypeLegacy:
|
||||
o.Type = C.DNSTypeLegacy
|
||||
options = new(LegacyDNSServerOptions)
|
||||
deprecated.Report(ctx, deprecated.OptionLegacyDNSTransport)
|
||||
return E.New(legacyDNSServerRemovedMessage)
|
||||
default:
|
||||
var loaded bool
|
||||
options, loaded = registry.CreateOptions(o.Type)
|
||||
@@ -163,169 +95,6 @@ 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
|
||||
}
|
||||
|
||||
@@ -350,16 +119,6 @@ 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"`
|
||||
@@ -367,10 +126,6 @@ type HostsDNSServerOptions struct {
|
||||
|
||||
type RawLocalDNSServerOptions struct {
|
||||
DialerOptions
|
||||
Legacy bool `json:"-"`
|
||||
LegacyStrategy DomainStrategy `json:"-"`
|
||||
LegacyDefaultDialer bool `json:"-"`
|
||||
LegacyClientSubnet netip.Prefix `json:"-"`
|
||||
}
|
||||
|
||||
type LocalDNSServerOptions struct {
|
||||
@@ -381,9 +136,6 @@ type LocalDNSServerOptions struct {
|
||||
type RemoteDNSServerOptions struct {
|
||||
RawLocalDNSServerOptions
|
||||
DNSServerAddressOptions
|
||||
LegacyAddressResolver string `json:"-"`
|
||||
LegacyAddressStrategy DomainStrategy `json:"-"`
|
||||
LegacyAddressFallbackDelay badoption.Duration `json:"-"`
|
||||
}
|
||||
|
||||
type RemoteTLSDNSServerOptions struct {
|
||||
|
||||
@@ -2,6 +2,7 @@ package option
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
const defaultDNSRecordTTL uint32 = 3600
|
||||
|
||||
type DNSRCode int
|
||||
|
||||
func (r DNSRCode) MarshalJSON() ([]byte, error) {
|
||||
@@ -76,10 +79,13 @@ func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error {
|
||||
if err == nil {
|
||||
return o.unmarshalBase64(binary)
|
||||
}
|
||||
record, err := dns.NewRR(stringValue)
|
||||
record, err := parseDNSRecord(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()
|
||||
}
|
||||
@@ -87,6 +93,16 @@ 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 {
|
||||
@@ -100,3 +116,10 @@ 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)
|
||||
}
|
||||
|
||||
52
option/dns_record_test.go
Normal file
52
option/dns_record_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
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))
|
||||
}
|
||||
72
option/dns_test.go
Normal file
72
option/dns_test.go
Normal file
@@ -0,0 +1,72 @@
|
||||
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)
|
||||
}
|
||||
@@ -19,6 +19,7 @@ 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"`
|
||||
}
|
||||
|
||||
@@ -112,13 +113,15 @@ type Hysteria2MasqueradeString struct {
|
||||
type Hysteria2OutboundOptions struct {
|
||||
DialerOptions
|
||||
ServerOptions
|
||||
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"`
|
||||
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"`
|
||||
OutboundTLSOptionsContainer
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
BBRProfile string `json:"bbr_profile,omitempty"`
|
||||
BrutalDebug bool `json:"brutal_debug,omitempty"`
|
||||
}
|
||||
|
||||
@@ -10,18 +10,19 @@ 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"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type Options _Options
|
||||
@@ -56,6 +57,25 @@ 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
|
||||
}
|
||||
|
||||
|
||||
76
option/origin_ca.go
Normal file
76
option/origin_ca.go
Normal file
@@ -0,0 +1,76 @@
|
||||
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
|
||||
}
|
||||
@@ -9,6 +9,8 @@ type RouteOptions struct {
|
||||
RuleSet []RuleSet `json:"rule_set,omitempty"`
|
||||
Final string `json:"final,omitempty"`
|
||||
FindProcess bool `json:"find_process,omitempty"`
|
||||
FindNeighbor bool `json:"find_neighbor,omitempty"`
|
||||
DHCPLeaseFiles badoption.Listable[string] `json:"dhcp_lease_files,omitempty"`
|
||||
AutoDetectInterface bool `json:"auto_detect_interface,omitempty"`
|
||||
OverrideAndroidVPN bool `json:"override_android_vpn,omitempty"`
|
||||
DefaultInterface string `json:"default_interface,omitempty"`
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user