Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
2cf78e1
Add MAC and hostname rule items
nekohasekai Mar 3, 2026
c69d396
Add Android support for MAC and hostname rule items
nekohasekai Mar 4, 2026
11783aa
Add macOS support for MAC and hostname rule items
nekohasekai Mar 6, 2026
149a14e
documentation: Update descriptions for neighbor rules
nekohasekai Mar 6, 2026
fc1cd52
Refactor ACME support to certificate provider
nekohasekai Mar 23, 2026
fa242b0
Add BBR profile and hop interval randomization for Hysteria2
nekohasekai Mar 30, 2026
9e2b757
platform: Add OOM Report & Crash Report
nekohasekai Apr 2, 2026
67fc71b
Also enable certificate store by default on Apple platforms
nekohasekai Apr 7, 2026
9a452fa
Add evaluate DNS rule action and related rule items
nekohasekai Apr 7, 2026
972e5e6
platform: Fix set local
nekohasekai Apr 7, 2026
98c9634
Fix deprecated warning double-formatting on localized clients
nekohasekai Apr 7, 2026
3352a85
oom-killer: Free memory on pressure notification and use gradual inte…
nekohasekai Apr 7, 2026
13d012e
tools: Network Quality & STUN
nekohasekai Apr 8, 2026
25cbe69
platform: Fix darwin signal handler
nekohasekai Apr 9, 2026
1a33c01
tools: Tailscale status
nekohasekai Apr 9, 2026
7137583
Revert "Also enable certificate store by default on Apple platforms"
nekohasekai Apr 9, 2026
4ab33ab
Fix rules lock
nekohasekai Apr 9, 2026
24a4994
Fix darwin local DNS transport
nekohasekai Apr 10, 2026
6c53404
tools: Tailscale status
nekohasekai Apr 10, 2026
523ed6e
Un-deprecate `ip_accept_any` DNS rule item
nekohasekai Apr 10, 2026
5af95ba
documentation: Fixes
nekohasekai Apr 10, 2026
2e80ba6
Add `package_name_regex` route, DNS and headless rule item
nekohasekai Apr 10, 2026
57896fc
platform: Wrap command RPC error returns with E.Cause
nekohasekai Apr 10, 2026
764e55e
Fix lint errors
nekohasekai Apr 10, 2026
6c62ca3
Add cloudflared inbound
nekohasekai Apr 10, 2026
a7c52dc
documentation: Fix missing update for `ip_version` and `query_type`
nekohasekai Apr 10, 2026
a604bef
Fix stun test
nekohasekai Apr 10, 2026
41ea60f
Fix darwin cgo DNS again
nekohasekai Apr 10, 2026
088adce
Fix tailscale error
nekohasekai Apr 11, 2026
a86337b
Add optimistic DNS cache
nekohasekai Apr 11, 2026
f5d045e
oom-killer: Record report before reset network
nekohasekai Apr 14, 2026
2b53c64
Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines
nekohasekai Apr 14, 2026
90a850b
Standardize hosts path
nekohasekai Apr 15, 2026
345311e
Add TLS spoof support
nekohasekai Apr 15, 2026
a5d8b14
Fix legacy rule-set download_detour blocked by empty direct check
nekohasekai Apr 15, 2026
5c704df
Reject pure-IP rule-set references without match_response
nekohasekai Apr 15, 2026
55b85bf
Fix use-after-free of pooled value buffers in bbolt Batch writes
nekohasekai Apr 15, 2026
ea9617b
Reject IP literal server name with TLS spoof
nekohasekai Apr 16, 2026
c16ae33
Fix macOS tlsspoof
nekohasekai Apr 17, 2026
3a1ac53
Scope HTTP/2 fallback and HTTP/3 broken state per authority
nekohasekai Apr 17, 2026
0b04ac8
Defer implicit default HTTP client fallback to first use
nekohasekai Apr 17, 2026
8f9df0d
Strip EDNS padding from upstream DNS responses
nekohasekai Apr 17, 2026
cf4b60e
Fix Apple TLS metadata capture
nekohasekai Apr 18, 2026
ab9fb37
Fix tls-spoof
nekohasekai Apr 17, 2026
2c2a406
Add search domain support for Tailscale DNS
nekohasekai Apr 20, 2026
3367bde
Bump version
nekohasekai Mar 7, 2026
a3d39e5
add tag "name" 1exmpl
Pushkinmazila2 Apr 20, 2026
bde3e28
Bump version
nekohasekai Apr 21, 2026
4c346c2
Add MAC and hostname rule items
nekohasekai Mar 3, 2026
dc3f220
Add Android support for MAC and hostname rule items
nekohasekai Mar 4, 2026
61fa4ab
Add macOS support for MAC and hostname rule items
nekohasekai Mar 6, 2026
f8f0a00
documentation: Update descriptions for neighbor rules
nekohasekai Mar 6, 2026
271231e
Refactor ACME support to certificate provider
nekohasekai Mar 23, 2026
20be2c8
Add BBR profile and hop interval randomization for Hysteria2
nekohasekai Mar 30, 2026
40a36d6
platform: Add OOM Report & Crash Report
nekohasekai Apr 2, 2026
0802463
Also enable certificate store by default on Apple platforms
nekohasekai Apr 7, 2026
f93003f
Add evaluate DNS rule action and related rule items
nekohasekai Apr 7, 2026
48e69ef
platform: Fix set local
nekohasekai Apr 7, 2026
86d0dbe
Fix deprecated warning double-formatting on localized clients
nekohasekai Apr 7, 2026
82a233d
oom-killer: Free memory on pressure notification and use gradual inte…
nekohasekai Apr 7, 2026
5125948
tools: Network Quality & STUN
nekohasekai Apr 8, 2026
0b0cfdd
platform: Fix darwin signal handler
nekohasekai Apr 9, 2026
16c91ec
tools: Tailscale status
nekohasekai Apr 9, 2026
0ee25fd
Revert "Also enable certificate store by default on Apple platforms"
nekohasekai Apr 9, 2026
ec3d303
Fix rules lock
nekohasekai Apr 9, 2026
12e966e
Fix darwin local DNS transport
nekohasekai Apr 10, 2026
34d3ace
tools: Tailscale status
nekohasekai Apr 10, 2026
345d8f2
Un-deprecate `ip_accept_any` DNS rule item
nekohasekai Apr 10, 2026
994aa39
documentation: Fixes
nekohasekai Apr 10, 2026
d39dc82
Add `package_name_regex` route, DNS and headless rule item
nekohasekai Apr 10, 2026
a492ff9
platform: Wrap command RPC error returns with E.Cause
nekohasekai Apr 10, 2026
dc1a007
Fix lint errors
nekohasekai Apr 10, 2026
08c4f2f
Add cloudflared inbound
nekohasekai Apr 10, 2026
0484a10
documentation: Fix missing update for `ip_version` and `query_type`
nekohasekai Apr 10, 2026
582d77c
Fix stun test
nekohasekai Apr 10, 2026
57aa063
Fix darwin cgo DNS again
nekohasekai Apr 10, 2026
e2f349f
Fix tailscale error
nekohasekai Apr 11, 2026
8d54ec7
Add optimistic DNS cache
nekohasekai Apr 11, 2026
aeb3533
oom-killer: Record report before reset network
nekohasekai Apr 14, 2026
fb9f89e
Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines
nekohasekai Apr 14, 2026
b2198de
Standardize hosts path
nekohasekai Apr 15, 2026
a24cbae
Add TLS spoof support
nekohasekai Apr 15, 2026
c2600c0
Fix legacy rule-set download_detour blocked by empty direct check
nekohasekai Apr 15, 2026
b3e1036
Reject pure-IP rule-set references without match_response
nekohasekai Apr 15, 2026
7ab4b73
Fix use-after-free of pooled value buffers in bbolt Batch writes
nekohasekai Apr 15, 2026
2dbdade
Reject IP literal server name with TLS spoof
nekohasekai Apr 16, 2026
9dbb044
Fix macOS tlsspoof
nekohasekai Apr 17, 2026
b304c56
Scope HTTP/2 fallback and HTTP/3 broken state per authority
nekohasekai Apr 17, 2026
6d4660e
Defer implicit default HTTP client fallback to first use
nekohasekai Apr 17, 2026
cc0572b
Strip EDNS padding from upstream DNS responses
nekohasekai Apr 17, 2026
f00ba20
Fix Apple TLS metadata capture
nekohasekai Apr 18, 2026
db0829b
Fix tls-spoof
nekohasekai Apr 17, 2026
9f04cd7
Add search domain support for Tailscale DNS
nekohasekai Apr 20, 2026
80004dd
Log DNS optimistic background refresh outcomes
nekohasekai Apr 21, 2026
f5ad218
Fix Tailscale search domain response name mismatch
nekohasekai Apr 21, 2026
38fca59
Fix goroutine leak in networkquality tool
nekohasekai Apr 21, 2026
112164e
Add ACME profile support for IP address certificates
nekohasekai Mar 26, 2026
6888d5c
Fix ACME HTTP-01 challenge for IPv6 literal addresses
nekohasekai Apr 21, 2026
6170f83
platform: Improve oom-killer
nekohasekai Apr 21, 2026
99e1ffe
Bump version
nekohasekai Mar 7, 2026
938b2f1
Merge branch 'testing' into testing
Pushkinmazila2 Apr 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
55 changes: 55 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Test

