Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
27ca71f
Add MAC and hostname rule items
nekohasekai Mar 3, 2026
a9decac
Add Android support for MAC and hostname rule items
nekohasekai Mar 4, 2026
92310b4
Add macOS support for MAC and hostname rule items
nekohasekai Mar 6, 2026
027f11c
documentation: Update descriptions for neighbor rules
nekohasekai Mar 6, 2026
47df58f
Refactor ACME support to certificate provider
nekohasekai Mar 23, 2026
ed165ee
Add BBR profile and hop interval randomization for Hysteria2
nekohasekai Mar 30, 2026
d89a712
platform: Add OOM Report & Crash Report
nekohasekai Apr 2, 2026
82d590a
Also enable certificate store by default on Apple platforms
nekohasekai Apr 7, 2026
144a3fb
Add evaluate DNS rule action and related rule items
nekohasekai Apr 7, 2026
df5f2cc
platform: Fix set local
nekohasekai Apr 7, 2026
f02472a
Fix deprecated warning double-formatting on localized clients
nekohasekai Apr 7, 2026
bc6d0fe
oom-killer: Free memory on pressure notification and use gradual inte…
nekohasekai Apr 7, 2026
34a5ae7
tools: Network Quality & STUN
nekohasekai Apr 8, 2026
48e18c7
platform: Fix darwin signal handler
nekohasekai Apr 9, 2026
5eec1a2
tools: Tailscale status
nekohasekai Apr 9, 2026
c669743
Revert "Also enable certificate store by default on Apple platforms"
nekohasekai Apr 9, 2026
84b5c2f
Fix rules lock
nekohasekai Apr 9, 2026
69b9198
Fix darwin local DNS transport
nekohasekai Apr 10, 2026
10f4481
tools: Tailscale status
nekohasekai Apr 10, 2026
eb2b0f4
Un-deprecate `ip_accept_any` DNS rule item
nekohasekai Apr 10, 2026
c884f9d
documentation: Fixes
nekohasekai Apr 10, 2026
7b70e4c
Add `package_name_regex` route, DNS and headless rule item
nekohasekai Apr 10, 2026
4413b5e
platform: Wrap command RPC error returns with E.Cause
nekohasekai Apr 10, 2026
2be423b
Fix lint errors
nekohasekai Apr 10, 2026
01207b2
Add cloudflared inbound
nekohasekai Apr 10, 2026
c680b4a
documentation: Fix missing update for `ip_version` and `query_type`
nekohasekai Apr 10, 2026
8389026
Fix stun test
nekohasekai Apr 10, 2026
70aaf29
Fix darwin cgo DNS again
nekohasekai Apr 10, 2026
3fe2687
Fix tailscale error
nekohasekai Apr 11, 2026
c6b3161
Add optimistic DNS cache
nekohasekai Apr 11, 2026
c902a9c
oom-killer: Record report before reset network
nekohasekai Apr 14, 2026
f02a178
Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines
nekohasekai Apr 14, 2026
0a904a5
Standardize hosts path
nekohasekai Apr 15, 2026
c17569e
Add TLS spoof support
nekohasekai Apr 15, 2026
65c79a4
Fix legacy rule-set download_detour blocked by empty direct check
nekohasekai Apr 15, 2026
222bb41
Reject pure-IP rule-set references without match_response
nekohasekai Apr 15, 2026
dd087f9
Fix use-after-free of pooled value buffers in bbolt Batch writes
nekohasekai Apr 15, 2026
4cc9404
Reject IP literal server name with TLS spoof
nekohasekai Apr 16, 2026
227df81
Fix macOS tlsspoof
nekohasekai Apr 17, 2026
4bc13fc
Scope HTTP/2 fallback and HTTP/3 broken state per authority
nekohasekai Apr 17, 2026
b96ef82
Defer implicit default HTTP client fallback to first use
nekohasekai Apr 17, 2026
32bcf1a
Strip EDNS padding from upstream DNS responses
nekohasekai Apr 17, 2026
b771448
Fix Apple TLS metadata capture
nekohasekai Apr 18, 2026
2e3d20d
Fix tls-spoof
nekohasekai Apr 17, 2026
fa791ae
Add search domain support for Tailscale DNS
nekohasekai Apr 20, 2026
87b8007
Log DNS optimistic background refresh outcomes
nekohasekai Apr 21, 2026
2640dd9
Fix Tailscale search domain response name mismatch
nekohasekai Apr 21, 2026
6904d91
Fix goroutine leak in networkquality tool
nekohasekai Apr 21, 2026
986c7f8
Add ACME profile support for IP address certificates
nekohasekai Mar 26, 2026
a20f5fd
Fix ACME HTTP-01 challenge for IPv6 literal addresses
nekohasekai Apr 21, 2026
cd4a4e6
platform: Improve oom-killer
nekohasekai Apr 21, 2026
a8bd2e2
Fix darwin cgo DNS again
nekohasekai Apr 22, 2026
76a2201
Bump version
nekohasekai Mar 7, 2026
0899318
Fix stderr deprecated manager
nekohasekai Apr 23, 2026
1955522
Improve UDP batch support
nekohasekai Apr 24, 2026
7ce5b6c
Add Windows TLS engine
nekohasekai Apr 24, 2026
75f0c0f
tun: Add compatibility with docker bridge
nekohasekai Apr 24, 2026
b2d5fb6
Bump version
nekohasekai Apr 24, 2026
794f691
Add reality ML-DSA-65 verification
oluceps 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
1 change: 1 addition & 0 deletions adapter/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type CertificateStore interface {
LifecycleService
Pool() *x509.CertPool
ExclusiveAnchors() bool
}

func RootPoolFromContext(ctx context.Context) *x509.CertPool {
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
}
17 changes: 17 additions & 0 deletions adapter/certificate_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build darwin && cgo

package adapter

import "unsafe"

type AppleAnchors interface {
Retain() AppleAnchors
Release()
// Ref returns the underlying CFArrayRef, or nil if the anchor set is empty.
Ref() unsafe.Pointer
}

type AppleCertificateStore interface {
CertificateStore
AppleAnchors() AppleAnchors
}
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