Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0d1ce79
Add MAC and hostname rule items
nekohasekai Mar 3, 2026
b28083b
Add Android support for MAC and hostname rule items
nekohasekai Mar 4, 2026
9805db3
Add macOS support for MAC and hostname rule items
nekohasekai Mar 6, 2026
c120382
documentation: Update descriptions for neighbor rules
nekohasekai Mar 6, 2026
f8b0579
cronet-go: Update chromium to 145.0.7632.159
nekohasekai Mar 11, 2026
e2727d9
Refactor ACME support to certificate provider
nekohasekai Mar 23, 2026
ddc181f
Add BBR profile and hop interval randomization for Hysteria2
nekohasekai Mar 30, 2026
574852b
platform: Add OOM Report & Crash Rerport
nekohasekai Apr 2, 2026
58d22df
Also enable certificate store by default on Apple platforms
nekohasekai Apr 7, 2026
d3fc58c
Add evaluate DNS rule action and related rule items
nekohasekai Apr 7, 2026
5d254d9
platform: Fix set local
nekohasekai Apr 7, 2026
51166f4
Fix deprecated warning double-formatting on localized clients
nekohasekai Apr 7, 2026
ac9c0e7
oom-killer: Free memory on pressure notification and use gradual inte…
nekohasekai Apr 7, 2026
a241706
tools: Network Quality & STUN
nekohasekai Apr 8, 2026
cfcc766
platform: Fix darwin signal handler
nekohasekai Apr 9, 2026
5e7e58f
tools: Tailscale status
nekohasekai Apr 9, 2026
6dfab92
Revert "Also enable certificate store by default on Apple platforms"
nekohasekai Apr 9, 2026
a48fd10
Fix rules lock
nekohasekai Apr 9, 2026
abd6baf
Fix darwin local DNS transport
nekohasekai Apr 10, 2026
57039ac
tools: Tailscale status
nekohasekai Apr 10, 2026
ddcaf04
Un-deprecate `ip_accept_any` DNS rule item
nekohasekai Apr 10, 2026
e0696f5
documentation: Fixes
nekohasekai Apr 10, 2026
6c7fb1d
Add `package_name_regex` route, DNS and headless rule item
nekohasekai Apr 10, 2026
97f4723
platform: Wrap command RPC error returns with E.Cause
nekohasekai Apr 10, 2026
6da0aa0
Fix lint errors
nekohasekai Apr 10, 2026
5cdf1aa
Add cloudflared inbound
nekohasekai Apr 10, 2026
c0d9551
documentation: Fix missing update for `ip_version` and `query_type`
nekohasekai Apr 10, 2026
e6427e8
Bump version
nekohasekai Mar 7, 2026
a5c0112
Update naiveproxy to v147.0.7727.49-1
nekohasekai Apr 10, 2026
b7e1a14
Fix darwin cgo DNS again
nekohasekai Apr 10, 2026
6ba7a6f
Fix tailscale error
nekohasekai Apr 11, 2026
ebd31ca
Add optimistic DNS cache
nekohasekai Apr 11, 2026
9675b09
Bump version
nekohasekai Apr 11, 2026
182c51a
Add new rule type ipset
major1201 Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CRONET_GO_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2fef65f9dba90ddb89a87d00a6eb6165487c10c1
335e5bef5d88fc4474c9a70b865561f45a67de83
21 changes: 21 additions & 0 deletions adapter/certificate/adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package certificate

type Adapter struct {
providerType string
providerTag string
}

func NewAdapter(providerType string, providerTag string) Adapter {
return Adapter{
providerType: providerType,
providerTag: providerTag,
}
}

func (a *Adapter) Type() string {
return a.providerType
}

func (a *Adapter) Tag() string {
return a.providerTag
}
158 changes: 158 additions & 0 deletions adapter/certificate/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package certificate

import (
"context"
"os"
"sync"
"time"

"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)

var _ adapter.CertificateProviderManager = (*Manager)(nil)

type Manager struct {
logger log.ContextLogger
registry adapter.CertificateProviderRegistry
access sync.Mutex
started bool
stage adapter.StartStage
providers []adapter.CertificateProviderService
providerByTag map[string]adapter.CertificateProviderService
}

func NewManager(logger log.ContextLogger, registry adapter.CertificateProviderRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
providerByTag: make(map[string]adapter.CertificateProviderService),
}
}

func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
providers := m.providers
m.access.Unlock()
for _, provider := range providers {
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err := adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return nil
}

func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
providers := m.providers
m.providers = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, provider := range providers {
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
m.logger.Trace("close ", name)
startTime := time.Now()
monitor.Start("close ", name)
err = E.Append(err, provider.Close(), func(err error) error {
return E.Cause(err, "close ", name)
})
monitor.Finish()
m.logger.Trace("close ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
return err
}

func (m *Manager) CertificateProviders() []adapter.CertificateProviderService {
m.access.Lock()
defer m.access.Unlock()
return m.providers
}

func (m *Manager) Get(tag string) (adapter.CertificateProviderService, bool) {
m.access.Lock()
provider, found := m.providerByTag[tag]
m.access.Unlock()
return provider, found
}

func (m *Manager) Remove(tag string) error {
m.access.Lock()
provider, found := m.providerByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.providerByTag, tag)
index := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
return it == provider
})
if index == -1 {
panic("invalid certificate provider index")
}
m.providers = append(m.providers[:index], m.providers[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return provider.Close()
}
return nil
}

