Compare commits

..

67 Commits

Author SHA1 Message Date
世界
43a3beb988 documentation: Bump version 2025-10-10 15:18:28 +08:00
世界
b156d08a96 Fix missing mTLS support in client options 2025-10-10 15:18:28 +08:00
世界
2df511690d Add curve preferences, pinned public key SHA256 and mTLS for TLS options 2025-10-10 15:18:28 +08:00
世界
ceba17dd6e Fix WireGuard input packet 2025-10-10 15:18:28 +08:00
世界
afb49d7d98 Update tfo-go to latest 2025-10-10 15:18:27 +08:00
世界
5842db34a7 Remove compatibility codes 2025-10-10 15:18:27 +08:00
世界
d9a1c6b7bd Do not use linkname by default to simplify debugging 2025-10-10 15:18:26 +08:00
世界
1c0cc689b3 documentation: Update chinese translations 2025-10-10 15:18:26 +08:00
世界
8986fa0eb9 Update quic-go to v0.54.0 2025-10-10 15:18:26 +08:00
世界
2559568e1b Update WireGuard and Tailscale 2025-10-10 15:18:25 +08:00
世界
8d6b2f8949 Fix preConnectionCopy 2025-10-10 15:18:25 +08:00
世界
9e542f41cc Fix ping domain 2025-10-10 15:18:25 +08:00
世界
e70094510d release: Fix linux build 2025-10-10 15:18:25 +08:00
世界
2ad3c70dd6 Improve ktls rx error handling 2025-10-10 15:18:25 +08:00
世界
87084ea354 Improve compatibility for kTLS 2025-10-10 15:18:24 +08:00
世界
f453091489 ktls: Add warning for inappropriate scenarios 2025-10-10 15:18:24 +08:00
世界
692ea1897c Add support for kTLS
Reference: https://gitlab.com/go-extension/tls
2025-10-10 15:18:23 +08:00
世界
f875c8bc2e Add proxy support for ICMP echo request 2025-10-10 15:18:23 +08:00
世界
bf956d8937 Fix resolve using resolved 2025-10-10 15:18:23 +08:00
世界
6af6a176bf documentation: Update behavior of local DNS server on darwin 2025-10-10 15:18:23 +08:00
世界
c80b439abe Stop using DHCP on iOS and tvOS
We do not have the `com.apple.developer.networking.multicast` entitlement and are unable to obtain it for non-technical reasons.
2025-10-10 15:18:23 +08:00
世界
81fd481a23 Remove use of ldflags -checklinkname=0 on darwin 2025-10-10 15:18:23 +08:00
世界
4d4e8761ad Fix local DNS server on darwin
We mistakenly believed that `libresolv`'s `search` function worked correctly in NetworkExtension, but it seems only `getaddrinfo` does.

This commit changes the behavior of the `local` DNS server in NetworkExtension to prefer DHCP, falling back to `getaddrinfo` if DHCP servers are unavailable.

It's worth noting that `prefer_go` does not disable DHCP since it respects Dial Fields, but `getaddrinfo` does the opposite. The new behavior only applies to NetworkExtension, not to all scenarios (primarily command-line binaries) as it did previously.

