Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
72a039a
Add MAC and hostname rule items
nekohasekai Mar 3, 2026
df806c9
Add Android support for MAC and hostname rule items
nekohasekai Mar 4, 2026
129396f
Add macOS support for MAC and hostname rule items
nekohasekai Mar 6, 2026
284c4d8
documentation: Update descriptions for neighbor rules
nekohasekai Mar 6, 2026
986de07
Refactor ACME support to certificate provider
nekohasekai Mar 23, 2026
a456393
Add BBR profile and hop interval randomization for Hysteria2
nekohasekai Mar 30, 2026
d4dacfc
platform: Add OOM Report & Crash Report
nekohasekai Apr 2, 2026
e4450ec
Also enable certificate store by default on Apple platforms
nekohasekai Apr 7, 2026
c208f4a
Add evaluate DNS rule action and related rule items
nekohasekai Apr 7, 2026
6912e27
platform: Fix set local
nekohasekai Apr 7, 2026
16e81c4
Fix deprecated warning double-formatting on localized clients
nekohasekai Apr 7, 2026
fcb43d7
oom-killer: Free memory on pressure notification and use gradual inte…
nekohasekai Apr 7, 2026
3769ad4
tools: Network Quality & STUN
nekohasekai Apr 8, 2026
47f09c9
platform: Fix darwin signal handler
nekohasekai Apr 9, 2026
d70bfb9
tools: Tailscale status
nekohasekai Apr 9, 2026
90c342e
Revert "Also enable certificate store by default on Apple platforms"
nekohasekai Apr 9, 2026
f72c4c1
Fix rules lock
nekohasekai Apr 9, 2026
32a34ef
Fix darwin local DNS transport
nekohasekai Apr 10, 2026
c80f4ad
tools: Tailscale status
nekohasekai Apr 10, 2026
e80b662
Un-deprecate `ip_accept_any` DNS rule item
nekohasekai Apr 10, 2026
5393025
documentation: Fixes
nekohasekai Apr 10, 2026
49704b4
Add `package_name_regex` route, DNS and headless rule item
nekohasekai Apr 10, 2026
1b5fd56
platform: Wrap command RPC error returns with E.Cause
nekohasekai Apr 10, 2026
34fdc29
Fix lint errors
nekohasekai Apr 10, 2026
00adb23
Add cloudflared inbound
nekohasekai Apr 10, 2026
83c6793
documentation: Fix missing update for `ip_version` and `query_type`
nekohasekai Apr 10, 2026
bf3454e
Fix stun test
nekohasekai Apr 10, 2026
845e94d
Fix darwin cgo DNS again
nekohasekai Apr 10, 2026
d89783b
Fix tailscale error
nekohasekai Apr 11, 2026
ca63351
Add optimistic DNS cache
nekohasekai Apr 11, 2026
a6fd060
oom-killer: Record report before reset network
nekohasekai Apr 14, 2026
4fdfda2
Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines
nekohasekai Apr 14, 2026
baa5556
Standardize hosts path
nekohasekai Apr 15, 2026
f2cf356
Add TLS spoof support
nekohasekai Apr 15, 2026
6288bb9
Fix legacy rule-set download_detour blocked by empty direct check
nekohasekai Apr 15, 2026
147cc00
Reject pure-IP rule-set references without match_response
nekohasekai Apr 15, 2026
ed2ca07
Fix use-after-free of pooled value buffers in bbolt Batch writes
nekohasekai Apr 15, 2026
78aef89
Fix cloudflared quic
nekohasekai Apr 16, 2026
d1cb61e
Reject IP literal server name with TLS spoof
nekohasekai Apr 16, 2026
031a147
Fix macOS tlsspoof
nekohasekai Apr 17, 2026
594a9cd
Scope HTTP/2 fallback and HTTP/3 broken state per authority
nekohasekai Apr 17, 2026
b9235e9
Bump version
nekohasekai Mar 7, 2026
59126c9
Defer implicit default HTTP client fallback to first use
nekohasekai Apr 17, 2026
e98bf30
Strip EDNS padding from upstream DNS responses
nekohasekai Apr 17, 2026
4c01502
Fix Apple TLS metadata capture
nekohasekai Apr 18, 2026
beaa6bd
Fix tls-spoof
nekohasekai Apr 17, 2026
baaedc7
sing: Fix interface finder
nekohasekai Apr 18, 2026
6ae8461
Bump version
nekohasekai Apr 18, 2026
df49a93
sing: Fix registry leak
nekohasekai Apr 19, 2026
e1d459c
Fix ACME HTTP-01 challenge for IPv6 literal addresses
nekohasekai Mar 28, 2026
86c44a3
Add ACME profile support for IP address certificates
nekohasekai Mar 26, 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
Loading