func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error {
provider, err := m.registry.Create(ctx, logger, tag, providerType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
name := "certificate-provider/" + provider.Type() + "[" + provider.Tag() + "]"
for _, stage := range adapter.ListStartStages {
m.logger.Trace(stage, " ", name)
startTime := time.Now()
err = adapter.LegacyStart(provider, stage)
if err != nil {
return E.Cause(err, stage, " ", name)
}
m.logger.Trace(stage, " ", name, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
}
}
if existsProvider, loaded := m.providerByTag[tag]; loaded {
if m.started {
err = existsProvider.Close()
if err != nil {
return E.Cause(err, "close certificate-provider/", existsProvider.Type(), "[", existsProvider.Tag(), "]")
}
}
existsIndex := common.Index(m.providers, func(it adapter.CertificateProviderService) bool {
return it == existsProvider
})
if existsIndex == -1 {
panic("invalid certificate provider index")
}
m.providers = append(m.providers[:existsIndex], m.providers[existsIndex+1:]...)
}
m.providers = append(m.providers, provider)
m.providerByTag[tag] = provider
return nil
}
72 changes: 72 additions & 0 deletions adapter/certificate/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package certificate

import (
"context"
"sync"

"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)

type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.CertificateProviderService, error)

func Register[Options any](registry *Registry, providerType string, constructor ConstructorFunc[Options]) {
registry.register(providerType, func() any {
return new(Options)
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.CertificateProviderService, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
})
}

var _ adapter.CertificateProviderRegistry = (*Registry)(nil)

type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.CertificateProviderService, error)
)

type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}

func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}

func (m *Registry) CreateOptions(providerType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[providerType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}

func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (adapter.CertificateProviderService, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[providerType]
if !loaded {
return nil, E.New("certificate provider type not found: " + providerType)
}
return constructor(ctx, logger, tag, options)
}

func (m *Registry) register(providerType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[providerType] = optionsConstructor
m.constructor[providerType] = constructor
}
38 changes: 38 additions & 0 deletions adapter/certificate_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package adapter

import (
"context"
"crypto/tls"

"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)

type CertificateProvider interface {
GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
}

type ACMECertificateProvider interface {
CertificateProvider
GetACMENextProtos() []string
}

type CertificateProviderService interface {
Lifecycle
Type() string
Tag() string
CertificateProvider
}

type CertificateProviderRegistry interface {
option.CertificateProviderOptionsRegistry
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) (CertificateProviderService, error)
}

type CertificateProviderManager interface {
Lifecycle
CertificateProviders() []CertificateProviderService
Get(tag string) (CertificateProviderService, bool)
Remove(tag string) error
Create(ctx context.Context, logger log.ContextLogger, tag string, providerType string, options any) error
}
41 changes: 23 additions & 18 deletions adapter/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package adapter
import (
"context"
"net/netip"
"time"

C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
Expand All @@ -25,18 +26,19 @@ type DNSRouter interface {

type DNSClient interface {
Start()
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) (*dns.Msg, error)
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(response *dns.Msg) bool) ([]netip.Addr, error)
ClearCache()
}

type DNSQueryOptions struct {
Transport DNSTransport
Strategy C.DomainStrategy
LookupStrategy C.DomainStrategy
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
Transport DNSTransport
Strategy C.DomainStrategy
LookupStrategy C.DomainStrategy
DisableCache bool
DisableOptimisticCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
}

func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
Expand All @@ -49,11 +51,12 @@ func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptio
return nil, E.New("domain resolver not found: " + options.Server)
}
return &DNSQueryOptions{
Transport: transport,
Strategy: C.DomainStrategy(options.Strategy),
DisableCache: options.DisableCache,
RewriteTTL: options.RewriteTTL,
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
Transport: transport,
Strategy: C.DomainStrategy(options.Strategy),
DisableCache: options.DisableCache,
DisableOptimisticCache: options.DisableOptimisticCache,
RewriteTTL: options.RewriteTTL,
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
}, nil
}

Expand All @@ -63,6 +66,13 @@ type RDRCStore interface {
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
}

type DNSCacheStore interface {
LoadDNSCache(transportName string, qName string, qType uint16) (rawMessage []byte, expireAt time.Time, loaded bool)
SaveDNSCache(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time) error
SaveDNSCacheAsync(transportName string, qName string, qType uint16, rawMessage []byte, expireAt time.Time, logger logger.Logger)
ClearDNSCache() error
}

type DNSTransport interface {
Lifecycle
Type() string
Expand All @@ -72,11 +82,6 @@ type DNSTransport interface {
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
}

type LegacyDNSTransport interface {
LegacyStrategy() C.DomainStrategy
LegacyClientSubnet() netip.Prefix
}

type DNSTransportRegistry interface {
option.DNSTransportOptionsRegistry
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
Expand Down
6 changes: 6 additions & 0 deletions adapter/experimental.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ type CacheFile interface {
StoreRDRC() bool
RDRCStore

StoreDNS() bool
DNSCacheStore

SetDisableExpire(disableExpire bool)
SetOptimisticTimeout(timeout time.Duration)

LoadMode() string
StoreMode(mode string) error
LoadSelected(group string) string
Expand Down
Loading