In addition, this commit also improves the DHCP DNS server to use the same robust query logic as `local`.
2025-10-10 15:18:22 +08:00
世界
5676e6693a Fix legacy DNS config 2025-10-10 15:18:22 +08:00
世界
55b7d54d26 Fix rule-set format 2025-10-10 15:18:22 +08:00
世界
cb5a4be7b3 documentation: Remove outdated icons 2025-10-10 15:18:21 +08:00
世界
0eb3736465 documentation: Improve local DNS server 2025-10-10 15:18:21 +08:00
世界
783c9cd8be Use libresolv in local DNS server on darwin 2025-10-10 15:18:21 +08:00
世界
7a0e33305d Use resolved in local DNS server if available 2025-10-10 15:18:20 +08:00
xchacha20-poly1305
1e5d14d498 Fix rule set version 2025-10-10 15:18:20 +08:00
世界
f862c53321 documentation: Add preferred_by route rule item 2025-10-10 15:18:19 +08:00
世界
0aeb13e725 Add preferred_by route rule item 2025-10-10 15:18:19 +08:00
世界
0731bd1e02 documentation: Add interface address rule items 2025-10-10 15:18:19 +08:00
世界
7bd1cc1dd5 Add interface address rule items 2025-10-10 15:18:19 +08:00
世界
9a5bf1a29a Fix ECH retry support 2025-10-10 15:18:19 +08:00
neletor
df1c11eda2 Add support for ech retry configs 2025-10-10 15:18:19 +08:00
Zephyruso
c0d5043fd1 Add /dns/flush-clash meta api 2025-10-10 15:18:18 +08:00
世界
279425b17c Fix DNS reject panic 2025-10-10 15:17:56 +08:00
世界
84b9757147 Disable TCP slow open for anytls
Fixes #3459
2025-10-10 15:17:56 +08:00
世界
dca9a6780d Update Go to 1.25.2 2025-10-10 11:58:23 +08:00
世界
36dc883c7c Fix DNS negative caching to comply with RFC 2308 2025-10-09 23:45:23 +08:00
Mahdi
6557bd7029 Fix dns cache in lookup 2025-10-09 23:45:23 +08:00
世界
41b30c91d9 Improve HTTPS DNS transport 2025-10-09 23:45:23 +08:00
世界
0f767d5ce1 Update .gitignore 2025-10-07 13:37:11 +08:00
世界
328a6de797 Bump version 2025-10-05 17:58:21 +08:00
Mahdi
886be6414d Fix dns truncate 2025-10-05 17:58:21 +08:00
世界
9362d3cab3 Attempt to fix leak in quic-go 2025-10-01 11:59:17 +08:00
世界
ced2e39dbf Update dependencies 2025-10-01 11:55:33 +08:00
anytls
2159d8877b Update anytls v0.0.11
Co-authored-by: anytls <anytls>
2025-10-01 10:23:15 +08:00
世界
cb7dba3eff release: Improve publish testflight 2025-10-01 10:22:44 +08:00
世界
d9d7f7880d Pin gofumpt and golangci-lint versions
As we don't want to remove naked returns
2025-09-23 16:33:42 +08:00
世界
a031aaf2c0 Do not reset network on sleep or wake 2025-09-23 16:17:44 +08:00
世界
4bca951773 Fix adguard matcher 2025-09-23 16:12:29 +08:00
世界
140735dbde Fix websocket log handling 2025-09-23 16:12:29 +08:00
世界
714a68bba1 Update .gitignore 2025-09-23 16:12:29 +08:00
世界
573c6179ab Bump version 2025-09-13 13:16:13 +08:00
世界
510bf05e36 Fix UDP exchange for local/dhcp DNS servers 2025-09-13 12:26:48 +08:00
世界
ae852e0be4 Bump version 2025-09-13 03:09:10 +08:00
世界
1955002ed8 Do not cache DNS responses with empty answers 2025-09-13 03:04:08 +08:00
世界
44559fb7b9 Bump version 2025-09-13 00:07:57 +08:00
世界
0977c5cf73 release: Disable Apple platform CI builds, since `-allowProvisioningUpdates is broken by Apple 2025-09-13 00:07:57 +08:00
世界
07697bf931 release: Fix xcode build 2025-09-12 22:57:44 +08:00
世界
5d1d1a1456 Fix TCP exchange for local/dhcp DNS servers 2025-09-12 21:58:48 +08:00
世界
146383499e Fix race codes 2025-09-12 21:58:48 +08:00
世界
e81a76fdf9 Fix DNS exchange 2025-09-12 18:05:02 +08:00
世界
de13137418 Fix auto redirect 2025-09-12 11:04:12 +08:00
世界
e42b818c2a Fix dhcp fetch 2025-09-12 11:03:13 +08:00
111 changed files with 2703 additions and 748 deletions

View File

@@ -1,25 +1,27 @@
#!/usr/bin/env bash
VERSION="1.23.12"
VERSION="1.25.2"
mkdir -p $HOME/go
cd $HOME/go
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
mv go go_legacy
cd go_legacy
mv go go_win7
cd go_win7
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.23.x
# that means after golang1.24 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
# this patch file only works on golang1.25.x
# that means after golang1.26 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.2
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -88,9 +88,9 @@ jobs:
- { os: linux, arch: loong64, debian: loongarch64, rpm: loongarch64, openwrt: "loongarch64_generic" }
- { os: windows, arch: amd64 }
- { os: windows, arch: amd64, legacy_go123: true, legacy_name: "windows-7" }
- { os: windows, arch: amd64, legacy_win7: true, legacy_name: "windows-7" }
- { os: windows, arch: "386" }
- { os: windows, arch: "386", legacy_go123: true, legacy_name: "windows-7" }
- { os: windows, arch: "386", legacy_win7: true, legacy_name: "windows-7" }
- { os: windows, arch: arm64 }
- { os: darwin, arch: amd64 }
@@ -110,29 +110,29 @@ jobs:
if: ${{ ! (matrix.legacy_go123 || matrix.legacy_go124) }}
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.2
- name: Setup Go 1.24
if: matrix.legacy_go124
uses: actions/setup-go@v5
with:
go-version: ~1.24.6
- name: Cache Go 1.23
if: matrix.legacy_go123
id: cache-legacy-go
- name: Cache Go for Windows 7
if: matrix.legacy_win7
id: cache-go-for-windows7
uses: actions/cache@v4
with:
path: |
~/go/go_legacy
key: go_legacy_12312
- name: Setup Go 1.23
if: matrix.legacy_go123 && steps.cache-legacy-go.outputs.cache-hit != 'true'
~/go/go_win7
key: go_win7_1252
- name: Setup Go for Windows 7
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
run: |-
.github/setup_legacy_go.sh
- name: Setup Go 1.23
if: matrix.legacy_go123
.github/setup_go_for_windows7.sh
- name: Setup Go for Windows 7
if: matrix.legacy_win7
run: |-
echo "PATH=$HOME/go/go_legacy/bin:$PATH" >> $GITHUB_ENV
echo "GOROOT=$HOME/go/go_legacy" >> $GITHUB_ENV
echo "PATH=$HOME/go/go_win7/bin:$PATH" >> $GITHUB_ENV
echo "GOROOT=$HOME/go/go_win7" >> $GITHUB_ENV
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
@@ -146,7 +146,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
if: matrix.os != 'android'
@@ -300,7 +300,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.2
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -380,7 +380,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.2
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -432,7 +432,8 @@ jobs:
SERVICE_ACCOUNT_CREDENTIALS: ${{ secrets.SERVICE_ACCOUNT_CREDENTIALS }}
build_apple:
name: Build Apple clients
runs-on: macos-15
runs-on: macos-26
if: false
needs:
- calculate_version
strategy:
@@ -478,15 +479,7 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
go-version: ^1.25.2
- name: Set tag
if: matrix.if
run: |-

View File

@@ -32,7 +32,7 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: latest
version: v2.4.0
args: --timeout=30m
install-mode: binary
verify: false

View File

@@ -30,7 +30,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.2
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -71,7 +71,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.25.1
go-version: ^1.25.2
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
@@ -85,7 +85,7 @@ jobs:
- name: Set build tags
run: |
set -xeuo pipefail
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0'
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
- name: Build
run: |

4
.gitignore vendored
View File

@@ -15,4 +15,6 @@
.DS_Store
/config.d/
/venv/
CLAUDE.md
AGENTS.md
/.claude/

View File

@@ -13,7 +13,7 @@ RUN set -ex \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& go build -v -trimpath -tags \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0" \
-o /go/bin/sing-box \
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
./cmd/sing-box

View File

@@ -1,6 +1,6 @@
NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD)
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,badlinkname,tfogo_checklinkname0
GOHOSTOS = $(shell go env GOHOSTOS)
GOHOSTARCH = $(shell go env GOHOSTARCH)
@@ -17,6 +17,10 @@ build:
export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN)
race:
export GOTOOLCHAIN=local && \
go build -race $(MAIN_PARAMS) $(MAIN)
ci_build:
export GOTOOLCHAIN=local && \
go build $(PARAMS) $(MAIN) && \
@@ -34,7 +38,7 @@ fmt:
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
fmt_install:
go install -v mvdan.cc/gofumpt@latest
go install -v mvdan.cc/gofumpt@v0.8.0
go install -v github.com/daixiang0/gci@latest
lint:
@@ -45,7 +49,7 @@ lint:
GOOS=freebsd golangci-lint run ./...
lint_install:
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
go install -v github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.4.0
proto:
@go run ./cmd/internal/protogen

View File

@@ -62,7 +62,7 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack", "badlinkname", "tfogo_checklinkname0")
macOSTags = append(macOSTags, "with_dhcp")
memcTags = append(memcTags, "with_tailscale")
notMemcTags = append(notMemcTags, "with_low_memory")
@@ -107,10 +107,8 @@ func buildAndroid() {
}
if !debugEnabled {
// sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
args = append(args, sharedFlags...)
} else {
// debugFlags[1] = debugFlags[1] + " -checklinkname=0"
args = append(args, debugFlags...)
}

View File

@@ -22,7 +22,7 @@ func initializeHTTP3Client(instance *box.Box) error {
}
http3Client = &http.Client{
Transport: &http3.Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
destination := M.ParseSocksaddr(addr)
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
if dErr != nil {

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && !without_badtls
//go:build go1.25 && badlinkname
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && !without_badtls
//go:build go1.25 && badlinkname
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && !without_badtls
//go:build go1.25 && badlinkname
package badtls

View File

@@ -1,4 +1,4 @@
//go:build !go1.25 || without_badtls
//go:build !go1.25 || !badlinkname
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && !without_badtls
//go:build go1.25 && badlinkname
package badtls

View File

@@ -1,4 +1,4 @@
//go:build go1.25 && !without_badtls
//go:build go1.25 && badlinkname
package badtls

View File

@@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"github.com/sagernet/fswatch"
"github.com/sagernet/sing-box/adapter"
@@ -21,6 +22,7 @@ import (
var _ adapter.CertificateStore = (*Store)(nil)
type Store struct {
access sync.RWMutex
systemPool *x509.CertPool
currentPool *x509.CertPool
certificate string
@@ -115,10 +117,14 @@ func (s *Store) Close() error {
}
func (s *Store) Pool() *x509.CertPool {
s.access.RLock()
defer s.access.RUnlock()
return s.currentPool
}
func (s *Store) update() error {
s.access.Lock()
defer s.access.Unlock()
var currentPool *x509.CertPool
if s.systemPool == nil {
currentPool = x509.NewCertPool()

View File

@@ -20,6 +20,8 @@ import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/database64128/tfo-go/v2"
)
var (
@@ -28,8 +30,8 @@ var (
)
type DefaultDialer struct {
dialer4 tcpDialer
dialer6 tcpDialer
dialer4 tfo.Dialer
dialer6 tfo.Dialer
udpDialer4 net.Dialer
udpDialer6 net.Dialer
udpListener net.ListenConfig
@@ -177,19 +179,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
}
if options.TCPMultiPath {
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&dialer4)
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil {
return nil, err
}
tcpDialer6, err := newTCPDialer(dialer6, options.TCPFastOpen)
if err != nil {
return nil, err
dialer4.SetMultipathTCP(true)
}
tcpDialer4 := tfo.Dialer{Dialer: dialer4, DisableTFO: !options.TCPFastOpen}
tcpDialer6 := tfo.Dialer{Dialer: dialer6, DisableTFO: !options.TCPFastOpen}
return &DefaultDialer{
dialer4: tcpDialer4,
dialer6: tcpDialer6,
@@ -269,7 +262,7 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
}
var dialer net.Dialer
if N.NetworkName(network) == N.NetworkTCP {
dialer = dialerFromTCPDialer(d.dialer4)
dialer = d.dialer4.Dialer
} else {
dialer = d.udpDialer4
}
@@ -317,9 +310,9 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
func (d *DefaultDialer) DialerForICMPDestination(destination netip.Addr) net.Dialer {
if !destination.Is6() {
return dialerFromTCPDialer(d.dialer6)
return d.dialer6.Dialer
} else {
return dialerFromTCPDialer(d.dialer4)
return d.dialer4.Dialer
}
}
@@ -356,18 +349,8 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
return trackPacketConn(packetConn, nil)
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
udpListener := d.udpListener
udpListener.Control = control.Append(udpListener.Control, func(network, address string, conn syscall.RawConn) error {
for _, wgControlFn := range WgControlFns {
err := wgControlFn(network, address, conn)
if err != nil {
return err
}
}
return nil
})
return udpListener.ListenPacket(context.Background(), network, address)
func (d *DefaultDialer) WireGuardControl() control.Func {
return d.udpListener.Control
}
func trackConn(conn net.Conn, err error) (net.Conn, error) {

View File

@@ -1,19 +0,0 @@
//go:build go1.20
package dialer
import (
"net"
"github.com/metacubex/tfo-go"
)
type tcpDialer = tfo.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
}
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
return dialer.Dialer
}

View File

@@ -1,11 +0,0 @@
//go:build go1.21
package dialer
import "net"
const go121Available = true
func setMultiPathTCP(dialer *net.Dialer) {
dialer.SetMultipathTCP(true)
}

View File

@@ -1,22 +0,0 @@
//go:build !go1.20
package dialer
import (
"net"
E "github.com/sagernet/sing/common/exceptions"
)
type tcpDialer = net.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
if tfoEnabled {
return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
}
return dialer, nil
}
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
return dialer
}

View File

@@ -1,12 +0,0 @@
//go:build !go1.21
package dialer
import (
"net"
)
const go121Available = false
func setMultiPathTCP(dialer *net.Dialer) {
}

View File

@@ -1,5 +1,3 @@
//go:build go1.20
package dialer
import (
@@ -16,7 +14,7 @@ import (
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/metacubex/tfo-go"
"github.com/database64128/tfo-go/v2"
)
type slowOpenConn struct {
@@ -32,7 +30,7 @@ type slowOpenConn struct {
err error
}
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:

View File

@@ -1,20 +0,0 @@
//go:build !go1.20
package dialer
import (
"context"
"net"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
switch N.NetworkName(network) {
case N.NetworkTCP, N.NetworkUDP:
return dialer.DialContext(ctx, network, destination.String())
default:
return dialer.DialContext(ctx, network, destination.AddrString())
}
}

View File

@@ -1,13 +1,9 @@
package dialer
import (
"net"
"github.com/sagernet/sing/common/control"
)
type WireGuardListener interface {
ListenPacketCompat(network, address string) (net.PacketConn, error)
WireGuardControl() control.Func
}
var WgControlFns []control.Func

View File

@@ -1,4 +1,4 @@
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -1,4 +1,4 @@
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -0,0 +1,15 @@
//go:build linux && go1.25 && !badlinkname
package ktls
import (
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
return nil, E.New("kTLS requires build flags `badlinkname` and `-ldflags=-checklinkname=0`, please recompile your binary")
}

View File

@@ -1,15 +1,15 @@
//go:build !linux || !go1.25 || without_badtls
//go:build !linux
package ktls
import (
"context"
"os"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
return nil, os.ErrInvalid
return nil, E.New("kTLS is only supported on Linux")
}

View File

@@ -0,0 +1,15 @@
//go:build linux && !go1.25
package ktls
import (
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
aTLS "github.com/sagernet/sing/common/tls"
)
func NewConn(ctx context.Context, logger logger.ContextLogger, conn aTLS.Conn, txOffload, rxOffload bool) (aTLS.Conn, error) {
return nil, E.New("kTLS requires Go 1.25 or later, please recompile your binary")
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && go1.25 && !without_badtls
//go:build linux && go1.25 && badlinkname
package ktls

View File

@@ -1,11 +0,0 @@
//go:build go1.21
package listener
import "net"
const go121Available = true
func setMultiPathTCP(listenConfig *net.ListenConfig) {
listenConfig.SetMultipathTCP(true)
}

View File

@@ -1,16 +0,0 @@
//go:build go1.23
package listener
import (
"net"
"time"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: idle,
Interval: interval,
}
}

View File

@@ -1,10 +0,0 @@
//go:build !go1.21
package listener
import "net"
const go121Available = false
func setMultiPathTCP(listenConfig *net.ListenConfig) {
}

View File

@@ -1,15 +0,0 @@
//go:build !go1.23
package listener
import (
"net"
"time"
"github.com/sagernet/sing/common/control"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAlive = idle
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
}

View File

@@ -17,7 +17,7 @@ import (
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
"github.com/metacubex/tfo-go"
"github.com/database64128/tfo-go/v2"
)
func (l *Listener) ListenTCP() (net.Listener, error) {
@@ -46,13 +46,14 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if keepInterval == 0 {
keepInterval = C.TCPKeepAliveInterval
}
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
listenConfig.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: keepIdle,
Interval: keepInterval,
}
}
if l.listenOptions.TCPMultiPath {
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&listenConfig)
listenConfig.SetMultipathTCP(true)
}
if l.tproxy {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {

View File

@@ -119,21 +119,19 @@ func (d *defaultDialer) dialContext(ctx context.Context, destination M.Socksaddr
if err != nil {
return nil, err
}
tlsConn, err := ClientHandshake(ctx, conn, d.config)
if err == nil {
return tlsConn, nil
}
conn.Close()
if echRetry {
tlsConn, err := aTLS.ClientHandshake(ctx, conn, d.config)
if err != nil {
conn.Close()
var echErr *tls.ECHRejectionError
if errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
if echRetry && errors.As(err, &echErr) && len(echErr.RetryConfigList) > 0 {
if echConfig, isECH := d.config.(ECHCapableConfig); isECH {
echConfig.SetECHConfigList(echErr.RetryConfigList)
return d.dialContext(ctx, destination, false)
}
}
return d.dialContext(ctx, destination, false)
return nil, err
}
return nil, err
return tlsConn, nil
}
func (d *defaultDialer) Upstream() any {

View File

@@ -69,11 +69,7 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
} else {
return E.New("missing ECH keys")
}
block, rest := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
return E.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
echKeys, err := parseECHKeys(echKey)
if err != nil {
return E.Cause(err, "parse ECH keys")
}
@@ -85,21 +81,29 @@ func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions,
return nil
}
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
echKey, err := os.ReadFile(echKeyPath)
func (c *STDServerConfig) setECHServerConfig(echKey []byte) error {
echKeys, err := parseECHKeys(echKey)
if err != nil {
return E.Cause(err, "reload ECH keys from ", echKeyPath)
return err
}
c.access.Lock()
config := c.config.Clone()
config.EncryptedClientHelloKeys = echKeys
c.config = config
c.access.Unlock()
return nil
}
func parseECHKeys(echKey []byte) ([]tls.EncryptedClientHelloKey, error) {
block, _ := pem.Decode(echKey)
if block == nil || block.Type != "ECH KEYS" {
return E.New("invalid ECH keys pem")
return nil, E.New("invalid ECH keys pem")
}
echKeys, err := UnmarshalECHKeys(block.Bytes)
if err != nil {
return E.Cause(err, "parse ECH keys")
return nil, E.Cause(err, "parse ECH keys")
}
tlsConfig.EncryptedClientHelloKeys = echKeys
return nil
return echKeys, nil
}
type ECHClientConfig struct {

View File

@@ -1,23 +0,0 @@
//go:build !go1.24
package tls
import (
"context"
"crypto/tls"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
return nil, E.New("ECH requires go1.24, please recompile your binary.")
}
func parseECHServerConfig(ctx context.Context, options option.InboundTLSOptions, tlsConfig *tls.Config, echKeyPath *string) error {
return E.New("ECH requires go1.24, please recompile your binary.")
}
func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
return E.New("ECH requires go1.24, please recompile your binary.")
}

View File

@@ -68,7 +68,10 @@ func NewRealityServer(ctx context.Context, logger log.ContextLogger, options opt
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
if len(options.Certificate) > 0 || options.CertificatePath != "" {
if len(options.CurvePreferences) > 0 {
return nil, E.New("curve preferences is unavailable in reality")
}
if len(options.Certificate) > 0 || options.CertificatePath != "" || len(options.ClientCertificatePublicKeySHA256) > 0 {
return nil, E.New("certificate is unavailable in reality")
}
if len(options.Key) > 0 || options.KeyPath != "" {

View File

@@ -1,9 +1,12 @@
package tls
import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"net"
"os"
"strings"
@@ -108,6 +111,15 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
return err
}
}
if len(options.CertificatePublicKeySHA256) > 0 {
if len(options.Certificate) > 0 || options.CertificatePath != "" {
return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path")
}
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
}
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
}
@@ -137,6 +149,9 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
for _, curve := range options.CurvePreferences {
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curve))
}
var certificate []byte
if len(options.Certificate) > 0 {
certificate = []byte(strings.Join(options.Certificate, "\n"))
@@ -154,6 +169,35 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
}
tlsConfig.RootCAs = certPool
}
var clientCertificate []byte
if len(options.ClientCertificate) > 0 {
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
} else if options.ClientCertificatePath != "" {
content, err := os.ReadFile(options.ClientCertificatePath)
if err != nil {
return nil, E.Cause(err, "read client certificate")
}
clientCertificate = content
}
var clientKey []byte
if len(options.ClientKey) > 0 {
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
} else if options.ClientKeyPath != "" {
content, err := os.ReadFile(options.ClientKeyPath)
if err != nil {
return nil, E.Cause(err, "read client key")
}
clientKey = content
}
if len(clientCertificate) > 0 && len(clientKey) > 0 {
keyPair, err := tls.X509KeyPair(clientCertificate, clientKey)
if err != nil {
return nil, E.Cause(err, "parse client x509 key pair")
}
tlsConfig.Certificates = []tls.Certificate{keyPair}
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
return nil, E.New("client certificate and client key must be provided together")
}
var config Config = &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.ECH != nil && options.ECH.Enabled {
var err error
@@ -175,3 +219,22 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
}
return config, nil
}
func verifyPublicKeySHA256(knownHashValues [][]byte, rawCerts [][]byte, timeFunc func() time.Time) error {
leafCertificate, err := x509.ParseCertificate(rawCerts[0])
if err != nil {
return E.Cause(err, "failed to parse leaf certificate")
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(leafCertificate.PublicKey)
if err != nil {
return E.Cause(err, "failed to marshal public key")
}
hashValue := sha256.Sum256(pubKeyBytes)
for _, value := range knownHashValues {
if bytes.Equal(value, hashValue[:]) {
return nil
}
}
return E.New("unrecognized remote public key: ", base64.StdEncoding.EncodeToString(hashValue[:]))
}

View File

@@ -3,9 +3,11 @@ package tls
import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"os"
"strings"
"sync"
"time"
"github.com/sagernet/fswatch"
@@ -21,26 +23,36 @@ import (
var errInsecureUnused = E.New("tls: insecure unused")
type STDServerConfig struct {
config *tls.Config
logger log.Logger
acmeService adapter.SimpleLifecycle
certificate []byte
key []byte
certificatePath string
keyPath string
echKeyPath string
watcher *fswatch.Watcher
access sync.RWMutex
config *tls.Config
logger log.Logger
acmeService adapter.SimpleLifecycle
certificate []byte
key []byte
certificatePath string
keyPath string
clientCertificatePath []string
echKeyPath string
watcher *fswatch.Watcher
}
func (c *STDServerConfig) ServerName() string {
c.access.RLock()
defer c.access.RUnlock()
return c.config.ServerName
}
func (c *STDServerConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
c.access.Lock()
defer c.access.Unlock()
config := c.config.Clone()
config.ServerName = serverName
c.config = config
}
func (c *STDServerConfig) NextProtos() []string {
c.access.RLock()
defer c.access.RUnlock()
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
return c.config.NextProtos[1:]
} else {
@@ -49,11 +61,15 @@ func (c *STDServerConfig) NextProtos() []string {
}
func (c *STDServerConfig) SetNextProtos(nextProto []string) {
c.access.Lock()
defer c.access.Unlock()
config := c.config.Clone()
if c.acmeService != nil && len(c.config.NextProtos) > 1 && c.config.NextProtos[0] == ACMETLS1Protocol {
c.config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
config.NextProtos = append(c.config.NextProtos[:1], nextProto...)
} else {
c.config.NextProtos = nextProto
config.NextProtos = nextProto
}
c.config = config
}
func (c *STDServerConfig) STDConfig() (*STDConfig, error) {
@@ -78,9 +94,6 @@ func (c *STDServerConfig) Start() error {
if c.acmeService != nil {
return c.acmeService.Start()
} else {
if c.certificatePath == "" && c.keyPath == "" {
return nil
}
err := c.startWatcher()
if err != nil {
c.logger.Warn("create fsnotify watcher: ", err)
@@ -100,6 +113,12 @@ func (c *STDServerConfig) startWatcher() error {
if c.echKeyPath != "" {
watchPath = append(watchPath, c.echKeyPath)
}
if len(c.clientCertificatePath) > 0 {
watchPath = append(watchPath, c.clientCertificatePath...)
}
if len(watchPath) == 0 {
return nil
}
watcher, err := fswatch.NewWatcher(fswatch.Options{
Path: watchPath,
Callback: func(path string) {
@@ -139,10 +158,42 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
if err != nil {
return E.Cause(err, "reload key pair")
}
c.config.Certificates = []tls.Certificate{keyPair}
c.access.Lock()
config := c.config.Clone()
config.Certificates = []tls.Certificate{keyPair}
c.config = config
c.access.Unlock()
c.logger.Info("reloaded TLS certificate")
} else if common.Contains(c.clientCertificatePath, path) {
clientCertificateCA := x509.NewCertPool()
var reloaded bool
for _, certPath := range c.clientCertificatePath {
content, err := os.ReadFile(certPath)
if err != nil {
c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath))
continue
}
if !clientCertificateCA.AppendCertsFromPEM(content) {
c.logger.Error(E.New("invalid client certificate file: ", certPath))
continue
}
reloaded = true
}
if !reloaded {
return E.New("client certificates is empty")
}
c.access.Lock()
config := c.config.Clone()
config.ClientCAs = clientCertificateCA
c.config = config
c.access.Unlock()
c.logger.Info("reloaded client certificates")
} else if path == c.echKeyPath {
err := reloadECHKeys(c.echKeyPath, c.config)
echKey, err := os.ReadFile(c.echKeyPath)
if err != nil {
return E.Cause(err, "reload ECH keys from ", c.echKeyPath)
}
err = c.setECHServerConfig(echKey)
if err != nil {
return err
}
@@ -213,8 +264,14 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
return nil, E.New("unknown cipher_suite: ", cipherSuite)
}
}
var certificate []byte
var key []byte
for _, curveID := range options.CurvePreferences {
tlsConfig.CurvePreferences = append(tlsConfig.CurvePreferences, tls.CurveID(curveID))
}
tlsConfig.ClientAuth = tls.ClientAuthType(options.ClientAuthentication)
var (
certificate []byte
key []byte
)
if acmeService == nil {
if len(options.Certificate) > 0 {
certificate = []byte(strings.Join(options.Certificate, "\n"))
@@ -256,6 +313,43 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
tlsConfig.Certificates = []tls.Certificate{keyPair}
}
}
if len(options.ClientCertificate) > 0 || len(options.ClientCertificatePath) > 0 {
if tlsConfig.ClientAuth == tls.NoClientCert {
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}
}
if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven || tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
if len(options.ClientCertificate) > 0 {
clientCertificateCA := x509.NewCertPool()
if !clientCertificateCA.AppendCertsFromPEM([]byte(strings.Join(options.ClientCertificate, "\n"))) {
return nil, E.New("invalid client certificate strings")
}
tlsConfig.ClientCAs = clientCertificateCA
} else if len(options.ClientCertificatePath) > 0 {
clientCertificateCA := x509.NewCertPool()
for _, path := range options.ClientCertificatePath {
content, err := os.ReadFile(path)
if err != nil {
return nil, E.Cause(err, "read client certificate from ", path)
}
if !clientCertificateCA.AppendCertsFromPEM(content) {
return nil, E.New("invalid client certificate file: ", path)
}
}
tlsConfig.ClientCAs = clientCertificateCA
} else if len(options.ClientCertificatePublicKeySHA256) > 0 {
if tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert {
tlsConfig.ClientAuth = tls.RequireAnyClientCert
} else if tlsConfig.ClientAuth == tls.VerifyClientCertIfGiven {
tlsConfig.ClientAuth = tls.RequestClientCert
}
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return verifyPublicKeySHA256(options.ClientCertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
}
} else {
return nil, E.New("missing client_certificate, client_certificate_path or client_certificate_public_key_sha256 for client authentication")
}
}
var echKeyPath string
if options.ECH != nil && options.ECH.Enabled {
err = parseECHServerConfig(ctx, options, tlsConfig, &echKeyPath)
@@ -263,16 +357,23 @@ func NewSTDServer(ctx context.Context, logger log.ContextLogger, options option.
return nil, err
}
}
var config ServerConfig = &STDServerConfig{
config: tlsConfig,
logger: logger,
acmeService: acmeService,
certificate: certificate,
key: key,
certificatePath: options.CertificatePath,
keyPath: options.KeyPath,
echKeyPath: echKeyPath,
serverConfig := &STDServerConfig{
config: tlsConfig,
logger: logger,
acmeService: acmeService,
certificate: certificate,
key: key,
certificatePath: options.CertificatePath,
clientCertificatePath: options.ClientCertificatePath,
keyPath: options.KeyPath,
echKeyPath: echKeyPath,
}
serverConfig.config.GetConfigForClient = func(info *tls.ClientHelloInfo) (*tls.Config, error) {
serverConfig.access.Lock()
defer serverConfig.access.Unlock()
return serverConfig.config, nil
}
var config ServerConfig = serverConfig
if options.KernelTx || options.KernelRx {
if !C.IsLinux {
return nil, E.New("kTLS is only supported on Linux")

View File

@@ -167,6 +167,15 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
}
tlsConfig.InsecureServerNameToVerify = serverName
}
if len(options.CertificatePublicKeySHA256) > 0 {
if len(options.Certificate) > 0 || options.CertificatePath != "" {
return nil, E.New("certificate_public_key_sha256 is conflict with certificate or certificate_path")
}
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return verifyPublicKeySHA256(options.CertificatePublicKeySHA256, rawCerts, tlsConfig.Time)
}
}
if len(options.ALPN) > 0 {
tlsConfig.NextProtos = options.ALPN
}
@@ -213,6 +222,35 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
}
tlsConfig.RootCAs = certPool
}
var clientCertificate []byte
if len(options.ClientCertificate) > 0 {
clientCertificate = []byte(strings.Join(options.ClientCertificate, "\n"))
} else if options.ClientCertificatePath != "" {
content, err := os.ReadFile(options.ClientCertificatePath)
if err != nil {
return nil, E.Cause(err, "read client certificate")
}
clientCertificate = content
}
var clientKey []byte
if len(options.ClientKey) > 0 {
clientKey = []byte(strings.Join(options.ClientKey, "\n"))
} else if options.ClientKeyPath != "" {
content, err := os.ReadFile(options.ClientKeyPath)
if err != nil {
return nil, E.Cause(err, "read client key")
}
clientKey = content
}
if len(clientCertificate) > 0 && len(clientKey) > 0 {
keyPair, err := utls.X509KeyPair(clientCertificate, clientKey)
if err != nil {
return nil, E.Cause(err, "parse client x509 key pair")
}
tlsConfig.Certificates = []utls.Certificate{keyPair}
} else if len(clientCertificate) > 0 || len(clientKey) > 0 {
return nil, E.New("client certificate and client key must be provided together")
}
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
if err != nil {
return nil, err

View File

@@ -46,15 +46,15 @@ func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory
func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
s.access.Lock()
delete(s.delayHistory, tag)
s.access.Unlock()
s.notifyUpdated()
s.access.Unlock()
}
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
s.access.Lock()
s.delayHistory[tag] = history
s.access.Unlock()
s.notifyUpdated()
s.access.Unlock()
}
func (s *HistoryStorage) notifyUpdated() {
@@ -68,6 +68,8 @@ func (s *HistoryStorage) notifyUpdated() {
}
func (s *HistoryStorage) Close() error {
s.access.Lock()
defer s.access.Unlock()
s.updateHook = nil
return nil
}

View File

@@ -95,6 +95,20 @@ func (c *Client) Start() {
}
}
func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
for _, record := range response.Ns {
if soa, isSOA := record.(*dns.SOA); isSOA {
soaTTL := soa.Header().Ttl
soaMinimum := soa.Minttl
if soaTTL < soaMinimum {
return soaTTL, true
}
return soaMinimum, true
}
}
return 0, false
}
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
if len(message.Question) == 0 {
if c.logger != nil {
@@ -214,6 +228,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
response.Answer = append(response.Answer, validResponse.Answer...)
}
}*/
disableCache = disableCache || (response.Rcode != dns.RcodeSuccess && response.Rcode != dns.RcodeNameError)
if responseChecker != nil {
var rejected bool
// TODO: add accept_any rule and support to check response instead of addresses
@@ -250,10 +265,17 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
}
var timeToLive uint32
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList {
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
timeToLive = record.Header().Ttl
if len(response.Answer) == 0 {
if soaTTL, hasSOA := extractNegativeTTL(response); hasSOA {
timeToLive = soaTTL
}
}
if timeToLive == 0 {
for _, recordList := range [][]dns.RR{response.Answer, response.Ns, response.Extra} {
for _, record := range recordList {
if timeToLive == 0 || record.Header().Ttl > 0 && record.Header().Ttl < timeToLive {
timeToLive = record.Header().Ttl
}
}
}
}
@@ -280,7 +302,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
}
logExchangedResponse(c.logger, ctx, response, timeToLive)
return response, err
return response, nil
}
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
@@ -363,14 +385,18 @@ func (c *Client) LookupCache(domain string, strategy C.DomainStrategy) ([]netip.
Qtype: dns.TypeA,
Qclass: dns.ClassINET,
}, nil)
if response4 == nil {
return nil, false
}
response6, _ := c.loadResponse(dns.Question{
Name: dnsName,
Qtype: dns.TypeAAAA,
Qclass: dns.ClassINET,
}, nil)
if response4 != nil || response6 != nil {
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
if response6 == nil {
return nil, false
}
return sortAddresses(MessageToAddresses(response4), MessageToAddresses(response6), strategy), true
}
return nil, false
}

View File

@@ -15,8 +15,7 @@ func TruncateDNSMessage(request *dns.Msg, response *dns.Msg, headroom int) (*buf
}
responseLen := response.Len()
if responseLen > maxLen {
copyResponse := *response
response = &copyResponse
response = response.Copy()
response.Truncate(maxLen)
}
buffer := buf.NewSize(headroom*2 + 1 + responseLen)

View File

@@ -5,6 +5,7 @@ import (
)
const (
RcodeSuccess RcodeError = mDNS.RcodeSuccess
RcodeFormatError RcodeError = mDNS.RcodeFormatError
RcodeNameError RcodeError = mDNS.RcodeNameError
RcodeRefused RcodeError = mDNS.RcodeRefused

View File

@@ -386,12 +386,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
if rule != nil {
switch action := rule.Action().(type) {
case *R.RuleActionReject:
switch action.Method {
case C.RuleActionRejectMethodDefault:
return nil, nil
case C.RuleActionRejectMethodDrop:
return nil, tun.ErrDrop
}
return nil, &R.RejectedError{Cause: action.Error(ctx)}
case *R.RuleActionPredefined:
if action.Rcode != mDNS.RcodeSuccess {
err = RcodeError(action.Rcode)

View File

@@ -2,10 +2,13 @@ package dhcp
import (
"context"
"errors"
"io"
"net"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -195,7 +198,17 @@ func (t *Transport) fetchServers0(ctx context.Context, iface *control.Interface)
if runtime.GOOS == "linux" || runtime.GOOS == "android" {
listenAddr = "255.255.255.255:68"
}
packetConn, err := listener.ListenPacket(t.ctx, "udp4", listenAddr)
var (
packetConn net.PacketConn
err error
)
for i := 0; i < 5; i++ {
packetConn, err = listener.ListenPacket(t.ctx, "udp4", listenAddr)
if err == nil || !errors.Is(err, syscall.EADDRINUSE) {
break
}
time.Sleep(time.Second)
}
if err != nil {
return err
}
@@ -232,6 +245,9 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
for {
_, _, err := buffer.ReadPacketFrom(packetConn)
if err != nil {
if errors.Is(err, io.ErrShortBuffer) {
continue
}
return err
}

View File

@@ -2,12 +2,13 @@ package dhcp
import (
"context"
"errors"
"math/rand"
"strings"
"time"
"syscall"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -43,7 +44,7 @@ func (t *Transport) exchangeParallel(ctx context.Context, servers []M.Socksaddr,
if response.Rcode != mDNS.RcodeSuccess {
err = dns.RcodeError(response.Rcode)
} else if len(dns.MessageToAddresses(response)) == 0 {
err = E.New(fqdn, ": empty result")
err = dns.RcodeSuccess
}
}
select {
@@ -83,7 +84,7 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn
server := servers[j]
question := message.Question[0]
question.Name = fqdn
response, err := t.exchangeOne(ctx, server, question, C.DNSTimeout, false, true)
response, err := t.exchangeOne(ctx, server, question)
if err != nil {
lastErr = err
continue
@@ -94,62 +95,77 @@ func (t *Transport) tryOneName(ctx context.Context, servers []M.Socksaddr, fqdn
return nil, E.Cause(lastErr, fqdn)
}
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question) (*mDNS.Msg, error) {
if server.Port == 0 {
server.Port = 53
}
var networks []string
if useTCP {
networks = []string{N.NetworkTCP}
} else {
networks = []string{N.NetworkUDP, N.NetworkTCP}
}
request := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: uint16(rand.Uint32()),
RecursionDesired: true,
AuthenticatedData: ad,
AuthenticatedData: true,
},
Question: []mDNS.Question{question},
Compress: true,
}
request.SetEdns0(buf.UDPBufferSize, false)
return t.exchangeUDP(ctx, server, request)
}
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
conn.SetDeadline(deadline)
}
buffer := buf.Get(buf.UDPBufferSize)
defer buf.Put(buffer)
for _, network := range networks {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
conn, err := t.dialer.DialContext(ctx, network, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
conn.SetDeadline(deadline)
}
rawMessage, err := request.PackBuffer(buffer)
if err != nil {
return nil, E.Cause(err, "pack request")
}
_, err = conn.Write(rawMessage)
if err != nil {
return nil, E.Cause(err, "write request")
}
n, err := conn.Read(buffer)
if err != nil {
return nil, E.Cause(err, "read response")
}
var response mDNS.Msg
err = response.Unpack(buffer[:n])
if err != nil {
return nil, E.Cause(err, "unpack response")
}
if response.Truncated && network == N.NetworkUDP {
continue
}
return &response, nil
rawMessage, err := request.PackBuffer(buffer)
if err != nil {
return nil, E.Cause(err, "pack request")
}
panic("unexpected")
_, err = conn.Write(rawMessage)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request)
}
return nil, E.Cause(err, "write request")
}
n, err := conn.Read(buffer)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request)
}
return nil, E.Cause(err, "read response")
}
var response mDNS.Msg
err = response.Unpack(buffer[:n])
if err != nil {
return nil, E.Cause(err, "unpack response")
}
if response.Truncated {
return t.exchangeTCP(ctx, server, request)
}
return &response, nil
}
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
conn.SetDeadline(deadline)
}
err = transport.WriteMessage(conn, 0, request)
if err != nil {
return nil, err
}
return transport.ReadMessage(conn)
}
func (t *Transport) nameList(name string) []string {

View File

@@ -25,7 +25,6 @@ import (
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
sHTTP "github.com/sagernet/sing/protocol/http"
mDNS "github.com/miekg/dns"
@@ -47,7 +46,7 @@ type HTTPSTransport struct {
destination *url.URL
headers http.Header
transportAccess sync.Mutex
transport *http.Transport
transport *HTTPSTransportWrapper
transportResetAt time.Time
}
@@ -62,11 +61,8 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
if err != nil {
return nil, err
}
if common.Error(tlsConfig.STDConfig()) == nil && !common.Contains(tlsConfig.NextProtos(), http2.NextProtoTLS) {
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), http2.NextProtoTLS))
}
if !common.Contains(tlsConfig.NextProtos(), "http/1.1") {
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), "http/1.1"))
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"})
}
headers := options.Headers.Build()
host := headers.Get("Host")
@@ -124,37 +120,13 @@ func NewHTTPSRaw(
serverAddr M.Socksaddr,
tlsConfig tls.Config,
) *HTTPSTransport {
var transport *http.Transport
if tlsConfig != nil {
transport = &http.Transport{
ForceAttemptHTTP2: true,
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
tcpConn, hErr := dialer.DialContext(ctx, network, serverAddr)
if hErr != nil {
return nil, hErr
}
tlsConn, hErr := aTLS.ClientHandshake(ctx, tcpConn, tlsConfig)
if hErr != nil {
tcpConn.Close()
return nil, hErr
}
return tlsConn, nil
},
}
} else {
transport = &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, serverAddr)
},
}
}
return &HTTPSTransport{
TransportAdapter: adapter,
logger: logger,
dialer: dialer,
destination: destination,
headers: headers,
transport: transport,
transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),
}
}

View File

@@ -0,0 +1,80 @@
package transport
import (
"context"
"errors"
"net"
"net/http"
"sync/atomic"
"github.com/sagernet/sing-box/common/tls"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"golang.org/x/net/http2"
)
var errFallback = E.New("fallback to HTTP/1.1")
type HTTPSTransportWrapper struct {
http2Transport *http2.Transport
httpTransport *http.Transport
fallback *atomic.Bool
}
func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper {
var fallback atomic.Bool
return &HTTPSTransportWrapper{
http2Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {
tlsConn, err := dialer.DialTLSContext(ctx, serverAddr)
if err != nil {
return nil, err
}
state := tlsConn.ConnectionState()
if state.NegotiatedProtocol == http2.NextProtoTLS {
return tlsConn, nil
}
tlsConn.Close()
fallback.Store(true)
return nil, errFallback
},
},
httpTransport: &http.Transport{
DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialTLSContext(ctx, serverAddr)
},
},
fallback: &fallback,
}
}
func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {
if h.fallback.Load() {
return h.httpTransport.RoundTrip(request)
} else {
response, err := h.http2Transport.RoundTrip(request)
if err != nil {
if errors.Is(err, errFallback) {
return h.httpTransport.RoundTrip(request)
}
return nil, err
}
return response, nil
}
}
func (h *HTTPSTransportWrapper) CloseIdleConnections() {
h.http2Transport.CloseIdleConnections()
h.httpTransport.CloseIdleConnections()
}
func (h *HTTPSTransportWrapper) Clone() *HTTPSTransportWrapper {
return &HTTPSTransportWrapper{
httpTransport: h.httpTransport,
http2Transport: &http2.Transport{
DialTLSContext: h.http2Transport.DialTLSContext,
},
fallback: h.fallback,
}
}

View File

@@ -2,11 +2,13 @@ package local
import (
"context"
"fmt"
"errors"
"math/rand"
"syscall"
"time"
"github.com/sagernet/sing-box/dns"
"github.com/sagernet/sing-box/dns/transport"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
@@ -17,7 +19,6 @@ import (
func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
systemConfig := getSystemDNSConfig(t.ctx)
fmt.Println(systemConfig.servers)
if systemConfig.singleRequest || !(message.Question[0].Qtype == mDNS.TypeA || message.Question[0].Qtype == mDNS.TypeAAAA) {
return t.exchangeSingleRequest(ctx, systemConfig, message, domain)
} else {
@@ -108,12 +109,6 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
if server.Port == 0 {
server.Port = 53
}
var networks []string
if useTCP {
networks = []string{N.NetworkTCP}
} else {
networks = []string{N.NetworkUDP, N.NetworkTCP}
}
request := &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: uint16(rand.Uint32()),
@@ -124,40 +119,73 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
Compress: true,
}
request.SetEdns0(buf.UDPBufferSize, false)
if !useTCP {
return t.exchangeUDP(ctx, server, request, timeout)
} else {
return t.exchangeTCP(ctx, server, request, timeout)
}
}
func (t *Transport) exchangeUDP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
newDeadline := time.Now().Add(timeout)
if deadline.After(newDeadline) {
deadline = newDeadline
}
conn.SetDeadline(deadline)
}
buffer := buf.Get(buf.UDPBufferSize)
defer buf.Put(buffer)
for _, network := range networks {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(timeout))
defer cancel()
conn, err := t.dialer.DialContext(ctx, network, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
conn.SetDeadline(deadline)
}
rawMessage, err := request.PackBuffer(buffer)
if err != nil {
return nil, E.Cause(err, "pack request")
}
_, err = conn.Write(rawMessage)
if err != nil {
return nil, E.Cause(err, "write request")
}
n, err := conn.Read(buffer)
if err != nil {
return nil, E.Cause(err, "read response")
}
var response mDNS.Msg
err = response.Unpack(buffer[:n])
if err != nil {
return nil, E.Cause(err, "unpack response")
}
if response.Truncated && network == N.NetworkUDP {
continue
}
return &response, nil
rawMessage, err := request.PackBuffer(buffer)
if err != nil {
return nil, E.Cause(err, "pack request")
}
panic("unexpected")
_, err = conn.Write(rawMessage)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request, timeout)
}
return nil, E.Cause(err, "write request")
}
n, err := conn.Read(buffer)
if err != nil {
if errors.Is(err, syscall.EMSGSIZE) {
return t.exchangeTCP(ctx, server, request, timeout)
}
return nil, E.Cause(err, "read response")
}
var response mDNS.Msg
err = response.Unpack(buffer[:n])
if err != nil {
return nil, E.Cause(err, "unpack response")
}
if response.Truncated {
return t.exchangeTCP(ctx, server, request, timeout)
}
return &response, nil
}
func (t *Transport) exchangeTCP(ctx context.Context, server M.Socksaddr, request *mDNS.Msg, timeout time.Duration) (*mDNS.Msg, error) {
conn, err := t.dialer.DialContext(ctx, N.NetworkTCP, server)
if err != nil {
return nil, err
}
defer conn.Close()
if deadline, loaded := ctx.Deadline(); loaded && !deadline.IsZero() {
newDeadline := time.Now().Add(timeout)
if deadline.After(newDeadline) {
deadline = newDeadline
}
conn.SetDeadline(deadline)
}
err = transport.WriteMessage(conn, 0, request)
if err != nil {
return nil, err
}
return transport.ReadMessage(conn)
}

View File

@@ -102,7 +102,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
destination: &destinationURL,
headers: headers,
transport: &http3.Transport{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (*quic.Conn, error) {
conn, dialErr := transportDialer.DialContext(ctx, N.NetworkUDP, serverAddr)
if dialErr != nil {
return nil, dialErr

View File

@@ -38,7 +38,7 @@ type Transport struct {
serverAddr M.Socksaddr
tlsConfig tls.Config
access sync.Mutex
connection quic.EarlyConnection
connection *quic.Conn
}
func NewQUIC(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteTLSDNSServerOptions) (adapter.DNSTransport, error) {
@@ -88,7 +88,7 @@ func (t *Transport) Close() error {
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
var (
conn quic.Connection
conn *quic.Conn
err error
response *mDNS.Msg
)
@@ -110,7 +110,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
return nil, err
}
func (t *Transport) openConnection() (quic.EarlyConnection, error) {
func (t *Transport) openConnection() (*quic.Conn, error) {
connection := t.connection
if connection != nil && !common.Done(connection.Context()) {
return connection, nil
@@ -139,7 +139,7 @@ func (t *Transport) openConnection() (quic.EarlyConnection, error) {
return earlyConnection, nil
}
func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn quic.Connection) (*mDNS.Msg, error) {
func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.Conn) (*mDNS.Msg, error) {
stream, err := conn.OpenStreamSync(ctx)
if err != nil {
return nil, err

View File

@@ -2,6 +2,35 @@
icon: material/alert-decagram
---
#### 1.13.0-alpha.21
* Fix missing mTLS support in client options **1**
* Fixes and improvements
See [TLS](/configuration/shared/tls/).
#### 1.12.9
* Fixes and improvements
#### 1.13.0-alpha.16
* Add curve preferences, pinned public key SHA256 and mTLS for TLS options **1**
* Fixes and improvements
See [TLS](/configuration/shared/tls/).
#### 1.13.0-alpha.15
* Update quic-go to v0.54.0
* Update gVisor to v20250811
* Update Tailscale to v1.86.5
* Fixes and improvements
#### 1.12.8
* Fixes and improvements
#### 1.13.0-alpha.11
* Fixes and improvements

View File

@@ -0,0 +1,54 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# 证书
### 结构
```json
{
"store": "",
"certificate": [],
"certificate_path": [],
"certificate_directory_path": []
}
```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
### 字段
#### store
默认的 X509 受信任 CA 证书列表。
| 类型 | 描述 |
|--------------------|--------------------------------------------------------------------------------------------|
| `system`(默认) | 系统受信任的 CA 证书 |
| `mozilla` | [Mozilla 包含列表](https://wiki.mozilla.org/CA/Included_Certificates)(已移除中国 CA 证书) |
| `none` | 空列表 |
#### certificate
要信任的证书行数组PEM 格式。
#### certificate_path
!!! note ""
文件修改时将自动重新加载。
要信任的证书路径PEM 格式。
#### certificate_directory_path
!!! note ""
文件修改时将自动重新加载。
搜索要信任的证书的目录路径PEM 格式。

View File

@@ -0,0 +1,38 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# DHCP
### 结构
```json
{
"dns": {
"servers": [
{
"type": "dhcp",
"tag": "",
"interface": "",
// 拨号字段
}
]
}
}
```
### 字段
#### interface
要监听的网络接口名称。
默认使用默认接口。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,35 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# Fake IP
### 结构
```json
{
"dns": {
"servers": [
{
"type": "fakeip",
"tag": "",
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
}
]
}
}
```
### 字段
#### inet4_range
FakeIP 的 IPv4 地址范围。
#### inet6_range
FakeIP 的 IPv6 地址范围。

View File

@@ -0,0 +1,96 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# Hosts
### 结构
```json
{
"dns": {
"servers": [
{
"type": "hosts",
"tag": "",
"path": [],
"predefined": {}
}
]
}
}
```
!!! note ""
当内容只有一项时,可以忽略 JSON 数组 [] 标签
### 字段
#### path
hosts 文件路径列表。
默认使用 `/etc/hosts`
在 Windows 上默认使用 `C:\Windows\System32\Drivers\etc\hosts`
示例:
```json
{
// "path": "/etc/hosts"
"path": [
"/etc/hosts",
"$HOME/.hosts"
]
}
```
#### predefined
预定义的 hosts。
示例:
```json
{
"predefined": {
"www.google.com": "127.0.0.1",
"localhost": [
"127.0.0.1",
"::1"
]
}
}
```
### 示例
=== "如果可用则使用 hosts"
```json
{
"dns": {
"servers": [
{
...
},
{
"type": "hosts",
"tag": "hosts"
}
],
"rules": [
{
"ip_accept_any": true,
"server": "hosts"
}
]
}
}
```

View File

@@ -0,0 +1,71 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# DNS over HTTP3 (DoH3)
### 结构
```json
{
"dns": {
"servers": [
{
"type": "h3",
"tag": "",
"server": "",
"server_port": 443,
"path": "",
"headers": {},
"tls": {},
// 拨号字段
}
]
}
}
```
!!! info "与旧版 H3 服务器的区别"
* 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。
* 旧服务器使用 `address_resolver``address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver``domain_strategy`
### 字段
#### server
==必填==
DNS 服务器的地址。
如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。
#### server_port
DNS 服务器的端口。
默认使用 `443`
#### path
DNS 服务器的路径。
默认使用 `/dns-query`
#### headers
发送到 DNS 服务器的额外标头。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,71 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# DNS over HTTPS (DoH)
### 结构
```json
{
"dns": {
"servers": [
{
"type": "https",
"tag": "",
"server": "",
"server_port": 443,
"path": "",
"headers": {},
"tls": {},
// 拨号字段
}
]
}
}
```
!!! info "与旧版 HTTPS 服务器的区别"
* 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。
* 旧服务器使用 `address_resolver``address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver``domain_strategy`
### 字段
#### server
==必填==
DNS 服务器的地址。
如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。
#### server_port
DNS 服务器的端口。
默认使用 `443`
#### path
DNS 服务器的路径。
默认使用 `/dns-query`
#### headers
发送到 DNS 服务器的额外标头。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,61 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [prefer_go](#prefer_go)
!!! question "自 sing-box 1.12.0 起"
# Local
### 结构
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "",
"prefer_go": false,
// 拨号字段
}
]
}
}
```
!!! info "与旧版本地服务器的区别"
* 旧的传统本地服务器只处理 IP 请求;新的服务器处理所有类型的请求,并支持 IP 请求的并发处理。
* 旧的本地服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。
### 字段
#### prefer_go
!!! question "自 sing-box 1.13.0 起"
启用后,`local` DNS 服务器将尽可能通过拨号自身来解析 DNS。
具体来说,它禁用了在 sing-box 1.13.0 中作为功能添加的以下行为:
1. 在 Apple 平台上:尝试在 NetworkExtension 中使用 `getaddrinfo` 解析 A/AAAA 请求。
2. 在 Linux 上:当可用时通过 `systemd-resolvd` 的 DBus 接口进行解析。
作为唯一的例外,它无法禁用以下行为:
1. 在 Android 图形客户端中,
`local` 将始终通过平台接口解析 DNS
因为没有其他方法来获取上游 DNS 服务器;
在运行 Android 10 以下版本的设备上,此接口只能解析 A/AAAA 请求。
2. 在 macOS 上,`local` 会在 Network Extension 中首先尝试 DHCP由于 DHCP 遵循拨号字段,
它不会被 `prefer_go` 禁用。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,58 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# DNS over QUIC (DoQ)
### 结构
```json
{
"dns": {
"servers": [
{
"type": "quic",
"tag": "",
"server": "",
"server_port": 853,
"tls": {},
// 拨号字段
}
]
}
}
```
!!! info "与旧版 QUIC 服务器的区别"
* 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。
* 旧服务器使用 `address_resolver``address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver``domain_strategy`
### 字段
#### server
==必填==
DNS 服务器的地址。
如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。
#### server_port
DNS 服务器的端口。
默认使用 `853`
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,83 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# Resolved
```json
{
"dns": {
"servers": [
{
"type": "resolved",
"tag": "",
"service": "resolved",
"accept_default_resolvers": false
}
]
}
}
```
### 字段
#### service
==必填==
[Resolved 服务](/zh/configuration/service/resolved) 的标签。
#### accept_default_resolvers
指示是否除了匹配域名外,还应接受默认 DNS 解析器以进行回退查询。
具体来说,默认 DNS 解析器是设置了 `SetLinkDefaultRoute``SetLinkDomains ~.` 的 DNS 服务器。
如果未启用,对于不匹配搜索域或匹配域的请求,将返回 `NXDOMAIN`
### 示例
=== "仅分割 DNS"
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "local"
},
{
"type": "resolved",
"tag": "resolved",
"service": "resolved"
}
],
"rules": [
{
"ip_accept_any": true,
"server": "resolved"
}
]
}
}
```
=== "用作全局 DNS"
```json
{
"dns": {
"servers": [
{
"type": "resolved",
"service": "resolved",
"accept_default_resolvers": true
}
]
}
}
```

View File

@@ -0,0 +1,83 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# Tailscale
### 结构
```json
{
"dns": {
"servers": [
{
"type": "tailscale",
"tag": "",
"endpoint": "ts-ep",
"accept_default_resolvers": false
}
]
}
}
```
### 字段
#### endpoint
==必填==
[Tailscale 端点](/zh/configuration/endpoint/tailscale) 的标签。
#### accept_default_resolvers
指示是否除了 MagicDNS 外,还应接受默认 DNS 解析器以进行回退查询。
如果未启用,对于非 Tailscale 域名查询将返回 `NXDOMAIN`
### 示例
=== "仅 MagicDNS"
```json
{
"dns": {
"servers": [
{
"type": "local",
"tag": "local"
},
{
"type": "tailscale",
"tag": "ts",
"endpoint": "ts-ep"
}
],
"rules": [
{
"ip_accept_any": true,
"server": "ts"
}
]
}
}
```
=== "用作全局 DNS"
```json
{
"dns": {
"servers": [
{
"type": "tailscale",
"endpoint": "ts-ep",
"accept_default_resolvers": true
}
]
}
}
```

View File

@@ -0,0 +1,52 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# TCP
### 结构
```json
{
"dns": {
"servers": [
{
"type": "tcp",
"tag": "",
"server": "",
"server_port": 53,
// 拨号字段
}
]
}
}
```
!!! info "与旧版 TCP 服务器的区别"
* 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。
* 旧服务器使用 `address_resolver``address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver``domain_strategy`
### 字段
#### server
==必填==
DNS 服务器的地址。
如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。
#### server_port
DNS 服务器的端口。
默认使用 `53`
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,58 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# DNS over TLS (DoT)
### 结构
```json
{
"dns": {
"servers": [
{
"type": "tls",
"tag": "",
"server": "",
"server_port": 853,
"tls": {},
// 拨号字段
}
]
}
}
```
!!! info "与旧版 TLS 服务器的区别"
* 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。
* 旧服务器使用 `address_resolver``address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver``domain_strategy`
### 字段
#### server
==必填==
DNS 服务器的地址。
如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。
#### server_port
DNS 服务器的端口。
默认使用 `853`
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,52 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# UDP
### 结构
```json
{
"dns": {
"servers": [
{
"type": "udp",
"tag": "",
"server": "",
"server_port": 53,
// 拨号字段
}
]
}
}
```
!!! info "与旧版 UDP 服务器的区别"
* 旧服务器默认使用默认出站,除非指定了绕行;新服务器像出站一样使用拨号器,相当于默认使用空的直连出站。
* 旧服务器使用 `address_resolver``address_strategy` 来解析服务器中的域名;新服务器改用 [拨号字段](/zh/configuration/shared/dial/) 中的 `domain_resolver``domain_strategy`
### 字段
#### server
==必填==
DNS 服务器的地址。
如果使用域名,还必须设置 `domain_resolver` 来解析 IP 地址。
#### server_port
DNS 服务器的端口。
默认使用 `53`
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -0,0 +1,103 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
### 结构
```json
{
"type": "tailscale",
"tag": "ts-ep",
"state_directory": "",
"auth_key": "",
"control_url": "",
"ephemeral": false,
"hostname": "",
"accept_routes": false,
"exit_node": "",
"exit_node_allow_lan_access": false,
"advertise_routes": [],
"advertise_exit_node": false,
"udp_timeout": "5m",
... // 拨号字段
}
```
### 字段
#### state_directory
存储 Tailscale 状态的目录。
默认使用 `tailscale`
示例:`$HOME/.tailscale`
#### auth_key
!!! note
认证密钥不是必需的。默认情况下sing-box 将记录登录 URL或在图形客户端上弹出通知
用于创建节点的认证密钥。如果节点已经创建(从之前存储的状态),则不使用此字段。
#### control_url
协调服务器 URL。
默认使用 `https://controlplane.tailscale.com`
#### ephemeral
指示实例是否应注册为临时节点 (https://tailscale.com/s/ephemeral-nodes)。
#### hostname
节点的主机名。
默认使用系统主机名。
示例:`localhost`
#### accept_routes
指示节点是否应接受其他节点通告的路由。
#### exit_node
要使用的出口节点名称或 IP 地址。
#### exit_node_allow_lan_access
!!! note
当出口节点没有相应的通告路由时,即使设置了 `exit_node_allow_lan_access`,私有流量也无法路由到出口节点。
指示本地可访问的子网应该直接路由还是通过出口节点路由。
#### advertise_routes
通告到 Tailscale 网络的 CIDR 前缀,作为可通过当前节点访问的路由。
示例:`["192.168.1.1/24"]`
#### advertise_exit_node
指示节点是否应将自己通告为出口节点。
#### udp_timeout
UDP NAT 过期时间。
默认使用 `5m`
### 拨号字段
!!! note
Tailscale 端点中的拨号字段仅控制它如何连接到控制平面,与实际连接无关。
参阅 [拨号字段](/zh/configuration/shared/dial/) 了解详情。

View File

@@ -43,13 +43,11 @@ Trojan 用户。
#### tls
==如果启用 HTTP3 则必填==
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### fallback
!!! quote ""
!!! failure ""
没有证据表明 GFW 基于 HTTP 响应检测并阻止 Trojan 服务器,并且在服务器上打开标准 http/s 端口是一个更大的特征。

View File

@@ -0,0 +1,135 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# DERP
DERP 服务是一个 Tailscale DERP 服务器,类似于 [derper](https://pkg.go.dev/tailscale.com/cmd/derper)。
### 结构
```json
{
"type": "derp",
... // 监听字段
"tls": {},
"config_path": "",
"verify_client_endpoint": [],
"verify_client_url": [],
"home": "",
"mesh_with": [],
"mesh_psk": "",
"mesh_psk_file": "",
"stun": {}
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。
### 字段
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### config_path
==必填==
Derper 配置文件路径。
示例:`derper.key`
#### verify_client_endpoint
用于验证客户端的 Tailscale 端点标签。
#### verify_client_url
用于验证客户端的 URL。
对象格式:
```json
{
"url": "https://my-headscale.com/verify",
... // 拨号字段
}
```
将数组值设置为字符串 `__URL__` 等同于配置:
```json
{ "url": __URL__ }
```
#### home
在根路径提供的内容。可以留空(默认值,显示默认主页)、`blank` 显示空白页面,或一个重定向的 URL。
#### mesh_with
与其他 DERP 服务器组网。
对象格式:
```json
{
"server": "",
"server_port": "",
"host": "",
"tls": {},
... // 拨号字段
}
```
对象字段:
- `server`**必填** DERP 服务器地址。
- `server_port`**必填** DERP 服务器端口。
- `host`:自定义 DERP 主机名。
- `tls`[TLS](/zh/configuration/shared/tls/#outbound)
- `拨号字段`[拨号字段](/zh/configuration/shared/dial/)
#### mesh_psk
DERP 组网的预共享密钥。
#### mesh_psk_file
DERP 组网的预共享密钥文件。
#### stun
STUN 服务器监听选项。
对象格式:
```json
{
"enabled": true,
... // 监听字段
}
```
对象字段:
- `enabled`**必填** 启用 STUN 服务器。
- `listen`**必填** STUN 服务器监听地址,默认为 `::`
- `listen_port`**必填** STUN 服务器监听端口,默认为 `3478`
- `其他监听字段`[监听字段](/zh/configuration/shared/listen/)
`stun` 值设置为数字 `__PORT__` 等同于配置:
```json
{ "enabled": true, "listen_port": __PORT__ }
```

View File

@@ -0,0 +1,32 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# 服务
### 结构
```json
{
"services": [
{
"type": "",
"tag": ""
}
]
}
```
### 字段
| 类型 | 格式 |
|-----------|------------------------|
| `derp` | [DERP](./derp) |
| `resolved`| [Resolved](./resolved) |
| `ssm-api` | [SSM API](./ssm-api) |
#### tag
端点的标签。

View File

@@ -0,0 +1,44 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# Resolved
Resolved 服务是一个伪造的 systemd-resolved DBUS 服务,用于从其他程序
(如 NetworkManager接收 DNS 设置并提供 DNS 解析。
另请参阅:[Resolved DNS 服务器](/zh/configuration/dns/server/resolved/)
### 结构
```json
{
"type": "resolved",
... // 监听字段
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。
### 字段
#### listen
==必填==
监听地址。
默认使用 `127.0.0.53`
#### listen_port
==必填==
监听端口。
默认使用 `53`

View File

@@ -0,0 +1,58 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.12.0 起"
# SSM API
SSM API 服务是一个用于管理 Shadowsocks 服务器的 RESTful API 服务器。
参阅 https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md
### 结构
```json
{
"type": "ssm-api",
... // 监听字段
"servers": {},
"cache_path": "",
"tls": {}
}
```
### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。
### 字段
#### servers
==必填==
从 HTTP 端点到 [Shadowsocks 入站](/zh/configuration/inbound/shadowsocks) 标签的映射对象。
选定的 Shadowsocks 入站必须配置启用 [managed](/zh/configuration/inbound/shadowsocks#managed)。
示例:
```json
{
"servers": {
"/": "ss-in"
}
}
```
#### cache_path
如果设置,当服务器即将停止时,流量和用户状态将保存到指定的 JSON 文件中,
以便在下次启动时恢复。
#### tls
TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。

View File

@@ -4,8 +4,16 @@ icon: material/new-box
!!! quote "Changes in sing-box 1.13.0"
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_rx](#kernel_rx)
:material-plus: [curve_preferences](#curve_preferences)
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
:material-plus: [client_certificate](#client_certificate)
:material-plus: [client_certificate_path](#client_certificate_path)
:material-plus: [client_key](#client_key)
:material-plus: [client_key_path](#client_key_path)
:material-plus: [client_authentication](#client_authentication)
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
!!! quote "Changes in sing-box 1.12.0"
@@ -29,8 +37,13 @@ icon: material/new-box
"min_version": "",
"max_version": "",
"cipher_suites": [],
"curve_preferences": [],
"certificate": [],
"certificate_path": "",
"client_authentication": "",
"client_certificate": [],
"client_certificate_path": [],
"client_certificate_public_key_sha256": [],
"key": [],
"key_path": "",
"kernel_tx": false,
@@ -90,8 +103,14 @@ icon: material/new-box
"min_version": "",
"max_version": "",
"cipher_suites": [],
"curve_preferences": [],
"certificate": "",
"certificate_path": "",
"certificate_public_key_sha256": [],
"client_certificate": [],
"client_certificate_path": "",
"client_key": [],
"client_key_path": "",
"fragment": false,
"fragment_fallback_delay": "",
"record_fragment": false,
@@ -195,14 +214,29 @@ By default, the maximum version is currently TLS 1.3.
#### cipher_suites
A list of enabled TLS 1.01.2 cipher suites. The order of the list is ignored.
List of enabled TLS 1.01.2 cipher suites. The order of the list is ignored.
Note that TLS 1.3 cipher suites are not configurable.
If empty, a safe default list is used. The default cipher suites might change over time.
#### curve_preferences
!!! question "Since sing-box 1.13.0"
Set of supported key exchange mechanisms. The order of the list is ignored, and key exchange mechanisms are chosen
from this list using an internal preference order by Golang.
Available values, also the default list:
* `P256`
* `P384`
* `P521`
* `X25519`
* `X25519MLKEM768`
#### certificate
The server certificate line array, in PEM format.
Server certificates chain line array, in PEM format.
#### certificate_path
@@ -210,7 +244,58 @@ The server certificate line array, in PEM format.
Will be automatically reloaded if file modified.
The path to the server certificate, in PEM format.
The path to server certificate chain, in PEM format.
#### certificate_public_key_sha256
!!! question "Since sing-box 1.13.0"
==Client only==
List of SHA-256 hashes of server certificate public keys, in base64 format.
To generate the SHA-256 hash for a certificate's public key, use the following commands:
```bash
# For a certificate file
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# For a certificate from a remote server
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```
#### client_certificate
!!! question "Since sing-box 1.13.0"
==Client only==
Client certificate chain line array, in PEM format.
#### client_certificate_path
!!! question "Since sing-box 1.13.0"
==Client only==
The path to client certificate chain, in PEM format.
#### client_key
!!! question "Since sing-box 1.13.0"
==Client only==
Client private key line array, in PEM format.
#### client_key_path
!!! question "Since sing-box 1.13.0"
==Client only==
The path to client private key, in PEM format.
#### key
@@ -228,6 +313,63 @@ The server private key line array, in PEM format.
The path to the server private key, in PEM format.
#### client_authentication
!!! question "Since sing-box 1.13.0"
==Server only==
The type of client authentication to use.
Available values:
* `no` (default)
* `request`
* `require-any`
* `verify-if-given`
* `require-and-verify`
One of `client_certificate`, `client_certificate_path`, or `client_certificate_public_key_sha256` is required
if this option is set to `verify-if-given`, or `require-and-verify`.
#### client_certificate
!!! question "Since sing-box 1.13.0"
==Server only==
Client certificate chain line array, in PEM format.
#### client_certificate_path
!!! question "Since sing-box 1.13.0"
==Server only==
!!! note ""
Will be automatically reloaded if file modified.
List of path to client certificate chain, in PEM format.
#### client_certificate_public_key_sha256
!!! question "Since sing-box 1.13.0"
==Server only==
List of SHA-256 hashes of client certificate public keys, in base64 format.
To generate the SHA-256 hash for a certificate's public key, use the following commands:
```bash
# For a certificate file
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# For a certificate from a remote server
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```
#### kernel_tx
!!! question "Since sing-box 1.13.0"

View File

@@ -1,18 +1,26 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! quote "sing-box 1.13.0 中的更改"
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_tx](#kernel_tx)
:material-plus: [kernel_rx](#kernel_rx)
:material-plus: [curve_preferences](#curve_preferences)
:material-plus: [certificate_public_key_sha256](#certificate_public_key_sha256)
:material-plus: [client_certificate](#client_certificate)
:material-plus: [client_certificate_path](#client_certificate_path)
:material-plus: [client_key](#client_key)
:material-plus: [client_key_path](#client_key_path)
:material-plus: [client_authentication](#client_authentication)
:material-plus: [client_certificate_public_key_sha256](#client_certificate_public_key_sha256)
!!! quote "sing-box 1.12.0 中的更改"
:material-plus: [tls_fragment](#tls_fragment)
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
:material-plus: [tls_record_fragment](#tls_record_fragment)
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
:material-plus: [fragment](#fragment)
:material-plus: [fragment_fallback_delay](#fragment_fallback_delay)
:material-plus: [record_fragment](#record_fragment)
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
!!! quote "sing-box 1.10.0 中的更改"
@@ -29,8 +37,13 @@ icon: material/alert-decagram
"min_version": "",
"max_version": "",
"cipher_suites": [],
"curve_preferences": [],
"certificate": [],
"certificate_path": "",
"client_authentication": "",
"client_certificate": [],
"client_certificate_path": [],
"client_certificate_public_key_sha256": [],
"key": [],
"key_path": "",
"kernel_tx": false,
@@ -90,17 +103,25 @@ icon: material/alert-decagram
"min_version": "",
"max_version": "",
"cipher_suites": [],
"certificate": [],
"curve_preferences": [],
"certificate": "",
"certificate_path": "",
"certificate_public_key_sha256": [],
"client_certificate": [],
"client_certificate_path": "",
"client_key": [],
"client_key_path": "",
"fragment": false,
"fragment_fallback_delay": "",
"record_fragment": false,
"ech": {
"enabled": false,
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false,
"config": [],
"config_path": ""
"config_path": "",
// 废弃的
"pq_signature_schemes_enabled": false,
"dynamic_record_sizing_disabled": false
},
"utls": {
"enabled": false,
@@ -191,13 +212,27 @@ TLS 版本值:
#### cipher_suites
启用的 TLS 1.0-1.2密码套件列表。列表的顺序被忽略。请注意TLS 1.3 的密码套件是不可配置的。
启用的 TLS 1.01.2 密码套件列表。列表的顺序被忽略。请注意TLS 1.3 的密码套件是不可配置的。
如果为空,则使用安全的默认列表。默认密码套件可能会随着时间的推移而改变。
#### curve_preferences
!!! question "自 sing-box 1.13.0 起"
支持的密钥交换机制集合。列表的顺序被忽略,密钥交换机制通过 Golang 的内部偏好顺序从此列表中选择。
可用值,同时也是默认列表:
* `P256`
* `P384`
* `P521`
* `X25519`
* `X25519MLKEM768`
#### certificate
服务器 PEM 证书行数组。
服务器证书行数组PEM 格式
#### certificate_path
@@ -205,7 +240,57 @@ TLS 版本值:
文件更改时将自动重新加载。
服务器 PEM 证书路径
服务器证书链路径PEM 格式
#### certificate_public_key_sha256
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
服务器证书公钥的 SHA-256 哈希列表base64 格式。
要生成证书公钥的 SHA-256 哈希,请使用以下命令:
```bash
# 对于证书文件
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# 对于远程服务器的证书
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```
#### client_certificate
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端证书链行数组PEM 格式。
#### client_certificate_path
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端证书链路径PEM 格式。
#### client_key
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端私钥行数组PEM 格式。
#### client_key_path
!!! question "自 sing-box 1.13.0 起"
==仅客户端==
客户端私钥路径PEM 格式。
#### key
@@ -221,7 +306,68 @@ TLS 版本值:
==仅服务器==
服务器 PEM 私钥路径。
!!! note ""
文件更改时将自动重新加载。
服务器私钥路径PEM 格式。
#### client_authentication
!!! question "自 sing-box 1.13.0 起"
==仅服务器==
要使用的客户端身份验证类型。
可用值:
* `no`(默认)
* `request`
* `require-any`
* `verify-if-given`
* `require-and-verify`
如果此选项设置为 `verify-if-given``require-and-verify`
则需要 `client_certificate``client_certificate_path``client_certificate_public_key_sha256` 中的一个。
#### client_certificate
!!! question "自 sing-box 1.13.0 起"
==仅服务器==
客户端证书链行数组PEM 格式。
#### client_certificate_path
!!! question "自 sing-box 1.13.0 起"
==仅服务器==
!!! note ""
文件更改时将自动重新加载。
客户端证书链路径列表PEM 格式。
#### client_certificate_public_key_sha256
!!! question "自 sing-box 1.13.0 起"
==仅服务器==
客户端证书公钥的 SHA-256 哈希列表base64 格式。
要生成证书公钥的 SHA-256 哈希,请使用以下命令:
```bash
# 对于证书文件
openssl x509 -in certificate.pem -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
# 对于远程服务器的证书
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
```
#### kernel_tx
@@ -300,44 +446,11 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
默认使用 chrome 指纹。
## ECH 字段
### ECH 字段
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分
信息。
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分信息。
ECH 配置和密钥可以通过 `sing-box generate ech-keypair [--pq-signature-schemes-enabled]` 生成。
#### key
==仅服务器==
ECH PEM 密钥行数组
#### key_path
==仅服务器==
!!! note ""
文件更改时将自动重新加载。
ECH PEM 密钥路径
#### config
==仅客户端==
ECH PEM 配置行数组
如果为空,将尝试从 DNS 加载。
#### config_path
==仅客户端==
ECH PEM 配置路径
如果为空,将尝试从 DNS 加载。
ECH 密钥和配置可以通过 `sing-box generate ech-keypair` 生成。
#### pq_signature_schemes_enabled
@@ -347,8 +460,6 @@ ECH PEM 配置路径
启用对后量子对等证书签名方案的支持。
建议匹配 `sing-box generate ech-keypair` 的参数。
#### dynamic_record_sizing_disabled
!!! failure "已在 sing-box 1.12.0 废弃"
@@ -357,57 +468,91 @@ ECH PEM 配置路径
禁用 TLS 记录的自适应大小调整。
如果为 true,则始终使用最大可能的 TLS 记录大小。
如果为 false可能会调整 TLS 记录的大小以尝试改善延迟。
为 true 时,总是使用最大可能的 TLS 记录大小。
为 false,可能会调整 TLS 记录的大小以尝试改善延迟。
#### tls_fragment
#### key
==仅服务器==
ECH 密钥行数组PEM 格式。
#### key_path
==仅服务器==
!!! note ""
文件更改时将自动重新加载。
ECH 密钥路径PEM 格式。
#### config
==仅客户端==
ECH 配置行数组PEM 格式。
如果为空,将尝试从 DNS 加载。
#### config_path
==仅客户端==
ECH 配置路径PEM 格式。
如果为空,将尝试从 DNS 加载。
#### fragment
!!! question "自 sing-box 1.12.0 起"
==仅客户端==
通过分段 TLS 握手数据包来绕过防火墙检测
通过分段 TLS 握手数据包来绕过防火墙。
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
由于性能不佳,请首先尝试 `record_fragment`,且仅应用于已知被阻止的服务器名称。
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
在 Linux、Apple 平台和需要管理员权限的Windows 系统上,
可以自动检测等待时间。否则,将回退到
等待 `fragment_fallback_delay` 指定的固定时间。
此外,实际等待时间于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
此外,如果实际等待时间于 20ms也会回退到等待固定时间
因为目标被认为是本地的或在透明代理后面。
#### tls_fragment_fallback_delay
#### fragment_fallback_delay
!!! question "自 sing-box 1.12.0 起"
==仅客户端==
当 TLS 分片功能无法自动定等待时间时使用的回退值。
当 TLS 分无法自动定等待时间时使用的回退值。
默认使用 `500ms`
#### tls_record_fragment
==仅客户端==
#### record_fragment
!!! question "自 sing-box 1.12.0 起"
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
==仅客户端==
将 TLS 握手分段为多个 TLS 记录以绕过防火墙。
### ACME 字段
#### domain
一组域名。
域名列表
默认禁用 ACME。
如果为空则禁用 ACME。
#### data_directory
ACME 数据目录。
ACME 数据存储目录。
默认使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`
如果为空则使用 `$XDG_DATA_HOME/certmagic|$HOME/.local/share/certmagic`
#### default_server_name
@@ -445,12 +590,11 @@ ACME 数据目录。
#### external_account
EAB外部帐户绑定包含将 ACME 帐户绑定或映射到其他已知帐户所需的信息由 CA
EAB外部帐户绑定包含将 ACME 帐户绑定或映射到 CA 已知的其他帐户所需的信息。
外部帐户绑定用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。
外部帐户绑定"用于将 ACME 帐户与非 ACME 系统中的现有帐户相关联,例如 CA 客户数据库。
为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要向 ACME 客户端提供 MAC 密钥和密钥标识符,使用 ACME 之外的一些机制。
§7.3.4
为了启用 ACME 帐户绑定,运行 ACME 服务器的 CA 需要使用 ACME 之外的某种机制向 ACME 客户端提供 MAC 密钥和密钥标识符。§7.3.4
#### external_account.key_id
@@ -500,6 +644,8 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。
#### max_time_difference
服务器与和客户端之间允许的最大时间差。
==仅服务器==
默认禁用检查。
服务器和客户端之间的最大时间差。
如果为空则禁用检查。

View File

@@ -0,0 +1,82 @@
!!! warning ""
这是 SagerNet 创建的专有协议,不是 shadowsocks 的一部分。
UDP over TCP 协议用于在 TCP 中传输 UDP 数据包。
### 结构
```json
{
"enabled": true,
"version": 2
}
```
!!! info ""
当不指定版本时,结构可以用布尔值替换。
### 字段
#### enabled
启用 UDP over TCP 协议。
#### version
协议版本,`1``2`
默认使用 2。
### 应用程序支持
| 项目 | UoT v1 | UoT v2 |
|--------------|----------------------|----------------------|
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
| Clash.Meta | v1.12.0 (2022/07/02) | v1.14.3 (2023/03/31) |
| Shadowrocket | v2.2.12 (2022/08/13) | / |
### 协议详情
#### 协议版本 1
客户端向上层代理协议请求魔法地址以表示请求:`sp.udp-over-tcp.arpa`
#### 流格式
| ATYP | 地址 | 端口 | 长度 | 数据 |
|------|----------|-------|--------|----------|
| u8 | 可变长 | u16be | u16be | 可变长 |
**ATYP / 地址 / 端口**:使用 SOCKS 地址格式,但使用不同的地址类型:
| ATYP | 地址类型 |
|--------|-----------|
| `0x00` | IPv4 地址 |
| `0x01` | IPv6 地址 |
| `0x02` | 域名 |
#### 协议版本 2
协议版本 2 使用新的魔法地址:`sp.v2.udp-over-tcp.arpa`
##### 请求格式
| isConnect | ATYP | 地址 | 端口 |
|-----------|------|----------|-------|
| u8 | u8 | 可变长 | u16be |
**isConnect**:设置为 1 表示流使用连接格式0 表示禁用。
**ATYP / 地址 / 端口**:请求目标,使用 SOCKS 地址格式。
##### 连接流格式
| 长度 | 数据 |
|--------|----------|
| u16be | 可变长 |
##### 非连接流格式
与协议版本 1 中的流格式相同。

View File

@@ -12,9 +12,7 @@ type iOSPauseFields struct {
func (s *BoxService) Pause() {
s.pauseManager.DevicePause()
if !C.IsIos {
s.instance.Router().ResetNetwork()
} else {
if C.IsIos {
if s.endPauseTimer == nil {
s.endPauseTimer = time.AfterFunc(time.Minute, s.pauseManager.DeviceWake)
} else {
@@ -26,7 +24,6 @@ func (s *BoxService) Pause() {
func (s *BoxService) Wake() {
if !C.IsIos {
s.pauseManager.DeviceWake()
s.instance.Router().ResetNetwork()
}
}

53
go.mod
View File

@@ -1,12 +1,13 @@
module github.com/sagernet/sing-box
go 1.23.1
go 1.24.7
require (
github.com/anytls/sing-anytls v0.0.8
github.com/anytls/sing-anytls v0.0.11
github.com/caddyserver/certmagic v0.23.0
github.com/coder/websocket v1.8.13
github.com/cretz/bine v0.2.0
github.com/database64128/tfo-go/v2 v2.2.2
github.com/go-chi/chi/v5 v5.2.2
github.com/go-chi/render v1.0.3
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
@@ -15,7 +16,6 @@ require (
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
github.com/metacubex/utls v1.8.0
github.com/mholt/acmez/v3 v3.1.2
github.com/miekg/dns v1.1.67
@@ -25,30 +25,30 @@ require (
github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.8
github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506
github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.8.0-beta.1
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
github.com/sagernet/quic-go v0.54.0-sing-box-mod.3
github.com/sagernet/sing v0.8.0-beta.5
github.com/sagernet/sing-mux v0.3.3
github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5
github.com/sagernet/sing-quic v0.6.0-beta.3
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.8.0-beta.8
github.com/sagernet/sing-tun v0.8.0-beta.10
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1
github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1
github.com/sagernet/wireguard-go v0.0.1-beta.7
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.3
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.11.1
github.com/vishvananda/netns v0.0.5
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.41.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
golang.org/x/mod v0.27.0
golang.org/x/net v0.43.0
golang.org/x/sys v0.35.0
golang.org/x/crypto v0.42.0
golang.org/x/exp v0.0.0-20250911091902-df9299821621
golang.org/x/mod v0.28.0
golang.org/x/net v0.44.0
golang.org/x/sys v0.36.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/grpc v1.73.0
google.golang.org/protobuf v1.36.6
@@ -63,18 +63,18 @@ require (
github.com/akutz/memconn v0.1.0 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gaissmai/bart v0.11.1 // indirect
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect
github.com/gaissmai/bart v0.18.0 // indirect
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
@@ -84,16 +84,13 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/illarion/gonotify/v2 v2.0.3 // indirect
github.com/illarion/gonotify/v3 v3.0.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
github.com/libdns/libdns v1.1.0 // indirect
github.com/mdlayher/genetlink v1.3.2 // indirect
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
@@ -110,23 +107,23 @@ require (
github.com/spf13/pflag v1.0.6 // indirect
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/tailscale/netlink v1.1.1-0.20240822203006-4d49adab4de7 // indirect
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/blake3 v0.2.4 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/time v0.9.0 // indirect
golang.org/x/tools v0.36.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.37.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect

110
go.sum
View File

@@ -8,10 +8,8 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/anytls/sing-anytls v0.0.8 h1:1u/fnH1HoeeMV5mX7/eUOjLBvPdkd1UJRmXiRi6Vymc=
github.com/anytls/sing-anytls v0.0.8/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
github.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
@@ -27,6 +25,10 @@ github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a h1:t4SDi0pmNkryzKdM4QF3o5vqSP4GRjeZD/6j3nyxNP0=
github.com/database64128/netx-go v0.0.0-20240905055117-62795b8b054a/go.mod h1:7K2NQKbabB5mBl41vF6YayYl5g7YpDwc4dQ5iMpP3Lg=
github.com/database64128/tfo-go/v2 v2.2.2 h1:BxynF4qGF5ct3DpPLEG62uyJZ3LQhqaf0Ken+kyy7PM=
github.com/database64128/tfo-go/v2 v2.2.2/go.mod h1:2IW8jppdBwdVMjA08uEyMNnqiAHKUlqAA+J8NrsfktY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -41,16 +43,16 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo=
github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY=
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@@ -76,22 +78,16 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M=
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A=
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
github.com/illarion/gonotify/v3 v3.0.2 h1:O7S6vcopHexutmpObkeWsnzMJt/r1hONIEogeVNmJMk=
github.com/illarion/gonotify/v3 v3.0.2/go.mod h1:HWGPdPe817GfvY3w7cx6zkbzNZfi3QjcBm/wgVvEL1U=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU=
@@ -103,8 +99,6 @@ github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IX
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ=
github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8=
@@ -122,8 +116,6 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ
github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.8.0 h1:mSYi6FMnmc5riARl5UZDmWVy710z+P5b7xuGW0lV9ac=
github.com/metacubex/utls v1.8.0/go.mod h1:FdjYzVfCtgtna19hX0ER1Xsa5uJInwdQ4IcaaI98lEQ=
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
@@ -158,37 +150,37 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.8 h1:vXgoN0pjsMONAaYCTdsKBX2T1kxuS7sbT/mZ7PElGoo=
github.com/sagernet/gomobile v0.1.8/go.mod h1:A8l3FlHi2D/+mfcd4HHvk5DGFPW/ShFb9jHP5VmSiDY=
github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506 h1:x/t3XqWshOlWqRuumpvbUvjtEr/6mJuBXAVovPefbUg=
github.com/sagernet/gvisor v0.0.0-20250909151924-850a370d8506/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 h1:AzCE2RhBjLJ4WIWc/GejpNh+z30d5H1hwaB0nD9eY3o=
github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1/go.mod h1:NJKBtm9nVEK3iyOYWsUlrDQuoGh4zJ4KOPhSYVidvQ4=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/quic-go v0.54.0-sing-box-mod.3 h1:12pJN/zdpRltLG8l8JA65QYy/a+Mz938yAN3ZQUinbo=
github.com/sagernet/quic-go v0.54.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.8.0-beta.1 h1:tBOdh/K/EBdXWuBxUJsZONyxDzyfzjdCF1Yq57QtpE4=
github.com/sagernet/sing v0.8.0-beta.1/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.8.0-beta.5 h1:Cm4CnLQGNyG5Jl1U9pKWAjFUcbjchGGqn1xeXzfI5kw=
github.com/sagernet/sing v0.8.0-beta.5/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5 h1:vnRNLE0bBnz5NNbBoFH7NA7mlvNSa2Z4w+1Eb8pyX48=
github.com/sagernet/sing-quic v0.5.2-0.20250909100920-da23407a63d5/go.mod h1:gi/sGED8gTWgTAp3GlzXo2D7mXYY+ERoxtGvSkNx3sI=
github.com/sagernet/sing-quic v0.6.0-beta.3 h1:Z2vt49f9vNtHc9BbF9foI859n4+NAOV3gBeB1LuzL1Q=
github.com/sagernet/sing-quic v0.6.0-beta.3/go.mod h1:2/swrSS6wG6MyQA5Blq31VEWitHgBju+yZE8cPK1J5I=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.8.0-beta.8 h1:K/tPAiuW4Qf1usZvdqjAMVwaxAVE/fb64YqO9SCvcTg=
github.com/sagernet/sing-tun v0.8.0-beta.8/go.mod h1:DCGwHe70ujuzmQ3bvUnf9u1FFRoRvBQ1dDpqZov1ZDA=
github.com/sagernet/sing-tun v0.8.0-beta.10 h1:sHqSXTvzKPDF67AwZdoBV5FA91tFdWGfA1AbenIbpA4=
github.com/sagernet/sing-tun v0.8.0-beta.10/go.mod h1:eWETzl4AwaxGKiZTpDIDVJLTBz9cfIdoZwaZY1jlSjg=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1 h1:aSwUNYUkVyVvdmBSufR8/nRFonwJeKSIROxHcm5br9o=
github.com/sagernet/sing-vmess v0.2.8-0.20250909125414-3aed155119a1/go.mod h1:P11scgTxMxVVQ8dlM27yNm3Cro40mD0+gHbnqrNGDuY=
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1 h1:cWM1iPwqIE1t06ft80wpvFB4xbhOpIFI+TFnTw2gnbs=
github.com/sagernet/tailscale v1.80.3-sing-box-1.13-mod.1/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.3 h1:OGoHEw76F3F4keIGcOwB/5U+P1N3i+hLlgC7rvSnub0=
github.com/sagernet/tailscale v1.86.5-sing-box-1.13-mod.3/go.mod h1:YdN/avjce8sqPFLT9E1uEh8gPewNSnC41U4ZhBJ+ACw=
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288 h1:E2tZFeg9mGYGQ7E7BbxMv1cU35HxwgRm6tPKI2Pp7DA=
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20250917110311-16510ac47288/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
@@ -210,8 +202,6 @@ github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 h1:rXZGgEa+k2vJM8xT0PoSKfVXwFGPQ3z3CJfmnHJkZZw=
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4/go.mod h1:ikbF+YT089eInTp9f2vmvy4+ZVnW5hzX1q2WknxSprQ=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
@@ -222,6 +212,8 @@ github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc h1:24heQPtnFR+y
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc/go.mod h1:f93CXfllFsO9ZQVq+Zocb1Gp4G5Fz0b0rXHLOzt/Djc=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:UBPHPtv8+nEAy2PD8RyAhOYvau1ek0HDJqLS/Pysi14=
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw=
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
@@ -262,21 +254,21 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -285,20 +277,20 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -320,6 +312,8 @@ gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633 h1:2gap+Kh/3F47cO6hAu3idFvsJ0ue6TRcEi2IUkv/F8k=
gvisor.dev/gvisor v0.0.0-20250205023644-9414b50a5633/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=

View File

@@ -1,24 +1,80 @@
package option
import "github.com/sagernet/sing/common/json/badoption"
import (
"crypto/tls"
"encoding/json"
"strings"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json/badoption"
)
type InboundTLSOptions struct {
Enabled bool `json:"enabled,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
Key badoption.Listable[string] `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"`
KernelTx bool `json:"kernel_tx,omitempty"`
KernelRx bool `json:"kernel_rx,omitempty"`
ACME *InboundACMEOptions `json:"acme,omitempty"`
ECH *InboundECHOptions `json:"ech,omitempty"`
Reality *InboundRealityOptions `json:"reality,omitempty"`
Enabled bool `json:"enabled,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
ClientAuthentication ClientAuthType `json:"client_authentication,omitempty"`
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
ClientCertificatePath badoption.Listable[string] `json:"client_certificate_path,omitempty"`
ClientCertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"client_certificate_public_key_sha256,omitempty"`
Key badoption.Listable[string] `json:"key,omitempty"`
KeyPath string `json:"key_path,omitempty"`
KernelTx bool `json:"kernel_tx,omitempty"`
KernelRx bool `json:"kernel_rx,omitempty"`
ACME *InboundACMEOptions `json:"acme,omitempty"`
ECH *InboundECHOptions `json:"ech,omitempty"`
Reality *InboundRealityOptions `json:"reality,omitempty"`
}
type ClientAuthType tls.ClientAuthType
func (t ClientAuthType) MarshalJSON() ([]byte, error) {
var stringValue string
switch t {
case ClientAuthType(tls.NoClientCert):
stringValue = "no"
case ClientAuthType(tls.RequestClientCert):
stringValue = "request"
case ClientAuthType(tls.RequireAnyClientCert):
stringValue = "require-any"
case ClientAuthType(tls.VerifyClientCertIfGiven):
stringValue = "verify-if-given"
case ClientAuthType(tls.RequireAndVerifyClientCert):
stringValue = "require-and-verify"
default:
return nil, E.New("unknown client authentication type: ", int(t))
}
return json.Marshal(stringValue)
}
func (t *ClientAuthType) UnmarshalJSON(data []byte) error {
var stringValue string
err := json.Unmarshal(data, &stringValue)
if err != nil {
return err
}
switch stringValue {
case "no":
*t = ClientAuthType(tls.NoClientCert)
case "request":
*t = ClientAuthType(tls.RequestClientCert)
case "require-any":
*t = ClientAuthType(tls.RequireAnyClientCert)
case "verify-if-given":
*t = ClientAuthType(tls.VerifyClientCertIfGiven)
case "require-and-verify":
*t = ClientAuthType(tls.RequireAndVerifyClientCert)
default:
return E.New("unknown client authentication type: ", stringValue)
}
return nil
}
type InboundTLSOptionsContainer struct {
@@ -39,24 +95,30 @@ func (o *InboundTLSOptionsContainer) ReplaceInboundTLSOptions(options *InboundTL
}
type OutboundTLSOptions struct {
Enabled bool `json:"enabled,omitempty"`
DisableSNI bool `json:"disable_sni,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
Fragment bool `json:"fragment,omitempty"`
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
RecordFragment bool `json:"record_fragment,omitempty"`
KernelTx bool `json:"kernel_tx,omitempty"`
KernelRx bool `json:"kernel_rx,omitempty"`
ECH *OutboundECHOptions `json:"ech,omitempty"`
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
Reality *OutboundRealityOptions `json:"reality,omitempty"`
Enabled bool `json:"enabled,omitempty"`
DisableSNI bool `json:"disable_sni,omitempty"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
ALPN badoption.Listable[string] `json:"alpn,omitempty"`
MinVersion string `json:"min_version,omitempty"`
MaxVersion string `json:"max_version,omitempty"`
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
CurvePreferences badoption.Listable[CurvePreference] `json:"curve_preferences,omitempty"`
Certificate badoption.Listable[string] `json:"certificate,omitempty"`
CertificatePath string `json:"certificate_path,omitempty"`
CertificatePublicKeySHA256 badoption.Listable[[]byte] `json:"certificate_public_key_sha256,omitempty"`
ClientCertificate badoption.Listable[string] `json:"client_certificate,omitempty"`
ClientCertificatePath string `json:"client_certificate_path,omitempty"`
ClientKey badoption.Listable[string] `json:"client_key,omitempty"`
ClientKeyPath string `json:"client_key_path,omitempty"`
Fragment bool `json:"fragment,omitempty"`
FragmentFallbackDelay badoption.Duration `json:"fragment_fallback_delay,omitempty"`
RecordFragment bool `json:"record_fragment,omitempty"`
KernelTx bool `json:"kernel_tx,omitempty"`
KernelRx bool `json:"kernel_rx,omitempty"`
ECH *OutboundECHOptions `json:"ech,omitempty"`
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
Reality *OutboundRealityOptions `json:"reality,omitempty"`
}
type OutboundTLSOptionsContainer struct {
@@ -76,6 +138,58 @@ func (o *OutboundTLSOptionsContainer) ReplaceOutboundTLSOptions(options *Outboun
o.TLS = options
}
type CurvePreference tls.CurveID
const (
CurveP256 = 23
CurveP384 = 24
CurveP521 = 25
X25519 = 29
X25519MLKEM768 = 4588
)
func (c CurvePreference) MarshalJSON() ([]byte, error) {
var stringValue string
switch c {
case CurvePreference(CurveP256):
stringValue = "P256"
case CurvePreference(CurveP384):
stringValue = "P384"
case CurvePreference(CurveP521):
stringValue = "P521"
case CurvePreference(X25519):
stringValue = "X25519"
case CurvePreference(X25519MLKEM768):
stringValue = "X25519MLKEM768"
default:
return nil, E.New("unknown curve id: ", int(c))
}
return json.Marshal(stringValue)
}
func (c *CurvePreference) UnmarshalJSON(data []byte) error {
var stringValue string
err := json.Unmarshal(data, &stringValue)
if err != nil {
return err
}
switch strings.ToUpper(stringValue) {
case "P256":
*c = CurvePreference(CurveP256)
case "P384":
*c = CurvePreference(CurveP384)
case "P521":
*c = CurvePreference(CurveP521)
case "X25519":
*c = CurvePreference(X25519)
case "X25519MLKEM768":
*c = CurvePreference(X25519MLKEM768)
default:
return E.New("unknown curve name: ", stringValue)
}
return nil
}
type InboundRealityOptions struct {
Enabled bool `json:"enabled,omitempty"`
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`

View File

@@ -13,6 +13,7 @@ import (
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot"
@@ -43,6 +44,13 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired
}
// TCP Fast Open is incompatible with anytls because TFO creates a lazy connection
// that only establishes on first write. The lazy connection returns an empty address
// before establishment, but anytls SOCKS wrapper tries to access the remote address
// during handshake, causing a null pointer dereference crash.
if options.DialerOptions.TCPFastOpen {
return nil, E.New("tcp_fast_open is not supported with anytls outbound")
}
tlsConfig, err := tls.NewClient(ctx, logger, options.Server, common.PtrValueOrDefault(options.TLS))
if err != nil {

View File

@@ -14,7 +14,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
tun "github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions"

View File

@@ -1,10 +0,0 @@
package wireguard
import (
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/wireguard-go/conn"
)
func init() {
dialer.WgControlFns = conn.ControlFns
}

View File

@@ -13,7 +13,7 @@ pushd $PROJECT
git fetch
git reset FETCH_HEAD --hard
git clean -fdx
go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_acme,debug ./cmd/sing-box
go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_acme,debug ./cmd/sing-box
popd
sudo systemctl stop sing-box

View File

@@ -10,7 +10,7 @@ DIR=$(dirname "$0")
PROJECT=$DIR/../..
pushd $PROJECT
go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box
go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box
popd
sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/

View File

@@ -10,7 +10,7 @@ DIR=$(dirname "$0")
PROJECT=$DIR/../..
pushd $PROJECT
go install -v -trimpath -ldflags "-s -w -buildid= -checklinkname=0" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box
go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box
popd
sudo systemctl stop sing-box

View File

@@ -235,7 +235,7 @@ func (m *ConnectionManager) preConnectionCopy(ctx context.Context, source net.Co
err = m.connectionCopyEarlyWrite(source, destination, readHandshake, writeHandshake)
if err == nil && N.NeedHandshakeForRead(source) {
continue
} else if err == os.ErrInvalid || err == context.DeadlineExceeded {
} else if E.IsMulti(err, os.ErrInvalid, context.DeadlineExceeded, io.EOF) {
err = nil
}
break
@@ -340,10 +340,19 @@ func (m *ConnectionManager) connectionCopyEarlyWrite(source net.Conn, destinatio
}
return err
}
var (
isTimeout bool
isEOF bool
)
_, err = payload.ReadOnceFrom(source)
isTimeout := E.IsTimeout(err)
if err != nil && !(isTimeout || errors.Is(err, io.EOF)) {
return E.Cause(err, "read payload")
if err != nil {
if E.IsTimeout(err) {
isTimeout = true
} else if errors.Is(err, io.EOF) {
isEOF = true
} else {
return E.Cause(err, "read payload")
}
}
_ = source.SetReadDeadline(time.Time{})
if !payload.IsEmpty() || writeHandshake {
@@ -354,6 +363,8 @@ func (m *ConnectionManager) connectionCopyEarlyWrite(source net.Conn, destinatio
}
if isTimeout {
return context.DeadlineExceeded
} else if isEOF {
return io.EOF
}
return nil
}

View File

@@ -17,6 +17,7 @@ import (
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-mux"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing-tun/ping"
"github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
@@ -271,6 +272,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
if err != nil {
return nil, err
}
var directRouteOutbound adapter.DirectRouteOutbound
if selectedRule != nil {
switch action := selectedRule.Action().(type) {
case *R.RuleActionReject:
@@ -296,17 +298,69 @@ func (r *Router) PreMatch(metadata adapter.InboundContext, routeContext tun.Dire
if !common.Contains(outbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by outbound: ", action.Outbound)
}
return outbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
directRouteOutbound = outbound.(adapter.DirectRouteOutbound)
}
}
if selectedRule != nil || metadata.Network != N.NetworkICMP {
return nil, nil
if directRouteOutbound == nil {
if selectedRule != nil || metadata.Network != N.NetworkICMP {
return nil, nil
}
defaultOutbound := r.outbound.Default()
if !common.Contains(defaultOutbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag())
}
directRouteOutbound = defaultOutbound.(adapter.DirectRouteOutbound)
}
defaultOutbound := r.outbound.Default()
if !common.Contains(defaultOutbound.Network(), metadata.Network) {
return nil, E.New(metadata.Network, " is not supported by default outbound: ", defaultOutbound.Tag())
if metadata.Destination.IsFqdn() {
if len(metadata.DestinationAddresses) == 0 {
var strategy C.DomainStrategy
if metadata.Source.IsIPv4() {
strategy = C.DomainStrategyIPv4Only
} else {
strategy = C.DomainStrategyIPv6Only
}
err = r.actionResolve(r.ctx, &metadata, &R.RuleActionResolve{
Strategy: strategy,
})
if err != nil {
return nil, err
}
}
var newDestination netip.Addr
if metadata.Source.IsIPv4() {
for _, address := range metadata.DestinationAddresses {
if address.Is4() {
newDestination = address
break
}
}
} else {
for _, address := range metadata.DestinationAddresses {
if address.Is6() {
newDestination = address
break
}
}
}
if !newDestination.IsValid() {
if metadata.Source.IsIPv4() {
return nil, E.New("no IPv4 address found for domain: ", metadata.Destination.Fqdn)
} else {
return nil, E.New("no IPv6 address found for domain: ", metadata.Destination.Fqdn)
}
}
metadata.Destination = M.Socksaddr{
Addr: newDestination,
}
routeContext = ping.NewContextDestinationWriter(routeContext, metadata.OriginDestination.Addr)
var routeDestination tun.DirectRouteDestination
routeDestination, err = directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout)
if err != nil {
return nil, err
}
return ping.NewDestinationWriter(routeDestination, newDestination), nil
}
return defaultOutbound.(adapter.DirectRouteOutbound).NewDirectRouteConnection(metadata, routeContext, timeout)
return directRouteOutbound.NewDirectRouteConnection(metadata, routeContext, timeout)
}
func (r *Router) matchRule(

Some files were not shown because too many files have changed in this diff Show More