Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
d3575cc
Add MAC and hostname rule items
nekohasekai Mar 3, 2026
c57e864
Add Android support for MAC and hostname rule items
nekohasekai Mar 4, 2026
1c02d7e
Add macOS support for MAC and hostname rule items
nekohasekai Mar 6, 2026
4f6d0ff
documentation: Update descriptions for neighbor rules
nekohasekai Mar 6, 2026
83fa58f
Refactor ACME support to certificate provider
nekohasekai Mar 23, 2026
0155352
Add BBR profile and hop interval randomization for Hysteria2
nekohasekai Mar 30, 2026
26ddb92
platform: Add OOM Report & Crash Report
nekohasekai Apr 2, 2026
ff94634
Also enable certificate store by default on Apple platforms
nekohasekai Apr 7, 2026
e1a7ab3
Add evaluate DNS rule action and related rule items
nekohasekai Apr 7, 2026
9e8f13c
platform: Fix set local
nekohasekai Apr 7, 2026
0f6d110
Fix deprecated warning double-formatting on localized clients
nekohasekai Apr 7, 2026
5ee373c
oom-killer: Free memory on pressure notification and use gradual inte…
nekohasekai Apr 7, 2026
0b8f380
tools: Network Quality & STUN
nekohasekai Apr 8, 2026
ccc2742
platform: Fix darwin signal handler
nekohasekai Apr 9, 2026
ce6d683
tools: Tailscale status
nekohasekai Apr 9, 2026
a7b02f9
Revert "Also enable certificate store by default on Apple platforms"
nekohasekai Apr 9, 2026
ec75e5e
Fix rules lock
nekohasekai Apr 9, 2026
eade677
Fix darwin local DNS transport
nekohasekai Apr 10, 2026
524578a
tools: Tailscale status
nekohasekai Apr 10, 2026
e75e1c9
Un-deprecate `ip_accept_any` DNS rule item
nekohasekai Apr 10, 2026
e714efa
documentation: Fixes
nekohasekai Apr 10, 2026
684ba79
Add `package_name_regex` route, DNS and headless rule item
nekohasekai Apr 10, 2026
012db0e
platform: Wrap command RPC error returns with E.Cause
nekohasekai Apr 10, 2026
5779b46
Fix lint errors
nekohasekai Apr 10, 2026
08260fa
Add cloudflared inbound
nekohasekai Apr 10, 2026
3828b4f
documentation: Fix missing update for `ip_version` and `query_type`
nekohasekai Apr 10, 2026
104c7ae
Fix stun test
nekohasekai Apr 10, 2026
8fb0191
Fix darwin cgo DNS again
nekohasekai Apr 10, 2026
e6f5c24
Fix tailscale error
nekohasekai Apr 11, 2026
0319b22
Add optimistic DNS cache
nekohasekai Apr 11, 2026
0d7230e
oom-killer: Record report before reset network
nekohasekai Apr 14, 2026
5a618c6
Refactor: HTTP clients, unified HTTP2/QUIC options, Apple engines
nekohasekai Apr 14, 2026
6ef3804
Standardize hosts path
nekohasekai Apr 15, 2026
08ce083
Add TLS spoof support
nekohasekai Apr 15, 2026
9bee532
Fix legacy rule-set download_detour blocked by empty direct check
nekohasekai Apr 15, 2026
90a642e
Reject pure-IP rule-set references without match_response
nekohasekai Apr 15, 2026
fee6afd
Fix use-after-free of pooled value buffers in bbolt Batch writes
nekohasekai Apr 15, 2026
4a68bc9
Reject IP literal server name with TLS spoof
nekohasekai Apr 16, 2026
9bffb64
Fix macOS tlsspoof
nekohasekai Apr 17, 2026
4b15fcc
Scope HTTP/2 fallback and HTTP/3 broken state per authority
nekohasekai Apr 17, 2026
32f56cc
Defer implicit default HTTP client fallback to first use
nekohasekai Apr 17, 2026
3c9c4ae
Strip EDNS padding from upstream DNS responses
nekohasekai Apr 17, 2026
a8b8f15
Fix Apple TLS metadata capture
nekohasekai Apr 18, 2026
6ecd3de
Fix tls-spoof
nekohasekai Apr 17, 2026
b358fdd
Add search domain support for Tailscale DNS
nekohasekai Apr 20, 2026
6b51bd6
Log DNS optimistic background refresh outcomes
nekohasekai Apr 21, 2026
acfb90a
Fix Tailscale search domain response name mismatch
nekohasekai Apr 21, 2026
5cd4214
Fix goroutine leak in networkquality tool
nekohasekai Apr 21, 2026
817f9c6
Add ACME profile support for IP address certificates
nekohasekai Mar 26, 2026
0f719a0
Fix ACME HTTP-01 challenge for IPv6 literal addresses
nekohasekai Apr 21, 2026
fbc1ca3
platform: Improve oom-killer
nekohasekai Apr 21, 2026
d06d3bf
Fix darwin cgo DNS again
nekohasekai Apr 22, 2026
968bbd8
Fix stderr deprecated manager
nekohasekai Apr 23, 2026
3d3c8a2
Improve UDP batch support
nekohasekai Apr 24, 2026
1610c18
Add Windows TLS engine
nekohasekai Apr 24, 2026
d9c9edd
tun: Add compatibility with docker bridge
nekohasekai Apr 24, 2026
fd1624e
Preserve comments between formatting
nekohasekai Apr 28, 2026
b418ee1
Improve oom-killer
nekohasekai Apr 28, 2026
78f502d
tun: Add read waiter support for gVisor conn
nekohasekai Apr 28, 2026
610aa94
ssh: Add cipher, MAC, and key exchange configuration
nekohasekai Apr 28, 2026
60a5f87
Cleanup compatible code for legacy Go
nekohasekai Apr 28, 2026
1bad8d8
dns: Add timeout configuration
nekohasekai Apr 28, 2026
abedea4
Bump version
nekohasekai Mar 7, 2026
ddc2035
sing: Fix contentjson crash
nekohasekai Apr 28, 2026
2a533b0
Fix tailscale start dependencies
nekohasekai Apr 28, 2026
e171852
dns: Add neighbor-based hostname resolution to local server
nekohasekai Apr 29, 2026
fdec2fe
dns: Add preferred_by rule item
nekohasekai Apr 29, 2026
98b2122
dns: Add mDNS server
nekohasekai Apr 30, 2026
85889fc
cronet: Remove additional QUIC certificate check
nekohasekai Apr 30, 2026
bb60b58
Allow customizing TUN DNS mode and hijack interface DNS by default
nekohasekai May 2, 2026
772ab58
Update naiveproxy to v148.0.7778.96-1
nekohasekai May 2, 2026
48c65a2
Add more spoof method
macronut Apr 29, 2026
9ee56ae
Bump version
nekohasekai Apr 28, 2026
0ee7592
release: Add replace_macos_standalone make target
nekohasekai May 3, 2026
246d6a5
Fix cronet close
nekohasekai May 3, 2026
793b81c
feat: DirectRoute support for UDP/TCP traceroute
xdqi May 4, 2026
d914eea
feat: configurable traceroute hop limit and TTL decrement
xdqi May 4, 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
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/CRONET_GO_VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e4926ba205fae5351e3d3eeafff7e7029654424a
359394912c9e1c1ec528fccb6ee05ce05fe9a17f
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
19 changes: 18 additions & 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 Expand Up @@ -174,14 +174,31 @@ upload_macos_pkg:
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"