on:
push:
branches:
- stable
- testing
- unstable
paths-ignore:
- '**.md'
- '.github/**'
- '!.github/workflows/test.yml'
pull_request:
branches:
- stable
- testing
- unstable

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}-${{ inputs.build }}
cancel-in-progress: true

jobs:
test:
name: Test
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-latest
go:
- ~1.24
- ~1.25
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Set build tags and ldflags
shell: bash
run: |
echo "BUILD_TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS)" >> "$GITHUB_ENV"
echo "LDFLAGS_SHARED=$(cat release/LDFLAGS)" >> "$GITHUB_ENV"
- name: Test (unix)
if: matrix.os != 'windows-latest'
run: go test -v -exec sudo -tags "$BUILD_TAGS" -ldflags "$LDFLAGS_SHARED" ./...
- name: Test (windows)
if: matrix.os == 'windows-latest'
shell: bash
run: go test -v -tags "$BUILD_TAGS" -ldflags "$LDFLAGS_SHARED" ./...
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ linters:
enable:
- govet
- ineffassign
- paralleltest
- staticcheck
settings:
staticcheck:
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ lint:
GOOS=android golangci-lint run ./...
GOOS=windows golangci-lint run ./...
GOOS=darwin golangci-lint run ./...
GOOS=freebsd golangci-lint run ./...
# GOOS=freebsd golangci-lint run ./...

lint_install:
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
Expand Down
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
}
Loading