mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
1 Commits
v1.14.0-al
...
cloudflare
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f1ba549fd |
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
|||||||
ea7cd33752aed62603775af3df946c1b83f4b0b3
|
2fef65f9dba90ddb89a87d00a6eb6165487c10c1
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
providerType string
|
|
||||||
providerTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(providerType string, providerTag string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
providerType: providerType,
|
|
||||||
providerTag: providerTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.providerType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.providerTag
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.CertificateProviderManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.CertificateProviderRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
providers []adapter.CertificateProviderService
|
|
||||||
providerByTag map[string]adapter.CertificateProviderService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger log.ContextLogger, registry adapter.CertificateProviderRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
providerByTag: make(map[string]adapter.CertificateProviderService),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
|
||||||
m.access.Lock()
|
|
||||||
if m.started && m.stage >= stage {
|
|
||||||
panic("already started")
|
|
||||||
}
|
|
||||||
m.started = true
|
|
||||||
m.stage = stage
|
|
||||||
providers := m.providers
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, provider := range providers {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err := adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if !m.started {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
providers := m.providers
|
|
||||||
m.providers = nil
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
var err error
|
|
||||||
for _, provider := range providers {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
m.logger.Trace("close ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
monitor.Start("close ", name)
|
|
||||||
err = E.Append(err, provider.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", name)
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) CertificateProviders() []adapter.CertificateProviderService {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.providers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.CertificateProviderService, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
m.access.Unlock()
|
|
||||||
return provider, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
provider, found := m.providerByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.providerByTag, tag)
|
|
||||||
index := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
|
|
||||||
return it == provider
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid certificate provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:index], m.providers[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return provider.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error {
|
|
||||||
provider, err := m.registry.Create(ctx, logger, tag, providerType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
m.logger.Trace(stage, " ", name)
|
|
||||||
startTime := time.Now()
|
|
||||||
err = adapter.LegacyStart(provider, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " ", name)
|
|
||||||
}
|
|
||||||
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsProvider, loaded := m.providerByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = existsProvider.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close certificate-provider/", existsProvider.Type(), "[", existsProvider.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
|
|
||||||
return it == existsProvider
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid certificate provider index")
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.providers = append(m.providers, provider)
|
|
||||||
m.providerByTag[tag] = provider
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.CertificateProviderService, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(providerType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.CertificateProviderService, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.CertificateProviderRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.CertificateProviderService, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructor map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructor: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) CreateOptions(providerType string) (any, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
optionsConstructor, loaded := m.optionsType[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (adapter.CertificateProviderService, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
constructor, loaded := m.constructor[providerType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("certificate provider type not found: " + providerType)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.optionsType[providerType] = optionsConstructor
|
|
||||||
m.constructor[providerType] = constructor
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CertificateProvider interface {
|
|
||||||
GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ACMECertificateProvider interface {
|
|
||||||
CertificateProvider
|
|
||||||
GetACMENextProtos() []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderService interface {
|
|
||||||
Lifecycle
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
CertificateProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderRegistry interface {
|
|
||||||
option.CertificateProviderOptionsRegistry
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (CertificateProviderService, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CertificateProviderManager interface {
|
|
||||||
Lifecycle
|
|
||||||
CertificateProviders() []CertificateProviderService
|
|
||||||
Get(tag string) (CertificateProviderService, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error
|
|
||||||
}
|
|
||||||
@@ -25,8 +25,8 @@ type DNSRouter interface {
|
|||||||
|
|
||||||
type DNSClient interface {
|
type DNSClient interface {
|
||||||
Start()
|
Start()
|
||||||
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error)
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error)
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
ClearCache()
|
ClearCache()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +72,11 @@ type DNSTransport interface {
|
|||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LegacyDNSTransport interface {
|
||||||
|
LegacyStrategy() C.DomainStrategy
|
||||||
|
LegacyClientSubnet() netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
type DNSTransportRegistry interface {
|
type DNSTransportRegistry interface {
|
||||||
option.DNSTransportOptionsRegistry
|
option.DNSTransportOptionsRegistry
|
||||||
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -10,8 +9,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
@@ -81,16 +78,12 @@ type InboundContext struct {
|
|||||||
FallbackNetworkType []C.InterfaceType
|
FallbackNetworkType []C.InterfaceType
|
||||||
FallbackDelay time.Duration
|
FallbackDelay time.Duration
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
DNSResponse *dns.Msg
|
SourceGeoIPCode string
|
||||||
DestinationAddressMatchFromResponse bool
|
GeoIPCode string
|
||||||
SourceGeoIPCode string
|
ProcessInfo *ConnectionOwner
|
||||||
GeoIPCode string
|
QueryType uint16
|
||||||
ProcessInfo *ConnectionOwner
|
FakeIP bool
|
||||||
SourceMACAddress net.HardwareAddr
|
|
||||||
SourceHostname string
|
|
||||||
QueryType uint16
|
|
||||||
FakeIP bool
|
|
||||||
|
|
||||||
// rule cache
|
// rule cache
|
||||||
|
|
||||||
@@ -119,51 +112,6 @@ func (c *InboundContext) ResetRuleMatchCache() {
|
|||||||
c.DidMatch = false
|
c.DidMatch = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *InboundContext) DNSResponseAddressesForMatch() []netip.Addr {
|
|
||||||
return DNSResponseAddresses(c.DNSResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DNSResponseAddresses(response *dns.Msg) []netip.Addr {
|
|
||||||
if response == nil || response.Rcode != dns.RcodeSuccess {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
addresses := make([]netip.Addr, 0, len(response.Answer))
|
|
||||||
for _, rawRecord := range response.Answer {
|
|
||||||
switch record := rawRecord.(type) {
|
|
||||||
case *dns.A:
|
|
||||||
addr := M.AddrFromIP(record.A)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
case *dns.AAAA:
|
|
||||||
addr := M.AddrFromIP(record.AAAA)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
case *dns.HTTPS:
|
|
||||||
for _, value := range record.SVCB.Value {
|
|
||||||
switch hint := value.(type) {
|
|
||||||
case *dns.SVCBIPv4Hint:
|
|
||||||
for _, ip := range hint.Hint {
|
|
||||||
addr := M.AddrFromIP(ip).Unmap()
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *dns.SVCBIPv6Hint:
|
|
||||||
for _, ip := range hint.Hint {
|
|
||||||
addr := M.AddrFromIP(ip)
|
|
||||||
if addr.IsValid() {
|
|
||||||
addresses = append(addresses, addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addresses
|
|
||||||
}
|
|
||||||
|
|
||||||
type inboundContextKey struct{}
|
type inboundContextKey struct{}
|
||||||
|
|
||||||
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
func WithContext(ctx context.Context, inboundContext *InboundContext) context.Context {
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDNSResponseAddressesUnmapsHTTPSIPv4Hints(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
ipv4Hint := net.ParseIP("1.1.1.1")
|
|
||||||
require.NotNil(t, ipv4Hint)
|
|
||||||
|
|
||||||
response := &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{
|
|
||||||
Response: true,
|
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
},
|
|
||||||
Answer: []dns.RR{
|
|
||||||
&dns.HTTPS{
|
|
||||||
SVCB: dns.SVCB{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: dns.Fqdn("example.com"),
|
|
||||||
Rrtype: dns.TypeHTTPS,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 60,
|
|
||||||
},
|
|
||||||
Priority: 1,
|
|
||||||
Target: ".",
|
|
||||||
Value: []dns.SVCBKeyValue{
|
|
||||||
&dns.SVCBIPv4Hint{Hint: []net.IP{ipv4Hint}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses := DNSResponseAddresses(response)
|
|
||||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("1.1.1.1")}, addresses)
|
|
||||||
require.True(t, addresses[0].Is4())
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NeighborEntry struct {
|
|
||||||
Address netip.Addr
|
|
||||||
MACAddress net.HardwareAddr
|
|
||||||
Hostname string
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborResolver interface {
|
|
||||||
LookupMAC(address netip.Addr) (net.HardwareAddr, bool)
|
|
||||||
LookupHostname(address netip.Addr) (string, bool)
|
|
||||||
Start() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type NeighborUpdateListener interface {
|
|
||||||
UpdateNeighborTable(entries []NeighborEntry)
|
|
||||||
}
|
|
||||||
@@ -36,10 +36,6 @@ type PlatformInterface interface {
|
|||||||
|
|
||||||
UsePlatformNotification() bool
|
UsePlatformNotification() bool
|
||||||
SendNotification(notification *Notification) error
|
SendNotification(notification *Notification) error
|
||||||
|
|
||||||
UsePlatformNeighborResolver() bool
|
|
||||||
StartNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
CloseNeighborMonitor(listener NeighborUpdateListener) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type FindConnectionOwnerRequest struct {
|
type FindConnectionOwnerRequest struct {
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ type Router interface {
|
|||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
NeedFindProcess() bool
|
NeedFindProcess() bool
|
||||||
NeedFindNeighbor() bool
|
|
||||||
NeighborResolver() NeighborResolver
|
|
||||||
AppendTracker(tracker ConnectionTracker)
|
AppendTracker(tracker ConnectionTracker)
|
||||||
ResetNetwork()
|
ResetNetwork()
|
||||||
}
|
}
|
||||||
@@ -66,16 +64,10 @@ type RuleSet interface {
|
|||||||
|
|
||||||
type RuleSetUpdateCallback func(it RuleSet)
|
type RuleSetUpdateCallback func(it RuleSet)
|
||||||
|
|
||||||
type DNSRuleSetUpdateValidator interface {
|
|
||||||
ValidateRuleSetMetadataUpdate(tag string, metadata RuleSetMetadata) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent.
|
|
||||||
type RuleSetMetadata struct {
|
type RuleSetMetadata struct {
|
||||||
ContainsProcessRule bool
|
ContainsProcessRule bool
|
||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
ContainsDNSQueryTypeRule bool
|
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
type HTTPStartContext struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HeadlessRule interface {
|
type HeadlessRule interface {
|
||||||
@@ -20,9 +18,8 @@ type Rule interface {
|
|||||||
|
|
||||||
type DNSRule interface {
|
type DNSRule interface {
|
||||||
Rule
|
Rule
|
||||||
LegacyPreMatch(metadata *InboundContext) bool
|
|
||||||
WithAddressLimit() bool
|
WithAddressLimit() bool
|
||||||
MatchAddressLimit(metadata *InboundContext, response *dns.Msg) bool
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleAction interface {
|
type RuleAction interface {
|
||||||
@@ -32,7 +29,7 @@ type RuleAction interface {
|
|||||||
|
|
||||||
func IsFinalAction(action RuleAction) bool {
|
func IsFinalAction(action RuleAction) bool {
|
||||||
switch action.Type() {
|
switch action.Type() {
|
||||||
case C.RuleActionTypeSniff, C.RuleActionTypeResolve, C.RuleActionTypeEvaluate:
|
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||||
return false
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
type TailscaleEndpoint interface {
|
|
||||||
SubscribeTailscaleStatus(ctx context.Context, fn func(*TailscaleEndpointStatus)) error
|
|
||||||
StartTailscalePing(ctx context.Context, peerIP string, fn func(*TailscalePingResult)) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscalePingResult struct {
|
|
||||||
LatencyMs float64
|
|
||||||
IsDirect bool
|
|
||||||
Endpoint string
|
|
||||||
DERPRegionID int32
|
|
||||||
DERPRegionCode string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscaleEndpointStatus struct {
|
|
||||||
BackendState string
|
|
||||||
AuthURL string
|
|
||||||
NetworkName string
|
|
||||||
MagicDNSSuffix string
|
|
||||||
Self *TailscalePeer
|
|
||||||
UserGroups []*TailscaleUserGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscaleUserGroup struct {
|
|
||||||
UserID int64
|
|
||||||
LoginName string
|
|
||||||
DisplayName string
|
|
||||||
ProfilePicURL string
|
|
||||||
Peers []*TailscalePeer
|
|
||||||
}
|
|
||||||
|
|
||||||
type TailscalePeer struct {
|
|
||||||
HostName string
|
|
||||||
DNSName string
|
|
||||||
OS string
|
|
||||||
TailscaleIPs []string
|
|
||||||
Online bool
|
|
||||||
ExitNode bool
|
|
||||||
ExitNodeOption bool
|
|
||||||
Active bool
|
|
||||||
RxBytes int64
|
|
||||||
TxBytes int64
|
|
||||||
UserID int64
|
|
||||||
KeyExpiry int64
|
|
||||||
}
|
|
||||||
136
box.go
136
box.go
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
@@ -20,6 +19,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -37,21 +37,20 @@ import (
|
|||||||
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
service *boxService.Manager
|
service *boxService.Manager
|
||||||
certificateProvider *boxCertificate.Manager
|
dnsTransport *dns.TransportManager
|
||||||
dnsTransport *dns.TransportManager
|
dnsRouter *dns.Router
|
||||||
dnsRouter *dns.Router
|
connection *route.ConnectionManager
|
||||||
connection *route.ConnectionManager
|
router *route.Router
|
||||||
router *route.Router
|
internalService []adapter.LifecycleService
|
||||||
internalService []adapter.LifecycleService
|
done chan struct{}
|
||||||
done chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -67,7 +66,6 @@ func Context(
|
|||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
serviceRegistry adapter.ServiceRegistry,
|
serviceRegistry adapter.ServiceRegistry,
|
||||||
certificateProviderRegistry adapter.CertificateProviderRegistry,
|
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -92,10 +90,6 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
||||||
ctx = service.ContextWith[adapter.ServiceRegistry](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
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +106,6 @@ func New(options Options) (*Box, error) {
|
|||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
||||||
certificateProviderRegistry := service.FromContext[adapter.CertificateProviderRegistry](ctx)
|
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -129,9 +122,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if serviceRegistry == nil {
|
if serviceRegistry == nil {
|
||||||
return nil, E.New("missing service registry in context")
|
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)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -189,16 +179,13 @@ func New(options Options) (*Box, error) {
|
|||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
||||||
certificateProviderManager := boxCertificate.NewManager(logFactory.NewLogger("certificate-provider"), certificateProviderRegistry)
|
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
||||||
service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager)
|
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter)
|
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
@@ -285,24 +272,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
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 {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
@@ -329,22 +298,22 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, certificateProviderOptions := range options.CertificateProviders {
|
for i, serviceOptions := range options.Services {
|
||||||
var tag string
|
var tag string
|
||||||
if certificateProviderOptions.Tag != "" {
|
if serviceOptions.Tag != "" {
|
||||||
tag = certificateProviderOptions.Tag
|
tag = serviceOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = certificateProviderManager.Create(
|
err = serviceManager.Create(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
certificateProviderOptions.Type,
|
serviceOptions.Type,
|
||||||
certificateProviderOptions.Options,
|
serviceOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize certificate provider[", i, "]")
|
return nil, E.Cause(err, "initialize service[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
outboundManager.Initialize(func() (adapter.Outbound, error) {
|
||||||
@@ -357,12 +326,11 @@ func New(options Options) (*Box, error) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) {
|
||||||
return dnsTransportRegistry.CreateDNSTransport(
|
return local.NewTransport(
|
||||||
ctx,
|
ctx,
|
||||||
logFactory.NewLogger("dns/local"),
|
logFactory.NewLogger("dns/local"),
|
||||||
"local",
|
"local",
|
||||||
C.DNSTypeLocal,
|
option.LocalDNSServerOptions{},
|
||||||
&option.LocalDNSServerOptions{},
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
@@ -415,21 +383,20 @@ func New(options Options) (*Box, error) {
|
|||||||
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
service: serviceManager,
|
service: serviceManager,
|
||||||
certificateProvider: certificateProviderManager,
|
dnsRouter: dnsRouter,
|
||||||
dnsRouter: dnsRouter,
|
connection: connectionManager,
|
||||||
connection: connectionManager,
|
router: router,
|
||||||
router: router,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
logFactory: logFactory,
|
||||||
logFactory: logFactory,
|
logger: logFactory.Logger(),
|
||||||
logger: logFactory.Logger(),
|
internalService: internalServices,
|
||||||
internalService: internalServices,
|
done: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,11 +450,11 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service, s.certificateProvider)
|
err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection, s.router, s.dnsRouter)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -503,19 +470,11 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint)
|
err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStart, s.certificateProvider)
|
err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -523,7 +482,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.endpoint, s.certificateProvider, s.inbound, s.service)
|
err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -547,9 +506,8 @@ func (s *Box) Close() error {
|
|||||||
service adapter.Lifecycle
|
service adapter.Lifecycle
|
||||||
}{
|
}{
|
||||||
{"service", s.service},
|
{"service", s.service},
|
||||||
{"inbound", s.inbound},
|
|
||||||
{"certificate-provider", s.certificateProvider},
|
|
||||||
{"endpoint", s.endpoint},
|
{"endpoint", s.endpoint},
|
||||||
|
{"inbound", s.inbound},
|
||||||
{"outbound", s.outbound},
|
{"outbound", s.outbound},
|
||||||
{"router", s.router},
|
{"router", s.router},
|
||||||
{"connection", s.connection},
|
{"connection", s.connection},
|
||||||
@@ -597,10 +555,6 @@ func (s *Box) Outbound() adapter.OutboundManager {
|
|||||||
return s.outbound
|
return s.outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Endpoint() adapter.EndpointManager {
|
|
||||||
return s.endpoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) LogFactory() log.Factory {
|
func (s *Box) LogFactory() log.Factory {
|
||||||
return s.logFactory
|
return s.logFactory
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule clients/android updated: fea0f3a7ba...4f0826b94d
@@ -82,11 +82,6 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
func downgradeRuleSetVersion(version uint8, options option.PlainRuleSet) uint8 {
|
||||||
if version == C.RuleSetVersion5 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.PackageNameRegex) > 0
|
|
||||||
}) {
|
|
||||||
version = C.RuleSetVersion4
|
|
||||||
}
|
|
||||||
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
if version == C.RuleSetVersion4 && !rule.HasHeadlessRule(options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||||
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
return rule.NetworkInterfaceAddress != nil && rule.NetworkInterfaceAddress.Size() > 0 ||
|
||||||
len(rule.DefaultInterfaceAddress) > 0
|
len(rule.DefaultInterfaceAddress) > 0
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/networkquality"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
commandNetworkQualityFlagConfigURL string
|
|
||||||
commandNetworkQualityFlagSerial bool
|
|
||||||
commandNetworkQualityFlagMaxRuntime int
|
|
||||||
commandNetworkQualityFlagHTTP3 bool
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandNetworkQuality = &cobra.Command{
|
|
||||||
Use: "networkquality",
|
|
||||||
Short: "Run a network quality test",
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := runNetworkQuality()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandNetworkQuality.Flags().StringVar(
|
|
||||||
&commandNetworkQualityFlagConfigURL,
|
|
||||||
"config-url", "",
|
|
||||||
"Network quality test config URL (default: Apple mensura)",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().BoolVar(
|
|
||||||
&commandNetworkQualityFlagSerial,
|
|
||||||
"serial", false,
|
|
||||||
"Run download and upload tests sequentially instead of in parallel",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().IntVar(
|
|
||||||
&commandNetworkQualityFlagMaxRuntime,
|
|
||||||
"max-runtime", int(networkquality.DefaultMaxRuntime/time.Second),
|
|
||||||
"Network quality maximum runtime in seconds",
|
|
||||||
)
|
|
||||||
commandNetworkQuality.Flags().BoolVar(
|
|
||||||
&commandNetworkQualityFlagHTTP3,
|
|
||||||
"http3", false,
|
|
||||||
"Use HTTP/3 (QUIC) for measurement traffic",
|
|
||||||
)
|
|
||||||
commandTools.AddCommand(commandNetworkQuality)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runNetworkQuality() error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
httpClient := networkquality.NewHTTPClient(dialer)
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
|
|
||||||
measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(dialer, commandNetworkQualityFlagHTTP3)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "==== NETWORK QUALITY TEST ====")
|
|
||||||
|
|
||||||
result, err := networkquality.Run(networkquality.Options{
|
|
||||||
ConfigURL: commandNetworkQualityFlagConfigURL,
|
|
||||||
HTTPClient: httpClient,
|
|
||||||
NewMeasurementClient: measurementClientFactory,
|
|
||||||
Serial: commandNetworkQualityFlagSerial,
|
|
||||||
MaxRuntime: time.Duration(commandNetworkQualityFlagMaxRuntime) * time.Second,
|
|
||||||
Context: globalCtx,
|
|
||||||
OnProgress: func(p networkquality.Progress) {
|
|
||||||
if !commandNetworkQualityFlagSerial && p.Phase != networkquality.PhaseIdle {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d Upload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM,
|
|
||||||
networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch networkquality.Phase(p.Phase) {
|
|
||||||
case networkquality.PhaseIdle:
|
|
||||||
if p.IdleLatencyMs > 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rIdle Latency: %d ms", p.IdleLatencyMs)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, "\rMeasuring idle latency...")
|
|
||||||
}
|
|
||||||
case networkquality.PhaseDownload:
|
|
||||||
fmt.Fprintf(os.Stderr, "\rDownload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.DownloadCapacity), p.DownloadRPM)
|
|
||||||
case networkquality.PhaseUpload:
|
|
||||||
fmt.Fprintf(os.Stderr, "\rUpload: %s RPM: %d",
|
|
||||||
networkquality.FormatBitrate(p.UploadCapacity), p.UploadRPM)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
fmt.Fprintln(os.Stderr, strings.Repeat("-", 40))
|
|
||||||
fmt.Fprintf(os.Stderr, "Idle Latency: %d ms\n", result.IdleLatencyMs)
|
|
||||||
fmt.Fprintf(os.Stderr, "Download Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.DownloadCapacity), result.DownloadCapacityAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Upload Capacity: %-20s Accuracy: %s\n", networkquality.FormatBitrate(result.UploadCapacity), result.UploadCapacityAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Download Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.DownloadRPM), result.DownloadRPMAccuracy)
|
|
||||||
fmt.Fprintf(os.Stderr, "Upload Responsiveness: %-20s Accuracy: %s\n", fmt.Sprintf("%d RPM", result.UploadRPM), result.UploadRPMAccuracy)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/stun"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var commandSTUNFlagServer string
|
|
||||||
|
|
||||||
var commandSTUN = &cobra.Command{
|
|
||||||
Use: "stun",
|
|
||||||
Short: "Run a STUN test",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
err := runSTUN()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
commandSTUN.Flags().StringVarP(&commandSTUNFlagServer, "server", "s", stun.DefaultServer, "STUN server address")
|
|
||||||
commandTools.AddCommand(commandSTUN)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runSTUN() error {
|
|
||||||
instance, err := createPreStartedClient()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer instance.Close()
|
|
||||||
|
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "==== STUN TEST ====")
|
|
||||||
|
|
||||||
result, err := stun.Run(stun.Options{
|
|
||||||
Server: commandSTUNFlagServer,
|
|
||||||
Dialer: dialer,
|
|
||||||
Context: globalCtx,
|
|
||||||
OnProgress: func(p stun.Progress) {
|
|
||||||
switch p.Phase {
|
|
||||||
case stun.PhaseBinding:
|
|
||||||
if p.ExternalAddr != "" {
|
|
||||||
fmt.Fprintf(os.Stderr, "\rExternal Address: %s (%d ms)", p.ExternalAddr, p.LatencyMs)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, "\rSending binding request...")
|
|
||||||
}
|
|
||||||
case stun.PhaseNATMapping:
|
|
||||||
fmt.Fprint(os.Stderr, "\rDetecting NAT mapping behavior...")
|
|
||||||
case stun.PhaseNATFiltering:
|
|
||||||
fmt.Fprint(os.Stderr, "\rDetecting NAT filtering behavior...")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr)
|
|
||||||
fmt.Fprintf(os.Stderr, "External Address: %s\n", result.ExternalAddr)
|
|
||||||
fmt.Fprintf(os.Stderr, "Latency: %d ms\n", result.LatencyMs)
|
|
||||||
if result.NATTypeSupported {
|
|
||||||
fmt.Fprintf(os.Stderr, "NAT Mapping: %s\n", result.NATMapping)
|
|
||||||
fmt.Fprintf(os.Stderr, "NAT Filtering: %s\n", result.NATFiltering)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintln(os.Stderr, "NAT Type Detection: not supported by server")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -149,10 +149,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
if options.DisableTCPKeepAlive {
|
if !options.DisableTCPKeepAlive {
|
||||||
dialer.KeepAlive = -1
|
|
||||||
dialer.KeepAliveConfig.Enable = false
|
|
||||||
} else {
|
|
||||||
keepIdle := time.Duration(options.TCPKeepAlive)
|
keepIdle := time.Duration(options.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
|||||||
@@ -37,10 +37,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if l.listenOptions.ReuseAddr {
|
if l.listenOptions.ReuseAddr {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
||||||
}
|
}
|
||||||
if l.listenOptions.DisableTCPKeepAlive {
|
if !l.listenOptions.DisableTCPKeepAlive {
|
||||||
listenConfig.KeepAlive = -1
|
|
||||||
listenConfig.KeepAliveConfig.Enable = false
|
|
||||||
} else {
|
|
||||||
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
if keepIdle == 0 {
|
if keepIdle == 0 {
|
||||||
keepIdle = C.TCPKeepAliveInitial
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
|||||||
@@ -1,142 +0,0 @@
|
|||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
sBufio "github.com/sagernet/sing/common/bufio"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FormatBitrate(bps int64) string {
|
|
||||||
switch {
|
|
||||||
case bps >= 1_000_000_000:
|
|
||||||
return fmt.Sprintf("%.1f Gbps", float64(bps)/1_000_000_000)
|
|
||||||
case bps >= 1_000_000:
|
|
||||||
return fmt.Sprintf("%.1f Mbps", float64(bps)/1_000_000)
|
|
||||||
case bps >= 1_000:
|
|
||||||
return fmt.Sprintf("%.1f Kbps", float64(bps)/1_000)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%d bps", bps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPClient(dialer N.Dialer) *http.Client {
|
|
||||||
transport := &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: C.TCPTimeout,
|
|
||||||
}
|
|
||||||
if dialer != nil {
|
|
||||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &http.Client{Transport: transport}
|
|
||||||
}
|
|
||||||
|
|
||||||
func baseTransportFromClient(client *http.Client) (*http.Transport, error) {
|
|
||||||
if client == nil {
|
|
||||||
return nil, E.New("http client is nil")
|
|
||||||
}
|
|
||||||
if client.Transport == nil {
|
|
||||||
return http.DefaultTransport.(*http.Transport).Clone(), nil
|
|
||||||
}
|
|
||||||
transport, ok := client.Transport.(*http.Transport)
|
|
||||||
if !ok {
|
|
||||||
return nil, E.New("http client transport must be *http.Transport")
|
|
||||||
}
|
|
||||||
return transport.Clone(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMeasurementClient(
|
|
||||||
baseClient *http.Client,
|
|
||||||
connectEndpoint string,
|
|
||||||
singleConnection bool,
|
|
||||||
disableKeepAlives bool,
|
|
||||||
readCounters []N.CountFunc,
|
|
||||||
writeCounters []N.CountFunc,
|
|
||||||
) (*http.Client, error) {
|
|
||||||
transport, err := baseTransportFromClient(baseClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
transport.DisableCompression = true
|
|
||||||
transport.DisableKeepAlives = disableKeepAlives
|
|
||||||
if singleConnection {
|
|
||||||
transport.MaxConnsPerHost = 1
|
|
||||||
transport.MaxIdleConnsPerHost = 1
|
|
||||||
transport.MaxIdleConns = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
baseDialContext := transport.DialContext
|
|
||||||
if baseDialContext == nil {
|
|
||||||
dialer := &net.Dialer{}
|
|
||||||
baseDialContext = dialer.DialContext
|
|
||||||
}
|
|
||||||
transport.DialContext = func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
|
||||||
dialAddr := addr
|
|
||||||
if connectEndpoint != "" {
|
|
||||||
dialAddr = rewriteDialAddress(addr, connectEndpoint)
|
|
||||||
}
|
|
||||||
conn, dialErr := baseDialContext(ctx, network, dialAddr)
|
|
||||||
if dialErr != nil {
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
if len(readCounters) > 0 || len(writeCounters) > 0 {
|
|
||||||
return sBufio.NewCounterConn(conn, readCounters, writeCounters), nil
|
|
||||||
}
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &http.Client{
|
|
||||||
Transport: transport,
|
|
||||||
CheckRedirect: baseClient.CheckRedirect,
|
|
||||||
Jar: baseClient.Jar,
|
|
||||||
Timeout: baseClient.Timeout,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type MeasurementClientFactory func(
|
|
||||||
connectEndpoint string,
|
|
||||||
singleConnection bool,
|
|
||||||
disableKeepAlives bool,
|
|
||||||
readCounters []N.CountFunc,
|
|
||||||
writeCounters []N.CountFunc,
|
|
||||||
) (*http.Client, error)
|
|
||||||
|
|
||||||
func defaultMeasurementClientFactory(baseClient *http.Client) MeasurementClientFactory {
|
|
||||||
return func(connectEndpoint string, singleConnection, disableKeepAlives bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) {
|
|
||||||
return newMeasurementClient(baseClient, connectEndpoint, singleConnection, disableKeepAlives, readCounters, writeCounters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOptionalHTTP3Factory(dialer N.Dialer, useHTTP3 bool) (MeasurementClientFactory, error) {
|
|
||||||
if !useHTTP3 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return NewHTTP3MeasurementClientFactory(dialer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func rewriteDialAddress(addr string, connectEndpoint string) string {
|
|
||||||
connectEndpoint = strings.TrimSpace(connectEndpoint)
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
endpointHost, endpointPort, err := net.SplitHostPort(connectEndpoint)
|
|
||||||
if err == nil {
|
|
||||||
host = endpointHost
|
|
||||||
if endpointPort != "" {
|
|
||||||
port = endpointPort
|
|
||||||
}
|
|
||||||
} else if connectEndpoint != "" {
|
|
||||||
host = connectEndpoint
|
|
||||||
}
|
|
||||||
return net.JoinHostPort(host, port)
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
//go:build with_quic
|
|
||||||
|
|
||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
|
||||||
"github.com/sagernet/quic-go/http3"
|
|
||||||
sBufio "github.com/sagernet/sing/common/bufio"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) {
|
|
||||||
// singleConnection and disableKeepAlives are not applied:
|
|
||||||
// HTTP/3 multiplexes streams over a single QUIC connection by default.
|
|
||||||
return func(connectEndpoint string, _, _ bool, readCounters, writeCounters []N.CountFunc) (*http.Client, error) {
|
|
||||||
transport := &http3.Transport{
|
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
|
|
||||||
dialAddr := addr
|
|
||||||
if connectEndpoint != "" {
|
|
||||||
dialAddr = rewriteDialAddress(addr, connectEndpoint)
|
|
||||||
}
|
|
||||||
destination := M.ParseSocksaddr(dialAddr)
|
|
||||||
var udpConn net.Conn
|
|
||||||
var dialErr error
|
|
||||||
if dialer != nil {
|
|
||||||
udpConn, dialErr = dialer.DialContext(ctx, N.NetworkUDP, destination)
|
|
||||||
} else {
|
|
||||||
var netDialer net.Dialer
|
|
||||||
udpConn, dialErr = netDialer.DialContext(ctx, N.NetworkUDP, destination.String())
|
|
||||||
}
|
|
||||||
if dialErr != nil {
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
wrappedConn := udpConn
|
|
||||||
if len(readCounters) > 0 || len(writeCounters) > 0 {
|
|
||||||
wrappedConn = sBufio.NewCounterConn(udpConn, readCounters, writeCounters)
|
|
||||||
}
|
|
||||||
packetConn := sBufio.NewUnbindPacketConn(wrappedConn)
|
|
||||||
quicConn, dialErr := quic.DialEarly(ctx, packetConn, udpConn.RemoteAddr(), tlsCfg, cfg)
|
|
||||||
if dialErr != nil {
|
|
||||||
udpConn.Close()
|
|
||||||
return nil, dialErr
|
|
||||||
}
|
|
||||||
return quicConn, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &http.Client{Transport: transport}, nil
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !with_quic
|
|
||||||
|
|
||||||
package networkquality
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewHTTP3MeasurementClientFactory(dialer N.Dialer) (MeasurementClientFactory, error) {
|
|
||||||
return nil, C.ErrQUICNotIncluded
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,6 @@ const (
|
|||||||
ruleItemNetworkIsConstrained
|
ruleItemNetworkIsConstrained
|
||||||
ruleItemNetworkInterfaceAddress
|
ruleItemNetworkInterfaceAddress
|
||||||
ruleItemDefaultInterfaceAddress
|
ruleItemDefaultInterfaceAddress
|
||||||
ruleItemPackageNameRegex
|
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -216,8 +215,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
rule.ProcessPathRegex, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageName:
|
case ruleItemPackageName:
|
||||||
rule.PackageName, err = readRuleItemString(reader)
|
rule.PackageName, err = readRuleItemString(reader)
|
||||||
case ruleItemPackageNameRegex:
|
|
||||||
rule.PackageNameRegex, err = readRuleItemString(reader)
|
|
||||||
case ruleItemWIFISSID:
|
case ruleItemWIFISSID:
|
||||||
rule.WIFISSID, err = readRuleItemString(reader)
|
rule.WIFISSID, err = readRuleItemString(reader)
|
||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
@@ -397,15 +394,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rule.PackageNameRegex) > 0 {
|
|
||||||
if generateVersion < C.RuleSetVersion5 {
|
|
||||||
return E.New("`package_name_regex` rule item is only supported in version 5 or later")
|
|
||||||
}
|
|
||||||
err = writeRuleItemString(writer, ruleItemPackageNameRegex, rule.PackageNameRegex)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(rule.NetworkType) > 0 {
|
if len(rule.NetworkType) > 0 {
|
||||||
if generateVersion < C.RuleSetVersion3 {
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
return E.New("`network_type` rule item is only supported in version 3 or later")
|
return E.New("`network_type` rule item is only supported in version 3 or later")
|
||||||
|
|||||||
@@ -1,607 +0,0 @@
|
|||||||
package stun
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DefaultServer = "stun.voipgate.com:3478"
|
|
||||||
|
|
||||||
magicCookie = 0x2112A442
|
|
||||||
headerSize = 20
|
|
||||||
|
|
||||||
bindingRequest = 0x0001
|
|
||||||
bindingSuccessResponse = 0x0101
|
|
||||||
bindingErrorResponse = 0x0111
|
|
||||||
|
|
||||||
attrMappedAddress = 0x0001
|
|
||||||
attrChangeRequest = 0x0003
|
|
||||||
attrErrorCode = 0x0009
|
|
||||||
attrXORMappedAddress = 0x0020
|
|
||||||
attrOtherAddress = 0x802c
|
|
||||||
|
|
||||||
familyIPv4 = 0x01
|
|
||||||
familyIPv6 = 0x02
|
|
||||||
|
|
||||||
changeIP = 0x04
|
|
||||||
changePort = 0x02
|
|
||||||
|
|
||||||
defaultRTO = 500 * time.Millisecond
|
|
||||||
minRTO = 250 * time.Millisecond
|
|
||||||
maxRetransmit = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type Phase int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
PhaseBinding Phase = iota
|
|
||||||
PhaseNATMapping
|
|
||||||
PhaseNATFiltering
|
|
||||||
PhaseDone
|
|
||||||
)
|
|
||||||
|
|
||||||
type NATMapping int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
NATMappingUnknown NATMapping = iota
|
|
||||||
_ // reserved
|
|
||||||
NATMappingEndpointIndependent
|
|
||||||
NATMappingAddressDependent
|
|
||||||
NATMappingAddressAndPortDependent
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m NATMapping) String() string {
|
|
||||||
switch m {
|
|
||||||
case NATMappingEndpointIndependent:
|
|
||||||
return "Endpoint Independent"
|
|
||||||
case NATMappingAddressDependent:
|
|
||||||
return "Address Dependent"
|
|
||||||
case NATMappingAddressAndPortDependent:
|
|
||||||
return "Address and Port Dependent"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NATFiltering int32
|
|
||||||
|
|
||||||
const (
|
|
||||||
NATFilteringUnknown NATFiltering = iota
|
|
||||||
NATFilteringEndpointIndependent
|
|
||||||
NATFilteringAddressDependent
|
|
||||||
NATFilteringAddressAndPortDependent
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f NATFiltering) String() string {
|
|
||||||
switch f {
|
|
||||||
case NATFilteringEndpointIndependent:
|
|
||||||
return "Endpoint Independent"
|
|
||||||
case NATFilteringAddressDependent:
|
|
||||||
return "Address Dependent"
|
|
||||||
case NATFilteringAddressAndPortDependent:
|
|
||||||
return "Address and Port Dependent"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TransactionID [12]byte
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Server string
|
|
||||||
Dialer N.Dialer
|
|
||||||
Context context.Context
|
|
||||||
OnProgress func(Progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Progress struct {
|
|
||||||
Phase Phase
|
|
||||||
ExternalAddr string
|
|
||||||
LatencyMs int32
|
|
||||||
NATMapping NATMapping
|
|
||||||
NATFiltering NATFiltering
|
|
||||||
}
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
ExternalAddr string
|
|
||||||
LatencyMs int32
|
|
||||||
NATMapping NATMapping
|
|
||||||
NATFiltering NATFiltering
|
|
||||||
NATTypeSupported bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type parsedResponse struct {
|
|
||||||
xorMappedAddr netip.AddrPort
|
|
||||||
mappedAddr netip.AddrPort
|
|
||||||
otherAddr netip.AddrPort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *parsedResponse) externalAddr() (netip.AddrPort, bool) {
|
|
||||||
if r.xorMappedAddr.IsValid() {
|
|
||||||
return r.xorMappedAddr, true
|
|
||||||
}
|
|
||||||
if r.mappedAddr.IsValid() {
|
|
||||||
return r.mappedAddr, true
|
|
||||||
}
|
|
||||||
return netip.AddrPort{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
type stunAttribute struct {
|
|
||||||
typ uint16
|
|
||||||
value []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTransactionID() TransactionID {
|
|
||||||
var id TransactionID
|
|
||||||
_, _ = rand.Read(id[:])
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBindingRequest(txID TransactionID, attrs ...stunAttribute) []byte {
|
|
||||||
attrLen := 0
|
|
||||||
for _, attr := range attrs {
|
|
||||||
attrLen += 4 + len(attr.value) + paddingLen(len(attr.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, headerSize+attrLen)
|
|
||||||
binary.BigEndian.PutUint16(buf[0:2], bindingRequest)
|
|
||||||
binary.BigEndian.PutUint16(buf[2:4], uint16(attrLen))
|
|
||||||
binary.BigEndian.PutUint32(buf[4:8], magicCookie)
|
|
||||||
copy(buf[8:20], txID[:])
|
|
||||||
|
|
||||||
offset := headerSize
|
|
||||||
for _, attr := range attrs {
|
|
||||||
binary.BigEndian.PutUint16(buf[offset:offset+2], attr.typ)
|
|
||||||
binary.BigEndian.PutUint16(buf[offset+2:offset+4], uint16(len(attr.value)))
|
|
||||||
copy(buf[offset+4:offset+4+len(attr.value)], attr.value)
|
|
||||||
offset += 4 + len(attr.value) + paddingLen(len(attr.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeRequestAttr(flags byte) stunAttribute {
|
|
||||||
return stunAttribute{
|
|
||||||
typ: attrChangeRequest,
|
|
||||||
value: []byte{0, 0, 0, flags},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseResponse(data []byte, expectedTxID TransactionID) (*parsedResponse, error) {
|
|
||||||
if len(data) < headerSize {
|
|
||||||
return nil, E.New("response too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
msgType := binary.BigEndian.Uint16(data[0:2])
|
|
||||||
if msgType&0xC000 != 0 {
|
|
||||||
return nil, E.New("invalid STUN message: top 2 bits not zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
cookie := binary.BigEndian.Uint32(data[4:8])
|
|
||||||
if cookie != magicCookie {
|
|
||||||
return nil, E.New("invalid magic cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
var txID TransactionID
|
|
||||||
copy(txID[:], data[8:20])
|
|
||||||
if txID != expectedTxID {
|
|
||||||
return nil, E.New("transaction ID mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
msgLen := int(binary.BigEndian.Uint16(data[2:4]))
|
|
||||||
if msgLen > len(data)-headerSize {
|
|
||||||
return nil, E.New("message length exceeds data")
|
|
||||||
}
|
|
||||||
|
|
||||||
attrData := data[headerSize : headerSize+msgLen]
|
|
||||||
|
|
||||||
if msgType == bindingErrorResponse {
|
|
||||||
return nil, parseErrorResponse(attrData)
|
|
||||||
}
|
|
||||||
if msgType != bindingSuccessResponse {
|
|
||||||
return nil, E.New("unexpected message type: ", fmt.Sprintf("0x%04x", msgType))
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := &parsedResponse{}
|
|
||||||
offset := 0
|
|
||||||
for offset+4 <= len(attrData) {
|
|
||||||
attrType := binary.BigEndian.Uint16(attrData[offset : offset+2])
|
|
||||||
attrLen := int(binary.BigEndian.Uint16(attrData[offset+2 : offset+4]))
|
|
||||||
if offset+4+attrLen > len(attrData) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
attrValue := attrData[offset+4 : offset+4+attrLen]
|
|
||||||
|
|
||||||
switch attrType {
|
|
||||||
case attrXORMappedAddress:
|
|
||||||
addr, err := parseXORMappedAddress(attrValue, txID)
|
|
||||||
if err == nil {
|
|
||||||
resp.xorMappedAddr = addr
|
|
||||||
}
|
|
||||||
case attrMappedAddress:
|
|
||||||
addr, err := parseMappedAddress(attrValue)
|
|
||||||
if err == nil {
|
|
||||||
resp.mappedAddr = addr
|
|
||||||
}
|
|
||||||
case attrOtherAddress:
|
|
||||||
addr, err := parseMappedAddress(attrValue)
|
|
||||||
if err == nil {
|
|
||||||
resp.otherAddr = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset += 4 + attrLen + paddingLen(attrLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseErrorResponse(data []byte) error {
|
|
||||||
offset := 0
|
|
||||||
for offset+4 <= len(data) {
|
|
||||||
attrType := binary.BigEndian.Uint16(data[offset : offset+2])
|
|
||||||
attrLen := int(binary.BigEndian.Uint16(data[offset+2 : offset+4]))
|
|
||||||
if offset+4+attrLen > len(data) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if attrType == attrErrorCode && attrLen >= 4 {
|
|
||||||
attrValue := data[offset+4 : offset+4+attrLen]
|
|
||||||
class := int(attrValue[2] & 0x07)
|
|
||||||
number := int(attrValue[3])
|
|
||||||
code := class*100 + number
|
|
||||||
if attrLen > 4 {
|
|
||||||
return E.New("STUN error ", code, ": ", string(attrValue[4:]))
|
|
||||||
}
|
|
||||||
return E.New("STUN error ", code)
|
|
||||||
}
|
|
||||||
offset += 4 + attrLen + paddingLen(attrLen)
|
|
||||||
}
|
|
||||||
return E.New("STUN error response")
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseXORMappedAddress(data []byte, txID TransactionID) (netip.AddrPort, error) {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
family := data[1]
|
|
||||||
xPort := binary.BigEndian.Uint16(data[2:4])
|
|
||||||
port := xPort ^ uint16(magicCookie>>16)
|
|
||||||
|
|
||||||
switch family {
|
|
||||||
case familyIPv4:
|
|
||||||
if len(data) < 8 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv4 too short")
|
|
||||||
}
|
|
||||||
var ip [4]byte
|
|
||||||
binary.BigEndian.PutUint32(ip[:], binary.BigEndian.Uint32(data[4:8])^magicCookie)
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom4(ip), port), nil
|
|
||||||
case familyIPv6:
|
|
||||||
if len(data) < 20 {
|
|
||||||
return netip.AddrPort{}, E.New("XOR-MAPPED-ADDRESS IPv6 too short")
|
|
||||||
}
|
|
||||||
var ip [16]byte
|
|
||||||
var xorKey [16]byte
|
|
||||||
binary.BigEndian.PutUint32(xorKey[0:4], magicCookie)
|
|
||||||
copy(xorKey[4:16], txID[:])
|
|
||||||
for i := range 16 {
|
|
||||||
ip[i] = data[4+i] ^ xorKey[i]
|
|
||||||
}
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil
|
|
||||||
default:
|
|
||||||
return netip.AddrPort{}, E.New("unknown address family: ", family)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseMappedAddress(data []byte) (netip.AddrPort, error) {
|
|
||||||
if len(data) < 4 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS too short")
|
|
||||||
}
|
|
||||||
|
|
||||||
family := data[1]
|
|
||||||
port := binary.BigEndian.Uint16(data[2:4])
|
|
||||||
|
|
||||||
switch family {
|
|
||||||
case familyIPv4:
|
|
||||||
if len(data) < 8 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv4 too short")
|
|
||||||
}
|
|
||||||
return netip.AddrPortFrom(
|
|
||||||
netip.AddrFrom4([4]byte{data[4], data[5], data[6], data[7]}), port,
|
|
||||||
), nil
|
|
||||||
case familyIPv6:
|
|
||||||
if len(data) < 20 {
|
|
||||||
return netip.AddrPort{}, E.New("MAPPED-ADDRESS IPv6 too short")
|
|
||||||
}
|
|
||||||
var ip [16]byte
|
|
||||||
copy(ip[:], data[4:20])
|
|
||||||
return netip.AddrPortFrom(netip.AddrFrom16(ip), port), nil
|
|
||||||
default:
|
|
||||||
return netip.AddrPort{}, E.New("unknown address family: ", family)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func roundTrip(conn net.PacketConn, addr net.Addr, txID TransactionID, attrs []stunAttribute, rto time.Duration) (*parsedResponse, time.Duration, error) {
|
|
||||||
request := buildBindingRequest(txID, attrs...)
|
|
||||||
currentRTO := rto
|
|
||||||
retransmitCount := 0
|
|
||||||
|
|
||||||
sendTime := time.Now()
|
|
||||||
_, err := conn.WriteTo(request, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "send STUN request")
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 1024)
|
|
||||||
for {
|
|
||||||
err = conn.SetReadDeadline(sendTime.Add(currentRTO))
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "set read deadline")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, _, readErr := conn.ReadFrom(buf)
|
|
||||||
if readErr != nil {
|
|
||||||
if E.IsTimeout(readErr) && retransmitCount < maxRetransmit {
|
|
||||||
retransmitCount++
|
|
||||||
currentRTO *= 2
|
|
||||||
sendTime = time.Now()
|
|
||||||
_, err = conn.WriteTo(request, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, E.Cause(err, "retransmit STUN request")
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return nil, 0, E.Cause(readErr, "read STUN response")
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < headerSize || buf[0]&0xC0 != 0 ||
|
|
||||||
binary.BigEndian.Uint32(buf[4:8]) != magicCookie {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var receivedTxID TransactionID
|
|
||||||
copy(receivedTxID[:], buf[8:20])
|
|
||||||
if receivedTxID != txID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
latency := time.Since(sendTime)
|
|
||||||
|
|
||||||
resp, parseErr := parseResponse(buf[:n], txID)
|
|
||||||
if parseErr != nil {
|
|
||||||
return nil, 0, parseErr
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, latency, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Run(options Options) (*Result, error) {
|
|
||||||
ctx := options.Context
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
server := options.Server
|
|
||||||
if server == "" {
|
|
||||||
server = DefaultServer
|
|
||||||
}
|
|
||||||
serverSocksaddr := M.ParseSocksaddr(server)
|
|
||||||
if serverSocksaddr.Port == 0 {
|
|
||||||
serverSocksaddr.Port = 3478
|
|
||||||
}
|
|
||||||
|
|
||||||
reportProgress := options.OnProgress
|
|
||||||
if reportProgress == nil {
|
|
||||||
reportProgress = func(Progress) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
packetConn net.PacketConn
|
|
||||||
serverAddr net.Addr
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if options.Dialer != nil {
|
|
||||||
packetConn, err = options.Dialer.ListenPacket(ctx, serverSocksaddr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create UDP socket")
|
|
||||||
}
|
|
||||||
serverAddr = serverSocksaddr
|
|
||||||
} else {
|
|
||||||
serverUDPAddr, resolveErr := net.ResolveUDPAddr("udp", serverSocksaddr.String())
|
|
||||||
if resolveErr != nil {
|
|
||||||
return nil, E.Cause(resolveErr, "resolve STUN server")
|
|
||||||
}
|
|
||||||
packetConn, err = net.ListenPacket("udp", "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create UDP socket")
|
|
||||||
}
|
|
||||||
serverAddr = serverUDPAddr
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = packetConn.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
rto := defaultRTO
|
|
||||||
|
|
||||||
// Phase 1: Binding
|
|
||||||
reportProgress(Progress{Phase: PhaseBinding})
|
|
||||||
|
|
||||||
txID := newTransactionID()
|
|
||||||
resp, latency, err := roundTrip(packetConn, serverAddr, txID, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "binding request")
|
|
||||||
}
|
|
||||||
|
|
||||||
rto = max(minRTO, 3*latency)
|
|
||||||
|
|
||||||
externalAddr, ok := resp.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return nil, E.New("no mapped address in response")
|
|
||||||
}
|
|
||||||
|
|
||||||
result := &Result{
|
|
||||||
ExternalAddr: externalAddr.String(),
|
|
||||||
LatencyMs: int32(latency.Milliseconds()),
|
|
||||||
}
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseBinding,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
|
|
||||||
otherAddr := resp.otherAddr
|
|
||||||
if !otherAddr.IsValid() {
|
|
||||||
result.NATTypeSupported = false
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseDone,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
result.NATTypeSupported = true
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2: NAT Mapping Detection (RFC 5780 Section 4.3)
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATMapping,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
})
|
|
||||||
|
|
||||||
result.NATMapping = detectNATMapping(
|
|
||||||
packetConn, serverSocksaddr.Port, externalAddr, otherAddr, rto,
|
|
||||||
)
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATMapping,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
})
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 3: NAT Filtering Detection (RFC 5780 Section 4.4)
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseNATFiltering,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
})
|
|
||||||
|
|
||||||
result.NATFiltering = detectNATFiltering(packetConn, serverAddr, rto)
|
|
||||||
|
|
||||||
reportProgress(Progress{
|
|
||||||
Phase: PhaseDone,
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NATMapping: result.NATMapping,
|
|
||||||
NATFiltering: result.NATFiltering,
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectNATMapping(
|
|
||||||
conn net.PacketConn,
|
|
||||||
serverPort uint16,
|
|
||||||
externalAddr netip.AddrPort,
|
|
||||||
otherAddr netip.AddrPort,
|
|
||||||
rto time.Duration,
|
|
||||||
) NATMapping {
|
|
||||||
// Mapping Test II: Send to other_ip:server_port
|
|
||||||
testIIAddr := net.UDPAddrFromAddrPort(
|
|
||||||
netip.AddrPortFrom(otherAddr.Addr(), serverPort),
|
|
||||||
)
|
|
||||||
txID2 := newTransactionID()
|
|
||||||
resp2, _, err := roundTrip(conn, testIIAddr, txID2, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
externalAddr2, ok := resp2.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
if externalAddr == externalAddr2 {
|
|
||||||
return NATMappingEndpointIndependent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mapping Test III: Send to other_ip:other_port
|
|
||||||
testIIIAddr := net.UDPAddrFromAddrPort(otherAddr)
|
|
||||||
txID3 := newTransactionID()
|
|
||||||
resp3, _, err := roundTrip(conn, testIIIAddr, txID3, nil, rto)
|
|
||||||
if err != nil {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
externalAddr3, ok := resp3.externalAddr()
|
|
||||||
if !ok {
|
|
||||||
return NATMappingUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
if externalAddr2 == externalAddr3 {
|
|
||||||
return NATMappingAddressDependent
|
|
||||||
}
|
|
||||||
return NATMappingAddressAndPortDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectNATFiltering(
|
|
||||||
conn net.PacketConn,
|
|
||||||
serverAddr net.Addr,
|
|
||||||
rto time.Duration,
|
|
||||||
) NATFiltering {
|
|
||||||
// Filtering Test II: Request response from different IP and port
|
|
||||||
txID := newTransactionID()
|
|
||||||
_, _, err := roundTrip(conn, serverAddr, txID,
|
|
||||||
[]stunAttribute{changeRequestAttr(changeIP | changePort)}, rto)
|
|
||||||
if err == nil {
|
|
||||||
return NATFilteringEndpointIndependent
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering Test III: Request response from different port only
|
|
||||||
txID = newTransactionID()
|
|
||||||
_, _, err = roundTrip(conn, serverAddr, txID,
|
|
||||||
[]stunAttribute{changeRequestAttr(changePort)}, rto)
|
|
||||||
if err == nil {
|
|
||||||
return NATFilteringAddressDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
return NATFilteringAddressAndPortDependent
|
|
||||||
}
|
|
||||||
|
|
||||||
func paddingLen(n int) int {
|
|
||||||
if n%4 == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 4 - n%4
|
|
||||||
}
|
|
||||||
@@ -38,6 +38,37 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
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) {
|
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
@@ -60,8 +91,8 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
zapLogger := zap.New(zapcore.NewCore(
|
||||||
zapcore.NewConsoleEncoder(ACMEEncoderConfig()),
|
zapcore.NewConsoleEncoder(encoderConfig()),
|
||||||
&ACMELogWriter{Logger: logger},
|
&acmeLogWriter{logger: logger},
|
||||||
zap.DebugLevel,
|
zap.DebugLevel,
|
||||||
))
|
))
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
@@ -127,7 +158,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{
|
tlsConfig = &tls.Config{
|
||||||
GetCertificate: config.GetCertificate,
|
GetCertificate: config.GetCertificate,
|
||||||
NextProtos: []string{C.ACMETLS1Protocol},
|
NextProtos: []string{ACMETLS1Protocol},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
return tlsConfig, &acmeWrapper{ctx: ctx, cfg: config, cache: cache, domain: options.Domain}, nil
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package constant
|
package tls
|
||||||
|
|
||||||
const ACMETLS1Protocol = "acme-tls/1"
|
const ACMETLS1Protocol = "acme-tls/1"
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ACMELogWriter struct {
|
|
||||||
Logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *ACMELogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.Logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.Logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.Logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.Logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.Logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *ACMELogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ACMEEncoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
@@ -32,10 +32,6 @@ type RealityServerConfig struct {
|
|||||||
func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewRealityServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
var tlsConfig utls.RealityConfig
|
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 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
return nil, E.New("acme is unavailable in reality")
|
return nil, E.New("acme is unavailable in reality")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,87 +13,19 @@ import (
|
|||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errInsecureUnused = E.New("tls: insecure unused")
|
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 {
|
type STDServerConfig struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
certificateProvider managedCertificateProvider
|
|
||||||
acmeService adapter.SimpleLifecycle
|
acmeService adapter.SimpleLifecycle
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
@@ -121,17 +53,18 @@ func (c *STDServerConfig) SetServerName(serverName string) {
|
|||||||
func (c *STDServerConfig) NextProtos() []string {
|
func (c *STDServerConfig) NextProtos() []string {
|
||||||
c.access.RLock()
|
c.access.RLock()
|
||||||
defer c.access.RUnlock()
|
defer c.access.RUnlock()
|
||||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
return c.config.NextProtos[1:]
|
return c.config.NextProtos[1:]
|
||||||
|
} else {
|
||||||
|
return c.config.NextProtos
|
||||||
}
|
}
|
||||||
return c.config.NextProtos
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
config := c.config.Clone()
|
config := c.config.Clone()
|
||||||
if c.hasACMEALPN() && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == C.ACMETLS1Protocol {
|
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
|
||||||
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
|
||||||
} else {
|
} else {
|
||||||
config.NextProtos = nextProto
|
config.NextProtos = nextProto
|
||||||
@@ -139,18 +72,6 @@ func (c *STDServerConfig) SetNextProtos(nextProto []string) {
|
|||||||
c.config = config
|
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) {
|
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return c.config, nil
|
||||||
}
|
}
|
||||||
@@ -170,39 +91,15 @@ func (c *STDServerConfig) Clone() Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Start() error {
|
func (c *STDServerConfig) Start() error {
|
||||||
if c.certificateProvider != nil {
|
|
||||||
err := c.certificateProvider.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if acmeProvider, isACME := c.certificateProvider.(adapter.ACMECertificateProvider); isACME {
|
|
||||||
nextProtos := acmeProvider.GetACMENextProtos()
|
|
||||||
if len(nextProtos) > 0 {
|
|
||||||
c.access.Lock()
|
|
||||||
config := c.config.Clone()
|
|
||||||
mergedNextProtos := append([]string{}, nextProtos...)
|
|
||||||
for _, nextProto := range config.NextProtos {
|
|
||||||
if !common.Contains(mergedNextProtos, nextProto) {
|
|
||||||
mergedNextProtos = append(mergedNextProtos, nextProto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
config.NextProtos = mergedNextProtos
|
|
||||||
c.config = config
|
|
||||||
c.access.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.acmeService != nil {
|
if c.acmeService != nil {
|
||||||
err := c.acmeService.Start()
|
return c.acmeService.Start()
|
||||||
|
} else {
|
||||||
|
err := c.startWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
c.logger.Warn("create fsnotify watcher: ", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
err := c.startWatcher()
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("create fsnotify watcher: ", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) startWatcher() error {
|
func (c *STDServerConfig) startWatcher() error {
|
||||||
@@ -306,34 +203,23 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Close() error {
|
func (c *STDServerConfig) Close() error {
|
||||||
return common.Close(c.certificateProvider, c.acmeService, c.watcher)
|
if c.acmeService != nil {
|
||||||
|
return c.acmeService.Close()
|
||||||
|
}
|
||||||
|
if c.watcher != nil {
|
||||||
|
return c.watcher.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
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 tlsConfig *tls.Config
|
||||||
var certificateProvider managedCertificateProvider
|
|
||||||
var acmeService adapter.SimpleLifecycle
|
var acmeService adapter.SimpleLifecycle
|
||||||
var err error
|
var err error
|
||||||
if options.CertificateProvider != nil {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
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
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -386,7 +272,7 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
)
|
)
|
||||||
if certificateProvider == nil && acmeService == nil {
|
if acmeService == nil {
|
||||||
if len(options.Certificate) > 0 {
|
if len(options.Certificate) > 0 {
|
||||||
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
certificate = []byte(strings.Join(options.Certificate, "\n"))
|
||||||
} else if options.CertificatePath != "" {
|
} else if options.CertificatePath != "" {
|
||||||
@@ -474,7 +360,6 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
serverConfig := &STDServerConfig{
|
serverConfig := &STDServerConfig{
|
||||||
config: tlsConfig,
|
config: tlsConfig,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
certificateProvider: certificateProvider,
|
|
||||||
acmeService: acmeService,
|
acmeService: acmeService,
|
||||||
certificate: certificate,
|
certificate: certificate,
|
||||||
key: key,
|
key: key,
|
||||||
@@ -484,8 +369,8 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
echKeyPath: echKeyPath,
|
echKeyPath: echKeyPath,
|
||||||
}
|
}
|
||||||
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||||
serverConfig.access.RLock()
|
serverConfig.access.Lock()
|
||||||
defer serverConfig.access.RUnlock()
|
defer serverConfig.access.Unlock()
|
||||||
return serverConfig.config, nil
|
return serverConfig.config, nil
|
||||||
}
|
}
|
||||||
var config ServerConfig = serverConfig
|
var config ServerConfig = serverConfig
|
||||||
@@ -502,27 +387,3 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
|
|||||||
}
|
}
|
||||||
return config, nil
|
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,18 +15,19 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DNSTypeLegacy = "legacy"
|
DNSTypeLegacy = "legacy"
|
||||||
DNSTypeUDP = "udp"
|
DNSTypeLegacyRcode = "legacy_rcode"
|
||||||
DNSTypeTCP = "tcp"
|
DNSTypeUDP = "udp"
|
||||||
DNSTypeTLS = "tls"
|
DNSTypeTCP = "tcp"
|
||||||
DNSTypeHTTPS = "https"
|
DNSTypeTLS = "tls"
|
||||||
DNSTypeQUIC = "quic"
|
DNSTypeHTTPS = "https"
|
||||||
DNSTypeHTTP3 = "h3"
|
DNSTypeQUIC = "quic"
|
||||||
DNSTypeLocal = "local"
|
DNSTypeHTTP3 = "h3"
|
||||||
DNSTypeHosts = "hosts"
|
DNSTypeLocal = "local"
|
||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeHosts = "hosts"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeTailscale = "tailscale"
|
DNSTypeDHCP = "dhcp"
|
||||||
|
DNSTypeTailscale = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,39 +1,37 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeTun = "tun"
|
TypeTun = "tun"
|
||||||
TypeRedirect = "redirect"
|
TypeRedirect = "redirect"
|
||||||
TypeTProxy = "tproxy"
|
TypeTProxy = "tproxy"
|
||||||
TypeDirect = "direct"
|
TypeDirect = "direct"
|
||||||
TypeBlock = "block"
|
TypeBlock = "block"
|
||||||
TypeDNS = "dns"
|
TypeDNS = "dns"
|
||||||
TypeSOCKS = "socks"
|
TypeSOCKS = "socks"
|
||||||
TypeHTTP = "http"
|
TypeHTTP = "http"
|
||||||
TypeMixed = "mixed"
|
TypeMixed = "mixed"
|
||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeAnyTLS = "anytls"
|
TypeAnyTLS = "anytls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeCloudflared = "cloudflared"
|
TypeCloudflared = "cloudflared"
|
||||||
TypeDERP = "derp"
|
TypeDERP = "derp"
|
||||||
TypeResolved = "resolved"
|
TypeResolved = "resolved"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeSSMAPI = "ssm-api"
|
||||||
TypeCCM = "ccm"
|
TypeCCM = "ccm"
|
||||||
TypeOCM = "ocm"
|
TypeOCM = "ocm"
|
||||||
TypeOOMKiller = "oom-killer"
|
TypeOOMKiller = "oom-killer"
|
||||||
TypeACME = "acme"
|
|
||||||
TypeCloudflareOriginCA = "cloudflare-origin-ca"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -23,15 +23,12 @@ const (
|
|||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
RuleSetVersion3
|
||||||
RuleSetVersion4
|
RuleSetVersion4
|
||||||
RuleSetVersion5
|
RuleSetVersionCurrent = RuleSetVersion4
|
||||||
RuleSetVersionCurrent = RuleSetVersion5
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RuleActionTypeRoute = "route"
|
RuleActionTypeRoute = "route"
|
||||||
RuleActionTypeRouteOptions = "route-options"
|
RuleActionTypeRouteOptions = "route-options"
|
||||||
RuleActionTypeEvaluate = "evaluate"
|
|
||||||
RuleActionTypeRespond = "respond"
|
|
||||||
RuleActionTypeDirect = "direct"
|
RuleActionTypeDirect = "direct"
|
||||||
RuleActionTypeBypass = "bypass"
|
RuleActionTypeBypass = "bypass"
|
||||||
RuleActionTypeReject = "reject"
|
RuleActionTypeReject = "reject"
|
||||||
|
|||||||
@@ -87,17 +87,12 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.oomKillerEnabled {
|
if s.oomKiller && C.IsIos {
|
||||||
if !common.Any(options.Services, func(it option.Service) bool {
|
if !common.Any(options.Services, func(it option.Service) bool {
|
||||||
return it.Type == C.TypeOOMKiller
|
return it.Type == C.TypeOOMKiller
|
||||||
}) {
|
}) {
|
||||||
oomOptions := &option.OOMKillerServiceOptions{
|
|
||||||
KillerDisabled: s.oomKillerDisabled,
|
|
||||||
MemoryLimitOverride: s.oomMemoryLimit,
|
|
||||||
}
|
|
||||||
options.Services = append(options.Services, option.Service{
|
options.Services = append(options.Services, option.Service{
|
||||||
Type: C.TypeOOMKiller,
|
Type: C.TypeOOMKiller,
|
||||||
Options: oomOptions,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ type PlatformHandler interface {
|
|||||||
ServiceReload() error
|
ServiceReload() error
|
||||||
SystemProxyStatus() (*SystemProxyStatus, error)
|
SystemProxyStatus() (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(enabled bool) error
|
SetSystemProxyEnabled(enabled bool) error
|
||||||
TriggerNativeCrash() error
|
|
||||||
WriteDebugMessage(message string)
|
WriteDebugMessage(message string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,20 +6,14 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
"github.com/sagernet/sing-box/common/networkquality"
|
|
||||||
"github.com/sagernet/sing-box/common/stun"
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||||
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
"github.com/sagernet/sing-box/experimental/clashapi/trafficontrol"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing-box/service/oomkiller"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -30,8 +24,6 @@ import (
|
|||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,12 +32,10 @@ var _ StartedServiceServer = (*StartedService)(nil)
|
|||||||
type StartedService struct {
|
type StartedService struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
// platform adapter.PlatformInterface
|
// platform adapter.PlatformInterface
|
||||||
handler PlatformHandler
|
handler PlatformHandler
|
||||||
debug bool
|
debug bool
|
||||||
logMaxLines int
|
logMaxLines int
|
||||||
oomKillerEnabled bool
|
oomKiller bool
|
||||||
oomKillerDisabled bool
|
|
||||||
oomMemoryLimit uint64
|
|
||||||
// workingDirectory string
|
// workingDirectory string
|
||||||
// tempDirectory string
|
// tempDirectory string
|
||||||
// userID int
|
// userID int
|
||||||
@@ -74,12 +64,10 @@ type StartedService struct {
|
|||||||
type ServiceOptions struct {
|
type ServiceOptions struct {
|
||||||
Context context.Context
|
Context context.Context
|
||||||
// Platform adapter.PlatformInterface
|
// Platform adapter.PlatformInterface
|
||||||
Handler PlatformHandler
|
Handler PlatformHandler
|
||||||
Debug bool
|
Debug bool
|
||||||
LogMaxLines int
|
LogMaxLines int
|
||||||
OOMKillerEnabled bool
|
OOMKiller bool
|
||||||
OOMKillerDisabled bool
|
|
||||||
OOMMemoryLimit uint64
|
|
||||||
// WorkingDirectory string
|
// WorkingDirectory string
|
||||||
// TempDirectory string
|
// TempDirectory string
|
||||||
// UserID int
|
// UserID int
|
||||||
@@ -91,12 +79,10 @@ func NewStartedService(options ServiceOptions) *StartedService {
|
|||||||
s := &StartedService{
|
s := &StartedService{
|
||||||
ctx: options.Context,
|
ctx: options.Context,
|
||||||
// platform: options.Platform,
|
// platform: options.Platform,
|
||||||
handler: options.Handler,
|
handler: options.Handler,
|
||||||
debug: options.Debug,
|
debug: options.Debug,
|
||||||
logMaxLines: options.LogMaxLines,
|
logMaxLines: options.LogMaxLines,
|
||||||
oomKillerEnabled: options.OOMKillerEnabled,
|
oomKiller: options.OOMKiller,
|
||||||
oomKillerDisabled: options.OOMKillerDisabled,
|
|
||||||
oomMemoryLimit: options.OOMMemoryLimit,
|
|
||||||
// workingDirectory: options.WorkingDirectory,
|
// workingDirectory: options.WorkingDirectory,
|
||||||
// tempDirectory: options.TempDirectory,
|
// tempDirectory: options.TempDirectory,
|
||||||
// userID: options.UserID,
|
// userID: options.UserID,
|
||||||
@@ -696,42 +682,7 @@ func (s *StartedService) SetSystemProxyEnabled(ctx context.Context, request *Set
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &emptypb.Empty{}, nil
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) TriggerDebugCrash(ctx context.Context, request *DebugCrashRequest) (*emptypb.Empty, error) {
|
|
||||||
if !s.debug {
|
|
||||||
return nil, status.Error(codes.PermissionDenied, "debug crash trigger unavailable")
|
|
||||||
}
|
|
||||||
if request == nil {
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "missing debug crash request")
|
|
||||||
}
|
|
||||||
switch request.Type {
|
|
||||||
case DebugCrashRequest_GO:
|
|
||||||
time.AfterFunc(200*time.Millisecond, func() {
|
|
||||||
*(*int)(unsafe.Pointer(uintptr(0))) = 0
|
|
||||||
})
|
|
||||||
case DebugCrashRequest_NATIVE:
|
|
||||||
err := s.handler.TriggerNativeCrash()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, status.Error(codes.InvalidArgument, "unknown debug crash type")
|
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) TriggerOOMReport(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
||||||
instance := s.Instance()
|
|
||||||
if instance == nil {
|
|
||||||
return nil, status.Error(codes.FailedPrecondition, "service not started")
|
|
||||||
}
|
|
||||||
reporter := service.FromContext[oomkiller.OOMReporter](instance.ctx)
|
|
||||||
if reporter == nil {
|
|
||||||
return nil, status.Error(codes.Unavailable, "OOM reporter not available")
|
|
||||||
}
|
|
||||||
return &emptypb.Empty{}, reporter.WriteReport(memory.Total())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
func (s *StartedService) SubscribeConnections(request *SubscribeConnectionsRequest, server grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||||
@@ -1068,12 +1019,9 @@ func (s *StartedService) GetDeprecatedWarnings(ctx context.Context, empty *empty
|
|||||||
return &DeprecatedWarnings{
|
return &DeprecatedWarnings{
|
||||||
Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {
|
Warnings: common.Map(notes, func(it deprecated.Note) *DeprecatedWarning {
|
||||||
return &DeprecatedWarning{
|
return &DeprecatedWarning{
|
||||||
Message: it.Message(),
|
Message: it.Message(),
|
||||||
Impending: it.Impending(),
|
Impending: it.Impending(),
|
||||||
MigrationLink: it.MigrationLink,
|
MigrationLink: it.MigrationLink,
|
||||||
Description: it.Description,
|
|
||||||
DeprecatedVersion: it.DeprecatedVersion,
|
|
||||||
ScheduledVersion: it.ScheduledVersion,
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}, nil
|
}, nil
|
||||||
@@ -1085,386 +1033,6 @@ func (s *StartedService) GetStartedAt(ctx context.Context, empty *emptypb.Empty)
|
|||||||
return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil
|
return &StartedAt{StartedAt: s.startedAt.UnixMilli()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StartedService) SubscribeOutbounds(_ *emptypb.Empty, server grpc.ServerStreamingServer[OutboundList]) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
subscription, done, err := s.urlTestObserver.Subscribe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.urlTestObserver.UnSubscribe(subscription)
|
|
||||||
for {
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
if s.serviceStatus.Status != ServiceStatus_STARTED {
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
historyStorage := boxService.urlTestHistoryStorage
|
|
||||||
var list OutboundList
|
|
||||||
for _, ob := range boxService.instance.Outbound().Outbounds() {
|
|
||||||
item := &GroupItem{
|
|
||||||
Tag: ob.Tag(),
|
|
||||||
Type: ob.Type(),
|
|
||||||
}
|
|
||||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ob)); history != nil {
|
|
||||||
item.UrlTestTime = history.Time.Unix()
|
|
||||||
item.UrlTestDelay = int32(history.Delay)
|
|
||||||
}
|
|
||||||
list.Outbounds = append(list.Outbounds, item)
|
|
||||||
}
|
|
||||||
for _, ep := range boxService.instance.Endpoint().Endpoints() {
|
|
||||||
item := &GroupItem{
|
|
||||||
Tag: ep.Tag(),
|
|
||||||
Type: ep.Type(),
|
|
||||||
}
|
|
||||||
if history := historyStorage.LoadURLTestHistory(adapter.OutboundTag(ep)); history != nil {
|
|
||||||
item.UrlTestTime = history.Time.Unix()
|
|
||||||
item.UrlTestDelay = int32(history.Delay)
|
|
||||||
}
|
|
||||||
list.Outbounds = append(list.Outbounds, item)
|
|
||||||
}
|
|
||||||
err = server.Send(&list)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-subscription:
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
return s.ctx.Err()
|
|
||||||
case <-server.Context().Done():
|
|
||||||
return server.Context().Err()
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveOutbound(instance *Instance, tag string) (adapter.Outbound, error) {
|
|
||||||
if tag == "" {
|
|
||||||
return instance.instance.Outbound().Default(), nil
|
|
||||||
}
|
|
||||||
outbound, loaded := instance.instance.Outbound().Outbound(tag)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound not found: ", tag)
|
|
||||||
}
|
|
||||||
return outbound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartNetworkQualityTest(
|
|
||||||
request *NetworkQualityTestRequest,
|
|
||||||
server grpc.ServerStreamingServer[NetworkQualityTestProgress],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
outbound, err := resolveOutbound(boxService, request.OutboundTag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0)
|
|
||||||
httpClient := networkquality.NewHTTPClient(resolvedDialer)
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
|
|
||||||
measurementClientFactory, err := networkquality.NewOptionalHTTP3Factory(resolvedDialer, request.Http3)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result, nqErr := networkquality.Run(networkquality.Options{
|
|
||||||
ConfigURL: request.ConfigURL,
|
|
||||||
HTTPClient: httpClient,
|
|
||||||
NewMeasurementClient: measurementClientFactory,
|
|
||||||
Serial: request.Serial,
|
|
||||||
MaxRuntime: time.Duration(request.MaxRuntimeSeconds) * time.Second,
|
|
||||||
Context: server.Context(),
|
|
||||||
OnProgress: func(p networkquality.Progress) {
|
|
||||||
_ = server.Send(&NetworkQualityTestProgress{
|
|
||||||
Phase: int32(p.Phase),
|
|
||||||
DownloadCapacity: p.DownloadCapacity,
|
|
||||||
UploadCapacity: p.UploadCapacity,
|
|
||||||
DownloadRPM: p.DownloadRPM,
|
|
||||||
UploadRPM: p.UploadRPM,
|
|
||||||
IdleLatencyMs: p.IdleLatencyMs,
|
|
||||||
ElapsedMs: p.ElapsedMs,
|
|
||||||
DownloadCapacityAccuracy: int32(p.DownloadCapacityAccuracy),
|
|
||||||
UploadCapacityAccuracy: int32(p.UploadCapacityAccuracy),
|
|
||||||
DownloadRPMAccuracy: int32(p.DownloadRPMAccuracy),
|
|
||||||
UploadRPMAccuracy: int32(p.UploadRPMAccuracy),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if nqErr != nil {
|
|
||||||
return server.Send(&NetworkQualityTestProgress{
|
|
||||||
IsFinal: true,
|
|
||||||
Error: nqErr.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return server.Send(&NetworkQualityTestProgress{
|
|
||||||
Phase: int32(networkquality.PhaseDone),
|
|
||||||
DownloadCapacity: result.DownloadCapacity,
|
|
||||||
UploadCapacity: result.UploadCapacity,
|
|
||||||
DownloadRPM: result.DownloadRPM,
|
|
||||||
UploadRPM: result.UploadRPM,
|
|
||||||
IdleLatencyMs: result.IdleLatencyMs,
|
|
||||||
IsFinal: true,
|
|
||||||
DownloadCapacityAccuracy: int32(result.DownloadCapacityAccuracy),
|
|
||||||
UploadCapacityAccuracy: int32(result.UploadCapacityAccuracy),
|
|
||||||
DownloadRPMAccuracy: int32(result.DownloadRPMAccuracy),
|
|
||||||
UploadRPMAccuracy: int32(result.UploadRPMAccuracy),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartSTUNTest(
|
|
||||||
request *STUNTestRequest,
|
|
||||||
server grpc.ServerStreamingServer[STUNTestProgress],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
outbound, err := resolveOutbound(boxService, request.OutboundTag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedDialer := dialer.NewResolveDialer(boxService.ctx, outbound, true, "", adapter.DNSQueryOptions{}, 0)
|
|
||||||
|
|
||||||
result, stunErr := stun.Run(stun.Options{
|
|
||||||
Server: request.Server,
|
|
||||||
Dialer: resolvedDialer,
|
|
||||||
Context: server.Context(),
|
|
||||||
OnProgress: func(p stun.Progress) {
|
|
||||||
_ = server.Send(&STUNTestProgress{
|
|
||||||
Phase: int32(p.Phase),
|
|
||||||
ExternalAddr: p.ExternalAddr,
|
|
||||||
LatencyMs: p.LatencyMs,
|
|
||||||
NatMapping: int32(p.NATMapping),
|
|
||||||
NatFiltering: int32(p.NATFiltering),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if stunErr != nil {
|
|
||||||
return server.Send(&STUNTestProgress{
|
|
||||||
IsFinal: true,
|
|
||||||
Error: stunErr.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return server.Send(&STUNTestProgress{
|
|
||||||
Phase: int32(stun.PhaseDone),
|
|
||||||
ExternalAddr: result.ExternalAddr,
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
NatMapping: int32(result.NATMapping),
|
|
||||||
NatFiltering: int32(result.NATFiltering),
|
|
||||||
IsFinal: true,
|
|
||||||
NatTypeSupported: result.NATTypeSupported,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) SubscribeTailscaleStatus(
|
|
||||||
_ *emptypb.Empty,
|
|
||||||
server grpc.ServerStreamingServer[TailscaleStatusUpdate],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx)
|
|
||||||
if endpointManager == nil {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint manager not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
type tailscaleEndpoint struct {
|
|
||||||
tag string
|
|
||||||
provider adapter.TailscaleEndpoint
|
|
||||||
}
|
|
||||||
var endpoints []tailscaleEndpoint
|
|
||||||
for _, endpoint := range endpointManager.Endpoints() {
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
provider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if !loaded {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
endpoints = append(endpoints, tailscaleEndpoint{
|
|
||||||
tag: endpoint.Tag(),
|
|
||||||
provider: provider,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if len(endpoints) == 0 {
|
|
||||||
return status.Error(codes.NotFound, "no Tailscale endpoint found")
|
|
||||||
}
|
|
||||||
|
|
||||||
type taggedStatus struct {
|
|
||||||
tag string
|
|
||||||
status *adapter.TailscaleEndpointStatus
|
|
||||||
}
|
|
||||||
updates := make(chan taggedStatus, len(endpoints))
|
|
||||||
ctx, cancel := context.WithCancel(server.Context())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
var waitGroup sync.WaitGroup
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
waitGroup.Add(1)
|
|
||||||
go func(tag string, provider adapter.TailscaleEndpoint) {
|
|
||||||
defer waitGroup.Done()
|
|
||||||
_ = provider.SubscribeTailscaleStatus(ctx, func(endpointStatus *adapter.TailscaleEndpointStatus) {
|
|
||||||
select {
|
|
||||||
case updates <- taggedStatus{tag: tag, status: endpointStatus}:
|
|
||||||
case <-ctx.Done():
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}(endpoint.tag, endpoint.provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
waitGroup.Wait()
|
|
||||||
close(updates)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var tags []string
|
|
||||||
statuses := make(map[string]*adapter.TailscaleEndpointStatus, len(endpoints))
|
|
||||||
for update := range updates {
|
|
||||||
if _, exists := statuses[update.tag]; !exists {
|
|
||||||
tags = append(tags, update.tag)
|
|
||||||
}
|
|
||||||
statuses[update.tag] = update.status
|
|
||||||
protoEndpoints := make([]*TailscaleEndpointStatus, 0, len(statuses))
|
|
||||||
for _, tag := range tags {
|
|
||||||
protoEndpoints = append(protoEndpoints, tailscaleEndpointStatusToProto(tag, statuses[tag]))
|
|
||||||
}
|
|
||||||
sendErr := server.Send(&TailscaleStatusUpdate{
|
|
||||||
Endpoints: protoEndpoints,
|
|
||||||
})
|
|
||||||
if sendErr != nil {
|
|
||||||
return sendErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailscaleEndpointStatusToProto(tag string, s *adapter.TailscaleEndpointStatus) *TailscaleEndpointStatus {
|
|
||||||
userGroups := make([]*TailscaleUserGroup, len(s.UserGroups))
|
|
||||||
for i, group := range s.UserGroups {
|
|
||||||
peers := make([]*TailscalePeer, len(group.Peers))
|
|
||||||
for j, peer := range group.Peers {
|
|
||||||
peers[j] = tailscalePeerToProto(peer)
|
|
||||||
}
|
|
||||||
userGroups[i] = &TailscaleUserGroup{
|
|
||||||
UserID: group.UserID,
|
|
||||||
LoginName: group.LoginName,
|
|
||||||
DisplayName: group.DisplayName,
|
|
||||||
ProfilePicURL: group.ProfilePicURL,
|
|
||||||
Peers: peers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := &TailscaleEndpointStatus{
|
|
||||||
EndpointTag: tag,
|
|
||||||
BackendState: s.BackendState,
|
|
||||||
AuthURL: s.AuthURL,
|
|
||||||
NetworkName: s.NetworkName,
|
|
||||||
MagicDNSSuffix: s.MagicDNSSuffix,
|
|
||||||
UserGroups: userGroups,
|
|
||||||
}
|
|
||||||
if s.Self != nil {
|
|
||||||
result.Self = tailscalePeerToProto(s.Self)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func tailscalePeerToProto(peer *adapter.TailscalePeer) *TailscalePeer {
|
|
||||||
return &TailscalePeer{
|
|
||||||
HostName: peer.HostName,
|
|
||||||
DnsName: peer.DNSName,
|
|
||||||
Os: peer.OS,
|
|
||||||
TailscaleIPs: peer.TailscaleIPs,
|
|
||||||
Online: peer.Online,
|
|
||||||
ExitNode: peer.ExitNode,
|
|
||||||
ExitNodeOption: peer.ExitNodeOption,
|
|
||||||
Active: peer.Active,
|
|
||||||
RxBytes: peer.RxBytes,
|
|
||||||
TxBytes: peer.TxBytes,
|
|
||||||
KeyExpiry: peer.KeyExpiry,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) StartTailscalePing(
|
|
||||||
request *TailscalePingRequest,
|
|
||||||
server grpc.ServerStreamingServer[TailscalePingResponse],
|
|
||||||
) error {
|
|
||||||
err := s.waitForStarted(server.Context())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.serviceAccess.RLock()
|
|
||||||
boxService := s.instance
|
|
||||||
s.serviceAccess.RUnlock()
|
|
||||||
|
|
||||||
endpointManager := service.FromContext[adapter.EndpointManager](boxService.ctx)
|
|
||||||
if endpointManager == nil {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint manager not available")
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider adapter.TailscaleEndpoint
|
|
||||||
if request.EndpointTag != "" {
|
|
||||||
endpoint, loaded := endpointManager.Get(request.EndpointTag)
|
|
||||||
if !loaded {
|
|
||||||
return status.Error(codes.NotFound, "endpoint not found: "+request.EndpointTag)
|
|
||||||
}
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
return status.Error(codes.InvalidArgument, "endpoint is not Tailscale: "+request.EndpointTag)
|
|
||||||
}
|
|
||||||
pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if !loaded {
|
|
||||||
return status.Error(codes.FailedPrecondition, "endpoint does not support ping")
|
|
||||||
}
|
|
||||||
provider = pingProvider
|
|
||||||
} else {
|
|
||||||
for _, endpoint := range endpointManager.Endpoints() {
|
|
||||||
if endpoint.Type() != C.TypeTailscale {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pingProvider, loaded := endpoint.(adapter.TailscaleEndpoint)
|
|
||||||
if loaded {
|
|
||||||
provider = pingProvider
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if provider == nil {
|
|
||||||
return status.Error(codes.NotFound, "no Tailscale endpoint found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider.StartTailscalePing(server.Context(), request.PeerIP, func(result *adapter.TailscalePingResult) {
|
|
||||||
_ = server.Send(&TailscalePingResponse{
|
|
||||||
LatencyMs: result.LatencyMs,
|
|
||||||
IsDirect: result.IsDirect,
|
|
||||||
Endpoint: result.Endpoint,
|
|
||||||
DerpRegionID: result.DERPRegionID,
|
|
||||||
DerpRegionCode: result.DERPRegionCode,
|
|
||||||
Error: result.Error,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
func (s *StartedService) mustEmbedUnimplementedStartedServiceServer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -26,20 +26,12 @@ service StartedService {
|
|||||||
|
|
||||||
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
rpc GetSystemProxyStatus(google.protobuf.Empty) returns(SystemProxyStatus) {}
|
||||||
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
rpc SetSystemProxyEnabled(SetSystemProxyEnabledRequest) returns(google.protobuf.Empty) {}
|
||||||
rpc TriggerDebugCrash(DebugCrashRequest) returns(google.protobuf.Empty) {}
|
|
||||||
rpc TriggerOOMReport(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
|
||||||
|
|
||||||
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
rpc SubscribeConnections(SubscribeConnectionsRequest) returns(stream ConnectionEvents) {}
|
||||||
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
rpc CloseConnection(CloseConnectionRequest) returns(google.protobuf.Empty) {}
|
||||||
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
rpc CloseAllConnections(google.protobuf.Empty) returns(google.protobuf.Empty) {}
|
||||||
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
rpc GetDeprecatedWarnings(google.protobuf.Empty) returns(DeprecatedWarnings) {}
|
||||||
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
rpc GetStartedAt(google.protobuf.Empty) returns(StartedAt) {}
|
||||||
|
|
||||||
rpc SubscribeOutbounds(google.protobuf.Empty) returns (stream OutboundList) {}
|
|
||||||
rpc StartNetworkQualityTest(NetworkQualityTestRequest) returns (stream NetworkQualityTestProgress) {}
|
|
||||||
rpc StartSTUNTest(STUNTestRequest) returns (stream STUNTestProgress) {}
|
|
||||||
rpc SubscribeTailscaleStatus(google.protobuf.Empty) returns (stream TailscaleStatusUpdate) {}
|
|
||||||
rpc StartTailscalePing(TailscalePingRequest) returns (stream TailscalePingResponse) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ServiceStatus {
|
message ServiceStatus {
|
||||||
@@ -149,15 +141,6 @@ message SetSystemProxyEnabledRequest {
|
|||||||
bool enabled = 1;
|
bool enabled = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DebugCrashRequest {
|
|
||||||
enum Type {
|
|
||||||
GO = 0;
|
|
||||||
NATIVE = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Type type = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SubscribeConnectionsRequest {
|
message SubscribeConnectionsRequest {
|
||||||
int64 interval = 1;
|
int64 interval = 1;
|
||||||
}
|
}
|
||||||
@@ -227,105 +210,8 @@ message DeprecatedWarning {
|
|||||||
string message = 1;
|
string message = 1;
|
||||||
bool impending = 2;
|
bool impending = 2;
|
||||||
string migrationLink = 3;
|
string migrationLink = 3;
|
||||||
string description = 4;
|
|
||||||
string deprecatedVersion = 5;
|
|
||||||
string scheduledVersion = 6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message StartedAt {
|
message StartedAt {
|
||||||
int64 startedAt = 1;
|
int64 startedAt = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message OutboundList {
|
|
||||||
repeated GroupItem outbounds = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetworkQualityTestRequest {
|
|
||||||
string configURL = 1;
|
|
||||||
string outboundTag = 2;
|
|
||||||
bool serial = 3;
|
|
||||||
int32 maxRuntimeSeconds = 4;
|
|
||||||
bool http3 = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetworkQualityTestProgress {
|
|
||||||
int32 phase = 1;
|
|
||||||
int64 downloadCapacity = 2;
|
|
||||||
int64 uploadCapacity = 3;
|
|
||||||
int32 downloadRPM = 4;
|
|
||||||
int32 uploadRPM = 5;
|
|
||||||
int32 idleLatencyMs = 6;
|
|
||||||
int64 elapsedMs = 7;
|
|
||||||
bool isFinal = 8;
|
|
||||||
string error = 9;
|
|
||||||
int32 downloadCapacityAccuracy = 10;
|
|
||||||
int32 uploadCapacityAccuracy = 11;
|
|
||||||
int32 downloadRPMAccuracy = 12;
|
|
||||||
int32 uploadRPMAccuracy = 13;
|
|
||||||
}
|
|
||||||
|
|
||||||
message STUNTestRequest {
|
|
||||||
string server = 1;
|
|
||||||
string outboundTag = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message STUNTestProgress {
|
|
||||||
int32 phase = 1;
|
|
||||||
string externalAddr = 2;
|
|
||||||
int32 latencyMs = 3;
|
|
||||||
int32 natMapping = 4;
|
|
||||||
int32 natFiltering = 5;
|
|
||||||
bool isFinal = 6;
|
|
||||||
string error = 7;
|
|
||||||
bool natTypeSupported = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleStatusUpdate {
|
|
||||||
repeated TailscaleEndpointStatus endpoints = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleEndpointStatus {
|
|
||||||
string endpointTag = 1;
|
|
||||||
string backendState = 2;
|
|
||||||
string authURL = 3;
|
|
||||||
string networkName = 4;
|
|
||||||
string magicDNSSuffix = 5;
|
|
||||||
TailscalePeer self = 6;
|
|
||||||
repeated TailscaleUserGroup userGroups = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscaleUserGroup {
|
|
||||||
int64 userID = 1;
|
|
||||||
string loginName = 2;
|
|
||||||
string displayName = 3;
|
|
||||||
string profilePicURL = 4;
|
|
||||||
repeated TailscalePeer peers = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePeer {
|
|
||||||
string hostName = 1;
|
|
||||||
string dnsName = 2;
|
|
||||||
string os = 3;
|
|
||||||
repeated string tailscaleIPs = 4;
|
|
||||||
bool online = 5;
|
|
||||||
bool exitNode = 6;
|
|
||||||
bool exitNodeOption = 7;
|
|
||||||
bool active = 8;
|
|
||||||
int64 rxBytes = 9;
|
|
||||||
int64 txBytes = 10;
|
|
||||||
int64 keyExpiry = 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePingRequest {
|
|
||||||
string endpointTag = 1;
|
|
||||||
string peerIP = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TailscalePingResponse {
|
|
||||||
double latencyMs = 1;
|
|
||||||
bool isDirect = 2;
|
|
||||||
string endpoint = 3;
|
|
||||||
int32 derpRegionID = 4;
|
|
||||||
string derpRegionCode = 5;
|
|
||||||
string error = 6;
|
|
||||||
}
|
|
||||||
@@ -15,34 +15,27 @@ import (
|
|||||||
const _ = grpc.SupportPackageIsVersion9
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
const (
|
const (
|
||||||
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
StartedService_StopService_FullMethodName = "/daemon.StartedService/StopService"
|
||||||
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
StartedService_ReloadService_FullMethodName = "/daemon.StartedService/ReloadService"
|
||||||
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
StartedService_SubscribeServiceStatus_FullMethodName = "/daemon.StartedService/SubscribeServiceStatus"
|
||||||
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
StartedService_SubscribeLog_FullMethodName = "/daemon.StartedService/SubscribeLog"
|
||||||
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
StartedService_GetDefaultLogLevel_FullMethodName = "/daemon.StartedService/GetDefaultLogLevel"
|
||||||
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
StartedService_ClearLogs_FullMethodName = "/daemon.StartedService/ClearLogs"
|
||||||
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
StartedService_SubscribeStatus_FullMethodName = "/daemon.StartedService/SubscribeStatus"
|
||||||
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
StartedService_SubscribeGroups_FullMethodName = "/daemon.StartedService/SubscribeGroups"
|
||||||
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
StartedService_GetClashModeStatus_FullMethodName = "/daemon.StartedService/GetClashModeStatus"
|
||||||
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
StartedService_SubscribeClashMode_FullMethodName = "/daemon.StartedService/SubscribeClashMode"
|
||||||
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
StartedService_SetClashMode_FullMethodName = "/daemon.StartedService/SetClashMode"
|
||||||
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
StartedService_URLTest_FullMethodName = "/daemon.StartedService/URLTest"
|
||||||
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
StartedService_SelectOutbound_FullMethodName = "/daemon.StartedService/SelectOutbound"
|
||||||
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
StartedService_SetGroupExpand_FullMethodName = "/daemon.StartedService/SetGroupExpand"
|
||||||
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
StartedService_GetSystemProxyStatus_FullMethodName = "/daemon.StartedService/GetSystemProxyStatus"
|
||||||
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
StartedService_SetSystemProxyEnabled_FullMethodName = "/daemon.StartedService/SetSystemProxyEnabled"
|
||||||
StartedService_TriggerDebugCrash_FullMethodName = "/daemon.StartedService/TriggerDebugCrash"
|
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
||||||
StartedService_TriggerOOMReport_FullMethodName = "/daemon.StartedService/TriggerOOMReport"
|
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
||||||
StartedService_SubscribeConnections_FullMethodName = "/daemon.StartedService/SubscribeConnections"
|
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
||||||
StartedService_CloseConnection_FullMethodName = "/daemon.StartedService/CloseConnection"
|
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
||||||
StartedService_CloseAllConnections_FullMethodName = "/daemon.StartedService/CloseAllConnections"
|
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
||||||
StartedService_GetDeprecatedWarnings_FullMethodName = "/daemon.StartedService/GetDeprecatedWarnings"
|
|
||||||
StartedService_GetStartedAt_FullMethodName = "/daemon.StartedService/GetStartedAt"
|
|
||||||
StartedService_SubscribeOutbounds_FullMethodName = "/daemon.StartedService/SubscribeOutbounds"
|
|
||||||
StartedService_StartNetworkQualityTest_FullMethodName = "/daemon.StartedService/StartNetworkQualityTest"
|
|
||||||
StartedService_StartSTUNTest_FullMethodName = "/daemon.StartedService/StartSTUNTest"
|
|
||||||
StartedService_SubscribeTailscaleStatus_FullMethodName = "/daemon.StartedService/SubscribeTailscaleStatus"
|
|
||||||
StartedService_StartTailscalePing_FullMethodName = "/daemon.StartedService/StartTailscalePing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartedServiceClient is the client API for StartedService service.
|
// StartedServiceClient is the client API for StartedService service.
|
||||||
@@ -65,18 +58,11 @@ type StartedServiceClient interface {
|
|||||||
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
SetGroupExpand(ctx context.Context, in *SetGroupExpandRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
GetSystemProxyStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
SetSystemProxyEnabled(ctx context.Context, in *SetSystemProxyEnabledRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
|
||||||
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error)
|
||||||
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
CloseConnection(ctx context.Context, in *CloseConnectionRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
CloseAllConnections(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||||
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
GetDeprecatedWarnings(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DeprecatedWarnings, error)
|
||||||
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
GetStartedAt(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*StartedAt, error)
|
||||||
SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error)
|
|
||||||
StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error)
|
|
||||||
StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error)
|
|
||||||
SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error)
|
|
||||||
StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type startedServiceClient struct {
|
type startedServiceClient struct {
|
||||||
@@ -292,26 +278,6 @@ func (c *startedServiceClient) SetSystemProxyEnabled(ctx context.Context, in *Se
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *startedServiceClient) TriggerDebugCrash(ctx context.Context, in *DebugCrashRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, StartedService_TriggerDebugCrash_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *startedServiceClient) TriggerOOMReport(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(emptypb.Empty)
|
|
||||||
err := c.cc.Invoke(ctx, StartedService_TriggerOOMReport_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
func (c *startedServiceClient) SubscribeConnections(ctx context.Context, in *SubscribeConnectionsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ConnectionEvents], error) {
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[5], StartedService_SubscribeConnections_FullMethodName, cOpts...)
|
||||||
@@ -371,101 +337,6 @@ func (c *startedServiceClient) GetStartedAt(ctx context.Context, in *emptypb.Emp
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeOutbounds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[OutboundList], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[6], StartedService_SubscribeOutbounds_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[emptypb.Empty, OutboundList]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeOutboundsClient = grpc.ServerStreamingClient[OutboundList]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartNetworkQualityTest(ctx context.Context, in *NetworkQualityTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NetworkQualityTestProgress], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[7], StartedService_StartNetworkQualityTest_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartNetworkQualityTestClient = grpc.ServerStreamingClient[NetworkQualityTestProgress]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartSTUNTest(ctx context.Context, in *STUNTestRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[STUNTestProgress], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[8], StartedService_StartSTUNTest_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[STUNTestRequest, STUNTestProgress]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartSTUNTestClient = grpc.ServerStreamingClient[STUNTestProgress]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) SubscribeTailscaleStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscaleStatusUpdate], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[9], StartedService_SubscribeTailscaleStatus_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[emptypb.Empty, TailscaleStatusUpdate]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeTailscaleStatusClient = grpc.ServerStreamingClient[TailscaleStatusUpdate]
|
|
||||||
|
|
||||||
func (c *startedServiceClient) StartTailscalePing(ctx context.Context, in *TailscalePingRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[TailscalePingResponse], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &StartedService_ServiceDesc.Streams[10], StartedService_StartTailscalePing_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[TailscalePingRequest, TailscalePingResponse]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartTailscalePingClient = grpc.ServerStreamingClient[TailscalePingResponse]
|
|
||||||
|
|
||||||
// StartedServiceServer is the server API for StartedService service.
|
// StartedServiceServer is the server API for StartedService service.
|
||||||
// All implementations must embed UnimplementedStartedServiceServer
|
// All implementations must embed UnimplementedStartedServiceServer
|
||||||
// for forward compatibility.
|
// for forward compatibility.
|
||||||
@@ -486,18 +357,11 @@ type StartedServiceServer interface {
|
|||||||
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
SetGroupExpand(context.Context, *SetGroupExpandRequest) (*emptypb.Empty, error)
|
||||||
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
GetSystemProxyStatus(context.Context, *emptypb.Empty) (*SystemProxyStatus, error)
|
||||||
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
SetSystemProxyEnabled(context.Context, *SetSystemProxyEnabledRequest) (*emptypb.Empty, error)
|
||||||
TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error)
|
|
||||||
TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
|
||||||
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error
|
||||||
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
CloseConnection(context.Context, *CloseConnectionRequest) (*emptypb.Empty, error)
|
||||||
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
CloseAllConnections(context.Context, *emptypb.Empty) (*emptypb.Empty, error)
|
||||||
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
GetDeprecatedWarnings(context.Context, *emptypb.Empty) (*DeprecatedWarnings, error)
|
||||||
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error)
|
||||||
SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error
|
|
||||||
StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error
|
|
||||||
StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error
|
|
||||||
SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error
|
|
||||||
StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error
|
|
||||||
mustEmbedUnimplementedStartedServiceServer()
|
mustEmbedUnimplementedStartedServiceServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,14 +436,6 @@ func (UnimplementedStartedServiceServer) SetSystemProxyEnabled(context.Context,
|
|||||||
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
return nil, status.Error(codes.Unimplemented, "method SetSystemProxyEnabled not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) TriggerDebugCrash(context.Context, *DebugCrashRequest) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "method TriggerDebugCrash not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) TriggerOOMReport(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
|
||||||
return nil, status.Error(codes.Unimplemented, "method TriggerOOMReport not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
func (UnimplementedStartedServiceServer) SubscribeConnections(*SubscribeConnectionsRequest, grpc.ServerStreamingServer[ConnectionEvents]) error {
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
return status.Error(codes.Unimplemented, "method SubscribeConnections not implemented")
|
||||||
}
|
}
|
||||||
@@ -599,26 +455,6 @@ func (UnimplementedStartedServiceServer) GetDeprecatedWarnings(context.Context,
|
|||||||
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
func (UnimplementedStartedServiceServer) GetStartedAt(context.Context, *emptypb.Empty) (*StartedAt, error) {
|
||||||
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
|
return nil, status.Error(codes.Unimplemented, "method GetStartedAt not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeOutbounds(*emptypb.Empty, grpc.ServerStreamingServer[OutboundList]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeOutbounds not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartNetworkQualityTest(*NetworkQualityTestRequest, grpc.ServerStreamingServer[NetworkQualityTestProgress]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartNetworkQualityTest not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartSTUNTest(*STUNTestRequest, grpc.ServerStreamingServer[STUNTestProgress]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartSTUNTest not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) SubscribeTailscaleStatus(*emptypb.Empty, grpc.ServerStreamingServer[TailscaleStatusUpdate]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method SubscribeTailscaleStatus not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UnimplementedStartedServiceServer) StartTailscalePing(*TailscalePingRequest, grpc.ServerStreamingServer[TailscalePingResponse]) error {
|
|
||||||
return status.Error(codes.Unimplemented, "method StartTailscalePing not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
func (UnimplementedStartedServiceServer) mustEmbedUnimplementedStartedServiceServer() {}
|
||||||
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
func (UnimplementedStartedServiceServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
@@ -893,42 +729,6 @@ func _StartedService_SetSystemProxyEnabled_Handler(srv interface{}, ctx context.
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _StartedService_TriggerDebugCrash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(DebugCrashRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: StartedService_TriggerDebugCrash_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(StartedServiceServer).TriggerDebugCrash(ctx, req.(*DebugCrashRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _StartedService_TriggerOOMReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(emptypb.Empty)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: StartedService_TriggerOOMReport_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(StartedServiceServer).TriggerOOMReport(ctx, req.(*emptypb.Empty))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
func _StartedService_SubscribeConnections_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
m := new(SubscribeConnectionsRequest)
|
m := new(SubscribeConnectionsRequest)
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
@@ -1012,61 +812,6 @@ func _StartedService_GetStartedAt_Handler(srv interface{}, ctx context.Context,
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _StartedService_SubscribeOutbounds_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(emptypb.Empty)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).SubscribeOutbounds(m, &grpc.GenericServerStream[emptypb.Empty, OutboundList]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeOutboundsServer = grpc.ServerStreamingServer[OutboundList]
|
|
||||||
|
|
||||||
func _StartedService_StartNetworkQualityTest_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(NetworkQualityTestRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).StartNetworkQualityTest(m, &grpc.GenericServerStream[NetworkQualityTestRequest, NetworkQualityTestProgress]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartNetworkQualityTestServer = grpc.ServerStreamingServer[NetworkQualityTestProgress]
|
|
||||||
|
|
||||||
func _StartedService_StartSTUNTest_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(STUNTestRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).StartSTUNTest(m, &grpc.GenericServerStream[STUNTestRequest, STUNTestProgress]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartSTUNTestServer = grpc.ServerStreamingServer[STUNTestProgress]
|
|
||||||
|
|
||||||
func _StartedService_SubscribeTailscaleStatus_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(emptypb.Empty)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).SubscribeTailscaleStatus(m, &grpc.GenericServerStream[emptypb.Empty, TailscaleStatusUpdate]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_SubscribeTailscaleStatusServer = grpc.ServerStreamingServer[TailscaleStatusUpdate]
|
|
||||||
|
|
||||||
func _StartedService_StartTailscalePing_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(TailscalePingRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(StartedServiceServer).StartTailscalePing(m, &grpc.GenericServerStream[TailscalePingRequest, TailscalePingResponse]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type StartedService_StartTailscalePingServer = grpc.ServerStreamingServer[TailscalePingResponse]
|
|
||||||
|
|
||||||
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
// StartedService_ServiceDesc is the grpc.ServiceDesc for StartedService service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@@ -1118,14 +863,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "SetSystemProxyEnabled",
|
MethodName: "SetSystemProxyEnabled",
|
||||||
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
Handler: _StartedService_SetSystemProxyEnabled_Handler,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
MethodName: "TriggerDebugCrash",
|
|
||||||
Handler: _StartedService_TriggerDebugCrash_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "TriggerOOMReport",
|
|
||||||
Handler: _StartedService_TriggerOOMReport_Handler,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
MethodName: "CloseConnection",
|
MethodName: "CloseConnection",
|
||||||
Handler: _StartedService_CloseConnection_Handler,
|
Handler: _StartedService_CloseConnection_Handler,
|
||||||
@@ -1174,31 +911,6 @@ var StartedService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _StartedService_SubscribeConnections_Handler,
|
Handler: _StartedService_SubscribeConnections_Handler,
|
||||||
ServerStreams: true,
|
ServerStreams: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
StreamName: "SubscribeOutbounds",
|
|
||||||
Handler: _StartedService_SubscribeOutbounds_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartNetworkQualityTest",
|
|
||||||
Handler: _StartedService_StartNetworkQualityTest_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartSTUNTest",
|
|
||||||
Handler: _StartedService_StartSTUNTest_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "SubscribeTailscaleStatus",
|
|
||||||
Handler: _StartedService_SubscribeTailscaleStatus_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "StartTailscalePing",
|
|
||||||
Handler: _StartedService_StartTailscalePing_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
Metadata: "daemon/started_service.proto",
|
Metadata: "daemon/started_service.proto",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/task"
|
"github.com/sagernet/sing/common/task"
|
||||||
"github.com/sagernet/sing/contrab/freelru"
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
"github.com/sagernet/sing/contrab/maphash"
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
@@ -107,7 +109,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error) {
|
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
|
||||||
if len(message.Question) == 0 {
|
if len(message.Question) == 0 {
|
||||||
if c.logger != nil {
|
if c.logger != nil {
|
||||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||||
@@ -237,10 +239,13 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
|
||||||
if responseChecker != nil {
|
if responseChecker != nil {
|
||||||
var rejected bool
|
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 {
|
if response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError {
|
||||||
rejected = true
|
rejected = true
|
||||||
|
} else if len(response.Answer) == 0 {
|
||||||
|
rejected = !responseChecker(nil)
|
||||||
} else {
|
} else {
|
||||||
rejected = !responseChecker(response)
|
rejected = !responseChecker(MessageToAddresses(response))
|
||||||
}
|
}
|
||||||
if rejected {
|
if rejected {
|
||||||
if !disableCache && c.rdrc != nil {
|
if !disableCache && c.rdrc != nil {
|
||||||
@@ -278,9 +283,6 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
if timeToLive == 0 {
|
if timeToLive == 0 {
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
for _, record := range recordList {
|
for _, record := range recordList {
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
|
||||||
timeToLive = record.Header().Ttl
|
timeToLive = record.Header().Ttl
|
||||||
}
|
}
|
||||||
@@ -292,9 +294,6 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
for _, record := range recordList {
|
for _, record := range recordList {
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = timeToLive
|
record.Header().Ttl = timeToLive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +315,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
domain = FqdnToDomain(domain)
|
domain = FqdnToDomain(domain)
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
var strategy C.DomainStrategy
|
var strategy C.DomainStrategy
|
||||||
@@ -382,26 +381,26 @@ func (c *Client) storeCache(transport adapter.DNSTransport, question dns.Questio
|
|||||||
}
|
}
|
||||||
if c.disableExpire {
|
if c.disableExpire {
|
||||||
if !c.independentCache {
|
if !c.independentCache {
|
||||||
c.cache.Add(question, message.Copy())
|
c.cache.Add(question, message)
|
||||||
} else {
|
} else {
|
||||||
c.transportCache.Add(transportCacheKey{
|
c.transportCache.Add(transportCacheKey{
|
||||||
Question: question,
|
Question: question,
|
||||||
transportTag: transport.Tag(),
|
transportTag: transport.Tag(),
|
||||||
}, message.Copy())
|
}, message)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !c.independentCache {
|
if !c.independentCache {
|
||||||
c.cache.AddWithLifetime(question, message.Copy(), time.Second*time.Duration(timeToLive))
|
c.cache.AddWithLifetime(question, message, time.Second*time.Duration(timeToLive))
|
||||||
} else {
|
} else {
|
||||||
c.transportCache.AddWithLifetime(transportCacheKey{
|
c.transportCache.AddWithLifetime(transportCacheKey{
|
||||||
Question: question,
|
Question: question,
|
||||||
transportTag: transport.Tag(),
|
transportTag: transport.Tag(),
|
||||||
}, message.Copy(), time.Second*time.Duration(timeToLive))
|
}, message, time.Second*time.Duration(timeToLive))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error) {
|
func (c *Client) lookupToExchange(ctx context.Context, transport adapter.DNSTransport, name string, qType uint16, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
question := dns.Question{
|
question := dns.Question{
|
||||||
Name: name,
|
Name: name,
|
||||||
Qtype: qType,
|
Qtype: qType,
|
||||||
@@ -487,9 +486,6 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
var originTTL int
|
var originTTL int
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
for _, record := range recordList {
|
for _, record := range recordList {
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
|
if originTTL == 0 || record.Header().Ttl > 0 && int(record.Header().Ttl) < originTTL {
|
||||||
originTTL = int(record.Header().Ttl)
|
originTTL = int(record.Header().Ttl)
|
||||||
}
|
}
|
||||||
@@ -504,18 +500,12 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
duration := uint32(originTTL - nowTTL)
|
duration := uint32(originTTL - nowTTL)
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
for _, record := range recordList {
|
for _, record := range recordList {
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = record.Header().Ttl - duration
|
record.Header().Ttl = record.Header().Ttl - duration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
|
||||||
for _, record := range recordList {
|
for _, record := range recordList {
|
||||||
if record.Header().Rrtype == dns.TypeOPT {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
record.Header().Ttl = uint32(nowTTL)
|
record.Header().Ttl = uint32(nowTTL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,7 +515,25 @@ func (c *Client) loadResponse(question dns.Question, transport adapter.DNSTransp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||||
return adapter.DNSResponseAddresses(response)
|
if response == nil || response.Rcode != dns.RcodeSuccess {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
addresses := make([]netip.Addr, 0, len(response.Answer))
|
||||||
|
for _, rawAnswer := range response.Answer {
|
||||||
|
switch answer := rawAnswer.(type) {
|
||||||
|
case *dns.A:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.A))
|
||||||
|
case *dns.AAAA:
|
||||||
|
addresses = append(addresses, M.AddrFromIP(answer.AAAA))
|
||||||
|
case *dns.HTTPS:
|
||||||
|
for _, value := range answer.SVCB.Value {
|
||||||
|
if value.Key() == dns.SVCB_IPV4HINT || value.Key() == dns.SVCB_IPV6HINT {
|
||||||
|
addresses = append(addresses, common.Map(strings.Split(value.String(), ","), M.ParseAddr)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapError(err error) error {
|
func wrapError(err error) error {
|
||||||
|
|||||||
@@ -5,11 +5,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RcodeSuccess RcodeError = mDNS.RcodeSuccess
|
RcodeSuccess RcodeError = mDNS.RcodeSuccess
|
||||||
RcodeServerFailure RcodeError = mDNS.RcodeServerFailure
|
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
||||||
RcodeFormatError RcodeError = mDNS.RcodeFormatError
|
RcodeNameError RcodeError = mDNS.RcodeNameError
|
||||||
RcodeNameError RcodeError = mDNS.RcodeNameError
|
RcodeRefused RcodeError = mDNS.RcodeRefused
|
||||||
RcodeRefused RcodeError = mDNS.RcodeRefused
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RcodeError int
|
type RcodeError int
|
||||||
|
|||||||
@@ -1,111 +0,0 @@
|
|||||||
package dns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReproLookupWithRulesUsesRequestStrategy(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
defaultTransport := &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP}
|
|
||||||
var qTypes []uint16
|
|
||||||
router := newTestRouter(t, nil, &fakeDNSTransportManager{
|
|
||||||
defaultTransport: defaultTransport,
|
|
||||||
transports: map[string]adapter.DNSTransport{
|
|
||||||
"default": defaultTransport,
|
|
||||||
},
|
|
||||||
}, &fakeDNSClient{
|
|
||||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
qTypes = append(qTypes, message.Question[0].Qtype)
|
|
||||||
if message.Question[0].Qtype == mDNS.TypeA {
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2.2.2.2")}, 60), nil
|
|
||||||
}
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("2001:db8::1")}, 60), nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
addresses, err := router.Lookup(context.Background(), "example.com", adapter.DNSQueryOptions{
|
|
||||||
Strategy: C.DomainStrategyIPv4Only,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []uint16{mDNS.TypeA}, qTypes)
|
|
||||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("2.2.2.2")}, addresses)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReproLogicalMatchResponseIPCIDR(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
transportManager := &fakeDNSTransportManager{
|
|
||||||
defaultTransport: &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
|
|
||||||
transports: map[string]adapter.DNSTransport{
|
|
||||||
"upstream": &fakeDNSTransport{tag: "upstream", transportType: C.DNSTypeUDP},
|
|
||||||
"selected": &fakeDNSTransport{tag: "selected", transportType: C.DNSTypeUDP},
|
|
||||||
"default": &fakeDNSTransport{tag: "default", transportType: C.DNSTypeUDP},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
client := &fakeDNSClient{
|
|
||||||
exchange: func(transport adapter.DNSTransport, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
switch transport.Tag() {
|
|
||||||
case "upstream":
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("1.1.1.1")}, 60), nil
|
|
||||||
case "selected":
|
|
||||||
return FixedResponse(0, message.Question[0], []netip.Addr{netip.MustParseAddr("8.8.8.8")}, 60), nil
|
|
||||||
default:
|
|
||||||
return nil, E.New("unexpected transport")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
rules := []option.DNSRule{
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultDNSRule{
|
|
||||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
|
||||||
Domain: badoption.Listable[string]{"example.com"},
|
|
||||||
},
|
|
||||||
DNSRuleAction: option.DNSRuleAction{
|
|
||||||
Action: C.RuleActionTypeEvaluate,
|
|
||||||
RouteOptions: option.DNSRouteActionOptions{Server: "upstream"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: C.RuleTypeLogical,
|
|
||||||
LogicalOptions: option.LogicalDNSRule{
|
|
||||||
RawLogicalDNSRule: option.RawLogicalDNSRule{
|
|
||||||
Mode: C.LogicalTypeOr,
|
|
||||||
Rules: []option.DNSRule{{
|
|
||||||
Type: C.RuleTypeDefault,
|
|
||||||
DefaultOptions: option.DefaultDNSRule{
|
|
||||||
RawDefaultDNSRule: option.RawDefaultDNSRule{
|
|
||||||
MatchResponse: true,
|
|
||||||
IPCIDR: badoption.Listable[string]{"1.1.1.0/24"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
DNSRuleAction: option.DNSRuleAction{
|
|
||||||
Action: C.RuleActionTypeRoute,
|
|
||||||
RouteOptions: option.DNSRouteActionOptions{Server: "selected"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
router := newTestRouter(t, rules, transportManager, client)
|
|
||||||
|
|
||||||
response, err := router.Exchange(context.Background(), &mDNS.Msg{
|
|
||||||
Question: []mDNS.Question{fixedQuestion("example.com", mDNS.TypeA)},
|
|
||||||
}, adapter.DNSQueryOptions{})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, []netip.Addr{netip.MustParseAddr("8.8.8.8")}, MessageToAddresses(response))
|
|
||||||
}
|
|
||||||
791
dns/router.go
791
dns/router.go
@@ -5,13 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
R "github.com/sagernet/sing-box/route/rule"
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
@@ -21,7 +19,6 @@ import (
|
|||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/task"
|
|
||||||
"github.com/sagernet/sing/contrab/freelru"
|
"github.com/sagernet/sing/contrab/freelru"
|
||||||
"github.com/sagernet/sing/contrab/maphash"
|
"github.com/sagernet/sing/contrab/maphash"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -29,10 +26,7 @@ import (
|
|||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var _ adapter.DNSRouter = (*Router)(nil)
|
||||||
_ adapter.DNSRouter = (*Router)(nil)
|
|
||||||
_ adapter.DNSRuleSetUpdateValidator = (*Router)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -40,15 +34,10 @@ type Router struct {
|
|||||||
transport adapter.DNSTransportManager
|
transport adapter.DNSTransportManager
|
||||||
outbound adapter.OutboundManager
|
outbound adapter.OutboundManager
|
||||||
client adapter.DNSClient
|
client adapter.DNSClient
|
||||||
rawRules []option.DNSRule
|
|
||||||
rules []adapter.DNSRule
|
rules []adapter.DNSRule
|
||||||
defaultDomainStrategy C.DomainStrategy
|
defaultDomainStrategy C.DomainStrategy
|
||||||
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
dnsReverseMapping freelru.Cache[netip.Addr, string]
|
||||||
platformInterface adapter.PlatformInterface
|
platformInterface adapter.PlatformInterface
|
||||||
legacyDNSMode bool
|
|
||||||
rulesAccess sync.RWMutex
|
|
||||||
started bool
|
|
||||||
closing bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOptions) *Router {
|
||||||
@@ -57,7 +46,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
logger: logFactory.NewLogger("dns"),
|
logger: logFactory.NewLogger("dns"),
|
||||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
rawRules: make([]option.DNSRule, 0, len(options.Rules)),
|
|
||||||
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
rules: make([]adapter.DNSRule, 0, len(options.Rules)),
|
||||||
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
defaultDomainStrategy: C.DomainStrategy(options.Strategy),
|
||||||
}
|
}
|
||||||
@@ -86,12 +74,13 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Initialize(rules []option.DNSRule) error {
|
func (r *Router) Initialize(rules []option.DNSRule) error {
|
||||||
r.rawRules = append(r.rawRules[:0], rules...)
|
for i, ruleOptions := range rules {
|
||||||
newRules, _, _, err := r.buildRules(false)
|
dnsRule, err := R.NewDNSRule(r.ctx, r.logger, ruleOptions, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return E.Cause(err, "parse dns rule[", i, "]")
|
||||||
|
}
|
||||||
|
r.rules = append(r.rules, dnsRule)
|
||||||
}
|
}
|
||||||
closeRules(newRules)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,146 +92,32 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
r.client.Start()
|
r.client.Start()
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
|
|
||||||
monitor.Start("initialize DNS rules")
|
for i, rule := range r.rules {
|
||||||
newRules, legacyDNSMode, modeFlags, err := r.buildRules(true)
|
monitor.Start("initialize DNS rule[", i, "]")
|
||||||
monitor.Finish()
|
err := rule.Start()
|
||||||
if err != nil {
|
monitor.Finish()
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return E.Cause(err, "initialize DNS rule[", i, "]")
|
||||||
r.rulesAccess.Lock()
|
}
|
||||||
if r.closing {
|
|
||||||
r.rulesAccess.Unlock()
|
|
||||||
closeRules(newRules)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
r.rules = newRules
|
|
||||||
r.legacyDNSMode = legacyDNSMode
|
|
||||||
r.started = true
|
|
||||||
r.rulesAccess.Unlock()
|
|
||||||
if legacyDNSMode && common.Any(newRules, func(rule adapter.DNSRule) bool { return rule.WithAddressLimit() }) {
|
|
||||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSAddressFilter)
|
|
||||||
}
|
|
||||||
if legacyDNSMode && modeFlags.neededFromStrategy {
|
|
||||||
deprecated.Report(r.ctx, deprecated.OptionLegacyDNSRuleStrategy)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Close() error {
|
func (r *Router) Close() error {
|
||||||
r.rulesAccess.Lock()
|
monitor := taskmonitor.New(r.logger, C.StopTimeout)
|
||||||
if r.closing {
|
var err error
|
||||||
r.rulesAccess.Unlock()
|
for i, rule := range r.rules {
|
||||||
return nil
|
monitor.Start("close dns rule[", i, "]")
|
||||||
|
err = E.Append(err, rule.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close dns rule[", i, "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
}
|
}
|
||||||
r.closing = true
|
return err
|
||||||
runtimeRules := r.rules
|
|
||||||
r.rules = nil
|
|
||||||
r.rulesAccess.Unlock()
|
|
||||||
closeRules(runtimeRules)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) buildRules(startRules bool) ([]adapter.DNSRule, bool, dnsRuleModeFlags, error) {
|
func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) {
|
||||||
for i, ruleOptions := range r.rawRules {
|
|
||||||
err := R.ValidateNoNestedDNSRuleActions(ruleOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, dnsRuleModeFlags{}, E.Cause(err, "parse dns rule[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
router := service.FromContext[adapter.Router](r.ctx)
|
|
||||||
legacyDNSMode, modeFlags, err := resolveLegacyDNSMode(router, r.rawRules, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, dnsRuleModeFlags{}, err
|
|
||||||
}
|
|
||||||
if !legacyDNSMode {
|
|
||||||
err = validateLegacyDNSModeDisabledRules(r.rawRules)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, dnsRuleModeFlags{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = validateEvaluateFakeIPRules(r.rawRules, r.transport)
|
|
||||||
if err != nil {
|
|
||||||
return nil, false, dnsRuleModeFlags{}, err
|
|
||||||
}
|
|
||||||
newRules := make([]adapter.DNSRule, 0, len(r.rawRules))
|
|
||||||
for i, ruleOptions := range r.rawRules {
|
|
||||||
var dnsRule adapter.DNSRule
|
|
||||||
dnsRule, err = R.NewDNSRule(r.ctx, r.logger, ruleOptions, true, legacyDNSMode)
|
|
||||||
if err != nil {
|
|
||||||
closeRules(newRules)
|
|
||||||
return nil, false, dnsRuleModeFlags{}, 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, dnsRuleModeFlags{}, E.Cause(err, "initialize DNS rule[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newRules, legacyDNSMode, modeFlags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeRules(rules []adapter.DNSRule) {
|
|
||||||
for _, rule := range rules {
|
|
||||||
_ = rule.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) ValidateRuleSetMetadataUpdate(tag string, metadata adapter.RuleSetMetadata) error {
|
|
||||||
if len(r.rawRules) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
router := service.FromContext[adapter.Router](r.ctx)
|
|
||||||
if router == nil {
|
|
||||||
return E.New("router service not found")
|
|
||||||
}
|
|
||||||
overrides := map[string]adapter.RuleSetMetadata{
|
|
||||||
tag: metadata,
|
|
||||||
}
|
|
||||||
r.rulesAccess.RLock()
|
|
||||||
started := r.started
|
|
||||||
legacyDNSMode := r.legacyDNSMode
|
|
||||||
closing := r.closing
|
|
||||||
r.rulesAccess.RUnlock()
|
|
||||||
if closing {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !started {
|
|
||||||
candidateLegacyDNSMode, _, err := resolveLegacyDNSMode(router, r.rawRules, overrides)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !candidateLegacyDNSMode {
|
|
||||||
return validateLegacyDNSModeDisabledRules(r.rawRules)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
candidateLegacyDNSMode, flags, err := resolveLegacyDNSMode(router, r.rawRules, overrides)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if legacyDNSMode {
|
|
||||||
if !candidateLegacyDNSMode && flags.disabled {
|
|
||||||
err := validateLegacyDNSModeDisabledRules(r.rawRules)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if candidateLegacyDNSMode {
|
|
||||||
return E.New(deprecated.OptionLegacyDNSAddressFilter.MessageWithLink())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFakeIP bool, ruleIndex int, isAddressQuery bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, adapter.DNSRule, int) {
|
|
||||||
metadata := adapter.ContextFrom(ctx)
|
metadata := adapter.ContextFrom(ctx)
|
||||||
if metadata == nil {
|
if metadata == nil {
|
||||||
panic("no context")
|
panic("no context")
|
||||||
@@ -251,18 +126,22 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
|||||||
if ruleIndex != -1 {
|
if ruleIndex != -1 {
|
||||||
currentRuleIndex = ruleIndex + 1
|
currentRuleIndex = ruleIndex + 1
|
||||||
}
|
}
|
||||||
for ; currentRuleIndex < len(rules); currentRuleIndex++ {
|
for ; currentRuleIndex < len(r.rules); currentRuleIndex++ {
|
||||||
currentRule := rules[currentRuleIndex]
|
currentRule := r.rules[currentRuleIndex]
|
||||||
if currentRule.WithAddressLimit() && !isAddressQuery {
|
if currentRule.WithAddressLimit() && !isAddressQuery {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
metadata.ResetRuleCache()
|
metadata.ResetRuleCache()
|
||||||
metadata.DestinationAddressMatchFromResponse = false
|
if currentRule.Match(metadata) {
|
||||||
if currentRule.LegacyPreMatch(metadata) {
|
displayRuleIndex := currentRuleIndex
|
||||||
if ruleDescription := currentRule.String(); ruleDescription != "" {
|
if displayRuleIndex != -1 {
|
||||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
displayRuleIndex += displayRuleIndex + 1
|
||||||
|
}
|
||||||
|
ruleDescription := currentRule.String()
|
||||||
|
if ruleDescription != "" {
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||||
} else {
|
} else {
|
||||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
}
|
}
|
||||||
switch action := currentRule.Action().(type) {
|
switch action := currentRule.Action().(type) {
|
||||||
case *R.RuleActionDNSRoute:
|
case *R.RuleActionDNSRoute:
|
||||||
@@ -287,6 +166,14 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
|||||||
if action.ClientSubnet.IsValid() {
|
if action.ClientSubnet.IsValid() {
|
||||||
options.ClientSubnet = action.ClientSubnet
|
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
|
return transport, currentRule, currentRuleIndex
|
||||||
case *R.RuleActionDNSRouteOptions:
|
case *R.RuleActionDNSRouteOptions:
|
||||||
if action.Strategy != C.DomainStrategyAsIS {
|
if action.Strategy != C.DomainStrategyAsIS {
|
||||||
@@ -309,272 +196,17 @@ func (r *Router) matchDNS(ctx context.Context, rules []adapter.DNSRule, allowFak
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
transport := r.transport.Default()
|
transport := r.transport.Default()
|
||||||
|
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, nil, -1
|
return transport, nil, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) applyDNSRouteOptions(options *adapter.DNSQueryOptions, routeOptions R.RuleActionDNSRouteOptions) {
|
|
||||||
// Strategy is intentionally skipped here. A non-default DNS rule action strategy
|
|
||||||
// forces legacy mode via resolveLegacyDNSMode, so this path is only reachable
|
|
||||||
// when strategy remains at its default value.
|
|
||||||
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(server string, routeOptions R.RuleActionDNSRouteOptions, allowFakeIP bool, options *adapter.DNSQueryOptions) (adapter.DNSTransport, dnsRouteStatus) {
|
|
||||||
transport, loaded := r.transport.Transport(server)
|
|
||||||
if !loaded {
|
|
||||||
return nil, dnsRouteStatusMissing
|
|
||||||
}
|
|
||||||
isFakeIP := transport.Type() == C.DNSTypeFakeIP
|
|
||||||
if isFakeIP && !allowFakeIP {
|
|
||||||
return transport, dnsRouteStatusSkipped
|
|
||||||
}
|
|
||||||
r.applyDNSRouteOptions(options, routeOptions)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
const dnsRespondMissingResponseMessage = "respond action requires an evaluated response from a preceding evaluate action"
|
|
||||||
|
|
||||||
func (r *Router) exchangeWithRules(ctx context.Context, rules []adapter.DNSRule, message *mDNS.Msg, options adapter.DNSQueryOptions, allowFakeIP bool) exchangeWithRulesResult {
|
|
||||||
metadata := adapter.ContextFrom(ctx)
|
|
||||||
if metadata == nil {
|
|
||||||
panic("no context")
|
|
||||||
}
|
|
||||||
effectiveOptions := options
|
|
||||||
var evaluatedResponse *mDNS.Msg
|
|
||||||
var evaluatedTransport adapter.DNSTransport
|
|
||||||
for currentRuleIndex, currentRule := range rules {
|
|
||||||
metadata.ResetRuleCache()
|
|
||||||
metadata.DNSResponse = evaluatedResponse
|
|
||||||
metadata.DestinationAddressMatchFromResponse = false
|
|
||||||
if !currentRule.Match(metadata) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.logRuleMatch(ctx, currentRuleIndex, currentRule)
|
|
||||||
switch action := currentRule.Action().(type) {
|
|
||||||
case *R.RuleActionDNSRouteOptions:
|
|
||||||
r.applyDNSRouteOptions(&effectiveOptions, *action)
|
|
||||||
case *R.RuleActionEvaluate:
|
|
||||||
queryOptions := effectiveOptions
|
|
||||||
transport, loaded := r.transport.Transport(action.Server)
|
|
||||||
if !loaded {
|
|
||||||
r.logger.ErrorContext(ctx, "transport not found: ", action.Server)
|
|
||||||
evaluatedResponse = nil
|
|
||||||
evaluatedTransport = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.applyDNSRouteOptions(&queryOptions, action.RuleActionDNSRouteOptions)
|
|
||||||
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())))
|
|
||||||
evaluatedResponse = nil
|
|
||||||
evaluatedTransport = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
evaluatedResponse = response
|
|
||||||
evaluatedTransport = transport
|
|
||||||
case *R.RuleActionRespond:
|
|
||||||
if evaluatedResponse == nil {
|
|
||||||
return exchangeWithRulesResult{
|
|
||||||
err: E.New(dnsRespondMissingResponseMessage),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return exchangeWithRulesResult{
|
|
||||||
response: evaluatedResponse,
|
|
||||||
transport: evaluatedTransport,
|
|
||||||
}
|
|
||||||
case *R.RuleActionDNSRoute:
|
|
||||||
queryOptions := effectiveOptions
|
|
||||||
transport, status := r.resolveDNSRoute(action.Server, action.RuleActionDNSRouteOptions, 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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
transport := r.transport.Default()
|
|
||||||
exchangeOptions := effectiveOptions
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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, rules []adapter.DNSRule, 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 {
|
|
||||||
return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions)
|
|
||||||
}
|
|
||||||
if strategy == C.DomainStrategyIPv6Only {
|
|
||||||
return r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
response4 []netip.Addr
|
|
||||||
response6 []netip.Addr
|
|
||||||
)
|
|
||||||
var group task.Group
|
|
||||||
group.Append("exchange4", func(ctx context.Context) error {
|
|
||||||
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeA, lookupOptions)
|
|
||||||
response4 = result
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
group.Append("exchange6", func(ctx context.Context) error {
|
|
||||||
result, err := r.lookupWithRulesType(ctx, rules, domain, mDNS.TypeAAAA, lookupOptions)
|
|
||||||
response6 = result
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
err := group.Run(ctx)
|
|
||||||
if len(response4) == 0 && len(response6) == 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return sortAddresses(response4, response6, strategy), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) lookupWithRulesType(ctx context.Context, rules []adapter.DNSRule, domain string, qType uint16, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
|
||||||
request := &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
RecursionDesired: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{{
|
|
||||||
Name: mDNS.Fqdn(domain),
|
|
||||||
Qtype: qType,
|
|
||||||
Qclass: mDNS.ClassINET,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
exchangeResult := r.exchangeWithRules(withLookupQueryMetadata(ctx, qType), rules, request, options, false)
|
|
||||||
if exchangeResult.rejectAction != nil {
|
|
||||||
return nil, exchangeResult.rejectAction.Error(ctx)
|
|
||||||
}
|
|
||||||
if exchangeResult.err != nil {
|
|
||||||
return nil, exchangeResult.err
|
|
||||||
}
|
|
||||||
if exchangeResult.response.Rcode != mDNS.RcodeSuccess {
|
|
||||||
return nil, RcodeError(exchangeResult.response.Rcode)
|
|
||||||
}
|
|
||||||
return filterAddressesByQueryType(MessageToAddresses(exchangeResult.response), qType), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
|
func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapter.DNSQueryOptions) (*mDNS.Msg, error) {
|
||||||
if len(message.Question) != 1 {
|
if len(message.Question) != 1 {
|
||||||
r.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
r.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||||
@@ -588,14 +220,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
}
|
}
|
||||||
return &responseMessage, nil
|
return &responseMessage, nil
|
||||||
}
|
}
|
||||||
r.rulesAccess.RLock()
|
|
||||||
if r.closing {
|
|
||||||
r.rulesAccess.RUnlock()
|
|
||||||
return nil, E.New("dns router closed")
|
|
||||||
}
|
|
||||||
rules := r.rules
|
|
||||||
legacyDNSMode := r.legacyDNSMode
|
|
||||||
r.rulesAccess.RUnlock()
|
|
||||||
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
r.logger.DebugContext(ctx, "exchange ", FormatQuestion(message.Question[0].String()))
|
||||||
var (
|
var (
|
||||||
response *mDNS.Msg
|
response *mDNS.Msg
|
||||||
@@ -606,8 +230,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
ctx, metadata = adapter.ExtendContext(ctx)
|
ctx, metadata = adapter.ExtendContext(ctx)
|
||||||
metadata.Destination = M.Socksaddr{}
|
metadata.Destination = M.Socksaddr{}
|
||||||
metadata.QueryType = message.Question[0].Qtype
|
metadata.QueryType = message.Question[0].Qtype
|
||||||
metadata.DNSResponse = nil
|
|
||||||
metadata.DestinationAddressMatchFromResponse = false
|
|
||||||
switch metadata.QueryType {
|
switch metadata.QueryType {
|
||||||
case mDNS.TypeA:
|
case mDNS.TypeA:
|
||||||
metadata.IPVersion = 4
|
metadata.IPVersion = 4
|
||||||
@@ -617,13 +239,18 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
metadata.Domain = FqdnToDomain(message.Question[0].Name)
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
transport = options.Transport
|
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 {
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
options.Strategy = r.defaultDomainStrategy
|
options.Strategy = r.defaultDomainStrategy
|
||||||
}
|
}
|
||||||
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
response, err = r.client.Exchange(ctx, transport, message, options, nil)
|
||||||
} else if !legacyDNSMode {
|
|
||||||
exchangeResult := r.exchangeWithRules(ctx, rules, message, options, true)
|
|
||||||
response, transport, err = exchangeResult.response, exchangeResult.transport, exchangeResult.err
|
|
||||||
} else {
|
} else {
|
||||||
var (
|
var (
|
||||||
rule adapter.DNSRule
|
rule adapter.DNSRule
|
||||||
@@ -633,7 +260,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
for {
|
for {
|
||||||
dnsCtx := adapter.OverrideContext(ctx)
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
dnsOptions := options
|
dnsOptions := options
|
||||||
transport, rule, ruleIndex = r.matchDNS(ctx, rules, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
switch action := rule.Action().(type) {
|
switch action := rule.Action().(type) {
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
@@ -651,9 +278,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
return nil, tun.ErrDrop
|
return nil, tun.ErrDrop
|
||||||
}
|
}
|
||||||
case *R.RuleActionPredefined:
|
case *R.RuleActionPredefined:
|
||||||
err = nil
|
return action.Response(message), nil
|
||||||
response = action.Response(message)
|
|
||||||
goto done
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responseCheck := addressLimitResponseCheck(rule, metadata)
|
responseCheck := addressLimitResponseCheck(rule, metadata)
|
||||||
@@ -681,7 +306,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done:
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -701,14 +325,6 @@ done:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQueryOptions) ([]netip.Addr, error) {
|
||||||
r.rulesAccess.RLock()
|
|
||||||
if r.closing {
|
|
||||||
r.rulesAccess.RUnlock()
|
|
||||||
return nil, E.New("dns router closed")
|
|
||||||
}
|
|
||||||
rules := r.rules
|
|
||||||
legacyDNSMode := r.legacyDNSMode
|
|
||||||
r.rulesAccess.RUnlock()
|
|
||||||
var (
|
var (
|
||||||
responseAddrs []netip.Addr
|
responseAddrs []netip.Addr
|
||||||
err error
|
err error
|
||||||
@@ -722,8 +338,6 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
r.logger.DebugContext(ctx, "response rejected for ", domain, " (cached)")
|
||||||
} else if errors.Is(err, ErrResponseRejected) {
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
r.logger.DebugContext(ctx, "response rejected for ", domain)
|
r.logger.DebugContext(ctx, "response rejected for ", domain)
|
||||||
} else if R.IsRejected(err) {
|
|
||||||
r.logger.DebugContext(ctx, "lookup rejected for ", domain)
|
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
r.logger.ErrorContext(ctx, E.Cause(err, "lookup failed for ", domain))
|
||||||
}
|
}
|
||||||
@@ -736,16 +350,20 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
metadata.Destination = M.Socksaddr{}
|
metadata.Destination = M.Socksaddr{}
|
||||||
metadata.Domain = FqdnToDomain(domain)
|
metadata.Domain = FqdnToDomain(domain)
|
||||||
metadata.DNSResponse = nil
|
|
||||||
metadata.DestinationAddressMatchFromResponse = false
|
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
transport := options.Transport
|
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 {
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
options.Strategy = r.defaultDomainStrategy
|
options.Strategy = r.defaultDomainStrategy
|
||||||
}
|
}
|
||||||
responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil)
|
responseAddrs, err = r.client.Lookup(ctx, transport, domain, options, nil)
|
||||||
} else if !legacyDNSMode {
|
|
||||||
responseAddrs, err = r.lookupWithRules(ctx, rules, domain, options)
|
|
||||||
} else {
|
} else {
|
||||||
var (
|
var (
|
||||||
transport adapter.DNSTransport
|
transport adapter.DNSTransport
|
||||||
@@ -756,7 +374,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
for {
|
for {
|
||||||
dnsCtx := adapter.OverrideContext(ctx)
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
dnsOptions := options
|
dnsOptions := options
|
||||||
transport, rule, ruleIndex = r.matchDNS(ctx, rules, false, ruleIndex, true, &dnsOptions)
|
transport, rule, ruleIndex = r.matchDNS(ctx, false, ruleIndex, true, &dnsOptions)
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
switch action := rule.Action().(type) {
|
switch action := rule.Action().(type) {
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
@@ -807,14 +425,15 @@ func isAddressQuery(message *mDNS.Msg) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(response *mDNS.Msg) bool {
|
func addressLimitResponseCheck(rule adapter.DNSRule, metadata *adapter.InboundContext) func(responseAddrs []netip.Addr) bool {
|
||||||
if rule == nil || !rule.WithAddressLimit() {
|
if rule == nil || !rule.WithAddressLimit() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
responseMetadata := *metadata
|
responseMetadata := *metadata
|
||||||
return func(response *mDNS.Msg) bool {
|
return func(responseAddrs []netip.Addr) bool {
|
||||||
checkMetadata := responseMetadata
|
checkMetadata := responseMetadata
|
||||||
return rule.MatchAddressLimit(&checkMetadata, response)
|
checkMetadata.DestinationAddresses = responseAddrs
|
||||||
|
return rule.MatchAddressLimit(&checkMetadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,265 +458,3 @@ func (r *Router) ResetNetwork() {
|
|||||||
transport.Reset()
|
transport.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule option.DefaultDNSRule) bool {
|
|
||||||
if rule.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return !rule.MatchResponse && (rule.IPAcceptAny || 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.Action == C.RuleActionTypeRespond ||
|
|
||||||
rule.IPVersion > 0 ||
|
|
||||||
len(rule.QueryType) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type dnsRuleModeFlags struct {
|
|
||||||
disabled bool
|
|
||||||
needed bool
|
|
||||||
neededFromStrategy bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *dnsRuleModeFlags) merge(other dnsRuleModeFlags) {
|
|
||||||
f.disabled = f.disabled || other.disabled
|
|
||||||
f.needed = f.needed || other.needed
|
|
||||||
f.neededFromStrategy = f.neededFromStrategy || other.neededFromStrategy
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveLegacyDNSMode(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (bool, dnsRuleModeFlags, error) {
|
|
||||||
flags, err := dnsRuleModeRequirements(router, rules, metadataOverrides)
|
|
||||||
if err != nil {
|
|
||||||
return false, flags, err
|
|
||||||
}
|
|
||||||
if flags.disabled && flags.neededFromStrategy {
|
|
||||||
return false, flags, E.New(deprecated.OptionLegacyDNSRuleStrategy.MessageWithLink())
|
|
||||||
}
|
|
||||||
if flags.disabled {
|
|
||||||
return false, flags, nil
|
|
||||||
}
|
|
||||||
return flags.needed, flags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsRuleModeRequirements(router adapter.Router, rules []option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
|
||||||
var flags dnsRuleModeFlags
|
|
||||||
for i, rule := range rules {
|
|
||||||
ruleFlags, err := dnsRuleModeRequirementsInRule(router, rule, metadataOverrides)
|
|
||||||
if err != nil {
|
|
||||||
return dnsRuleModeFlags{}, E.Cause(err, "dns rule[", i, "]")
|
|
||||||
}
|
|
||||||
flags.merge(ruleFlags)
|
|
||||||
}
|
|
||||||
return flags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsRuleModeRequirementsInRule(router adapter.Router, rule option.DNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
|
||||||
switch rule.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
return dnsRuleModeRequirementsInDefaultRule(router, rule.DefaultOptions, metadataOverrides)
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
flags := dnsRuleModeFlags{
|
|
||||||
disabled: dnsRuleActionType(rule) == C.RuleActionTypeEvaluate || dnsRuleActionType(rule) == C.RuleActionTypeRespond,
|
|
||||||
neededFromStrategy: dnsRuleActionHasStrategy(rule.LogicalOptions.DNSRuleAction),
|
|
||||||
}
|
|
||||||
flags.needed = flags.neededFromStrategy
|
|
||||||
for i, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
subFlags, err := dnsRuleModeRequirementsInRule(router, subRule, metadataOverrides)
|
|
||||||
if err != nil {
|
|
||||||
return dnsRuleModeFlags{}, E.Cause(err, "sub rule[", i, "]")
|
|
||||||
}
|
|
||||||
flags.merge(subFlags)
|
|
||||||
}
|
|
||||||
return flags, nil
|
|
||||||
default:
|
|
||||||
return dnsRuleModeFlags{}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsRuleModeRequirementsInDefaultRule(router adapter.Router, rule option.DefaultDNSRule, metadataOverrides map[string]adapter.RuleSetMetadata) (dnsRuleModeFlags, error) {
|
|
||||||
flags := dnsRuleModeFlags{
|
|
||||||
disabled: defaultRuleDisablesLegacyDNSMode(rule),
|
|
||||||
neededFromStrategy: dnsRuleActionHasStrategy(rule.DNSRuleAction),
|
|
||||||
}
|
|
||||||
flags.needed = defaultRuleNeedsLegacyDNSModeFromAddressFilter(rule) || flags.neededFromStrategy
|
|
||||||
if len(rule.RuleSet) == 0 {
|
|
||||||
return flags, nil
|
|
||||||
}
|
|
||||||
if router == nil {
|
|
||||||
return dnsRuleModeFlags{}, E.New("router service not found")
|
|
||||||
}
|
|
||||||
for _, tag := range rule.RuleSet {
|
|
||||||
metadata, err := lookupDNSRuleSetMetadata(router, tag, metadataOverrides)
|
|
||||||
if err != nil {
|
|
||||||
return dnsRuleModeFlags{}, err
|
|
||||||
}
|
|
||||||
// ip_version is not a headless-rule item, so ContainsIPVersionRule is intentionally absent.
|
|
||||||
flags.disabled = flags.disabled || metadata.ContainsDNSQueryTypeRule
|
|
||||||
if !rule.RuleSetIPCIDRMatchSource && metadata.ContainsIPCIDRRule {
|
|
||||||
flags.needed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupDNSRuleSetMetadata(router adapter.Router, tag string, metadataOverrides map[string]adapter.RuleSetMetadata) (adapter.RuleSetMetadata, error) {
|
|
||||||
if metadataOverrides != nil {
|
|
||||||
if metadata, loaded := metadataOverrides[tag]; loaded {
|
|
||||||
return metadata, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ruleSet, loaded := router.RuleSet(tag)
|
|
||||||
if !loaded {
|
|
||||||
return adapter.RuleSetMetadata{}, E.New("rule-set not found: ", tag)
|
|
||||||
}
|
|
||||||
return ruleSet.Metadata(), 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 {
|
|
||||||
var seenEvaluate bool
|
|
||||||
for i, rule := range rules {
|
|
||||||
requiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(rule)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "validate dns rule[", i, "]")
|
|
||||||
}
|
|
||||||
if requiresPriorEvaluate && !seenEvaluate {
|
|
||||||
return E.New("dns rule[", i, "]: response-based matching requires a preceding evaluate action")
|
|
||||||
}
|
|
||||||
if dnsRuleActionType(rule) == C.RuleActionTypeEvaluate {
|
|
||||||
seenEvaluate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateEvaluateFakeIPRules(rules []option.DNSRule, transportManager adapter.DNSTransportManager) error {
|
|
||||||
if transportManager == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i, rule := range rules {
|
|
||||||
if dnsRuleActionType(rule) != C.RuleActionTypeEvaluate {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
server := dnsRuleActionServer(rule)
|
|
||||||
if server == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
transport, loaded := transportManager.Transport(server)
|
|
||||||
if !loaded || transport.Type() != C.DNSTypeFakeIP {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return E.New("dns rule[", i, "]: evaluate action cannot use fakeip server: ", server)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateLegacyDNSModeDisabledRuleTree(rule option.DNSRule) (bool, error) {
|
|
||||||
switch rule.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
return validateLegacyDNSModeDisabledDefaultRule(rule.DefaultOptions)
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
requiresPriorEvaluate := dnsRuleActionType(rule) == C.RuleActionTypeRespond
|
|
||||||
for i, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
subRequiresPriorEvaluate, err := validateLegacyDNSModeDisabledRuleTree(subRule)
|
|
||||||
if err != nil {
|
|
||||||
return false, E.Cause(err, "sub rule[", i, "]")
|
|
||||||
}
|
|
||||||
requiresPriorEvaluate = requiresPriorEvaluate || subRequiresPriorEvaluate
|
|
||||||
}
|
|
||||||
return requiresPriorEvaluate, nil
|
|
||||||
default:
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateLegacyDNSModeDisabledDefaultRule(rule option.DefaultDNSRule) (bool, error) {
|
|
||||||
hasResponseRecords := hasResponseMatchFields(rule)
|
|
||||||
if (hasResponseRecords || len(rule.IPCIDR) > 0 || rule.IPIsPrivate || rule.IPAcceptAny) && !rule.MatchResponse {
|
|
||||||
return false, E.New("Response Match Fields (ip_cidr, ip_is_private, ip_accept_any, response_rcode, response_answer, response_ns, response_extra) require match_response to be enabled")
|
|
||||||
}
|
|
||||||
// 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.RuleSetIPCIDRAcceptEmpty { //nolint:staticcheck
|
|
||||||
return false, E.New(deprecated.OptionRuleSetIPCIDRAcceptEmpty.MessageWithLink())
|
|
||||||
}
|
|
||||||
return rule.MatchResponse || rule.Action == C.RuleActionTypeRespond, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func dnsRuleActionServer(rule option.DNSRule) string {
|
|
||||||
switch rule.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
return rule.DefaultOptions.RouteOptions.Server
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
return rule.LogicalOptions.RouteOptions.Server
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
2547
dns/router_test.go
2547
dns/router_test.go
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,8 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
@@ -12,6 +14,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -32,8 +35,10 @@ type Transport struct {
|
|||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
hosts *hosts.File
|
hosts *hosts.File
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
|
preferGo bool
|
||||||
fallback bool
|
fallback bool
|
||||||
dhcpTransport dhcpTransport
|
dhcpTransport dhcpTransport
|
||||||
|
resolver net.Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type dhcpTransport interface {
|
type dhcpTransport interface {
|
||||||
@@ -47,12 +52,14 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
transportAdapter := dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options)
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: transportAdapter,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
hosts: hosts.NewFile(hosts.DefaultPath),
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
|
preferGo: options.PreferGo,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,3 +97,44 @@ func (t *Transport) Reset() {
|
|||||||
t.dhcpTransport.Reset()
|
t.dhcpTransport.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
|
question := message.Question[0]
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
||||||
|
if len(addresses) > 0 {
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !t.fallback {
|
||||||
|
return t.exchange(ctx, message, question.Name)
|
||||||
|
}
|
||||||
|
if t.dhcpTransport != nil {
|
||||||
|
dhcpTransports := t.dhcpTransport.Fetch()
|
||||||
|
if len(dhcpTransports) > 0 {
|
||||||
|
return t.dhcpTransport.Exchange0(ctx, message, dhcpTransports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if t.preferGo {
|
||||||
|
// Assuming the user knows what they are doing, we still execute the query which will fail.
|
||||||
|
return t.exchange(ctx, message, question.Name)
|
||||||
|
}
|
||||||
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
|
var network string
|
||||||
|
if question.Qtype == mDNS.TypeA {
|
||||||
|
network = "ip4"
|
||||||
|
} else {
|
||||||
|
network = "ip6"
|
||||||
|
}
|
||||||
|
addresses, err := t.resolver.LookupNetIP(ctx, network, question.Name)
|
||||||
|
if err != nil {
|
||||||
|
var dnsError *net.DNSError
|
||||||
|
if errors.As(err, &dnsError) && dnsError.IsNotFound {
|
||||||
|
return nil, dns.RcodeRefused
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
||||||
|
}
|
||||||
|
return nil, E.New("only A and AAAA queries are supported on Apple platforms when using TUN and DHCP unavailable.")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <resolv.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
|
|
||||||
static void *cgo_res_init() {
|
|
||||||
res_state state = calloc(1, sizeof(struct __res_state));
|
|
||||||
if (state == NULL) return NULL;
|
|
||||||
if (res_ninit(state) != 0) {
|
|
||||||
free(state);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void cgo_res_destroy(void *opaque) {
|
|
||||||
res_state state = (res_state)opaque;
|
|
||||||
res_ndestroy(state);
|
|
||||||
free(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int cgo_res_nsearch(void *opaque, const char *dname, int class, int type,
|
|
||||||
unsigned char *answer, int anslen,
|
|
||||||
int timeout_seconds,
|
|
||||||
int *out_h_errno) {
|
|
||||||
res_state state = (res_state)opaque;
|
|
||||||
state->retrans = timeout_seconds;
|
|
||||||
state->retry = 1;
|
|
||||||
int n = res_nsearch(state, dname, class, type, answer, anslen);
|
|
||||||
if (n < 0) {
|
|
||||||
*out_h_errno = state->res_h_errno;
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
boxC "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func resolvSearch(name string, class, qtype int, timeoutSeconds int) (*mDNS.Msg, error) {
|
|
||||||
state := C.cgo_res_init()
|
|
||||||
if state == nil {
|
|
||||||
return nil, E.New("res_ninit failed")
|
|
||||||
}
|
|
||||||
defer C.cgo_res_destroy(state)
|
|
||||||
|
|
||||||
cName := C.CString(name)
|
|
||||||
defer C.free(unsafe.Pointer(cName))
|
|
||||||
bufSize := 1232
|
|
||||||
for {
|
|
||||||
answer := make([]byte, bufSize)
|
|
||||||
var hErrno C.int
|
|
||||||
n := C.cgo_res_nsearch(state, cName, C.int(class), C.int(qtype),
|
|
||||||
(*C.uchar)(unsafe.Pointer(&answer[0])), C.int(len(answer)),
|
|
||||||
C.int(timeoutSeconds),
|
|
||||||
&hErrno)
|
|
||||||
if n >= 0 {
|
|
||||||
if int(n) > bufSize {
|
|
||||||
bufSize = int(n)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var response mDNS.Msg
|
|
||||||
err := response.Unpack(answer[:int(n)])
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "unpack res_nsearch response")
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
var response mDNS.Msg
|
|
||||||
_ = response.Unpack(answer[:bufSize])
|
|
||||||
if response.Response {
|
|
||||||
if response.Truncated && bufSize < 65535 {
|
|
||||||
bufSize *= 2
|
|
||||||
if bufSize > 65535 {
|
|
||||||
bufSize = 65535
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return &response, nil
|
|
||||||
}
|
|
||||||
switch hErrno {
|
|
||||||
case C.HOST_NOT_FOUND:
|
|
||||||
return nil, dns.RcodeNameError
|
|
||||||
case C.TRY_AGAIN:
|
|
||||||
return nil, dns.RcodeNameError
|
|
||||||
case C.NO_RECOVERY:
|
|
||||||
return nil, dns.RcodeServerFailure
|
|
||||||
case C.NO_DATA:
|
|
||||||
return nil, dns.RcodeSuccess
|
|
||||||
default:
|
|
||||||
return nil, E.New("res_nsearch: unknown error ", int(hErrno), " for ", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
question := message.Question[0]
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
|
||||||
addresses := t.hosts.Lookup(dns.FqdnToDomain(question.Name))
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
return dns.FixedResponse(message.Id, question, addresses, boxC.DefaultDNSTTL), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if t.fallback && t.dhcpTransport != nil {
|
|
||||||
dhcpServers := t.dhcpTransport.Fetch()
|
|
||||||
if len(dhcpServers) > 0 {
|
|
||||||
return t.dhcpTransport.Exchange0(ctx, message, dhcpServers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
name := question.Name
|
|
||||||
timeoutSeconds := int(boxC.DNSTimeout / time.Second)
|
|
||||||
if deadline, hasDeadline := ctx.Deadline(); hasDeadline {
|
|
||||||
remaining := time.Until(deadline)
|
|
||||||
if remaining <= 0 {
|
|
||||||
return nil, context.DeadlineExceeded
|
|
||||||
}
|
|
||||||
seconds := int(remaining.Seconds())
|
|
||||||
if seconds < 1 {
|
|
||||||
seconds = 1
|
|
||||||
}
|
|
||||||
timeoutSeconds = seconds
|
|
||||||
}
|
|
||||||
type resolvResult struct {
|
|
||||||
response *mDNS.Msg
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
resultCh := make(chan resolvResult, 1)
|
|
||||||
go func() {
|
|
||||||
response, err := resolvSearch(name, int(question.Qclass), int(question.Qtype), timeoutSeconds)
|
|
||||||
resultCh <- resolvResult{response, err}
|
|
||||||
}()
|
|
||||||
var result resolvResult
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, ctx.Err()
|
|
||||||
case result = <-resultCh:
|
|
||||||
}
|
|
||||||
if result.err != nil {
|
|
||||||
var rcodeError dns.RcodeError
|
|
||||||
if errors.As(result.err, &rcodeError) {
|
|
||||||
return dns.FixedResponseStatus(message, int(rcodeError)), nil
|
|
||||||
}
|
|
||||||
return nil, result.err
|
|
||||||
}
|
|
||||||
result.response.Id = message.Id
|
|
||||||
return result.response, nil
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build !darwin
|
|
||||||
|
|
||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ adapter.LegacyDNSTransport = (*TransportAdapter)(nil)
|
||||||
|
|
||||||
type TransportAdapter struct {
|
type TransportAdapter struct {
|
||||||
transportType string
|
transportType string
|
||||||
transportTag string
|
transportTag string
|
||||||
dependencies []string
|
dependencies []string
|
||||||
|
strategy C.DomainStrategy
|
||||||
|
clientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
|
func NewTransportAdapter(transportType string, transportTag string, dependencies []string) TransportAdapter {
|
||||||
@@ -27,6 +35,8 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
|||||||
transportType: transportType,
|
transportType: transportType,
|
||||||
transportTag: transportTag,
|
transportTag: transportTag,
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
|
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||||
|
clientSubnet: localOptions.LegacyClientSubnet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,10 +45,15 @@ func NewTransportAdapterWithRemoteOptions(transportType string, transportTag str
|
|||||||
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
||||||
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
||||||
}
|
}
|
||||||
|
if remoteOptions.LegacyAddressResolver != "" {
|
||||||
|
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
|
||||||
|
}
|
||||||
return TransportAdapter{
|
return TransportAdapter{
|
||||||
transportType: transportType,
|
transportType: transportType,
|
||||||
transportTag: transportTag,
|
transportTag: transportTag,
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
|
strategy: C.DomainStrategy(remoteOptions.LegacyStrategy),
|
||||||
|
clientSubnet: remoteOptions.LegacyClientSubnet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,3 +68,11 @@ func (a *TransportAdapter) Tag() string {
|
|||||||
func (a *TransportAdapter) Dependencies() []string {
|
func (a *TransportAdapter) Dependencies() []string {
|
||||||
return a.dependencies
|
return a.dependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
|
||||||
|
return a.strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *TransportAdapter) LegacyClientSubnet() netip.Prefix {
|
||||||
|
return a.clientSubnet
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,25 +2,104 @@ package dns
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"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"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {
|
func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (N.Dialer, error) {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
if options.LegacyDefaultDialer {
|
||||||
Context: ctx,
|
return dialer.NewDefaultOutbound(ctx), nil
|
||||||
Options: options.DialerOptions,
|
} else {
|
||||||
DirectResolver: true,
|
return dialer.NewWithOptions(dialer.Options{
|
||||||
})
|
Context: ctx,
|
||||||
|
Options: options.DialerOptions,
|
||||||
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
if options.LegacyDefaultDialer {
|
||||||
Context: ctx,
|
transportDialer := dialer.NewDefaultOutbound(ctx)
|
||||||
Options: options.DialerOptions,
|
if options.LegacyAddressResolver != "" {
|
||||||
RemoteIsDomain: options.ServerIsDomain(),
|
transport := service.FromContext[adapter.DNSTransportManager](ctx)
|
||||||
DirectResolver: true,
|
resolverTransport, loaded := transport.Transport(options.LegacyAddressResolver)
|
||||||
})
|
if !loaded {
|
||||||
|
return nil, E.New("address resolver not found: ", options.LegacyAddressResolver)
|
||||||
|
}
|
||||||
|
transportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay))
|
||||||
|
} else if options.ServerIsDomain() {
|
||||||
|
return nil, E.New("missing address resolver for server: ", options.Server)
|
||||||
|
}
|
||||||
|
return transportDialer, nil
|
||||||
|
} else {
|
||||||
|
return dialer.NewWithOptions(dialer.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options.DialerOptions,
|
||||||
|
RemoteIsDomain: options.ServerIsDomain(),
|
||||||
|
DirectResolver: true,
|
||||||
|
LegacyDNSDialer: options.Legacy,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type legacyTransportDialer struct {
|
||||||
|
dialer N.Dialer
|
||||||
|
dnsRouter adapter.DNSRouter
|
||||||
|
transport adapter.DNSTransport
|
||||||
|
strategy C.DomainStrategy
|
||||||
|
fallbackDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {
|
||||||
|
return &legacyTransportDialer{
|
||||||
|
dialer,
|
||||||
|
dnsRouter,
|
||||||
|
transport,
|
||||||
|
strategy,
|
||||||
|
fallbackDelay,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if destination.IsIP() {
|
||||||
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
|
}
|
||||||
|
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
|
||||||
|
Transport: d.transport,
|
||||||
|
Strategy: d.strategy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
if destination.IsIP() {
|
||||||
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
|
}
|
||||||
|
addresses, err := d.dnsRouter.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{
|
||||||
|
Transport: d.transport,
|
||||||
|
Strategy: d.strategy,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, _, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *legacyTransportDialer) Upstream() any {
|
||||||
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,109 +2,18 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.14.0-alpha.10
|
|
||||||
|
|
||||||
* Add `evaluate` DNS rule action and Response Match Fields **1**
|
|
||||||
* `ip_version` and `query_type` now also take effect on internal DNS lookups **2**
|
|
||||||
* Add `package_name_regex` route, DNS and headless rule item **3**
|
|
||||||
* Add cloudflared inbound **4**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
Response Match Fields
|
|
||||||
([`response_rcode`](/configuration/dns/rule/#response_rcode),
|
|
||||||
[`response_answer`](/configuration/dns/rule/#response_answer),
|
|
||||||
[`response_ns`](/configuration/dns/rule/#response_ns),
|
|
||||||
and [`response_extra`](/configuration/dns/rule/#response_extra))
|
|
||||||
match the evaluated DNS response. They are gated by the new
|
|
||||||
[`match_response`](/configuration/dns/rule/#match_response) field and
|
|
||||||
populated by a preceding
|
|
||||||
[`evaluate`](/configuration/dns/rule_action/#evaluate) DNS rule action;
|
|
||||||
the evaluated response can also be returned directly by a
|
|
||||||
[`respond`](/configuration/dns/rule_action/#respond) action.
|
|
||||||
|
|
||||||
This deprecates the Legacy Address Filter Fields (`ip_cidr`,
|
|
||||||
`ip_is_private` without `match_response`) in DNS rules, the Legacy
|
|
||||||
`strategy` DNS rule action option, and the Legacy
|
|
||||||
`rule_set_ip_cidr_accept_empty` DNS rule item; all three will be removed
|
|
||||||
in sing-box 1.16.0.
|
|
||||||
See [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
`ip_version` and `query_type` in DNS rules, together with `query_type` in
|
|
||||||
referenced rule-sets, now take effect on every DNS rule evaluation,
|
|
||||||
including matches from internal domain resolutions that do not target a
|
|
||||||
specific DNS server (for example a `resolve` route rule action without
|
|
||||||
`server` set). In earlier versions they were silently ignored in that
|
|
||||||
path. Combining these fields with any of the legacy DNS fields deprecated
|
|
||||||
in **1** in the same DNS configuration is no longer supported and is
|
|
||||||
rejected at startup.
|
|
||||||
See [Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/#package_name_regex),
|
|
||||||
[DNS Rule](/configuration/dns/rule/#package_name_regex) and
|
|
||||||
[Headless Rule](/configuration/rule-set/headless-rule/#package_name_regex).
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
See [Cloudflared](/configuration/inbound/cloudflared/).
|
|
||||||
|
|
||||||
#### 1.13.7
|
|
||||||
|
|
||||||
* Fixes and improvement
|
|
||||||
|
|
||||||
#### 1.13.6
|
#### 1.13.6
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 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
|
#### 1.13.5
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.14.0-alpha.7
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.13.4
|
#### 1.13.4
|
||||||
|
|
||||||
* Fixes and improvements
|
* 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
|
#### 1.13.3
|
||||||
|
|
||||||
* Add OpenWrt and Alpine APK packages to release **1**
|
* Add OpenWrt and Alpine APK packages to release **1**
|
||||||
@@ -129,59 +38,6 @@ from [SagerNet/go](https://github.com/SagerNet/go).
|
|||||||
|
|
||||||
See [OCM](/configuration/service/ocm).
|
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
|
#### 1.13.2
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@@ -803,7 +659,7 @@ DNS servers are refactored for better performance and scalability.
|
|||||||
|
|
||||||
See [DNS server](/configuration/dns/server/).
|
See [DNS server](/configuration/dns/server/).
|
||||||
|
|
||||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats).
|
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||||
|
|
||||||
@@ -1273,7 +1129,7 @@ DNS servers are refactored for better performance and scalability.
|
|||||||
|
|
||||||
See [DNS server](/configuration/dns/server/).
|
See [DNS server](/configuration/dns/server/).
|
||||||
|
|
||||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-server-formats).
|
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||||
|
|
||||||
@@ -2109,7 +1965,7 @@ See [Migration](/migration/#process_path-format-update-on-windows).
|
|||||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||||
if using this method.
|
if using this method.
|
||||||
|
|
||||||
See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields).
|
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||||
|
|
||||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||||
|
|
||||||
@@ -2123,7 +1979,7 @@ the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users
|
|||||||
**5**:
|
**5**:
|
||||||
|
|
||||||
The new feature allows you to cache the check results of
|
The new feature allows you to cache the check results of
|
||||||
[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration.
|
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||||
|
|
||||||
**6**:
|
**6**:
|
||||||
|
|
||||||
@@ -2304,7 +2160,7 @@ See [TUN](/configuration/inbound/tun) inbound.
|
|||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
The new feature allows you to cache the check results of
|
The new feature allows you to cache the check results of
|
||||||
[Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields) until expiration.
|
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||||
|
|
||||||
#### 1.9.0-alpha.7
|
#### 1.9.0-alpha.7
|
||||||
|
|
||||||
@@ -2351,7 +2207,7 @@ See [Migration](/migration/#process_path-format-update-on-windows).
|
|||||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||||
if using this method.
|
if using this method.
|
||||||
|
|
||||||
See [Legacy Address Filter Fields](/configuration/dns/rule#legacy-address-filter-fields).
|
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||||
|
|
||||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
|
|||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
| `process_path_regex` | :material-close: | No permission |
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-check: | / |
|
| `package_name` | :material-check: | / |
|
||||||
| `package_name_regex` | :material-check: | / |
|
|
||||||
| `user` | :material-close: | Use `package_name` instead |
|
| `user` | :material-close: | Use `package_name` instead |
|
||||||
| `user_id` | :material-close: | Use `package_name` instead |
|
| `user_id` | :material-close: | Use `package_name` instead |
|
||||||
| `wifi_ssid` | :material-check: | Fine location permission required |
|
| `wifi_ssid` | :material-check: | Fine location permission required |
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension
|
|||||||
| `process_path` | :material-close: | No permission |
|
| `process_path` | :material-close: | No permission |
|
||||||
| `process_path_regex` | :material-close: | No permission |
|
| `process_path_regex` | :material-close: | No permission |
|
||||||
| `package_name` | :material-close: | / |
|
| `package_name` | :material-close: | / |
|
||||||
| `package_name_regex` | :material-close: | / |
|
|
||||||
| `user` | :material-close: | No permission |
|
| `user` | :material-close: | No permission |
|
||||||
| `user_id` | :material-close: | No permission |
|
| `user_id` | :material-close: | No permission |
|
||||||
| `wifi_ssid` | :material-alert: | Only supported on iOS |
|
| `wifi_ssid` | :material-alert: | Only supported on iOS |
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Removed in sing-box 1.14.0"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
Legacy fake-ip configuration is deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats).
|
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
@@ -26,6 +26,6 @@ Enable FakeIP service.
|
|||||||
|
|
||||||
IPv4 address range for FakeIP.
|
IPv4 address range for FakeIP.
|
||||||
|
|
||||||
#### inet6_range
|
#### inet6_address
|
||||||
|
|
||||||
IPv6 address range for FakeIP.
|
IPv6 address range for FakeIP.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 移除"
|
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||||
|
|
||||||
旧的 fake-ip 配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ icon: material/alert-decagram
|
|||||||
|----------|---------------------------------|
|
|----------|---------------------------------|
|
||||||
| `server` | List of [DNS Server](./server/) |
|
| `server` | List of [DNS Server](./server/) |
|
||||||
| `rules` | List of [DNS Rule](./rule/) |
|
| `rules` | List of [DNS Rule](./rule/) |
|
||||||
| `fakeip` | :material-note-remove: [FakeIP](./fakeip/) |
|
| `fakeip` | [FakeIP](./fakeip/) |
|
||||||
|
|
||||||
#### final
|
#### final
|
||||||
|
|
||||||
@@ -88,4 +88,4 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Can be overridden by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||||
|
|||||||
@@ -88,6 +88,6 @@ LRU 缓存容量。
|
|||||||
|
|
||||||
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
||||||
|
|
||||||
#### fakeip :material-note-remove:
|
#### fakeip
|
||||||
|
|
||||||
[FakeIP](./fakeip/) 设置。
|
[FakeIP](./fakeip/) 设置。
|
||||||
|
|||||||
@@ -2,20 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
|
||||||
:material-plus: [match_response](#match_response)
|
|
||||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
|
||||||
: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: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [ip_version](#ip_version)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -103,6 +89,12 @@ icon: material/alert-decagram
|
|||||||
"192.168.0.1"
|
"192.168.0.1"
|
||||||
],
|
],
|
||||||
"source_ip_is_private": false,
|
"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": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
@@ -132,9 +124,6 @@ icon: material/alert-decagram
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -160,12 +149,6 @@ icon: material/alert-decagram
|
|||||||
"default_interface_address": [
|
"default_interface_address": [
|
||||||
"2000::/3"
|
"2000::/3"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -177,17 +160,7 @@ icon: material/alert-decagram
|
|||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
],
|
],
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"match_response": false,
|
"rule_set_ip_cidr_accept_empty": false,
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_is_private": false,
|
|
||||||
"ip_accept_any": false,
|
|
||||||
"response_rcode": "",
|
|
||||||
"response_answer": [],
|
|
||||||
"response_ns": [],
|
|
||||||
"response_extra": [],
|
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -196,8 +169,7 @@ icon: material/alert-decagram
|
|||||||
"server": "local",
|
"server": "local",
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
|
|
||||||
"rule_set_ip_cidr_accept_empty": false,
|
|
||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"geosite": [
|
"geosite": [
|
||||||
"cn"
|
"cn"
|
||||||
@@ -245,46 +217,12 @@ Tags of [Inbound](/configuration/inbound/).
|
|||||||
|
|
||||||
#### ip_version
|
#### ip_version
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
This field now also applies when a DNS rule is matched from an internal
|
|
||||||
domain resolution that does not target a specific DNS server, such as a
|
|
||||||
[`resolve`](../../route/rule_action/#resolve) route rule action without a
|
|
||||||
`server` set. In earlier versions, only DNS queries received from a
|
|
||||||
client evaluated this field. See
|
|
||||||
[Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules)
|
|
||||||
for the full list.
|
|
||||||
|
|
||||||
Setting this field makes the DNS rule incompatible in the same DNS
|
|
||||||
configuration with Legacy Address Filter Fields in DNS rules, the Legacy
|
|
||||||
`strategy` DNS rule action option, and the Legacy
|
|
||||||
`rule_set_ip_cidr_accept_empty` DNS rule item. To combine with
|
|
||||||
address-based filtering, use the [`evaluate`](../rule_action/#evaluate)
|
|
||||||
action and [`match_response`](#match_response).
|
|
||||||
|
|
||||||
4 (A DNS query) or 6 (AAAA DNS query).
|
4 (A DNS query) or 6 (AAAA DNS query).
|
||||||
|
|
||||||
Not limited if empty.
|
Not limited if empty.
|
||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
This field now also applies when a DNS rule is matched from an internal
|
|
||||||
domain resolution that does not target a specific DNS server, such as a
|
|
||||||
[`resolve`](../../route/rule_action/#resolve) route rule action without a
|
|
||||||
`server` set. In earlier versions, only DNS queries received from a
|
|
||||||
client evaluated this field. See
|
|
||||||
[Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules)
|
|
||||||
for the full list.
|
|
||||||
|
|
||||||
Setting this field makes the DNS rule incompatible in the same DNS
|
|
||||||
configuration with Legacy Address Filter Fields in DNS rules, the Legacy
|
|
||||||
`strategy` DNS rule action option, and the Legacy
|
|
||||||
`rule_set_ip_cidr_accept_empty` DNS rule item. To combine with
|
|
||||||
address-based filtering, use the [`evaluate`](../rule_action/#evaluate)
|
|
||||||
action and [`match_response`](#match_response).
|
|
||||||
|
|
||||||
DNS query type. Values can be integers or type name strings.
|
DNS query type. Values can be integers or type name strings.
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -387,12 +325,6 @@ Match process path using regular expression.
|
|||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Match android package name using regular expression.
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -476,26 +408,6 @@ Matches network interface (same values as `network_type`) address.
|
|||||||
|
|
||||||
Match default interface 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
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -534,25 +446,6 @@ Make `ip_cidr` rule items in rule-sets match the source IP.
|
|||||||
|
|
||||||
Make `ip_cidr` rule items in rule-sets match the source IP.
|
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 the evaluated response
|
|
||||||
(set by a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action)
|
|
||||||
instead of only matching the original query.
|
|
||||||
|
|
||||||
The evaluated response can also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action.
|
|
||||||
|
|
||||||
Required for Response Match Fields (`response_rcode`, `response_answer`, `response_ns`, `response_extra`).
|
|
||||||
Also required for `ip_cidr`, `ip_is_private`, and `ip_accept_any` when used with `evaluate` or Response Match Fields.
|
|
||||||
|
|
||||||
#### ip_accept_any
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
Match when the DNS query response contains at least one address.
|
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
@@ -597,12 +490,7 @@ See [DNS Rule Actions](../rule_action/) for details.
|
|||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
Moved to [DNS Rule Action](../rule_action#route).
|
||||||
|
|
||||||
### Legacy Address Filter Fields
|
### Address Filter Fields
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
Legacy Address Filter Fields are deprecated and will be removed in sing-box 1.16.0,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
|
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
|
||||||
|
|
||||||
@@ -628,61 +516,23 @@ Match GeoIP with query response.
|
|||||||
|
|
||||||
Match IP CIDR with query response.
|
Match IP CIDR with query response.
|
||||||
|
|
||||||
As a Legacy Address Filter Field, deprecated. Use with `match_response` instead,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
#### ip_is_private
|
#### ip_is_private
|
||||||
|
|
||||||
!!! question "Since sing-box 1.9.0"
|
!!! question "Since sing-box 1.9.0"
|
||||||
|
|
||||||
Match private IP with query response.
|
Match private IP with query response.
|
||||||
|
|
||||||
As a Legacy Address Filter Field, deprecated. Use with `match_response` instead,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
#### rule_set_ip_cidr_accept_empty
|
#### rule_set_ip_cidr_accept_empty
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! 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,
|
|
||||||
check [Migration](/migration/#migrate-address-filter-fields-to-response-matching).
|
|
||||||
|
|
||||||
Make `ip_cidr` rules in rule-sets accept empty query response.
|
Make `ip_cidr` rules in rule-sets accept empty query response.
|
||||||
|
|
||||||
### Response Match Fields
|
#### ip_accept_any
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
Match fields for the evaluated response. Require `match_response` to be set to `true`
|
Match any IP with query response.
|
||||||
and a preceding rule with [`evaluate`](/configuration/dns/rule_action/#evaluate) action to populate the response.
|
|
||||||
|
|
||||||
That evaluated response may also be returned directly by a later [`respond`](/configuration/dns/rule_action/#respond) action.
|
|
||||||
|
|
||||||
#### 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
|
### Logical Fields
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
|
||||||
:material-plus: [match_response](#match_response)
|
|
||||||
:material-delete-clock: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
|
||||||
: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: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [ip_version](#ip_version)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -103,6 +89,12 @@ icon: material/alert-decagram
|
|||||||
"192.168.0.1"
|
"192.168.0.1"
|
||||||
],
|
],
|
||||||
"source_ip_is_private": false,
|
"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": [
|
"source_port": [
|
||||||
12345
|
12345
|
||||||
],
|
],
|
||||||
@@ -132,9 +124,6 @@ icon: material/alert-decagram
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -160,12 +149,6 @@ icon: material/alert-decagram
|
|||||||
"default_interface_address": [
|
"default_interface_address": [
|
||||||
"2000::/3"
|
"2000::/3"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -177,17 +160,7 @@ icon: material/alert-decagram
|
|||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
],
|
],
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"match_response": false,
|
"rule_set_ip_cidr_accept_empty": false,
|
||||||
"ip_cidr": [
|
|
||||||
"10.0.0.0/24",
|
|
||||||
"192.168.0.1"
|
|
||||||
],
|
|
||||||
"ip_is_private": false,
|
|
||||||
"ip_accept_any": false,
|
|
||||||
"response_rcode": "",
|
|
||||||
"response_answer": [],
|
|
||||||
"response_ns": [],
|
|
||||||
"response_extra": [],
|
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
@@ -196,8 +169,6 @@ icon: material/alert-decagram
|
|||||||
"server": "local",
|
"server": "local",
|
||||||
|
|
||||||
// 已弃用
|
// 已弃用
|
||||||
|
|
||||||
"rule_set_ip_cidr_accept_empty": false,
|
|
||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"geosite": [
|
"geosite": [
|
||||||
"cn"
|
"cn"
|
||||||
@@ -245,38 +216,12 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
#### ip_version
|
#### ip_version
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
此字段现在也会在 DNS 规则被未指定具体 DNS 服务器的内部域名解析匹配时生效,
|
|
||||||
例如未设置 `server` 的 [`resolve`](../../route/rule_action/#resolve) 路由规则动作。
|
|
||||||
此前只有来自客户端的 DNS 查询才会评估此字段。完整列表参阅
|
|
||||||
[迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。
|
|
||||||
|
|
||||||
在 DNS 规则中设置此字段后,该 DNS 规则在同一 DNS 配置中不能与
|
|
||||||
旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项,
|
|
||||||
或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。如需与
|
|
||||||
基于地址的筛选组合,请使用 [`evaluate`](../rule_action/#evaluate) 动作和
|
|
||||||
[`match_response`](#match_response)。
|
|
||||||
|
|
||||||
4 (A DNS 查询) 或 6 (AAAA DNS 查询)。
|
4 (A DNS 查询) 或 6 (AAAA DNS 查询)。
|
||||||
|
|
||||||
默认不限制。
|
默认不限制。
|
||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
此字段现在也会在 DNS 规则被未指定具体 DNS 服务器的内部域名解析匹配时生效,
|
|
||||||
例如未设置 `server` 的 [`resolve`](../../route/rule_action/#resolve) 路由规则动作。
|
|
||||||
此前只有来自客户端的 DNS 查询才会评估此字段。完整列表参阅
|
|
||||||
[迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。
|
|
||||||
|
|
||||||
在 DNS 规则中设置此字段后,该 DNS 规则在同一 DNS 配置中不能与
|
|
||||||
旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项,
|
|
||||||
或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。如需与
|
|
||||||
基于地址的筛选组合,请使用 [`evaluate`](../rule_action/#evaluate) 动作和
|
|
||||||
[`match_response`](#match_response)。
|
|
||||||
|
|
||||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -379,12 +324,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
使用正则表达式匹配 Android 应用包名。
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -468,26 +407,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
匹配默认接口地址。
|
匹配默认接口地址。
|
||||||
|
|
||||||
#### source_mac_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
|
||||||
|
|
||||||
匹配源设备 MAC 地址。
|
|
||||||
|
|
||||||
#### source_hostname
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
仅支持 Linux、macOS,或在 Android 和 macOS 图形客户端中支持。参阅 [邻居解析](/configuration/shared/neighbor/) 了解设置方法。
|
|
||||||
|
|
||||||
匹配源设备从 DHCP 租约获取的主机名。
|
|
||||||
|
|
||||||
#### wifi_ssid
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -526,23 +445,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||||
|
|
||||||
#### match_response
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
启用响应匹配。启用后,此规则将匹配已评估的响应(由前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作设置),而不仅是匹配原始查询。
|
|
||||||
|
|
||||||
该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。
|
|
||||||
|
|
||||||
响应匹配字段(`response_rcode`、`response_answer`、`response_ns`、`response_extra`)需要此选项。
|
|
||||||
当与 `evaluate` 或响应匹配字段一起使用时,`ip_cidr`、`ip_is_private` 和 `ip_accept_any` 也需要此选项。
|
|
||||||
|
|
||||||
#### ip_accept_any
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
当 DNS 查询响应包含至少一个地址时匹配。
|
|
||||||
|
|
||||||
#### invert
|
#### invert
|
||||||
|
|
||||||
反选匹配结果。
|
反选匹配结果。
|
||||||
@@ -587,12 +489,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
已移动到 [DNS 规则动作](../rule_action#route).
|
已移动到 [DNS 规则动作](../rule_action#route).
|
||||||
|
|
||||||
### 旧版地址筛选字段
|
### 地址筛选字段
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
旧版地址筛选字段已废弃,且将在 sing-box 1.16.0 中被移除,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
||||||
|
|
||||||
@@ -619,62 +516,24 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
与查询响应匹配 IP CIDR。
|
与查询响应匹配 IP CIDR。
|
||||||
|
|
||||||
作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
#### ip_is_private
|
#### ip_is_private
|
||||||
|
|
||||||
!!! question "自 sing-box 1.9.0 起"
|
!!! question "自 sing-box 1.9.0 起"
|
||||||
|
|
||||||
与查询响应匹配非公开 IP。
|
与查询响应匹配非公开 IP。
|
||||||
|
|
||||||
作为旧版地址筛选字段已废弃。请改为配合 `match_response` 使用,
|
#### ip_accept_any
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
|
匹配任意 IP。
|
||||||
|
|
||||||
#### rule_set_ip_cidr_accept_empty
|
#### rule_set_ip_cidr_accept_empty
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
`rule_set_ip_cidr_accept_empty` 已废弃且将在 sing-box 1.16.0 中被移除,
|
|
||||||
参阅[迁移指南](/zh/migration/#迁移地址筛选字段到响应匹配)。
|
|
||||||
|
|
||||||
使规则集中的 `ip_cidr` 规则接受空查询响应。
|
使规则集中的 `ip_cidr` 规则接受空查询响应。
|
||||||
|
|
||||||
### 响应匹配字段
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
已评估的响应的匹配字段。需要将 `match_response` 设为 `true`,
|
|
||||||
且需要前序规则使用 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作来填充响应。
|
|
||||||
|
|
||||||
该已评估的响应也可以被后续的 [`respond`](/zh/configuration/dns/rule_action/#respond) 动作直接返回。
|
|
||||||
|
|
||||||
#### 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
|
#### type
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [strategy](#strategy)
|
|
||||||
:material-plus: [evaluate](#evaluate)
|
|
||||||
:material-plus: [respond](#respond)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
:material-plus: [strategy](#strategy)
|
||||||
@@ -40,10 +34,6 @@ Tag of target server.
|
|||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.14.0"
|
|
||||||
|
|
||||||
`strategy` is deprecated in sing-box 1.14.0 and will be removed in sing-box 1.16.0.
|
|
||||||
|
|
||||||
Set domain strategy for this query.
|
Set domain strategy for this query.
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
@@ -62,68 +52,7 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
Will overrides `dns.client_subnet`.
|
||||||
|
|
||||||
### 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 evaluated 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).
|
|
||||||
Rules that use [`match_response`](/configuration/dns/rule/#match_response) or Response Match Fields
|
|
||||||
require a preceding top-level rule with `evaluate` action. A rule's own `evaluate` action
|
|
||||||
does not satisfy this requirement, because matching happens before the action runs.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Tag of target server.
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
Disable cache and save cache in this query.
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
|
||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
|
||||||
|
|
||||||
### respond
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`respond` terminates rule evaluation and returns the evaluated response from a preceding [`evaluate`](/configuration/dns/rule_action/#evaluate) action.
|
|
||||||
|
|
||||||
This action does not send a new DNS query and has no extra options.
|
|
||||||
|
|
||||||
Only allowed after a preceding top-level `evaluate` rule. If the action is reached without an evaluated response at runtime, the request fails with an error instead of falling through to later rules.
|
|
||||||
|
|
||||||
### route-options
|
### route-options
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [strategy](#strategy)
|
|
||||||
:material-plus: [evaluate](#evaluate)
|
|
||||||
:material-plus: [respond](#respond)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
:material-plus: [strategy](#strategy)
|
||||||
@@ -40,10 +34,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 废弃"
|
|
||||||
|
|
||||||
`strategy` 已在 sing-box 1.14.0 废弃,且将在 sing-box 1.16.0 中被移除。
|
|
||||||
|
|
||||||
为此查询设置域名策略。
|
为此查询设置域名策略。
|
||||||
|
|
||||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
@@ -64,65 +54,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
将覆盖 `dns.client_subnet`.
|
将覆盖 `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 规则中使用(不可在逻辑子规则内部使用)。
|
|
||||||
使用 [`match_response`](/zh/configuration/dns/rule/#match_response) 或响应匹配字段的规则,
|
|
||||||
需要位于更早的顶层 `evaluate` 规则之后。规则自身的 `evaluate` 动作不能满足这个条件,
|
|
||||||
因为匹配发生在动作执行之前。
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
目标 DNS 服务器的标签。
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
在此查询中禁用缓存。
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
|
||||||
|
|
||||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
|
||||||
|
|
||||||
将覆盖 `dns.client_subnet`.
|
|
||||||
|
|
||||||
### respond
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`respond` 会终止规则评估,并直接返回前序 [`evaluate`](/zh/configuration/dns/rule_action/#evaluate) 动作保存的已评估的响应。
|
|
||||||
|
|
||||||
此动作不会发起新的 DNS 查询,也没有额外选项。
|
|
||||||
|
|
||||||
只能用于前面已有顶层 `evaluate` 规则的场景。如果运行时命中该动作时没有已评估的响应,则请求会直接返回错误,而不是继续匹配后续规则。
|
|
||||||
|
|
||||||
### route-options
|
### route-options
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -153,7 +84,7 @@ icon: material/new-box
|
|||||||
- `default`: 返回 REFUSED。
|
- `default`: 返回 REFUSED。
|
||||||
- `drop`: 丢弃请求。
|
- `drop`: 丢弃请求。
|
||||||
|
|
||||||
默认使用 `default`。
|
默认使用 `defualt`。
|
||||||
|
|
||||||
#### no_drop
|
#### no_drop
|
||||||
|
|
||||||
|
|||||||
@@ -73,55 +73,24 @@ Example:
|
|||||||
|
|
||||||
=== "Use hosts if available"
|
=== "Use hosts if available"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
...
|
||||||
{
|
},
|
||||||
...
|
{
|
||||||
},
|
"type": "hosts",
|
||||||
{
|
"tag": "hosts"
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "hosts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "hosts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
...
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "hosts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -73,55 +73,24 @@ hosts 文件路径列表。
|
|||||||
|
|
||||||
=== "如果可用则使用 hosts"
|
=== "如果可用则使用 hosts"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
...
|
||||||
{
|
},
|
||||||
...
|
{
|
||||||
},
|
"type": "hosts",
|
||||||
{
|
"tag": "hosts"
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "hosts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "hosts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
...
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "hosts",
|
|
||||||
"tag": "hosts"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "hosts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -29,7 +29,7 @@ The type of the DNS server.
|
|||||||
|
|
||||||
| Type | Format |
|
| Type | Format |
|
||||||
|-----------------|---------------------------|
|
|-----------------|---------------------------|
|
||||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `local` | [Local](./local/) |
|
| `local` | [Local](./local/) |
|
||||||
| `hosts` | [Hosts](./hosts/) |
|
| `hosts` | [Hosts](./hosts/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ DNS 服务器的类型。
|
|||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-----------------|---------------------------|
|
|-----------------|---------------------------|
|
||||||
| empty (default) | :material-note-remove: [Legacy](./legacy/) |
|
| empty (default) | [Legacy](./legacy/) |
|
||||||
| `local` | [Local](./local/) |
|
| `local` | [Local](./local/) |
|
||||||
| `hosts` | [Hosts](./hosts/) |
|
| `hosts` | [Hosts](./hosts/) |
|
||||||
| `tcp` | [TCP](./tcp/) |
|
| `tcp` | [TCP](./tcp/) |
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Removed in sing-box 1.14.0"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
Legacy DNS servers are deprecated in sing-box 1.12.0 and removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-server-formats).
|
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
@@ -108,6 +108,6 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Can be overridden by `rules.[].client_subnet`.
|
Can be overrides by `rules.[].client_subnet`.
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
Will overrides `dns.client_subnet`.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.14.0 移除"
|
!!! failure "Deprecated in sing-box 1.12.0"
|
||||||
|
|
||||||
旧的 DNS 服务器配置已在 sing-box 1.12.0 废弃且已在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/zh/migration/#迁移到新的-dns-服务器格式)。
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
|
|||||||
@@ -43,62 +43,29 @@ If not enabled, `NXDOMAIN` will be returned for requests that do not match searc
|
|||||||
|
|
||||||
=== "Split DNS only"
|
=== "Split DNS only"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "resolved",
|
||||||
{
|
"tag": "resolved",
|
||||||
"type": "resolved",
|
"service": "resolved"
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "resolved"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "resolved"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "resolved"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "Use as global DNS"
|
=== "Use as global DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -42,62 +42,29 @@ icon: material/new-box
|
|||||||
|
|
||||||
=== "仅分割 DNS"
|
=== "仅分割 DNS"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "resolved",
|
||||||
{
|
"tag": "resolved",
|
||||||
"type": "resolved",
|
"service": "resolved"
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "resolved"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "resolved"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "resolved"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "用作全局 DNS"
|
=== "用作全局 DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -42,62 +42,29 @@ if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.
|
|||||||
|
|
||||||
=== "MagicDNS only"
|
=== "MagicDNS only"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "tailscale",
|
||||||
{
|
"tag": "ts",
|
||||||
"type": "tailscale",
|
"endpoint": "ts-ep"
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "ts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tailscale",
|
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "ts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "Use as global DNS"
|
=== "Use as global DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -42,62 +42,29 @@ icon: material/new-box
|
|||||||
|
|
||||||
=== "仅 MagicDNS"
|
=== "仅 MagicDNS"
|
||||||
|
|
||||||
=== ":material-card-multiple: sing-box 1.14.0"
|
```json
|
||||||
|
{
|
||||||
```json
|
"dns": {
|
||||||
{
|
"servers": [
|
||||||
"dns": {
|
{
|
||||||
"servers": [
|
"type": "local",
|
||||||
{
|
"tag": "local"
|
||||||
"type": "local",
|
},
|
||||||
"tag": "local"
|
{
|
||||||
},
|
"type": "tailscale",
|
||||||
{
|
"tag": "ts",
|
||||||
"type": "tailscale",
|
"endpoint": "ts-ep"
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "evaluate",
|
|
||||||
"server": "ts"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"match_response": true,
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"action": "respond"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
```
|
"rules": [
|
||||||
|
{
|
||||||
=== ":material-card-remove: sing-box < 1.14.0"
|
"ip_accept_any": true,
|
||||||
|
"server": "ts"
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tailscale",
|
|
||||||
"tag": "ts",
|
|
||||||
"endpoint": "ts-ep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "ts"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
```
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
=== "用作全局 DNS"
|
=== "用作全局 DNS"
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Store fakeip in the cache file
|
|||||||
|
|
||||||
Store rejected DNS response cache in the cache file
|
Store rejected DNS response cache in the cache file
|
||||||
|
|
||||||
The check results of [Legacy Address Filter Fields](/configuration/dns/rule/#legacy-address-filter-fields)
|
The check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields)
|
||||||
will be cached until expiration.
|
will be cached until expiration.
|
||||||
|
|
||||||
#### rdrc_timeout
|
#### rdrc_timeout
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
||||||
|
|
||||||
[旧版地址筛选字段](/zh/configuration/dns/rule/#旧版地址筛选字段) 的检查结果将被缓存至过期。
|
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#地址筛选字段) 的检查结果将被缓存至过期。
|
||||||
|
|
||||||
#### rdrc_timeout
|
#### rdrc_timeout
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
`cloudflared` inbound runs an embedded Cloudflare Tunnel client and routes all
|
|
||||||
incoming tunnel traffic (TCP, UDP, ICMP) through sing-box's routing engine.
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflared",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"token": "",
|
|
||||||
"ha_connections": 0,
|
|
||||||
"protocol": "",
|
|
||||||
"post_quantum": false,
|
|
||||||
"edge_ip_version": 0,
|
|
||||||
"datagram_version": "",
|
|
||||||
"grace_period": "",
|
|
||||||
"region": "",
|
|
||||||
"control_dialer": {
|
|
||||||
... // Dial Fields
|
|
||||||
},
|
|
||||||
"tunnel_dialer": {
|
|
||||||
... // Dial Fields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### token
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Base64-encoded tunnel token from the Cloudflare Zero Trust dashboard
|
|
||||||
(`Networks → Tunnels → Install connector`).
|
|
||||||
|
|
||||||
#### ha_connections
|
|
||||||
|
|
||||||
Number of high-availability connections to the Cloudflare edge.
|
|
||||||
|
|
||||||
Capped by the number of discovered edge addresses.
|
|
||||||
|
|
||||||
#### protocol
|
|
||||||
|
|
||||||
Transport protocol for edge connections.
|
|
||||||
|
|
||||||
One of `quic` `http2`.
|
|
||||||
|
|
||||||
#### post_quantum
|
|
||||||
|
|
||||||
Enable post-quantum key exchange on the control connection.
|
|
||||||
|
|
||||||
#### edge_ip_version
|
|
||||||
|
|
||||||
IP version used when connecting to the Cloudflare edge.
|
|
||||||
|
|
||||||
One of `0` (automatic) `4` `6`.
|
|
||||||
|
|
||||||
#### datagram_version
|
|
||||||
|
|
||||||
Datagram protocol version used for UDP proxying over QUIC.
|
|
||||||
|
|
||||||
One of `v2` `v3`. Only meaningful when `protocol` is `quic`.
|
|
||||||
|
|
||||||
#### grace_period
|
|
||||||
|
|
||||||
Graceful shutdown window for in-flight edge connections.
|
|
||||||
|
|
||||||
#### region
|
|
||||||
|
|
||||||
Cloudflare edge region selector.
|
|
||||||
|
|
||||||
Conflict with endpoints embedded in `token`.
|
|
||||||
|
|
||||||
#### control_dialer
|
|
||||||
|
|
||||||
[Dial Fields](/configuration/shared/dial/) used when the tunnel client dials the
|
|
||||||
Cloudflare control plane.
|
|
||||||
|
|
||||||
#### tunnel_dialer
|
|
||||||
|
|
||||||
[Dial Fields](/configuration/shared/dial/) used when the tunnel client dials the
|
|
||||||
Cloudflare edge data plane.
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
`cloudflared` 入站运行一个内嵌的 Cloudflare Tunnel 客户端,并将所有传入的隧道流量
|
|
||||||
(TCP、UDP、ICMP)通过 sing-box 的路由引擎转发。
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflared",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"token": "",
|
|
||||||
"ha_connections": 0,
|
|
||||||
"protocol": "",
|
|
||||||
"post_quantum": false,
|
|
||||||
"edge_ip_version": 0,
|
|
||||||
"datagram_version": "",
|
|
||||||
"grace_period": "",
|
|
||||||
"region": "",
|
|
||||||
"control_dialer": {
|
|
||||||
... // 拨号字段
|
|
||||||
},
|
|
||||||
"tunnel_dialer": {
|
|
||||||
... // 拨号字段
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### token
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
来自 Cloudflare Zero Trust 仪表板的 Base64 编码隧道令牌
|
|
||||||
(`Networks → Tunnels → Install connector`)。
|
|
||||||
|
|
||||||
#### ha_connections
|
|
||||||
|
|
||||||
到 Cloudflare edge 的高可用连接数。
|
|
||||||
|
|
||||||
上限为已发现的 edge 地址数量。
|
|
||||||
|
|
||||||
#### protocol
|
|
||||||
|
|
||||||
edge 连接使用的传输协议。
|
|
||||||
|
|
||||||
`quic` `http2` 之一。
|
|
||||||
|
|
||||||
#### post_quantum
|
|
||||||
|
|
||||||
在控制连接上启用后量子密钥交换。
|
|
||||||
|
|
||||||
#### edge_ip_version
|
|
||||||
|
|
||||||
连接 Cloudflare edge 时使用的 IP 版本。
|
|
||||||
|
|
||||||
`0`(自动)`4` `6` 之一。
|
|
||||||
|
|
||||||
#### datagram_version
|
|
||||||
|
|
||||||
通过 QUIC 进行 UDP 代理时使用的数据报协议版本。
|
|
||||||
|
|
||||||
`v2` `v3` 之一。仅在 `protocol` 为 `quic` 时有效。
|
|
||||||
|
|
||||||
#### grace_period
|
|
||||||
|
|
||||||
正在处理的 edge 连接的优雅关闭窗口。
|
|
||||||
|
|
||||||
#### region
|
|
||||||
|
|
||||||
Cloudflare edge 区域选择器。
|
|
||||||
|
|
||||||
与 `token` 中嵌入的 endpoint 冲突。
|
|
||||||
|
|
||||||
#### control_dialer
|
|
||||||
|
|
||||||
隧道客户端拨向 Cloudflare 控制面时使用的
|
|
||||||
[拨号字段](/zh/configuration/shared/dial/)。
|
|
||||||
|
|
||||||
#### tunnel_dialer
|
|
||||||
|
|
||||||
隧道客户端拨向 Cloudflare edge 数据面时使用的
|
|
||||||
[拨号字段](/zh/configuration/shared/dial/)。
|
|
||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
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"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-alert: [masquerade](#masquerade)
|
:material-alert: [masquerade](#masquerade)
|
||||||
@@ -35,7 +31,6 @@ icon: material/alert-decagram
|
|||||||
"ignore_client_bandwidth": false,
|
"ignore_client_bandwidth": false,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"masquerade": "", // or {}
|
"masquerade": "", // or {}
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false
|
"brutal_debug": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -146,14 +141,6 @@ Fixed response headers.
|
|||||||
|
|
||||||
Fixed response content.
|
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
|
#### brutal_debug
|
||||||
|
|
||||||
Enable debug information logging for Hysteria Brutal CC.
|
Enable debug information logging for Hysteria Brutal CC.
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [bbr_profile](#bbr_profile)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-alert: [masquerade](#masquerade)
|
:material-alert: [masquerade](#masquerade)
|
||||||
@@ -35,7 +31,6 @@ icon: material/alert-decagram
|
|||||||
"ignore_client_bandwidth": false,
|
"ignore_client_bandwidth": false,
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"masquerade": "", // 或 {}
|
"masquerade": "", // 或 {}
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false
|
"brutal_debug": false
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -143,14 +138,6 @@ HTTP3 服务器认证失败时的行为 (对象配置)。
|
|||||||
|
|
||||||
固定响应内容。
|
固定响应内容。
|
||||||
|
|
||||||
#### bbr_profile
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
|
||||||
|
|
||||||
默认使用 `standard`。
|
|
||||||
|
|
||||||
#### brutal_debug
|
#### brutal_debug
|
||||||
|
|
||||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
| `cloudflared` | [Cloudflared](./cloudflared/) | :material-close: |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
| `cloudflared` | [Cloudflared](./cloudflared/) | :material-close: |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
!!! 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)
|
:material-plus: [exclude_mac_address](#exclude_mac_address)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.3"
|
!!! quote "Changes in sing-box 1.13.3"
|
||||||
@@ -134,12 +134,6 @@ icon: material/new-box
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
"include_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"exclude_mac_address": [
|
|
||||||
"66:77:88:99:aa:bb"
|
|
||||||
],
|
|
||||||
"platform": {
|
"platform": {
|
||||||
"http_proxy": {
|
"http_proxy": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -566,30 +560,6 @@ Limit android packages in route.
|
|||||||
|
|
||||||
Exclude 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
|
||||||
|
|
||||||
Platform-specific settings, provided by client applications.
|
Platform-specific settings, provided by client applications.
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
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 中的更改"
|
!!! quote "sing-box 1.13.3 中的更改"
|
||||||
|
|
||||||
:material-alert: [strict_route](#strict_route)
|
:material-alert: [strict_route](#strict_route)
|
||||||
@@ -135,12 +130,6 @@ icon: material/new-box
|
|||||||
"exclude_package": [
|
"exclude_package": [
|
||||||
"com.android.captiveportallogin"
|
"com.android.captiveportallogin"
|
||||||
],
|
],
|
||||||
"include_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"exclude_mac_address": [
|
|
||||||
"66:77:88:99:aa:bb"
|
|
||||||
],
|
|
||||||
"platform": {
|
"platform": {
|
||||||
"http_proxy": {
|
"http_proxy": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@@ -554,30 +543,6 @@ TCP/IP 栈。
|
|||||||
|
|
||||||
排除路由的 Android 应用包名。
|
排除路由的 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
|
#### platform
|
||||||
|
|
||||||
平台特定的设置,由客户端应用提供。
|
平台特定的设置,由客户端应用提供。
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
sing-box uses JSON for configuration files.
|
sing-box uses JSON for configuration files.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -9,7 +10,6 @@ sing-box uses JSON for configuration files.
|
|||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
"ntp": {},
|
||||||
"certificate": {},
|
"certificate": {},
|
||||||
"certificate_providers": [],
|
|
||||||
"endpoints": [],
|
"endpoints": [],
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
@@ -27,7 +27,6 @@ sing-box uses JSON for configuration files.
|
|||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `ntp` | [NTP](./ntp/) |
|
| `ntp` | [NTP](./ntp/) |
|
||||||
| `certificate` | [Certificate](./certificate/) |
|
| `certificate` | [Certificate](./certificate/) |
|
||||||
| `certificate_providers` | [Certificate Provider](./shared/certificate-provider/) |
|
|
||||||
| `endpoints` | [Endpoint](./endpoint/) |
|
| `endpoints` | [Endpoint](./endpoint/) |
|
||||||
| `inbounds` | [Inbound](./inbound/) |
|
| `inbounds` | [Inbound](./inbound/) |
|
||||||
| `outbounds` | [Outbound](./outbound/) |
|
| `outbounds` | [Outbound](./outbound/) |
|
||||||
@@ -51,4 +50,4 @@ sing-box format -w -c config.json -D config_directory
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sing-box merge output.json -c config.json -D config_directory
|
sing-box merge output.json -c config.json -D config_directory
|
||||||
```
|
```
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# 引言
|
# 引言
|
||||||
|
|
||||||
sing-box 使用 JSON 作为配置文件格式。
|
sing-box 使用 JSON 作为配置文件格式。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -9,7 +10,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
"ntp": {},
|
||||||
"certificate": {},
|
"certificate": {},
|
||||||
"certificate_providers": [],
|
|
||||||
"endpoints": [],
|
"endpoints": [],
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
@@ -27,7 +27,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `ntp` | [NTP](./ntp/) |
|
| `ntp` | [NTP](./ntp/) |
|
||||||
| `certificate` | [证书](./certificate/) |
|
| `certificate` | [证书](./certificate/) |
|
||||||
| `certificate_providers` | [证书提供者](./shared/certificate-provider/) |
|
|
||||||
| `endpoints` | [端点](./endpoint/) |
|
| `endpoints` | [端点](./endpoint/) |
|
||||||
| `inbounds` | [入站](./inbound/) |
|
| `inbounds` | [入站](./inbound/) |
|
||||||
| `outbounds` | [出站](./outbound/) |
|
| `outbounds` | [出站](./outbound/) |
|
||||||
@@ -51,4 +50,4 @@ sing-box format -w -c config.json -D config_directory
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sing-box merge output.json -c config.json -D config_directory
|
sing-box merge output.json -c config.json -D config_directory
|
||||||
```
|
```
|
||||||
@@ -1,8 +1,3 @@
|
|||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [hop_interval_max](#hop_interval_max)
|
|
||||||
:material-plus: [bbr_profile](#bbr_profile)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
@@ -14,14 +9,13 @@
|
|||||||
{
|
{
|
||||||
"type": "hysteria2",
|
"type": "hysteria2",
|
||||||
"tag": "hy2-out",
|
"tag": "hy2-out",
|
||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"server_ports": [
|
"server_ports": [
|
||||||
"2080:3000"
|
"2080:3000"
|
||||||
],
|
],
|
||||||
"hop_interval": "",
|
"hop_interval": "",
|
||||||
"hop_interval_max": "",
|
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
"obfs": {
|
"obfs": {
|
||||||
@@ -31,9 +25,8 @@
|
|||||||
"password": "goofy_ahh_password",
|
"password": "goofy_ahh_password",
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false,
|
"brutal_debug": false,
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -82,14 +75,6 @@ Port hopping interval.
|
|||||||
|
|
||||||
`30s` is used by default.
|
`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
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
Max bandwidth, in Mbps.
|
Max bandwidth, in Mbps.
|
||||||
@@ -124,14 +109,6 @@ Both is enabled by default.
|
|||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
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
|
#### brutal_debug
|
||||||
|
|
||||||
Enable debug information logging for Hysteria Brutal CC.
|
Enable debug information logging for Hysteria Brutal CC.
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [hop_interval_max](#hop_interval_max)
|
|
||||||
:material-plus: [bbr_profile](#bbr_profile)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
:material-plus: [server_ports](#server_ports)
|
||||||
@@ -21,7 +16,6 @@
|
|||||||
"2080:3000"
|
"2080:3000"
|
||||||
],
|
],
|
||||||
"hop_interval": "",
|
"hop_interval": "",
|
||||||
"hop_interval_max": "",
|
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down_mbps": 100,
|
"down_mbps": 100,
|
||||||
"obfs": {
|
"obfs": {
|
||||||
@@ -31,9 +25,8 @@
|
|||||||
"password": "goofy_ahh_password",
|
"password": "goofy_ahh_password",
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"bbr_profile": "",
|
|
||||||
"brutal_debug": false,
|
"brutal_debug": false,
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -80,14 +73,6 @@
|
|||||||
|
|
||||||
默认使用 `30s`。
|
默认使用 `30s`。
|
||||||
|
|
||||||
#### hop_interval_max
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
最大端口跳跃间隔,用于随机化。
|
|
||||||
|
|
||||||
如果设置,实际跳跃间隔将在 `hop_interval` 和 `hop_interval_max` 之间随机选择。
|
|
||||||
|
|
||||||
#### up_mbps, down_mbps
|
#### up_mbps, down_mbps
|
||||||
|
|
||||||
最大带宽。
|
最大带宽。
|
||||||
@@ -122,14 +107,6 @@ QUIC 流量混淆器密码.
|
|||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。
|
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#出站)。
|
||||||
|
|
||||||
#### bbr_profile
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
BBR 拥塞控制算法配置,可选 `conservative` `standard` `aggressive`。
|
|
||||||
|
|
||||||
默认使用 `standard`。
|
|
||||||
|
|
||||||
#### brutal_debug
|
#### brutal_debug
|
||||||
|
|
||||||
启用 Hysteria Brutal CC 的调试信息日志记录。
|
启用 Hysteria Brutal CC 的调试信息日志记录。
|
||||||
|
|||||||
@@ -4,11 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
# Route
|
# 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"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||||
@@ -40,9 +35,6 @@ icon: material/alert-decagram
|
|||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "",
|
||||||
"default_mark": 0,
|
"default_mark": 0,
|
||||||
"find_process": false,
|
|
||||||
"find_neighbor": false,
|
|
||||||
"dhcp_lease_files": [],
|
|
||||||
"default_domain_resolver": "", // or {}
|
"default_domain_resolver": "", // or {}
|
||||||
"default_network_strategy": "",
|
"default_network_strategy": "",
|
||||||
"default_network_type": [],
|
"default_network_type": [],
|
||||||
@@ -115,45 +107,13 @@ Set routing mark by default.
|
|||||||
|
|
||||||
Takes no effect if `outbound.routing_mark` is set.
|
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
|
#### default_domain_resolver
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
!!! question "Since sing-box 1.12.0"
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.
|
See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.
|
||||||
|
|
||||||
Can be overridden by `outbound.domain_resolver`.
|
Can be overrides by `outbound.domain_resolver`.
|
||||||
|
|
||||||
#### default_network_strategy
|
#### default_network_strategy
|
||||||
|
|
||||||
@@ -163,7 +123,7 @@ See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
|
|||||||
|
|
||||||
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.
|
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.
|
||||||
|
|
||||||
Can be overridden by `outbound.network_strategy`.
|
Can be overrides by `outbound.network_strategy`.
|
||||||
|
|
||||||
Conflicts with `default_interface`.
|
Conflicts with `default_interface`.
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
# 路由
|
# 路由
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [find_neighbor](#find_neighbor)
|
|
||||||
:material-plus: [dhcp_lease_files](#dhcp_lease_files)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||||
@@ -42,9 +37,6 @@ icon: material/alert-decagram
|
|||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "",
|
||||||
"default_mark": 0,
|
"default_mark": 0,
|
||||||
"find_process": false,
|
|
||||||
"find_neighbor": false,
|
|
||||||
"dhcp_lease_files": [],
|
|
||||||
"default_network_strategy": "",
|
"default_network_strategy": "",
|
||||||
"default_fallback_delay": ""
|
"default_fallback_delay": ""
|
||||||
}
|
}
|
||||||
@@ -114,38 +106,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `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
|
#### default_domain_resolver
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
!!! question "自 sing-box 1.12.0 起"
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
icon: material/new-box
|
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)
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -130,9 +124,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -168,12 +159,6 @@ icon: material/new-box
|
|||||||
"tailscale",
|
"tailscale",
|
||||||
"wireguard"
|
"wireguard"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@@ -358,12 +343,6 @@ Match process path using regular expression.
|
|||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Match android package name using regular expression.
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -470,26 +449,6 @@ Match specified outbounds' preferred routes.
|
|||||||
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
| `tailscale` | Match MagicDNS domains and peers' allowed IPs |
|
||||||
| `wireguard` | Match peers's 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
|
#### rule_set
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [source_mac_address](#source_mac_address)
|
|
||||||
:material-plus: [source_hostname](#source_hostname)
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [interface_address](#interface_address)
|
:material-plus: [interface_address](#interface_address)
|
||||||
@@ -128,9 +122,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"user": [
|
"user": [
|
||||||
"sekai"
|
"sekai"
|
||||||
],
|
],
|
||||||
@@ -166,12 +157,6 @@ icon: material/new-box
|
|||||||
"tailscale",
|
"tailscale",
|
||||||
"wireguard"
|
"wireguard"
|
||||||
],
|
],
|
||||||
"source_mac_address": [
|
|
||||||
"00:11:22:33:44:55"
|
|
||||||
],
|
|
||||||
"source_hostname": [
|
|
||||||
"my-device"
|
|
||||||
],
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
"geoip-cn",
|
"geoip-cn",
|
||||||
"geosite-cn"
|
"geosite-cn"
|
||||||
@@ -356,12 +341,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
使用正则表达式匹配 Android 应用包名。
|
|
||||||
|
|
||||||
#### user
|
#### user
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -468,26 +447,6 @@ icon: material/new-box
|
|||||||
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
| `tailscale` | 匹配 MagicDNS 域名和对端的 allowed IPs |
|
||||||
| `wireguard` | 匹配对端的 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
|
#### rule_set
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|||||||
@@ -316,4 +316,4 @@ Append a `edns0-subnet` OPT extra record with the specified IP prefix to every q
|
|||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
Will override `dns.client_subnet`.
|
Will overrides `dns.client_subnet`.
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
@@ -83,9 +78,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"network_type": [
|
"network_type": [
|
||||||
"wifi"
|
"wifi"
|
||||||
],
|
],
|
||||||
@@ -133,20 +125,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
When a DNS rule references this rule-set, this field now also applies
|
|
||||||
when the DNS rule is matched from an internal domain resolution that
|
|
||||||
does not target a specific DNS server. In earlier versions, only DNS
|
|
||||||
queries received from a client evaluated this field. See
|
|
||||||
[Migration](/migration/#ip_version-and-query_type-behavior-changes-in-dns-rules)
|
|
||||||
for the full list.
|
|
||||||
|
|
||||||
When a DNS rule references a rule-set containing this field, the DNS
|
|
||||||
rule is incompatible in the same DNS configuration with Legacy Address
|
|
||||||
Filter Fields in DNS rules, the Legacy `strategy` DNS rule action
|
|
||||||
option, and the Legacy `rule_set_ip_cidr_accept_empty` DNS rule item.
|
|
||||||
|
|
||||||
DNS query type. Values can be integers or type name strings.
|
DNS query type. Values can be integers or type name strings.
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -227,12 +205,6 @@ Match process path using regular expression.
|
|||||||
|
|
||||||
Match android package name.
|
Match android package name.
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
Match android package name using regular expression.
|
|
||||||
|
|
||||||
#### network_type
|
#### network_type
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [package_name_regex](#package_name_regex)
|
|
||||||
:material-alert: [query_type](#query_type)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [network_interface_address](#network_interface_address)
|
:material-plus: [network_interface_address](#network_interface_address)
|
||||||
@@ -83,9 +78,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"package_name_regex": [
|
|
||||||
"^com\\.termux.*"
|
|
||||||
],
|
|
||||||
"network_type": [
|
"network_type": [
|
||||||
"wifi"
|
"wifi"
|
||||||
],
|
],
|
||||||
@@ -133,17 +125,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### query_type
|
#### query_type
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
当 DNS 规则引用此规则集时,此字段现在也会在 DNS 规则被未指定具体
|
|
||||||
DNS 服务器的内部域名解析匹配时生效。此前只有来自客户端的 DNS 查询
|
|
||||||
才会评估此字段。完整列表参阅
|
|
||||||
[迁移指南](/zh/migration/#dns-规则中的-ip_version-和-query_type-行为更改)。
|
|
||||||
|
|
||||||
当 DNS 规则引用了包含此字段的规则集时,该 DNS 规则在同一 DNS 配置中
|
|
||||||
不能与旧版地址筛选字段 (DNS 规则)、旧版 DNS 规则动作 `strategy` 选项,
|
|
||||||
或旧版 `rule_set_ip_cidr_accept_empty` DNS 规则项共存。
|
|
||||||
|
|
||||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
@@ -220,12 +201,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配 Android 应用包名。
|
匹配 Android 应用包名。
|
||||||
|
|
||||||
#### package_name_regex
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
使用正则表达式匹配 Android 应用包名。
|
|
||||||
|
|
||||||
#### network_type
|
#### network_type
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: version `5`
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.13.0"
|
!!! quote "Changes in sing-box 1.13.0"
|
||||||
|
|
||||||
:material-plus: version `4`
|
:material-plus: version `4`
|
||||||
@@ -45,7 +41,6 @@ Version of rule-set.
|
|||||||
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
||||||
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
||||||
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
|
* 4: sing-box 1.13.0: Added `network_interface_address` and `default_interface_address` rule items.
|
||||||
* 5: sing-box 1.14.0: Added `package_name_regex` rule item.
|
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: version `5`
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.13.0 中的更改"
|
!!! quote "sing-box 1.13.0 中的更改"
|
||||||
|
|
||||||
:material-plus: version `4`
|
:material-plus: version `4`
|
||||||
@@ -45,7 +41,6 @@ icon: material/new-box
|
|||||||
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
||||||
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
||||||
* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。
|
* 4: sing-box 1.13.0: 添加了 `network_interface_address` 和 `default_interface_address` 规则项。
|
||||||
* 5: sing-box 1.14.0: 添加了 `package_name_regex` 规则项。
|
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.14.0"
|
|
||||||
|
|
||||||
:material-plus: [account_key](#account_key)
|
|
||||||
:material-plus: [key_type](#key_type)
|
|
||||||
:material-plus: [detour](#detour)
|
|
||||||
|
|
||||||
# ACME
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
`with_acme` build tag required.
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "acme",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"default_server_name": "",
|
|
||||||
"email": "",
|
|
||||||
"provider": "",
|
|
||||||
"account_key": "",
|
|
||||||
"disable_http_challenge": false,
|
|
||||||
"disable_tls_alpn_challenge": false,
|
|
||||||
"alternative_http_port": 0,
|
|
||||||
"alternative_tls_port": 0,
|
|
||||||
"external_account": {
|
|
||||||
"key_id": "",
|
|
||||||
"mac_key": ""
|
|
||||||
},
|
|
||||||
"dns01_challenge": {},
|
|
||||||
"key_type": "",
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
List of domains.
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
The directory to store ACME data.
|
|
||||||
|
|
||||||
`$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic` will be used if empty.
|
|
||||||
|
|
||||||
#### default_server_name
|
|
||||||
|
|
||||||
Server name to use when choosing a certificate if the ClientHello's ServerName field is empty.
|
|
||||||
|
|
||||||
#### email
|
|
||||||
|
|
||||||
The email address to use when creating or selecting an existing ACME server account.
|
|
||||||
|
|
||||||
#### provider
|
|
||||||
|
|
||||||
The ACME CA provider to use.
|
|
||||||
|
|
||||||
| Value | Provider |
|
|
||||||
|-------------------------|---------------|
|
|
||||||
| `letsencrypt (default)` | Let's Encrypt |
|
|
||||||
| `zerossl` | ZeroSSL |
|
|
||||||
| `https://...` | Custom |
|
|
||||||
|
|
||||||
When `provider` is `zerossl`, sing-box will automatically request ZeroSSL EAB credentials if `email` is set and
|
|
||||||
`external_account` is empty.
|
|
||||||
|
|
||||||
When `provider` is `zerossl`, at least one of `external_account`, `email`, or `account_key` is required.
|
|
||||||
|
|
||||||
#### account_key
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The PEM-encoded private key of an existing ACME account.
|
|
||||||
|
|
||||||
#### disable_http_challenge
|
|
||||||
|
|
||||||
Disable all HTTP challenges.
|
|
||||||
|
|
||||||
#### disable_tls_alpn_challenge
|
|
||||||
|
|
||||||
Disable all TLS-ALPN challenges
|
|
||||||
|
|
||||||
#### alternative_http_port
|
|
||||||
|
|
||||||
The alternate port to use for the ACME HTTP challenge; if non-empty, this port will be used instead of 80 to spin up a
|
|
||||||
listener for the HTTP challenge.
|
|
||||||
|
|
||||||
#### alternative_tls_port
|
|
||||||
|
|
||||||
The alternate port to use for the ACME TLS-ALPN challenge; the system must forward 443 to this port for challenge to
|
|
||||||
succeed.
|
|
||||||
|
|
||||||
#### external_account
|
|
||||||
|
|
||||||
EAB (External Account Binding) contains information necessary to bind or map an ACME account to some other account known
|
|
||||||
by the CA.
|
|
||||||
|
|
||||||
External account bindings are used to associate an ACME account with an existing account in a non-ACME system, such as
|
|
||||||
a CA customer database.
|
|
||||||
|
|
||||||
To enable ACME account binding, the CA operating the ACME server needs to provide the ACME client with a MAC key and a
|
|
||||||
key identifier, using some mechanism outside of ACME. §7.3.4
|
|
||||||
|
|
||||||
#### external_account.key_id
|
|
||||||
|
|
||||||
The key identifier.
|
|
||||||
|
|
||||||
#### external_account.mac_key
|
|
||||||
|
|
||||||
The MAC key.
|
|
||||||
|
|
||||||
#### dns01_challenge
|
|
||||||
|
|
||||||
ACME DNS01 challenge field. If configured, other challenge methods will be disabled.
|
|
||||||
|
|
||||||
See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details.
|
|
||||||
|
|
||||||
#### key_type
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The private key type to generate for new certificates.
|
|
||||||
|
|
||||||
| Value | Type |
|
|
||||||
|------------|---------|
|
|
||||||
| `ed25519` | Ed25519 |
|
|
||||||
| `p256` | P-256 |
|
|
||||||
| `p384` | P-384 |
|
|
||||||
| `rsa2048` | RSA |
|
|
||||||
| `rsa4096` | RSA |
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
The tag of the upstream outbound.
|
|
||||||
|
|
||||||
All provider HTTP requests will use this outbound.
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.14.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [account_key](#account_key)
|
|
||||||
:material-plus: [key_type](#key_type)
|
|
||||||
:material-plus: [detour](#detour)
|
|
||||||
|
|
||||||
# ACME
|
|
||||||
|
|
||||||
!!! quote ""
|
|
||||||
|
|
||||||
需要 `with_acme` 构建标签。
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "acme",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"default_server_name": "",
|
|
||||||
"email": "",
|
|
||||||
"provider": "",
|
|
||||||
"account_key": "",
|
|
||||||
"disable_http_challenge": false,
|
|
||||||
"disable_tls_alpn_challenge": false,
|
|
||||||
"alternative_http_port": 0,
|
|
||||||
"alternative_tls_port": 0,
|
|
||||||
"external_account": {
|
|
||||||
"key_id": "",
|
|
||||||
"mac_key": ""
|
|
||||||
},
|
|
||||||
"dns01_challenge": {},
|
|
||||||
"key_type": "",
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
域名列表。
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
ACME 数据存储目录。
|
|
||||||
|
|
||||||
如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`。
|
|
||||||
|
|
||||||
#### default_server_name
|
|
||||||
|
|
||||||
如果 ClientHello 的 ServerName 字段为空,则选择证书时要使用的服务器名称。
|
|
||||||
|
|
||||||
#### email
|
|
||||||
|
|
||||||
创建或选择现有 ACME 服务器帐户时使用的电子邮件地址。
|
|
||||||
|
|
||||||
#### provider
|
|
||||||
|
|
||||||
要使用的 ACME CA 提供商。
|
|
||||||
|
|
||||||
| 值 | 提供商 |
|
|
||||||
|--------------------|---------------|
|
|
||||||
| `letsencrypt (默认)` | Let's Encrypt |
|
|
||||||
| `zerossl` | ZeroSSL |
|
|
||||||
| `https://...` | 自定义 |
|
|
||||||
|
|
||||||
当 `provider` 为 `zerossl` 时,如果设置了 `email` 且未设置 `external_account`,
|
|
||||||
sing-box 会自动向 ZeroSSL 请求 EAB 凭据。
|
|
||||||
|
|
||||||
当 `provider` 为 `zerossl` 时,必须至少设置 `external_account`、`email` 或 `account_key` 之一。
|
|
||||||
|
|
||||||
#### account_key
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
现有 ACME 帐户的 PEM 编码私钥。
|
|
||||||
|
|
||||||
#### disable_http_challenge
|
|
||||||
|
|
||||||
禁用所有 HTTP 质询。
|
|
||||||
|
|
||||||
#### disable_tls_alpn_challenge
|
|
||||||
|
|
||||||
禁用所有 TLS-ALPN 质询。
|
|
||||||
|
|
||||||
#### alternative_http_port
|
|
||||||
|
|
||||||
用于 ACME HTTP 质询的备用端口;如果非空,将使用此端口而不是 80 来启动 HTTP 质询的侦听器。
|
|
||||||
|
|
||||||
#### alternative_tls_port
|
|
||||||
|
|
||||||
用于 ACME TLS-ALPN 质询的备用端口; 系统必须将 443 转发到此端口以使质询成功。
|
|
||||||
|
|
||||||
#### external_account
|
|
||||||
|
|
||||||
EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。
|
|
||||||
|
|
||||||
外部帐户绑定用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。
|
|
||||||
|
|
||||||
为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4
|
|
||||||
|
|
||||||
#### external_account.key_id
|
|
||||||
|
|
||||||
密钥标识符。
|
|
||||||
|
|
||||||
#### external_account.mac_key
|
|
||||||
|
|
||||||
MAC 密钥。
|
|
||||||
|
|
||||||
#### dns01_challenge
|
|
||||||
|
|
||||||
ACME DNS01 质询字段。如果配置,将禁用其他质询方法。
|
|
||||||
|
|
||||||
参阅 [DNS01 质询字段](/zh/configuration/shared/dns01_challenge/)。
|
|
||||||
|
|
||||||
#### key_type
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
为新证书生成的私钥类型。
|
|
||||||
|
|
||||||
| 值 | 类型 |
|
|
||||||
|-----------|----------|
|
|
||||||
| `ed25519` | Ed25519 |
|
|
||||||
| `p256` | P-256 |
|
|
||||||
| `p384` | P-384 |
|
|
||||||
| `rsa2048` | RSA |
|
|
||||||
| `rsa4096` | RSA |
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
上游出站的标签。
|
|
||||||
|
|
||||||
所有提供者 HTTP 请求将使用此出站。
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
# Cloudflare Origin CA
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflare-origin-ca",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"api_token": "",
|
|
||||||
"origin_ca_key": "",
|
|
||||||
"request_type": "",
|
|
||||||
"requested_validity": 0,
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
List of domain names or wildcard domain names to include in the certificate.
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
Root directory used to store the issued certificate, private key, and metadata.
|
|
||||||
|
|
||||||
If empty, sing-box uses the same default data directory as the ACME certificate provider:
|
|
||||||
`$XDG_DATA_HOME/certmagic` or `$HOME/.local/share/certmagic`.
|
|
||||||
|
|
||||||
#### api_token
|
|
||||||
|
|
||||||
Cloudflare API token used to create the certificate.
|
|
||||||
|
|
||||||
Get or create one in [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens).
|
|
||||||
|
|
||||||
Requires the `Zone / SSL and Certificates / Edit` permission.
|
|
||||||
|
|
||||||
Conflict with `origin_ca_key`.
|
|
||||||
|
|
||||||
#### origin_ca_key
|
|
||||||
|
|
||||||
Cloudflare Origin CA Key.
|
|
||||||
|
|
||||||
Get it in [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens).
|
|
||||||
|
|
||||||
Conflict with `api_token`.
|
|
||||||
|
|
||||||
#### request_type
|
|
||||||
|
|
||||||
The signature type to request from Cloudflare.
|
|
||||||
|
|
||||||
| Value | Type |
|
|
||||||
|----------------------|-------------|
|
|
||||||
| `origin-rsa` | RSA |
|
|
||||||
| `origin-ecc` | ECDSA P-256 |
|
|
||||||
|
|
||||||
`origin-rsa` is used if empty.
|
|
||||||
|
|
||||||
#### requested_validity
|
|
||||||
|
|
||||||
The requested certificate validity in days.
|
|
||||||
|
|
||||||
Available values: `7`, `30`, `90`, `365`, `730`, `1095`, `5475`.
|
|
||||||
|
|
||||||
`5475` days (15 years) is used if empty.
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
The tag of the upstream outbound.
|
|
||||||
|
|
||||||
All provider HTTP requests will use this outbound.
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
# Cloudflare Origin CA
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "cloudflare-origin-ca",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"domain": [],
|
|
||||||
"data_directory": "",
|
|
||||||
"api_token": "",
|
|
||||||
"origin_ca_key": "",
|
|
||||||
"request_type": "",
|
|
||||||
"requested_validity": 0,
|
|
||||||
"detour": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### domain
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
要写入证书的域名或通配符域名列表。
|
|
||||||
|
|
||||||
#### data_directory
|
|
||||||
|
|
||||||
保存签发证书、私钥和元数据的根目录。
|
|
||||||
|
|
||||||
如果为空,sing-box 会使用与 ACME 证书提供者相同的默认数据目录:
|
|
||||||
`$XDG_DATA_HOME/certmagic` 或 `$HOME/.local/share/certmagic`。
|
|
||||||
|
|
||||||
#### api_token
|
|
||||||
|
|
||||||
用于创建证书的 Cloudflare API Token。
|
|
||||||
|
|
||||||
可在 [Cloudflare Dashboard > My Profile > API Tokens](https://dash.cloudflare.com/profile/api-tokens) 获取或创建。
|
|
||||||
|
|
||||||
需要 `Zone / SSL and Certificates / Edit` 权限。
|
|
||||||
|
|
||||||
与 `origin_ca_key` 冲突。
|
|
||||||
|
|
||||||
#### origin_ca_key
|
|
||||||
|
|
||||||
Cloudflare Origin CA Key。
|
|
||||||
|
|
||||||
可在 [Cloudflare Dashboard > My Profile > API Tokens > API Keys > Origin CA Key](https://dash.cloudflare.com/profile/api-tokens) 获取。
|
|
||||||
|
|
||||||
与 `api_token` 冲突。
|
|
||||||
|
|
||||||
#### request_type
|
|
||||||
|
|
||||||
向 Cloudflare 请求的签名类型。
|
|
||||||
|
|
||||||
| 值 | 类型 |
|
|
||||||
|----------------------|-------------|
|
|
||||||
| `origin-rsa` | RSA |
|
|
||||||
| `origin-ecc` | ECDSA P-256 |
|
|
||||||
|
|
||||||
如果为空,使用 `origin-rsa`。
|
|
||||||
|
|
||||||
#### requested_validity
|
|
||||||
|
|
||||||
请求的证书有效期,单位为天。
|
|
||||||
|
|
||||||
可用值:`7`、`30`、`90`、`365`、`730`、`1095`、`5475`。
|
|
||||||
|
|
||||||
如果为空,使用 `5475` 天(15 年)。
|
|
||||||
|
|
||||||
#### detour
|
|
||||||
|
|
||||||
上游出站的标签。
|
|
||||||
|
|
||||||
所有提供者 HTTP 请求将使用此出站。
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.14.0"
|
|
||||||
|
|
||||||
# Certificate Provider
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"certificate_providers": [
|
|
||||||
{
|
|
||||||
"type": "",
|
|
||||||
"tag": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
| Type | Format |
|
|
||||||
|--------|------------------|
|
|
||||||
| `acme` | [ACME](/configuration/shared/certificate-provider/acme) |
|
|
||||||
| `tailscale` | [Tailscale](/configuration/shared/certificate-provider/tailscale) |
|
|
||||||
| `cloudflare-origin-ca` | [Cloudflare Origin CA](/configuration/shared/certificate-provider/cloudflare-origin-ca) |
|
|
||||||
|
|
||||||
#### tag
|
|
||||||
|
|
||||||
The tag of the certificate provider.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.14.0 起"
|
|
||||||
|
|
||||||
# 证书提供者
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"certificate_providers": [
|
|
||||||
{
|
|
||||||
"type": "",
|
|
||||||
"tag": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
| 类型 | 格式 |
|
|
||||||
|--------|------------------|
|
|
||||||
| `acme` | [ACME](/zh/configuration/shared/certificate-provider/acme) |
|
|
||||||
| `tailscale` | [Tailscale](/zh/configuration/shared/certificate-provider/tailscale) |
|
|
||||||
| `cloudflare-origin-ca` | [Cloudflare Origin CA](/zh/configuration/shared/certificate-provider/cloudflare-origin-ca) |
|
|
||||||
|
|
||||||
#### tag
|
|
||||||
|
|
||||||
证书提供者的标签。
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user