replace_macos_pkg:
mkdir -p dist/SFM
cp ../sing-box-for-apple/build/SFM-Apple.pkg "dist/SFM/SFM-${VERSION}-Apple.pkg"
cp ../sing-box-for-apple/build/SFM-Intel.pkg "dist/SFM/SFM-${VERSION}-Intel.pkg"
cp ../sing-box-for-apple/build/SFM-Universal.pkg "dist/SFM/SFM-${VERSION}-Universal.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Apple.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Intel.pkg"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}-Universal.pkg"

upload_macos_dsyms:
mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
ghr --replace --draft --prerelease "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"

replace_macos_dsyms:
mkdir -p dist/SFM
cd ../sing-box-for-apple/build/SFM.System-universal.xcarchive && zip -r SFM.dSYMs.zip dSYMs
cp ../sing-box-for-apple/build/SFM.System-universal.xcarchive/SFM.dSYMs.zip "dist/SFM/SFM-${VERSION}.dSYMs.zip"
ghr --replace "v${VERSION}" "dist/SFM/SFM-${VERSION}.dSYMs.zip"

release_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms

replace_macos_standalone: build_macos_pkg notarize_macos_pkg upload_macos_pkg upload_macos_dsyms

build_tvos:
cd ../sing-box-for-apple && \
rm -rf build/SFT.xcarchive && \
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
}
Loading