mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
66 Commits
dev-tls-fr
...
dev-ping
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00870d2833 | ||
|
|
f2d26f5842 | ||
|
|
baa68b5c6f | ||
|
|
9a6fb1e0c4 | ||
|
|
8f16eec0de | ||
|
|
d979e1e492 | ||
|
|
a7d882b62a | ||
|
|
e903804270 | ||
|
|
b15bda23f6 | ||
|
|
abc0535b83 | ||
|
|
9543a3e854 | ||
|
|
ba41c54d92 | ||
|
|
d1bc331042 | ||
|
|
1412e17bd9 | ||
|
|
25e71023dc | ||
|
|
49f23cfb8a | ||
|
|
f2b507a551 | ||
|
|
5e1a15e99c | ||
|
|
6ec2bda264 | ||
|
|
5154ec9c7d | ||
|
|
a4aadbc24e | ||
|
|
971a2350ae | ||
|
|
cf3a2d49ed | ||
|
|
6502d789cd | ||
|
|
cb17c08db8 | ||
|
|
7500b699e5 | ||
|
|
45a8a709e5 | ||
|
|
65d37cce85 | ||
|
|
04cd4cbee1 | ||
|
|
e054deb55e | ||
|
|
3fe2fa6fcb | ||
|
|
76cbe5fe5c | ||
|
|
3faaa0c0bb | ||
|
|
df76db0fe2 | ||
|
|
ff61980d80 | ||
|
|
ef75508286 | ||
|
|
7cec397f8e | ||
|
|
6ec57e5e7e | ||
|
|
ff31c469a0 | ||
|
|
fbe390268c | ||
|
|
07ac01dcb7 | ||
|
|
badfdb62cd | ||
|
|
986a410b30 | ||
|
|
9db2d58545 | ||
|
|
4eed46ac59 | ||
|
|
abc38d1dab | ||
|
|
8d6c4f1289 | ||
|
|
a2d40eb8b8 | ||
|
|
17b502bb4b | ||
|
|
a0d4421085 | ||
|
|
0d443072d1 | ||
|
|
c9fb99b799 | ||
|
|
92d245ad04 | ||
|
|
0908627297 | ||
|
|
7f79458b4f | ||
|
|
9b4c11ba95 | ||
|
|
27c31eac5d | ||
|
|
bab8dc0b82 | ||
|
|
d09d2fb665 | ||
|
|
e64cf3b7df | ||
|
|
9b73222314 | ||
|
|
3923b57abf | ||
|
|
4807e64609 | ||
|
|
eeb37d89f1 | ||
|
|
08c1ec4b7e | ||
|
|
6b4cf67add |
22
.github/setup_legacy_go.sh
vendored
Executable file
22
.github/setup_legacy_go.sh
vendored
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
VERSION="1.23.6"
|
||||
wget "https://dl.google.com/go/go${VERSION}.linux-amd64.tar.gz"
|
||||
tar -xzf "go${VERSION}.linux-amd64.tar.gz"
|
||||
mv go $HOME/go/go_legacy
|
||||
cd $HOME/go/go_legacy
|
||||
|
||||
# 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/
|
||||
# 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
|
||||
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@@ -112,7 +112,6 @@ jobs:
|
||||
- name: darwin_amd64
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
require_legacy_go: true
|
||||
- name: android_arm64
|
||||
goos: android
|
||||
goarch: arm64
|
||||
@@ -134,32 +133,29 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Cache legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/go1.20.14
|
||||
key: go120
|
||||
~/go/go_legacy
|
||||
key: go_legacy_1236
|
||||
- name: Setup legacy Go
|
||||
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||
run: |-
|
||||
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||
mv go $HOME/go/go1.20.14
|
||||
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||
run: bash .github/setup_legacy_go.sh
|
||||
- name: Setup Android NDK
|
||||
if: matrix.goos == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
ndk-version: r28
|
||||
local-cache: true
|
||||
- name: Setup Goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
version: 2.5.1
|
||||
install-only: true
|
||||
- name: Extract signing key
|
||||
run: |-
|
||||
@@ -219,12 +215,12 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
ndk-version: r28
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
@@ -256,9 +252,16 @@ jobs:
|
||||
with:
|
||||
path: ~/.gradle
|
||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||
- name: Build
|
||||
- name: Update version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci
|
||||
- name: Update nightly version
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
run: |-
|
||||
go run -v ./cmd/internal/update_android_version --ci --nightly
|
||||
- name: Build
|
||||
run: |-
|
||||
mkdir clients/android/app/libs
|
||||
cp libbox.aar clients/android/app/libs
|
||||
cd clients/android
|
||||
@@ -294,12 +297,12 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
with:
|
||||
ndk-version: r28-beta2
|
||||
ndk-version: r28
|
||||
- name: Setup OpenJDK
|
||||
run: |-
|
||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||
@@ -392,7 +395,7 @@ jobs:
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
@@ -548,7 +551,7 @@ jobs:
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
version: 2.5.1
|
||||
install-only: true
|
||||
- name: Cache ghr
|
||||
uses: actions/cache@v4
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.23
|
||||
go-version: ^1.24
|
||||
- name: Extract signing key
|
||||
run: |-
|
||||
mkdir -p $HOME/.gnupg
|
||||
|
||||
@@ -21,7 +21,7 @@ linters-settings:
|
||||
- -SA1003
|
||||
|
||||
run:
|
||||
go: "1.23"
|
||||
go: "1.24"
|
||||
build-tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
|
||||
@@ -19,6 +19,7 @@ builds:
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
|
||||
@@ -21,6 +21,7 @@ builds:
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
@@ -49,14 +50,14 @@ builds:
|
||||
- with_reality_server
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
||||
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
||||
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
||||
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
|
||||
targets:
|
||||
- windows_amd64_v1
|
||||
- windows_386
|
||||
- darwin_amd64_v1
|
||||
- id: android
|
||||
<<: *template
|
||||
env:
|
||||
@@ -122,8 +123,8 @@ nfpms:
|
||||
- deb
|
||||
- rpm
|
||||
- archlinux
|
||||
# - apk
|
||||
# - ipk
|
||||
# - apk
|
||||
# - ipk
|
||||
priority: extra
|
||||
contents:
|
||||
- src: release/config/config.json
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||
COPY . /go/src/github.com/sagernet/sing-box
|
||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||
|
||||
6
Makefile
6
Makefile
@@ -2,7 +2,8 @@ NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||
TAGS_GO121 = with_ech
|
||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||
TAGS_GO123 = with_tailscale
|
||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121),$(TAGS_GO123)
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
@@ -61,6 +62,9 @@ proto_install:
|
||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
update_certificates:
|
||||
go run ./cmd/internal/update_certificates
|
||||
|
||||
release:
|
||||
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||
mkdir dist/release
|
||||
|
||||
21
adapter/certificate.go
Normal file
21
adapter/certificate.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
type CertificateStore interface {
|
||||
LifecycleService
|
||||
Pool() *x509.CertPool
|
||||
}
|
||||
|
||||
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
||||
store := service.FromContext[CertificateStore](ctx)
|
||||
if store == nil {
|
||||
return nil
|
||||
}
|
||||
return store.Pool()
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
@@ -15,7 +14,20 @@ type ClashServer interface {
|
||||
ConnectionTracker
|
||||
Mode() string
|
||||
ModeList() []string
|
||||
HistoryStorage() *urltest.HistoryStorage
|
||||
HistoryStorage() URLTestHistoryStorage
|
||||
}
|
||||
|
||||
type URLTestHistory struct {
|
||||
Time time.Time `json:"time"`
|
||||
Delay uint16 `json:"delay"`
|
||||
}
|
||||
|
||||
type URLTestHistoryStorage interface {
|
||||
SetHook(hook chan<- struct{})
|
||||
LoadURLTestHistory(tag string) *URLTestHistory
|
||||
DeleteURLTestHistory(tag string)
|
||||
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type V2RayServer interface {
|
||||
@@ -38,17 +50,17 @@ type CacheFile interface {
|
||||
StoreSelected(group string, selected string) error
|
||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||
StoreGroupExpand(group string, expand bool) error
|
||||
LoadRuleSet(tag string) *SavedRuleSet
|
||||
SaveRuleSet(tag string, set *SavedRuleSet) error
|
||||
LoadRuleSet(tag string) *SavedBinary
|
||||
SaveRuleSet(tag string, set *SavedBinary) error
|
||||
}
|
||||
|
||||
type SavedRuleSet struct {
|
||||
type SavedBinary struct {
|
||||
Content []byte
|
||||
LastUpdated time.Time
|
||||
LastEtag string
|
||||
}
|
||||
|
||||
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
||||
var buffer bytes.Buffer
|
||||
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
||||
if err != nil {
|
||||
@@ -69,7 +81,7 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
||||
reader := bytes.NewReader(data)
|
||||
var version uint8
|
||||
err := binary.Read(reader, binary.BigEndian, &version)
|
||||
|
||||
@@ -28,12 +28,14 @@ type NetworkManager interface {
|
||||
}
|
||||
|
||||
type NetworkOptions struct {
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
BindInterface string
|
||||
RoutingMark uint32
|
||||
BindInterface string
|
||||
RoutingMark uint32
|
||||
DomainResolver string
|
||||
DomainResolveOptions DNSQueryOptions
|
||||
NetworkStrategy *C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,11 @@ type Outbound interface {
|
||||
N.Dialer
|
||||
}
|
||||
|
||||
type DirectRouteOutbound interface {
|
||||
Outbound
|
||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error)
|
||||
}
|
||||
|
||||
type OutboundRegistry interface {
|
||||
option.OutboundOptionsRegistry
|
||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||
|
||||
@@ -2,13 +2,16 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-tun"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"go4.org/netipx"
|
||||
@@ -17,7 +20,7 @@ import (
|
||||
type Router interface {
|
||||
Lifecycle
|
||||
ConnectionRouter
|
||||
PreMatch(metadata InboundContext) error
|
||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext) (tun.DirectRouteDestination, error)
|
||||
ConnectionRouterEx
|
||||
RuleSet(tag string) (RuleSet, bool)
|
||||
NeedWIFIState() bool
|
||||
@@ -66,12 +69,14 @@ type RuleSetMetadata struct {
|
||||
ContainsIPCIDRRule bool
|
||||
}
|
||||
type HTTPStartContext struct {
|
||||
ctx context.Context
|
||||
access sync.Mutex
|
||||
httpClientCache map[string]*http.Client
|
||||
}
|
||||
|
||||
func NewHTTPStartContext() *HTTPStartContext {
|
||||
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
|
||||
return &HTTPStartContext{
|
||||
ctx: ctx,
|
||||
httpClientCache: make(map[string]*http.Client),
|
||||
}
|
||||
}
|
||||
@@ -89,6 +94,10 @@ func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Clie
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
TLSClientConfig: &tls.Config{
|
||||
Time: ntp.TimeFuncFromContext(c.ctx),
|
||||
RootCAs: RootPoolFromContext(c.ctx),
|
||||
},
|
||||
},
|
||||
}
|
||||
c.httpClientCache[detour] = httpClient
|
||||
|
||||
20
box.go
20
box.go
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/common/certificate"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
@@ -141,6 +142,20 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "create log factory")
|
||||
}
|
||||
|
||||
var services []adapter.LifecycleService
|
||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||
len(certificateOptions.Certificate) > 0 ||
|
||||
len(certificateOptions.CertificatePath) > 0 ||
|
||||
len(certificateOptions.CertificateDirectoryPath) > 0 {
|
||||
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||
services = append(services, certificateStore)
|
||||
}
|
||||
|
||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||
@@ -187,7 +202,7 @@ func New(options Options) (*Box, error) {
|
||||
transportOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||
return nil, E.Cause(err, "initialize DNS server[", i, "]")
|
||||
}
|
||||
}
|
||||
err = dnsRouter.Initialize(dnsOptions.Rules)
|
||||
@@ -210,7 +225,7 @@ func New(options Options) (*Box, error) {
|
||||
endpointOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||
return nil, E.Cause(err, "initialize endpoint[", i, "]")
|
||||
}
|
||||
}
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
@@ -280,7 +295,6 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "initialize platform interface")
|
||||
}
|
||||
}
|
||||
var services []adapter.LifecycleService
|
||||
if needCacheFile {
|
||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||
|
||||
Submodule clients/android updated: b17fb6d857...3a2fc9c880
Submodule clients/apple updated: 64a4614aca...3d5d7343fb
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/asc-go/asc"
|
||||
@@ -194,6 +195,10 @@ func publishTestflight(ctx context.Context) error {
|
||||
log.Info(string(platform), " ", tag, " create submission")
|
||||
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
||||
log.Error(err)
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ func init() {
|
||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api", "with_tailscale")
|
||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||
debugTags = append(debugTags, "debug")
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func FindSDK() {
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
const fixedVersion = "28.0.12674087"
|
||||
const fixedVersion = "28.0.13004108"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
|
||||
@@ -13,10 +13,14 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
var flagRunInCI bool
|
||||
var (
|
||||
flagRunInCI bool
|
||||
flagRunNightly bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -46,21 +50,23 @@ func main() {
|
||||
switch propPair[0] {
|
||||
case "VERSION_NAME":
|
||||
if propPair[1] != newVersion {
|
||||
log.Info("updated version from ", propPair[1], " to ", newVersion)
|
||||
versionUpdated = true
|
||||
propPair[1] = newVersion
|
||||
log.Info("updated version to ", newVersion)
|
||||
}
|
||||
case "GO_VERSION":
|
||||
if propPair[1] != runtime.Version() {
|
||||
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
|
||||
goVersionUpdated = true
|
||||
propPair[1] = runtime.Version()
|
||||
log.Info("updated Go version to ", runtime.Version())
|
||||
}
|
||||
}
|
||||
}
|
||||
if !(versionUpdated || goVersionUpdated) {
|
||||
log.Info("version not changed")
|
||||
return
|
||||
} else if flagRunInCI && !flagRunNightly {
|
||||
log.Fatal("version changed, commit changes first.")
|
||||
}
|
||||
for _, propPair := range propsList {
|
||||
switch propPair[0] {
|
||||
|
||||
71
cmd/internal/update_certificates/main.go
Normal file
71
cmd/internal/update_certificates/main.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := updateMozillaIncludedRootCAs()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func updateMozillaIncludedRootCAs() error {
|
||||
response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
reader := csv.NewReader(response.Body)
|
||||
header, err := reader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
geoIndex := slices.Index(header, "Geographic Focus")
|
||||
nameIndex := slices.Index(header, "Common Name or Certificate Name")
|
||||
certIndex := slices.Index(header, "PEM Info")
|
||||
|
||||
generated := strings.Builder{}
|
||||
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
||||
|
||||
package certificate
|
||||
|
||||
import "crypto/x509"
|
||||
|
||||
var mozillaIncluded *x509.CertPool
|
||||
|
||||
func init() {
|
||||
mozillaIncluded = x509.NewCertPool()
|
||||
`)
|
||||
for {
|
||||
record, err := reader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if record[geoIndex] == "China" {
|
||||
continue
|
||||
}
|
||||
generated.WriteString("\n // ")
|
||||
generated.WriteString(record[nameIndex])
|
||||
generated.WriteString("\n")
|
||||
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
|
||||
cert := record[certIndex]
|
||||
// Remove single quotes
|
||||
cert = cert[1 : len(cert)-1]
|
||||
generated.WriteString(cert)
|
||||
generated.WriteString("`))\n")
|
||||
}
|
||||
generated.WriteString("}\n")
|
||||
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func init() {
|
||||
}
|
||||
|
||||
func generateTLSKeyPair(serverName string) error {
|
||||
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -61,14 +61,15 @@ func upgradeRuleSet(sourcePath string) error {
|
||||
log.Info("already up-to-date")
|
||||
return nil
|
||||
}
|
||||
plainRuleSet, err := plainRuleSetCompat.Upgrade()
|
||||
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(plainRuleSet)
|
||||
err = encoder.Encode(plainRuleSetCompat)
|
||||
if err != nil {
|
||||
return E.Cause(err, "encode config")
|
||||
}
|
||||
|
||||
4394
common/certificate/mozilla.go
Normal file
4394
common/certificate/mozilla.go
Normal file
File diff suppressed because it is too large
Load Diff
182
common/certificate/store.go
Normal file
182
common/certificate/store.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package certificate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
var _ adapter.CertificateStore = (*Store)(nil)
|
||||
|
||||
type Store struct {
|
||||
systemPool *x509.CertPool
|
||||
currentPool *x509.CertPool
|
||||
certificate string
|
||||
certificatePaths []string
|
||||
certificateDirectoryPaths []string
|
||||
watcher *fswatch.Watcher
|
||||
}
|
||||
|
||||
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
|
||||
var systemPool *x509.CertPool
|
||||
switch options.Store {
|
||||
case C.CertificateStoreSystem, "":
|
||||
systemPool = x509.NewCertPool()
|
||||
var systemValid bool
|
||||
for _, cert := range service.FromContext[platform.Interface](ctx).SystemCertificates() {
|
||||
if systemPool.AppendCertsFromPEM([]byte(cert)) {
|
||||
systemValid = true
|
||||
}
|
||||
}
|
||||
if !systemValid {
|
||||
certPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
systemPool = certPool
|
||||
}
|
||||
case C.CertificateStoreMozilla:
|
||||
systemPool = mozillaIncluded
|
||||
case C.CertificateStoreNone:
|
||||
systemPool = nil
|
||||
default:
|
||||
return nil, E.New("unknown certificate store: ", options.Store)
|
||||
}
|
||||
store := &Store{
|
||||
systemPool: systemPool,
|
||||
certificate: strings.Join(options.Certificate, "\n"),
|
||||
certificatePaths: options.CertificatePath,
|
||||
certificateDirectoryPaths: options.CertificateDirectoryPath,
|
||||
}
|
||||
var watchPaths []string
|
||||
for _, target := range options.CertificatePath {
|
||||
watchPaths = append(watchPaths, target)
|
||||
}
|
||||
for _, target := range options.CertificateDirectoryPath {
|
||||
watchPaths = append(watchPaths, target)
|
||||
}
|
||||
if len(watchPaths) > 0 {
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: watchPaths,
|
||||
Logger: logger,
|
||||
Callback: func(_ string) {
|
||||
err := store.update()
|
||||
if err != nil {
|
||||
logger.Error(E.Cause(err, "reload certificates"))
|
||||
}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "fswatch: create fsnotify watcher")
|
||||
}
|
||||
store.watcher = watcher
|
||||
}
|
||||
err := store.update()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initializing certificate store")
|
||||
}
|
||||
return store, nil
|
||||
}
|
||||
|
||||
func (s *Store) Name() string {
|
||||
return "certificate"
|
||||
}
|
||||
|
||||
func (s *Store) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
if s.watcher != nil {
|
||||
return s.watcher.Start()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Close() error {
|
||||
if s.watcher != nil {
|
||||
return s.watcher.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) Pool() *x509.CertPool {
|
||||
return s.currentPool
|
||||
}
|
||||
|
||||
func (s *Store) update() error {
|
||||
var currentPool *x509.CertPool
|
||||
if s.systemPool == nil {
|
||||
currentPool = x509.NewCertPool()
|
||||
} else {
|
||||
currentPool = s.systemPool.Clone()
|
||||
}
|
||||
if s.certificate != "" {
|
||||
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
|
||||
return E.New("invalid certificate PEM strings")
|
||||
}
|
||||
}
|
||||
for _, path := range s.certificatePaths {
|
||||
pemContent, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !currentPool.AppendCertsFromPEM(pemContent) {
|
||||
return E.New("invalid certificate PEM file: ", path)
|
||||
}
|
||||
}
|
||||
var firstErr error
|
||||
for _, directoryPath := range s.certificateDirectoryPaths {
|
||||
directoryEntries, err := readUniqueDirectoryEntries(directoryPath)
|
||||
if err != nil {
|
||||
if firstErr == nil && !os.IsNotExist(err) {
|
||||
firstErr = E.Cause(err, "invalid certificate directory: ", directoryPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, directoryEntry := range directoryEntries {
|
||||
pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))
|
||||
if err == nil {
|
||||
currentPool.AppendCertsFromPEM(pemContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
if firstErr != nil {
|
||||
return firstErr
|
||||
}
|
||||
s.currentPool = currentPool
|
||||
return nil
|
||||
}
|
||||
|
||||
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uniq := files[:0]
|
||||
for _, f := range files {
|
||||
if !isSameDirSymlink(f, dir) {
|
||||
uniq = append(uniq, f)
|
||||
}
|
||||
}
|
||||
return uniq, nil
|
||||
}
|
||||
|
||||
func isSameDirSymlink(f fs.DirEntry, dir string) bool {
|
||||
if f.Type()&fs.ModeSymlink == 0 {
|
||||
return false
|
||||
}
|
||||
target, err := os.Readlink(filepath.Join(dir, f.Name()))
|
||||
return err == nil && !strings.Contains(target, "/")
|
||||
}
|
||||
@@ -35,7 +35,6 @@ type DefaultDialer struct {
|
||||
udpListener net.ListenConfig
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
isWireGuardListener bool
|
||||
networkManager adapter.NetworkManager
|
||||
networkStrategy *C.NetworkStrategy
|
||||
defaultNetworkStrategy bool
|
||||
@@ -183,11 +182,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
}
|
||||
setMultiPathTCP(&dialer4)
|
||||
}
|
||||
if options.IsWireGuardListener {
|
||||
for _, controlFn := range WgControlFns {
|
||||
listener.Control = control.Append(listener.Control, controlFn)
|
||||
}
|
||||
}
|
||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -204,7 +198,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
udpListener: listener,
|
||||
udpAddr4: udpAddr4,
|
||||
udpAddr6: udpAddr6,
|
||||
isWireGuardListener: options.IsWireGuardListener,
|
||||
networkManager: networkManager,
|
||||
networkStrategy: networkStrategy,
|
||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||
|
||||
@@ -29,16 +29,18 @@ func (d *DetourDialer) Start() error {
|
||||
}
|
||||
|
||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||
d.initOnce.Do(func() {
|
||||
var loaded bool
|
||||
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
||||
if !loaded {
|
||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||
}
|
||||
})
|
||||
d.initOnce.Do(d.init)
|
||||
return d.dialer, d.initErr
|
||||
}
|
||||
|
||||
func (d *DetourDialer) init() {
|
||||
var loaded bool
|
||||
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
||||
if !loaded {
|
||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
dialer, err := d.Dialer()
|
||||
if err != nil {
|
||||
|
||||
@@ -16,81 +16,108 @@ import (
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
Context context.Context
|
||||
Options option.DialerOptions
|
||||
RemoteIsDomain bool
|
||||
DirectResolver bool
|
||||
ResolverOnDetour bool
|
||||
NewDialer bool
|
||||
}
|
||||
|
||||
// TODO: merge with NewWithOptions
|
||||
func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(ctx, options)
|
||||
}
|
||||
return NewWithOptions(Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
RemoteIsDomain: remoteIsDomain,
|
||||
})
|
||||
}
|
||||
|
||||
func NewWithOptions(options Options) (N.Dialer, error) {
|
||||
dialOptions := options.Options
|
||||
var (
|
||||
dialer N.Dialer
|
||||
err error
|
||||
)
|
||||
if options.Detour == "" {
|
||||
dialer, err = NewDefault(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
|
||||
if dialOptions.Detour != "" {
|
||||
outboundManager := service.FromContext[adapter.OutboundManager](options.Context)
|
||||
if outboundManager == nil {
|
||||
return nil, E.New("missing outbound manager")
|
||||
}
|
||||
dialer = NewDetour(outboundManager, options.Detour)
|
||||
}
|
||||
if remoteIsDomain && options.Detour == "" && options.DomainResolver == "" {
|
||||
deprecated.Report(ctx, deprecated.OptionMissingDomainResolverInDialOptions)
|
||||
}
|
||||
if (options.Detour == "" && remoteIsDomain) || options.DomainResolver != "" {
|
||||
router := service.FromContext[adapter.DNSRouter](ctx)
|
||||
if router != nil {
|
||||
var resolveTransport adapter.DNSTransport
|
||||
if options.DomainResolver != "" {
|
||||
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
|
||||
if !loaded {
|
||||
return nil, E.New("DNS server not found: " + options.DomainResolver)
|
||||
}
|
||||
resolveTransport = transport
|
||||
}
|
||||
dialer = NewResolveDialer(
|
||||
router,
|
||||
dialer,
|
||||
options.Detour == "" && !options.TCPFastOpen,
|
||||
resolveTransport,
|
||||
C.DomainStrategy(options.DomainStrategy),
|
||||
time.Duration(options.FallbackDelay))
|
||||
dialer = NewDetour(outboundManager, dialOptions.Detour)
|
||||
} else {
|
||||
dialer, err = NewDefault(options.Context, dialOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) {
|
||||
networkManager := service.FromContext[adapter.NetworkManager](options.Context)
|
||||
dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)
|
||||
var defaultOptions adapter.NetworkOptions
|
||||
if networkManager != nil {
|
||||
defaultOptions = networkManager.DefaultOptions()
|
||||
}
|
||||
var (
|
||||
server string
|
||||
dnsQueryOptions adapter.DNSQueryOptions
|
||||
resolveFallbackDelay time.Duration
|
||||
)
|
||||
if dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "" {
|
||||
var transport adapter.DNSTransport
|
||||
if !options.DirectResolver {
|
||||
var loaded bool
|
||||
transport, loaded = dnsTransport.Transport(dialOptions.DomainResolver.Server)
|
||||
if !loaded {
|
||||
return nil, E.New("domain resolver not found: " + dialOptions.DomainResolver.Server)
|
||||
}
|
||||
}
|
||||
var strategy C.DomainStrategy
|
||||
if dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||
strategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy)
|
||||
} else if
|
||||
//nolint:staticcheck
|
||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||
//nolint:staticcheck
|
||||
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||
}
|
||||
server = dialOptions.DomainResolver.Server
|
||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||
Transport: transport,
|
||||
Strategy: strategy,
|
||||
DisableCache: dialOptions.DomainResolver.DisableCache,
|
||||
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
||||
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||
}
|
||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||
} else if options.DirectResolver {
|
||||
return nil, E.New("missing domain resolver for domain server address")
|
||||
} else if defaultOptions.DomainResolver != "" {
|
||||
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
||||
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
||||
if !loaded {
|
||||
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
|
||||
}
|
||||
dnsQueryOptions.Transport = transport
|
||||
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||
} else if options.NewDialer {
|
||||
return nil, E.New("missing domain resolver for domain server address")
|
||||
} else {
|
||||
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||
}
|
||||
dialer = NewResolveDialer(
|
||||
options.Context,
|
||||
dialer,
|
||||
dialOptions.Detour == "" && !dialOptions.TCPFastOpen,
|
||||
server,
|
||||
dnsQueryOptions,
|
||||
resolveFallbackDelay,
|
||||
)
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) {
|
||||
if options.Detour != "" {
|
||||
return nil, E.New("`detour` is not supported in direct context")
|
||||
}
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(ctx, options)
|
||||
}
|
||||
dialer, err := NewDefault(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resolveTransport adapter.DNSTransport
|
||||
if options.DomainResolver != "" {
|
||||
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
|
||||
if !loaded {
|
||||
return nil, E.New("DNS server not found: " + options.DomainResolver)
|
||||
}
|
||||
resolveTransport = transport
|
||||
}
|
||||
return NewResolveParallelInterfaceDialer(
|
||||
service.FromContext[adapter.DNSRouter](ctx),
|
||||
dialer,
|
||||
true,
|
||||
resolveTransport,
|
||||
C.DomainStrategy(options.DomainStrategy),
|
||||
time.Duration(options.FallbackDelay),
|
||||
), nil
|
||||
}
|
||||
|
||||
type ParallelInterfaceDialer interface {
|
||||
N.Dialer
|
||||
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||
|
||||
@@ -3,14 +3,17 @@ package dialer
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
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/service"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -18,23 +21,37 @@ var (
|
||||
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
||||
)
|
||||
|
||||
type ResolveDialer interface {
|
||||
N.Dialer
|
||||
QueryOptions() adapter.DNSQueryOptions
|
||||
}
|
||||
|
||||
type ParallelInterfaceResolveDialer interface {
|
||||
ParallelInterfaceDialer
|
||||
QueryOptions() adapter.DNSQueryOptions
|
||||
}
|
||||
|
||||
type resolveDialer struct {
|
||||
transport adapter.DNSTransportManager
|
||||
router adapter.DNSRouter
|
||||
dialer N.Dialer
|
||||
parallel bool
|
||||
router adapter.DNSRouter
|
||||
transport adapter.DNSTransport
|
||||
strategy C.DomainStrategy
|
||||
server string
|
||||
initOnce sync.Once
|
||||
initErr error
|
||||
queryOptions adapter.DNSQueryOptions
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
||||
func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
|
||||
return &resolveDialer{
|
||||
dialer,
|
||||
parallel,
|
||||
router,
|
||||
transport,
|
||||
strategy,
|
||||
fallbackDelay,
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||
dialer: dialer,
|
||||
parallel: parallel,
|
||||
server: server,
|
||||
queryOptions: queryOptions,
|
||||
fallbackDelay: fallbackDelay,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,42 +60,68 @@ type resolveParallelNetworkDialer struct {
|
||||
dialer ParallelInterfaceDialer
|
||||
}
|
||||
|
||||
func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
|
||||
func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
|
||||
return &resolveParallelNetworkDialer{
|
||||
resolveDialer{
|
||||
dialer,
|
||||
parallel,
|
||||
router,
|
||||
transport,
|
||||
strategy,
|
||||
fallbackDelay,
|
||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||
dialer: dialer,
|
||||
parallel: parallel,
|
||||
server: server,
|
||||
queryOptions: queryOptions,
|
||||
fallbackDelay: fallbackDelay,
|
||||
},
|
||||
dialer,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveDialer) initialize() error {
|
||||
d.initOnce.Do(d.initServer)
|
||||
return d.initErr
|
||||
}
|
||||
|
||||
func (d *resolveDialer) initServer() {
|
||||
if d.server == "" {
|
||||
return
|
||||
}
|
||||
transport, loaded := d.transport.Transport(d.server)
|
||||
if !loaded {
|
||||
d.initErr = E.New("domain resolver not found: " + d.server)
|
||||
return
|
||||
}
|
||||
d.queryOptions.Transport = transport
|
||||
}
|
||||
|
||||
func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
err := d.initialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.parallel {
|
||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||
} else {
|
||||
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
err := d.initialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,12 +132,24 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *resolveDialer) QueryOptions() adapter.DNSQueryOptions {
|
||||
return d.queryOptions
|
||||
}
|
||||
|
||||
func (d *resolveDialer) Upstream() any {
|
||||
return d.dialer
|
||||
}
|
||||
|
||||
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
err := d.initialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -102,21 +157,28 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
|
||||
fallbackDelay = d.fallbackDelay
|
||||
}
|
||||
if d.parallel {
|
||||
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
} else {
|
||||
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
err := d.initialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = d.fallbackDelay
|
||||
}
|
||||
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -124,6 +186,10 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *resolveDialer) Upstream() any {
|
||||
func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions {
|
||||
return d.queryOptions
|
||||
}
|
||||
|
||||
func (d *resolveParallelNetworkDialer) Upstream() any {
|
||||
return d.dialer
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
ProcessID uint32
|
||||
ProcessPath string
|
||||
PackageName string
|
||||
User string
|
||||
|
||||
@@ -2,14 +2,11 @@ package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/winiphlpapi"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
@@ -26,209 +23,39 @@ func NewSearcher(_ Config) (Searcher, error) {
|
||||
return &windowsSearcher{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
|
||||
procGetExtendedUdpTable = modiphlpapi.NewProc("GetExtendedUdpTable")
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
|
||||
)
|
||||
|
||||
func initWin32API() error {
|
||||
err := modiphlpapi.Load()
|
||||
if err != nil {
|
||||
return E.Cause(err, "load iphlpapi.dll")
|
||||
}
|
||||
|
||||
err = procGetExtendedTcpTable.Find()
|
||||
if err != nil {
|
||||
return E.Cause(err, "load iphlpapi::GetExtendedTcpTable")
|
||||
}
|
||||
|
||||
err = procGetExtendedUdpTable.Find()
|
||||
if err != nil {
|
||||
return E.Cause(err, "load iphlpapi::GetExtendedUdpTable")
|
||||
}
|
||||
|
||||
err = modkernel32.Load()
|
||||
if err != nil {
|
||||
return E.Cause(err, "load kernel32.dll")
|
||||
}
|
||||
|
||||
err = procQueryFullProcessImageNameW.Find()
|
||||
if err != nil {
|
||||
return E.Cause(err, "load kernel32::QueryFullProcessImageNameW")
|
||||
}
|
||||
|
||||
return nil
|
||||
return winiphlpapi.LoadExtendedTable()
|
||||
}
|
||||
|
||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
||||
pid, err := winiphlpapi.FindPid(network, source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Info{ProcessPath: processName, UserId: -1}, nil
|
||||
}
|
||||
|
||||
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
|
||||
family := windows.AF_INET
|
||||
if ip.Is6() {
|
||||
family = windows.AF_INET6
|
||||
}
|
||||
|
||||
const (
|
||||
tcpTablePidConn = 4
|
||||
udpTablePid = 1
|
||||
)
|
||||
|
||||
var class int
|
||||
var fn uintptr
|
||||
switch network {
|
||||
case N.NetworkTCP:
|
||||
fn = procGetExtendedTcpTable.Addr()
|
||||
class = tcpTablePidConn
|
||||
case N.NetworkUDP:
|
||||
fn = procGetExtendedUdpTable.Addr()
|
||||
class = udpTablePid
|
||||
default:
|
||||
return "", os.ErrInvalid
|
||||
}
|
||||
|
||||
buf, err := getTransportTable(fn, family, class)
|
||||
path, err := getProcessPath(pid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return &Info{ProcessID: pid, UserId: -1}, err
|
||||
}
|
||||
|
||||
s := newSearcher(family == windows.AF_INET, network == N.NetworkTCP)
|
||||
|
||||
pid, err := s.Search(buf, ip, uint16(srcPort))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return getExecPathFromPID(pid)
|
||||
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
|
||||
}
|
||||
|
||||
type searcher struct {
|
||||
itemSize int
|
||||
port int
|
||||
ip int
|
||||
ipSize int
|
||||
pid int
|
||||
tcpState int
|
||||
}
|
||||
|
||||
func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {
|
||||
n := int(readNativeUint32(b[:4]))
|
||||
itemSize := s.itemSize
|
||||
for i := 0; i < n; i++ {
|
||||
row := b[4+itemSize*i : 4+itemSize*(i+1)]
|
||||
|
||||
if s.tcpState >= 0 {
|
||||
tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
|
||||
// MIB_TCP_STATE_ESTAB, only check established connections for TCP
|
||||
if tcpState != 5 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
|
||||
// this field can be illustrated as follows depends on different machine endianess:
|
||||
// little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
|
||||
// big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB)
|
||||
// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
|
||||
srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
|
||||
if srcPort != port {
|
||||
continue
|
||||
}
|
||||
|
||||
srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
|
||||
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
||||
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
||||
continue
|
||||
}
|
||||
|
||||
pid := readNativeUint32(row[s.pid : s.pid+4])
|
||||
return pid, nil
|
||||
}
|
||||
return 0, ErrNotFound
|
||||
}
|
||||
|
||||
func newSearcher(isV4, isTCP bool) *searcher {
|
||||
var itemSize, port, ip, ipSize, pid int
|
||||
tcpState := -1
|
||||
switch {
|
||||
case isV4 && isTCP:
|
||||
// struct MIB_TCPROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
|
||||
case isV4 && !isTCP:
|
||||
// struct MIB_UDPROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
|
||||
case !isV4 && isTCP:
|
||||
// struct MIB_TCP6ROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
|
||||
case !isV4 && !isTCP:
|
||||
// struct MIB_UDP6ROW_OWNER_PID
|
||||
itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
|
||||
}
|
||||
|
||||
return &searcher{
|
||||
itemSize: itemSize,
|
||||
port: port,
|
||||
ip: ip,
|
||||
ipSize: ipSize,
|
||||
pid: pid,
|
||||
tcpState: tcpState,
|
||||
}
|
||||
}
|
||||
|
||||
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
|
||||
for size, buf := uint32(8), make([]byte, 8); ; {
|
||||
ptr := unsafe.Pointer(&buf[0])
|
||||
err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
|
||||
|
||||
switch err {
|
||||
case 0:
|
||||
return buf, nil
|
||||
case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
|
||||
buf = make([]byte, size)
|
||||
default:
|
||||
return nil, fmt.Errorf("syscall error: %d", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readNativeUint32(b []byte) uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
||||
}
|
||||
|
||||
func getExecPathFromPID(pid uint32) (string, error) {
|
||||
// kernel process starts with a colon in order to distinguish with normal processes
|
||||
func getProcessPath(pid uint32) (string, error) {
|
||||
switch pid {
|
||||
case 0:
|
||||
// reserved pid for system idle process
|
||||
return ":System Idle Process", nil
|
||||
case 4:
|
||||
// reserved pid for windows kernel image
|
||||
return ":System", nil
|
||||
}
|
||||
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
||||
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer windows.CloseHandle(h)
|
||||
|
||||
defer windows.CloseHandle(handle)
|
||||
size := uint32(syscall.MAX_LONG_PATH)
|
||||
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
||||
size := uint32(len(buf))
|
||||
r1, _, err := syscall.SyscallN(
|
||||
procQueryFullProcessImageNameW.Addr(),
|
||||
uintptr(h),
|
||||
uintptr(0),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
if r1 == 0 {
|
||||
err = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return syscall.UTF16ToString(buf[:size]), nil
|
||||
return windows.UTF16ToString(buf[:size]), nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/libdns/alidns"
|
||||
"github.com/libdns/cloudflare"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"github.com/mholt/acmez/v3/acme"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
@@ -100,6 +100,7 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb
|
||||
|
||||
var tlsConfig cftls.Config
|
||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
|
||||
@@ -90,7 +90,7 @@ func (c *echServerConfig) startWatcher() error {
|
||||
Callback: func(path string) {
|
||||
err := c.credentialsUpdated(path)
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "reload credentials from ", path))
|
||||
c.logger.Error(E.Cause(err, "reload credentials"))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Cer
|
||||
return &certificate, err
|
||||
}
|
||||
|
||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||
if timeFunc == nil {
|
||||
timeFunc = time.Now
|
||||
}
|
||||
@@ -47,7 +47,11 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T
|
||||
},
|
||||
DNSNames: []string{serverName},
|
||||
}
|
||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||
if parent == nil {
|
||||
parent = template
|
||||
parentKey = key
|
||||
}
|
||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -27,9 +27,11 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
@@ -40,6 +42,7 @@ import (
|
||||
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
||||
|
||||
type RealityClientConfig struct {
|
||||
ctx context.Context
|
||||
uClient *UTLSClientConfig
|
||||
publicKey []byte
|
||||
shortID [8]byte
|
||||
@@ -70,7 +73,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
||||
if decodedLen > 8 {
|
||||
return nil, E.New("invalid short_id")
|
||||
}
|
||||
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
||||
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) ServerName() string {
|
||||
@@ -180,20 +183,24 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
}
|
||||
|
||||
if !verifier.verified {
|
||||
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
||||
go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)
|
||||
return nil, E.New("reality verification failed")
|
||||
}
|
||||
|
||||
return &realityClientConnWrapper{uConn}, nil
|
||||
}
|
||||
|
||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||
func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||
defer uConn.Close()
|
||||
client := &http.Client{
|
||||
Transport: &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
return uConn, nil
|
||||
},
|
||||
TLSClientConfig: &tls.Config{
|
||||
Time: ntp.TimeFuncFromContext(ctx),
|
||||
RootCAs: adapter.RootPoolFromContext(ctx),
|
||||
},
|
||||
},
|
||||
}
|
||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||
@@ -213,6 +220,7 @@ func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello [
|
||||
|
||||
func (e *RealityClientConfig) Clone() Config {
|
||||
return &RealityClientConfig{
|
||||
e.ctx,
|
||||
e.uClient.Clone().(*UTLSClientConfig),
|
||||
e.publicKey,
|
||||
e.shortID,
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
@@ -58,6 +59,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
||||
|
||||
var tlsConfig tls.Config
|
||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
|
||||
@@ -99,7 +99,7 @@ func (c *STDServerConfig) startWatcher() error {
|
||||
Callback: func(path string) {
|
||||
err := c.certificateUpdated(path)
|
||||
if err != nil {
|
||||
c.logger.Error(err)
|
||||
c.logger.Error(E.Cause(err, "reload certificate"))
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
}
|
||||
if certificate == nil && key == nil && options.Insecure {
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||
}
|
||||
} else {
|
||||
if certificate == nil {
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
@@ -130,6 +131,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
||||
|
||||
var tlsConfig utls.Config
|
||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
|
||||
@@ -5,26 +5,26 @@ import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
syscallConn syscall.Conn
|
||||
tcpConn *net.TCPConn
|
||||
ctx context.Context
|
||||
firstPacketWritten bool
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||
syscallConn, _ := N.UnwrapReader(conn).(syscall.Conn)
|
||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
syscallConn: syscallConn,
|
||||
tcpConn: tcpConn,
|
||||
ctx: ctx,
|
||||
fallbackDelay: fallbackDelay,
|
||||
}, nil
|
||||
@@ -37,39 +37,18 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}()
|
||||
serverName := indexTLSServerName(b)
|
||||
if serverName != nil {
|
||||
tcpConn, isTCPConn := c.syscallConn.(interface {
|
||||
SetNoDelay(bool) error
|
||||
})
|
||||
if isTCPConn {
|
||||
err = tcpConn.SetNoDelay(true)
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
splits := strings.Split(string(b[serverName.Index:serverName.Index+serverName.Length]), ".")
|
||||
splits := strings.Split(serverName.ServerName, ".")
|
||||
currentIndex := serverName.Index
|
||||
var striped bool
|
||||
if len(splits) > 3 {
|
||||
suffix := splits[len(splits)-3] + "." + splits[len(splits)-2] + "." + splits[len(splits)-1]
|
||||
if publicSuffixMatcher().Match(suffix) {
|
||||
splits = splits[:len(splits)-3]
|
||||
}
|
||||
striped = true
|
||||
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
|
||||
splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")]
|
||||
}
|
||||
if !striped && len(splits) > 2 {
|
||||
suffix := splits[len(splits)-2] + "." + splits[len(splits)-1]
|
||||
if publicSuffixMatcher().Match(suffix) {
|
||||
splits = splits[:len(splits)-2]
|
||||
}
|
||||
striped = true
|
||||
}
|
||||
if !striped && len(splits) > 1 {
|
||||
suffix := splits[len(splits)-1]
|
||||
if publicSuffixMatcher().Match(suffix) {
|
||||
splits = splits[:len(splits)-1]
|
||||
}
|
||||
}
|
||||
if len(splits) > 1 && common.Contains(publicPrefix, splits[0]) {
|
||||
if len(splits) > 1 && splits[0] == "..." {
|
||||
currentIndex += len(splits[0]) + 1
|
||||
splits = splits[1:]
|
||||
}
|
||||
@@ -83,25 +62,28 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
for i := 0; i <= len(splitIndexes); i++ {
|
||||
var payload []byte
|
||||
if i == 0 {
|
||||
_, err = c.Conn.Write(b[:splitIndexes[i]])
|
||||
payload = b[:splitIndexes[i]]
|
||||
} else if i == len(splitIndexes) {
|
||||
_, err = c.Conn.Write(b[splitIndexes[i-1]:])
|
||||
payload = b[splitIndexes[i-1]:]
|
||||
} else {
|
||||
_, err = c.Conn.Write(b[splitIndexes[i-1]:splitIndexes[i]])
|
||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if c.syscallConn != nil && i != len(splitIndexes) {
|
||||
err = waitAck(c.ctx, c.syscallConn, c.fallbackDelay)
|
||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
_, err = c.Conn.Write(payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if isTCPConn {
|
||||
err = tcpConn.SetNoDelay(false)
|
||||
if c.tcpConn != nil {
|
||||
err = c.tcpConn.SetNoDelay(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ const (
|
||||
)
|
||||
|
||||
type myServerName struct {
|
||||
Index int
|
||||
Length int
|
||||
sex []byte
|
||||
Index int
|
||||
Length int
|
||||
ServerName string
|
||||
}
|
||||
|
||||
func indexTLSServerName(payload []byte) *myServerName {
|
||||
@@ -68,15 +68,9 @@ func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||
return nil
|
||||
}
|
||||
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||
numCiphers := int(csLen / 2)
|
||||
cipherSuites := make([]uint16, 0, numCiphers)
|
||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < numCiphers; i++ {
|
||||
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
|
||||
cipherSuites = append(cipherSuites, cipherSuite)
|
||||
}
|
||||
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||
return nil
|
||||
@@ -125,9 +119,9 @@ func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||
sex = sex[sniExtensionHeaderLen:]
|
||||
return &myServerName{
|
||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||
Length: int(sniLen),
|
||||
sex: sex,
|
||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||
Length: int(sniLen),
|
||||
ServerName: string(sex),
|
||||
}
|
||||
}
|
||||
exs = exs[4+exLen:]
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package tf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing/common/domain"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
var publicPrefix = []string{
|
||||
"www",
|
||||
}
|
||||
|
||||
//go:generate wget -O public_suffix_list.dat https://publicsuffix.org/list/public_suffix_list.dat
|
||||
|
||||
//go:embed public_suffix_list.dat
|
||||
var publicSuffix []byte
|
||||
|
||||
var publicSuffixMatcher = sync.OnceValue(func() *domain.Matcher {
|
||||
matcher, err := initPublicSuffixMatcher()
|
||||
if err != nil {
|
||||
panic(F.ToString("error in initialize public suffix matcher"))
|
||||
}
|
||||
return matcher
|
||||
})
|
||||
|
||||
func initPublicSuffixMatcher() (*domain.Matcher, error) {
|
||||
reader := bufio.NewReader(bytes.NewReader(publicSuffix))
|
||||
var domainList []string
|
||||
for {
|
||||
line, isPrefix, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if isPrefix {
|
||||
return nil, E.New("unexpected prefix line")
|
||||
}
|
||||
lineStr := string(line)
|
||||
lineStr = strings.TrimSpace(lineStr)
|
||||
if lineStr == "" || strings.HasPrefix(lineStr, "//") {
|
||||
continue
|
||||
}
|
||||
domainList = append(domainList, lineStr)
|
||||
}
|
||||
return domain.NewMatcher(domainList, nil, false), nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ package tf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -63,7 +63,11 @@ func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32)
|
||||
}
|
||||
*/
|
||||
|
||||
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
||||
_, err := conn.Write(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return control.Conn(conn, func(fd uintptr) error {
|
||||
start := time.Now()
|
||||
for {
|
||||
|
||||
@@ -2,7 +2,7 @@ package tf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -10,7 +10,11 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
||||
_, err := conn.Write(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return control.Conn(conn, func(fd uintptr) error {
|
||||
start := time.Now()
|
||||
for {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//go:build !(linux || darwin)
|
||||
//go:build !(linux || darwin || windows)
|
||||
|
||||
package tf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"syscall"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
||||
time.Sleep(fallbackDelay)
|
||||
return nil
|
||||
}
|
||||
|
||||
28
common/tlsfragment/wait_windows.go
Normal file
28
common/tlsfragment/wait_windows.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package tf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/winiphlpapi"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
||||
start := time.Now()
|
||||
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
||||
time.Sleep(fallbackDelay)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if time.Since(start) <= 20*time.Millisecond {
|
||||
time.Sleep(fallbackDelay)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2,32 +2,32 @@ package urltest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
)
|
||||
|
||||
type History struct {
|
||||
Time time.Time `json:"time"`
|
||||
Delay uint16 `json:"delay"`
|
||||
}
|
||||
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
||||
|
||||
type HistoryStorage struct {
|
||||
access sync.RWMutex
|
||||
delayHistory map[string]*History
|
||||
delayHistory map[string]*adapter.URLTestHistory
|
||||
updateHook chan<- struct{}
|
||||
}
|
||||
|
||||
func NewHistoryStorage() *HistoryStorage {
|
||||
return &HistoryStorage{
|
||||
delayHistory: make(map[string]*History),
|
||||
delayHistory: make(map[string]*adapter.URLTestHistory),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
|
||||
s.updateHook = hook
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
||||
func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
||||
s.notifyUpdated()
|
||||
}
|
||||
|
||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
||||
s.access.Lock()
|
||||
s.delayHistory[tag] = history
|
||||
s.access.Unlock()
|
||||
@@ -110,6 +110,10 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return instance, nil
|
||||
},
|
||||
TLSClientConfig: &tls.Config{
|
||||
Time: ntp.TimeFuncFromContext(ctx),
|
||||
RootCAs: adapter.RootPoolFromContext(ctx),
|
||||
},
|
||||
},
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
|
||||
7
constant/certificate.go
Normal file
7
constant/certificate.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
CertificateStoreSystem = "system"
|
||||
CertificateStoreMozilla = "mozilla"
|
||||
CertificateStoreNone = "none"
|
||||
)
|
||||
@@ -27,6 +27,7 @@ const (
|
||||
DNSTypePreDefined = "predefined"
|
||||
DNSTypeFakeIP = "fakeip"
|
||||
DNSTypeDHCP = "dhcp"
|
||||
DNSTypeTailscale = "tailscale"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
TypeVLESS = "vless"
|
||||
TypeTUIC = "tuic"
|
||||
TypeHysteria2 = "hysteria2"
|
||||
TypeTailscale = "tailscale"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -174,7 +174,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||
}
|
||||
}
|
||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||
return transport, currentRule, currentRuleIndex
|
||||
case *R.RuleActionDNSRouteOptions:
|
||||
if action.Strategy != C.DomainStrategyAsIS {
|
||||
@@ -189,9 +188,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
||||
if action.ClientSubnet.IsValid() {
|
||||
options.ClientSubnet = action.ClientSubnet
|
||||
}
|
||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||
case *R.RuleActionReject:
|
||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||
return nil, currentRule, currentRuleIndex
|
||||
}
|
||||
}
|
||||
@@ -252,7 +249,8 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
ruleIndex = -1
|
||||
for {
|
||||
dnsCtx := adapter.OverrideContext(ctx)
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &options)
|
||||
dnsOptions := options
|
||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
||||
if rule != nil {
|
||||
switch action := rule.Action().(type) {
|
||||
case *R.RuleActionReject:
|
||||
@@ -271,10 +269,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
return rule.MatchAddressLimit(metadata)
|
||||
}
|
||||
}
|
||||
if options.Strategy == C.DomainStrategyAsIS {
|
||||
options.Strategy = r.defaultDomainStrategy
|
||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
||||
}
|
||||
response, err = r.client.Exchange(dnsCtx, transport, message, options, responseCheck)
|
||||
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
|
||||
var rejected bool
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrResponseRejectedCached) {
|
||||
@@ -300,7 +298,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
||||
return nil, err
|
||||
}
|
||||
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
|
||||
if transport.Type() != C.DNSTypeFakeIP {
|
||||
if transport == nil || transport.Type() != C.DNSTypeFakeIP {
|
||||
for _, answer := range response.Answer {
|
||||
switch record := answer.(type) {
|
||||
case *mDNS.A:
|
||||
@@ -344,7 +342,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
||||
r.logger.DebugContext(ctx, "lookup domain ", domain)
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
metadata.Destination = M.Socksaddr{}
|
||||
metadata.Domain = domain
|
||||
metadata.Domain = FqdnToDomain(domain)
|
||||
if options.Transport != nil {
|
||||
transport := options.Transport
|
||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||
|
||||
@@ -36,6 +36,17 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *Transport) Start(stage adapter.StartStage) error {
|
||||
if stage != adapter.StartStateStart {
|
||||
return nil
|
||||
}
|
||||
return t.store.Start()
|
||||
}
|
||||
|
||||
func (t *Transport) Close() error {
|
||||
return t.store.Close()
|
||||
}
|
||||
|
||||
func (t *Transport) Reset() {
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestHosts(t *testing.T) {
|
||||
t.Parallel()
|
||||
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
|
||||
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
|
||||
}
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
package hosts
|
||||
|
||||
import _ "unsafe"
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
var DefaultPath = getSystemDirectory() + "/Drivers/etc/hosts"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//go:linkname getSystemDirectory internal/syscall/windows.GetSystemDirectory
|
||||
func getSystemDirectory() string
|
||||
var DefaultPath string
|
||||
|
||||
func init() {
|
||||
systemDirectory, err := windows.GetSystemDirectory()
|
||||
if err != nil {
|
||||
systemDirectory = "C:\\Windows\\System32"
|
||||
}
|
||||
DefaultPath = filepath.Join(systemDirectory, "Drivers/etc/hosts")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package local
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -85,9 +86,11 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
||||
results := make(chan queryResult)
|
||||
startRacer := func(ctx context.Context, fqdn string) {
|
||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||
addresses, _ := dns.MessageToAddresses(response)
|
||||
if len(addresses) == 0 {
|
||||
err = E.New(fqdn, ": empty result")
|
||||
if err == nil {
|
||||
addresses, _ := dns.MessageToAddresses(response)
|
||||
if len(addresses) == 0 {
|
||||
err = E.New(fqdn, ": empty result")
|
||||
}
|
||||
}
|
||||
select {
|
||||
case results <- queryResult{response, err}:
|
||||
@@ -147,7 +150,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
||||
}
|
||||
request := &mDNS.Msg{
|
||||
MsgHdr: mDNS.MsgHdr{
|
||||
Id: uint16(randInt()),
|
||||
Id: uint16(rand.Uint32()),
|
||||
RecursionDesired: true,
|
||||
AuthenticatedData: ad,
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -118,7 +117,7 @@ func (conf *dnsConfig) nameList(name string) []string {
|
||||
|
||||
hasNdots := strings.Count(name, ".") >= conf.ndots
|
||||
name += "."
|
||||
l++
|
||||
// l++
|
||||
|
||||
names := make([]string, 0, 1+len(conf.search))
|
||||
if hasNdots && !avoidDNS(name) {
|
||||
@@ -136,13 +135,6 @@ func (conf *dnsConfig) nameList(name string) []string {
|
||||
return names
|
||||
}
|
||||
|
||||
//go:linkname runtime_rand runtime.rand
|
||||
func runtime_rand() uint64
|
||||
|
||||
func randInt() int {
|
||||
return int(uint(runtime_rand()) >> 1) // clear sign bit
|
||||
}
|
||||
|
||||
func avoidDNS(name string) bool {
|
||||
if name == "" {
|
||||
return true
|
||||
|
||||
@@ -144,6 +144,13 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
||||
func (t *UDPTransport) open(ctx context.Context) (*dnsConnection, error) {
|
||||
t.access.Lock()
|
||||
defer t.access.Unlock()
|
||||
if t.conn != nil {
|
||||
select {
|
||||
case <-t.conn.done:
|
||||
default:
|
||||
return t.conn, nil
|
||||
}
|
||||
}
|
||||
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -154,6 +161,7 @@ func (t *UDPTransport) open(ctx context.Context) (*dnsConnection, error) {
|
||||
callbacks: make(map[uint16]*dnsCallback),
|
||||
}
|
||||
go t.recvLoop(dnsConn)
|
||||
t.conn = dnsConn
|
||||
return dnsConn, nil
|
||||
}
|
||||
|
||||
@@ -201,8 +209,6 @@ type dnsConnection struct {
|
||||
}
|
||||
|
||||
func (c *dnsConnection) Close(err error) {
|
||||
c.access.Lock()
|
||||
defer c.access.Unlock()
|
||||
c.closeOnce.Do(func() {
|
||||
close(c.done)
|
||||
c.err = err
|
||||
|
||||
@@ -27,9 +27,14 @@ func NewTransportAdapter(transportType string, transportTag string, dependencies
|
||||
}
|
||||
|
||||
func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter {
|
||||
var dependencies []string
|
||||
if localOptions.DomainResolver != nil && localOptions.DomainResolver.Server != "" {
|
||||
dependencies = append(dependencies, localOptions.DomainResolver.Server)
|
||||
}
|
||||
return TransportAdapter{
|
||||
transportType: transportType,
|
||||
transportTag: transportTag,
|
||||
dependencies: dependencies,
|
||||
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||
clientSubnet: localOptions.LegacyClientSubnet,
|
||||
}
|
||||
@@ -37,8 +42,11 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
||||
|
||||
func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter {
|
||||
var dependencies []string
|
||||
if remoteOptions.AddressResolver != "" {
|
||||
dependencies = []string{remoteOptions.AddressResolver}
|
||||
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
||||
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
||||
}
|
||||
if remoteOptions.LegacyAddressResolver != "" {
|
||||
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
|
||||
}
|
||||
return TransportAdapter{
|
||||
transportType: transportType,
|
||||
|
||||
@@ -19,37 +19,39 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
|
||||
if options.LegacyDefaultDialer {
|
||||
return dialer.NewDefaultOutbound(ctx), nil
|
||||
} else {
|
||||
return dialer.New(ctx, options.DialerOptions, false)
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
DirectResolver: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||
var (
|
||||
transportDialer N.Dialer
|
||||
err error
|
||||
)
|
||||
if options.LegacyDefaultDialer {
|
||||
transportDialer = dialer.NewDefaultOutbound(ctx)
|
||||
} else {
|
||||
transportDialer, err = dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.AddressResolver != "" {
|
||||
transport := service.FromContext[adapter.DNSTransportManager](ctx)
|
||||
resolverTransport, loaded := transport.Transport(options.AddressResolver)
|
||||
if !loaded {
|
||||
return nil, E.New("address resolver not found: ", options.AddressResolver)
|
||||
transportDialer := dialer.NewDefaultOutbound(ctx)
|
||||
if options.LegacyAddressResolver != "" {
|
||||
transport := service.FromContext[adapter.DNSTransportManager](ctx)
|
||||
resolverTransport, loaded := transport.Transport(options.LegacyAddressResolver)
|
||||
if !loaded {
|
||||
return nil, E.New("address resolver not found: ", options.LegacyAddressResolver)
|
||||
}
|
||||
transportDialer = newTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.LegacyAddressStrategy), time.Duration(options.LegacyAddressFallbackDelay))
|
||||
} else if options.ServerIsDomain() {
|
||||
return nil, E.New("missing address resolver for server: ", options.Server)
|
||||
}
|
||||
transportDialer = NewTransportDialer(transportDialer, service.FromContext[adapter.DNSRouter](ctx), resolverTransport, C.DomainStrategy(options.AddressStrategy), time.Duration(options.AddressFallbackDelay))
|
||||
} else if options.ServerIsDomain() {
|
||||
return nil, E.New("missing address resolver for server: ", options.Server)
|
||||
return transportDialer, nil
|
||||
} else {
|
||||
return dialer.NewWithOptions(dialer.Options{
|
||||
Context: ctx,
|
||||
Options: options.DialerOptions,
|
||||
RemoteIsDomain: options.ServerIsDomain(),
|
||||
DirectResolver: true,
|
||||
})
|
||||
}
|
||||
return transportDialer, nil
|
||||
}
|
||||
|
||||
type TransportDialer struct {
|
||||
type legacyTransportDialer struct {
|
||||
dialer N.Dialer
|
||||
dnsRouter adapter.DNSRouter
|
||||
transport adapter.DNSTransport
|
||||
@@ -57,8 +59,8 @@ type TransportDialer struct {
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer {
|
||||
return &TransportDialer{
|
||||
func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {
|
||||
return &legacyTransportDialer{
|
||||
dialer,
|
||||
dnsRouter,
|
||||
transport,
|
||||
@@ -67,7 +69,7 @@ func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport
|
||||
}
|
||||
}
|
||||
|
||||
func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if destination.IsIP() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
@@ -81,7 +83,7 @@ func (d *TransportDialer) DialContext(ctx context.Context, network string, desti
|
||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||
}
|
||||
|
||||
func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if destination.IsIP() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
@@ -96,6 +98,6 @@ func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksa
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (d *TransportDialer) Upstream() any {
|
||||
func (d *legacyTransportDialer) Upstream() any {
|
||||
return d.dialer
|
||||
}
|
||||
|
||||
@@ -2,10 +2,224 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.11.0-beta.24
|
||||
#### 1.12.0-alpha.8
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-alpha.7
|
||||
|
||||
* Add Tailscale DNS server **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Tailscale](/configuration/dns/server/tailscale/).
|
||||
|
||||
#### 1.12.0-alpha.6
|
||||
|
||||
* Add Tailscale endpoint **1**
|
||||
* Drop support for go1.22 **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [Tailscale](/configuration/endpoint/tailscale/).
|
||||
|
||||
**2**:
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.12.0 requires at least Go 1.23 to compile.
|
||||
|
||||
For Windows 7 users, legacy binaries now continue to compile with Go 1.23 and patches from [MetaCubeX/go](https://github.com/MetaCubeX/go).
|
||||
|
||||
### 1.11.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_This version overwrites 1.11.2, as incorrect binaries were released due to a bug in the continuous integration process._
|
||||
|
||||
#### 1.12.0-alpha.5
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.11.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-alpha.2
|
||||
|
||||
* Update quic-go to v0.49.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.12.0-alpha.1
|
||||
|
||||
* Refactor DNS servers **1**
|
||||
* Add domain resolver options**2**
|
||||
* Add TLS fragment route options **3**
|
||||
* Add certificate options **4**
|
||||
|
||||
**1**:
|
||||
|
||||
DNS servers are refactored for better performance and scalability.
|
||||
|
||||
See [DNS server](/configuration/dns/server/).
|
||||
|
||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
||||
|
||||
**2**:
|
||||
|
||||
Legacy `outbound` DNS rules are deprecated
|
||||
and can be replaced by the new `domain_resolver` option.
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) and
|
||||
[Route](/configuration/route/#default_domain_resolver).
|
||||
|
||||
For migration,
|
||||
see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
||||
|
||||
**3**:
|
||||
|
||||
The new TLS fragment route options allow you to fragment TLS handshakes to bypass firewalls.
|
||||
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used
|
||||
to circumvent real censorship.
|
||||
|
||||
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are
|
||||
known to be blocked.
|
||||
|
||||
See [Route Action](/configuration/route/rule_action/#tls_fragment).
|
||||
|
||||
**4**:
|
||||
|
||||
New certificate options allow you to manage the default list of trusted X509 CA certificates.
|
||||
|
||||
For the system certificate list, fixed Go not reading Android trusted certificates correctly.
|
||||
|
||||
You can also use the Mozilla Included List instead, or add trusted certificates yourself.
|
||||
|
||||
See [Certificate](/configuration/certificate/).
|
||||
|
||||
### 1.11.0
|
||||
|
||||
Important changes since 1.10:
|
||||
|
||||
* Introducing rule actions **1**
|
||||
* Improve tun compatibility **3**
|
||||
* Merge route options to route actions **4**
|
||||
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**
|
||||
* Add multi network dialing **6**
|
||||
* Add `cache_capacity` DNS option **7**
|
||||
* Add `override_address` and `override_port` route options **8**
|
||||
* Upgrade WireGuard outbound to endpoint **9**
|
||||
* Add UDP GSO support for WireGuard
|
||||
* Make GSO adaptive **10**
|
||||
* Add UDP timeout route option **11**
|
||||
* Add more masquerade options for hysteria2 **12**
|
||||
* Add `rule-set merge` command
|
||||
* Add port hopping support for Hysteria2 **13**
|
||||
* Hysteria2 `ignore_client_bandwidth` behavior update **14**
|
||||
|
||||
**1**:
|
||||
|
||||
New rule actions replace legacy inbound fields and special outbound fields,
|
||||
and can be used for pre-matching **2**.
|
||||
|
||||
See [Rule](/configuration/route/rule/),
|
||||
[Rule Action](/configuration/route/rule_action/),
|
||||
[DNS Rule](/configuration/dns/rule/) and
|
||||
[DNS Rule Action](/configuration/dns/rule_action/).
|
||||
|
||||
For migration, see
|
||||
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
|
||||
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
|
||||
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
||||
|
||||
**2**:
|
||||
|
||||
Similar to Surge's pre-matching.
|
||||
|
||||
Specifically, new rule actions allow you to reject connections with
|
||||
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
|
||||
before connection established to improve tun's compatibility.
|
||||
|
||||
See [Rule Action](/configuration/route/rule_action/).
|
||||
|
||||
**3**:
|
||||
|
||||
When `gvisor` tun stack is enabled, even if the request passes routing,
|
||||
if the outbound connection establishment fails,
|
||||
the connection still does not need to be established and a TCP RST is replied.
|
||||
|
||||
**4**:
|
||||
|
||||
Route options in DNS route actions will no longer be considered deprecated,
|
||||
see [DNS Route Action](/configuration/dns/rule_action/).
|
||||
|
||||
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
|
||||
see [Route Action](/configuration/route/rule_action/).
|
||||
|
||||
**5**:
|
||||
|
||||
When using in graphical clients, new routing rule items allow you to match on
|
||||
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
|
||||
|
||||
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
||||
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
||||
|
||||
**6**:
|
||||
|
||||
Similar to Surge's strategy.
|
||||
|
||||
New options allow you to connect using multiple network interfaces,
|
||||
prefer or only use one type of interface,
|
||||
and configure a timeout to fallback to other interfaces.
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_strategy),
|
||||
[Rule Action](/configuration/route/rule_action/#network_strategy)
|
||||
and [Route](/configuration/route/#default_network_strategy).
|
||||
|
||||
**7**:
|
||||
|
||||
See [DNS](/configuration/dns/#cache_capacity).
|
||||
|
||||
**8**:
|
||||
|
||||
See [Rule Action](/configuration/route/#override_address) and
|
||||
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
**9**:
|
||||
|
||||
The new WireGuard endpoint combines inbound and outbound capabilities,
|
||||
and the old outbound will be removed in sing-box 1.13.0.
|
||||
|
||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
**10**:
|
||||
|
||||
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
|
||||
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
|
||||
|
||||
For TUN, GSO has been removed,
|
||||
see [Deprecated](/deprecated/#gso-option-in-tun).
|
||||
|
||||
**11**:
|
||||
|
||||
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
|
||||
|
||||
**12**:
|
||||
|
||||
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
|
||||
|
||||
**13**:
|
||||
|
||||
See [Hysteria2](/configuration/outbound/hysteria2/).
|
||||
|
||||
**14**:
|
||||
|
||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
||||
|
||||
### 1.10.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
54
docs/configuration/certificate/index.md
Normal file
54
docs/configuration/certificate/index.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Certificate
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"store": "",
|
||||
"certificate": [],
|
||||
"certificate_path": [],
|
||||
"certificate_directory_path": []
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Fields
|
||||
|
||||
#### store
|
||||
|
||||
The default X509 trusted CA certificate list.
|
||||
|
||||
| Type | Description |
|
||||
|--------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| `system` (default) | System trusted CA certificates |
|
||||
| `mozilla` | [Mozilla Included List](https://wiki.mozilla.org/CA/Included_Certificates) with China CA certificates removed |
|
||||
| `none` | Empty list |
|
||||
|
||||
#### certificate
|
||||
|
||||
The certificate line array to trust, in PEM format.
|
||||
|
||||
#### certificate_path
|
||||
|
||||
!!! note ""
|
||||
|
||||
Will be automatically reloaded if file modified.
|
||||
|
||||
The paths to certificates to trust, in PEM format.
|
||||
|
||||
#### certificate_directory_path
|
||||
|
||||
!!! note ""
|
||||
|
||||
Will be automatically reloaded if file modified.
|
||||
|
||||
The directory path to search for certificates to trust,in PEM format.
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
|
||||
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
|
||||
@@ -49,8 +49,6 @@ Default domain strategy for resolving the domain names.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
Take no effect if `server.strategy` is set.
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable dns cache.
|
||||
|
||||
@@ -48,8 +48,6 @@ icon: material/new-box
|
||||
|
||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
如果设置了 `server.strategy`,则不生效。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
禁用 DNS 缓存。
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-delete-clock: [outbound](#outbound)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [action](#action)
|
||||
@@ -395,6 +399,10 @@ Invert match result.
|
||||
|
||||
#### outbound
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
`outbound` rule items are deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
||||
|
||||
Match outbound.
|
||||
|
||||
`any` can be used as a value to match any outbound.
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-delete-clock: [outbound](#outbound)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [action](#action)
|
||||
@@ -395,6 +399,10 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### outbound
|
||||
|
||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
||||
|
||||
`outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver)。
|
||||
|
||||
匹配出站。
|
||||
|
||||
`any` 可作为值用于匹配任意出站。
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
### route
|
||||
@@ -10,6 +14,7 @@ icon: material/new-box
|
||||
{
|
||||
"action": "route", // default
|
||||
"server": "",
|
||||
"strategy": "",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 0,
|
||||
"client_subnet": null
|
||||
@@ -24,6 +29,12 @@ icon: material/new-box
|
||||
|
||||
Tag of target server.
|
||||
|
||||
#### strategy
|
||||
|
||||
Set domain strategy for this query.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable cache and save cache in this query.
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [strategy](#strategy)
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
### route
|
||||
@@ -10,8 +14,8 @@ icon: material/new-box
|
||||
{
|
||||
"action": "route", // 默认
|
||||
"server": "",
|
||||
|
||||
// 兼容性
|
||||
|
||||
"strategy": "",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 0,
|
||||
"client_subnet": null
|
||||
@@ -26,6 +30,12 @@ icon: material/new-box
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### strategy
|
||||
|
||||
为此查询设置域名策略。
|
||||
|
||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
|
||||
38
docs/configuration/dns/server/dhcp.md
Normal file
38
docs/configuration/dns/server/dhcp.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# DHCP
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "dhcp",
|
||||
"tag": "",
|
||||
|
||||
"interface": "",
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### interface
|
||||
|
||||
Interface name to listen on.
|
||||
|
||||
Tge default interface will be used by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
35
docs/configuration/dns/server/fakeip.md
Normal file
35
docs/configuration/dns/server/fakeip.md
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Fake IP
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "fakeip",
|
||||
"tag": "",
|
||||
|
||||
"inet4_range": "198.18.0.0/15",
|
||||
"inet6_range": "fc00::/18"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### inet4_range
|
||||
|
||||
IPv4 address range for FakeIP.
|
||||
|
||||
#### inet6_address
|
||||
|
||||
IPv6 address range for FakeIP.
|
||||
71
docs/configuration/dns/server/http3.md
Normal file
71
docs/configuration/dns/server/http3.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# DNS over HTTP3 (DoH3)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "h3",
|
||||
"tag": "",
|
||||
|
||||
"server": "",
|
||||
"server_port": 443,
|
||||
|
||||
"path": "",
|
||||
"headers": {},
|
||||
|
||||
"tls": {},
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Difference from legacy H3 server"
|
||||
|
||||
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The address of the DNS server.
|
||||
|
||||
If domain name is used, `domain_resolver` must also be set to resolve IP address.
|
||||
|
||||
#### server_port
|
||||
|
||||
The port of the DNS server.
|
||||
|
||||
`853` will be used by default.
|
||||
|
||||
#### path
|
||||
|
||||
The path of the DNS server.
|
||||
|
||||
`/dns-query` will be used by default.
|
||||
|
||||
#### headers
|
||||
|
||||
Additional headers to be sent to the DNS server.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
71
docs/configuration/dns/server/https.md
Normal file
71
docs/configuration/dns/server/https.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# DNS over HTTPS (DoH)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "https",
|
||||
"tag": "",
|
||||
|
||||
"server": "",
|
||||
"server_port": 443,
|
||||
|
||||
"path": "",
|
||||
"headers": {},
|
||||
|
||||
"tls": {},
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Difference from legacy HTTPS server"
|
||||
|
||||
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The address of the DNS server.
|
||||
|
||||
If domain name is used, `domain_resolver` must also be set to resolve IP address.
|
||||
|
||||
#### server_port
|
||||
|
||||
The port of the DNS server.
|
||||
|
||||
`853` will be used by default.
|
||||
|
||||
#### path
|
||||
|
||||
The path of the DNS server.
|
||||
|
||||
`/dns-query` will be used by default.
|
||||
|
||||
#### headers
|
||||
|
||||
Additional headers to be sent to the DNS server.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
46
docs/configuration/dns/server/index.md
Normal file
46
docs/configuration/dns/server/index.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [type](#type)
|
||||
|
||||
# DNS Server
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### type
|
||||
|
||||
The type of the DNS server.
|
||||
|
||||
| Type | Format |
|
||||
|-----------------|-----------------------------------------------------|
|
||||
| empty (default) | [Legacy](/configuration/dns/server/legacy/) |
|
||||
| `tcp` | [TCP](/configuration/dns/server/tcp/) |
|
||||
| `udp` | [UDP](/configuration/dns/server/udp/) |
|
||||
| `tls` | [TLS](/configuration/dns/server/tls/) |
|
||||
| `https` | [HTTPS](/configuration/dns/server/https/) |
|
||||
| `quic` | [QUIC](/configuration/dns/server/quic/) |
|
||||
| `h3` | [HTTP/3](/configuration/dns/server/http3/) |
|
||||
| `predefined` | [Predefined](/configuration/dns/server/predefined/) |
|
||||
| `dhcp` | [DHCP](/configuration/dns/server/dhcp/) |
|
||||
| `fakeip` | [Fake IP](/configuration/dns/server/fakeip/) |
|
||||
|
||||
|
||||
#### tag
|
||||
|
||||
The tag of the DNS server.
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.12.0"
|
||||
|
||||
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
33
docs/configuration/dns/server/local.md
Normal file
33
docs/configuration/dns/server/local.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Local
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "local",
|
||||
"tag": "",
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Difference from legacy local server"
|
||||
|
||||
* The old legacy local server only handles IP requests; the new one handles all types of requests and supports concurrent for IP requests.
|
||||
* The old local server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
93
docs/configuration/dns/server/predefined.md
Normal file
93
docs/configuration/dns/server/predefined.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Predefined
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "predefined",
|
||||
"tag": "",
|
||||
"responses": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### responses
|
||||
|
||||
==Required==
|
||||
|
||||
List of [Response](#response-structure).
|
||||
|
||||
### Response Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"query": [],
|
||||
"query_type": [],
|
||||
"rcode": "",
|
||||
"answer": [],
|
||||
"ns": [],
|
||||
"extra": []
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Response Fields
|
||||
|
||||
#### query
|
||||
|
||||
List of domain name to match.
|
||||
|
||||
#### query_type
|
||||
|
||||
List of query type to match.
|
||||
|
||||
#### rcode
|
||||
|
||||
The response code.
|
||||
|
||||
| Value | Value in the legacy rcode server | Description |
|
||||
|------------|----------------------------------|-----------------|
|
||||
| `NOERROR` | `success` | Ok |
|
||||
| `FORMERR` | `format_error` | Bad request |
|
||||
| `SERVFAIL` | `server_failure` | Server failure |
|
||||
| `NXDOMAIN` | `name_error` | Not found |
|
||||
| `NOTIMP` | `not_implemented` | Not implemented |
|
||||
| `REFUSED` | `refused` | Refused |
|
||||
|
||||
`NOERROR` will be used by default.
|
||||
|
||||
#### answer
|
||||
|
||||
List of text DNS record to respond as answers.
|
||||
|
||||
Examples:
|
||||
|
||||
| Record Type | Example |
|
||||
|-------------|-------------------------------|
|
||||
| `A` | `localhost. IN A 127.0.0.1` |
|
||||
| `AAAA` | `localhost. IN AAAA ::1` |
|
||||
| `TXT` | `localhost. IN TXT \"Hello\"` |
|
||||
|
||||
#### ns
|
||||
|
||||
List of text DNS record to respond as name servers.
|
||||
|
||||
#### extra
|
||||
|
||||
List of text DNS record to respond as extra records.
|
||||
58
docs/configuration/dns/server/quic.md
Normal file
58
docs/configuration/dns/server/quic.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# DNS over QUIC (DoQ)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "quic",
|
||||
"tag": "",
|
||||
|
||||
"server": "",
|
||||
"server_port": 853,
|
||||
|
||||
"tls": {},
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Difference from legacy QUIC server"
|
||||
|
||||
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The address of the DNS server.
|
||||
|
||||
If domain name is used, `domain_resolver` must also be set to resolve IP address.
|
||||
|
||||
#### server_port
|
||||
|
||||
The port of the DNS server.
|
||||
|
||||
`853` will be used by default.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
83
docs/configuration/dns/server/tailscale.md
Normal file
83
docs/configuration/dns/server/tailscale.md
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# Tailscale
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "tailscale",
|
||||
"tag": "",
|
||||
|
||||
"endpoint": "ts-ep",
|
||||
"accept_default_resolvers": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### endpoint
|
||||
|
||||
==Required==
|
||||
|
||||
The tag of the Tailscale endpoint.
|
||||
|
||||
#### accept_default_resolvers
|
||||
|
||||
Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。
|
||||
|
||||
if not enabled, NXDOMAIN will be returned for non-Tailscale domain queries.
|
||||
|
||||
### Examples
|
||||
|
||||
=== "MagicDNS only"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "local",
|
||||
"tag": "local"
|
||||
},
|
||||
{
|
||||
"type": "tailscale",
|
||||
"tag": "ts",
|
||||
"endpoint": "ts-ep"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"ip_accept_any": true,
|
||||
"server": "ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Use as global DNS"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "tailscale",
|
||||
"endpoint": "ts-ep",
|
||||
"accept_default_resolvers": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
52
docs/configuration/dns/server/tcp.md
Normal file
52
docs/configuration/dns/server/tcp.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# TCP
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "tcp",
|
||||
"tag": "",
|
||||
|
||||
"server": "",
|
||||
"server_port": 53,
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Difference from legacy TCP server"
|
||||
|
||||
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The address of the DNS server.
|
||||
|
||||
If domain name is used, `domain_resolver` must also be set to resolve IP address.
|
||||
|
||||
#### server_port
|
||||
|
||||
The port of the DNS server.
|
||||
|
||||
`53` will be used by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
58
docs/configuration/dns/server/tls.md
Normal file
58
docs/configuration/dns/server/tls.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# DNS over TLS (DoT)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "tls",
|
||||
"tag": "",
|
||||
|
||||
"server": "",
|
||||
"server_port": 853,
|
||||
|
||||
"tls": {},
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Difference from legacy TLS server"
|
||||
|
||||
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The address of the DNS server.
|
||||
|
||||
If domain name is used, `domain_resolver` must also be set to resolve IP address.
|
||||
|
||||
#### server_port
|
||||
|
||||
The port of the DNS server.
|
||||
|
||||
`853` will be used by default.
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
52
docs/configuration/dns/server/udp.md
Normal file
52
docs/configuration/dns/server/udp.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
# TCP
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"type": "udp",
|
||||
"tag": "",
|
||||
|
||||
"server": "",
|
||||
"server_port": 53,
|
||||
|
||||
// Dial Fields
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! info "Difference from legacy UDP server"
|
||||
|
||||
* The old server uses default outbound by default unless detour is specified; the new one uses dialer just like outbound, which is equivalent to using an empty direct outbound by default.
|
||||
* The old server uses `address_resolver` and `address_strategy` to resolve the domain name in the server; the new one uses `domain_resolver` and `domain_strategy` in [Dial Fields](/configuration/shared/dial/) instead.
|
||||
|
||||
### Fields
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
The address of the DNS server.
|
||||
|
||||
If domain name is used, `domain_resolver` must also be set to resolve IP address.
|
||||
|
||||
#### server_port
|
||||
|
||||
The port of the DNS server.
|
||||
|
||||
`53` will be used by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
99
docs/configuration/endpoint/tailscale.md
Normal file
99
docs/configuration/endpoint/tailscale.md
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "tailscale",
|
||||
"tag": "ts-ep",
|
||||
"state_directory": "",
|
||||
"auth_key": "",
|
||||
"control_url": "",
|
||||
"ephemeral": false,
|
||||
"hostname": "",
|
||||
"exit_node": "",
|
||||
"exit_node_allow_lan_access": false,
|
||||
"advertise_routes": [],
|
||||
"advertise_exit_node": false,
|
||||
"udp_timeout": "5m",
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
#### state_directory
|
||||
|
||||
The directory where the Tailscale state is stored.
|
||||
|
||||
`tailscale` is used by default.
|
||||
|
||||
Example: `$HOME/.tailscale`
|
||||
|
||||
#### auth_key
|
||||
|
||||
!!! note
|
||||
|
||||
Auth key is not required. By default, sing-box will log the login URL (or popup a notification on graphical clients).
|
||||
|
||||
The auth key to create the node. If the node is already created (from state previously stored), then this field is not
|
||||
used.
|
||||
|
||||
#### control_url
|
||||
|
||||
The coordination server URL.
|
||||
|
||||
`https://controlplane.tailscale.com` is used by default.
|
||||
|
||||
#### ephemeral
|
||||
|
||||
Indicates whether the instance should register as an Ephemeral node (https://tailscale.com/s/ephemeral-nodes).
|
||||
|
||||
#### hostname
|
||||
|
||||
The hostname of the node.
|
||||
|
||||
System hostname is used by default.
|
||||
|
||||
Example: `localhost`
|
||||
|
||||
#### exit_node
|
||||
|
||||
The exit node name or IP address to use.
|
||||
|
||||
#### exit_node_allow_lan_access
|
||||
|
||||
!!! note
|
||||
|
||||
When the exit node does not have a corresponding advertised route, private traffics cannot be routed to the exit node even if `exit_node_allow_lan_access is` set.
|
||||
|
||||
Indicates whether locally accessible subnets should be routed directly or via the exit node.
|
||||
|
||||
#### advertise_routes
|
||||
|
||||
CIDR prefixes to advertise into the Tailscale network as reachable through the current node.
|
||||
|
||||
Example: `["192.168.1.1/24"]`
|
||||
|
||||
#### advertise_exit_node
|
||||
|
||||
Indicates whether the node should advertise itself as an exit node.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time.
|
||||
|
||||
`5m` will be used by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
!!! note
|
||||
|
||||
Dial Fields in Tailscale endpoints only control how it connects to the control plane and have nothing to do with actual connections.
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
||||
|
||||
@@ -8,6 +8,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
{
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
@@ -22,6 +23,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
|----------------|------------------------|
|
||||
| `log` | [日志](./log/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
| `ntp` | [NTP](./ntp/) |
|
||||
| `endpoints` | [端点](./endpoint/) |
|
||||
| `inbounds` | [入站](./inbound/) |
|
||||
| `outbounds` | [出站](./outbound/) |
|
||||
|
||||
@@ -8,7 +8,7 @@ icon: material/delete-clock
|
||||
|
||||
### Structure
|
||||
|
||||
```json F
|
||||
```json
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
:material-plus: [hop_interval](#hop_interval)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -7,6 +16,10 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"server_ports": [
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"up": "100 Mbps",
|
||||
"up_mbps": 100,
|
||||
"down": "100 Mbps",
|
||||
@@ -38,6 +51,22 @@ The server address.
|
||||
|
||||
The server port.
|
||||
|
||||
#### server_ports
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Server port range list.
|
||||
|
||||
Conflicts with `server_port`.
|
||||
|
||||
#### hop_interval
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Port hopping interval.
|
||||
|
||||
`30s` is used by default.
|
||||
|
||||
#### up, down
|
||||
|
||||
==Required==
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [server_ports](#server_ports)
|
||||
:material-plus: [hop_interval](#hop_interval)
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
@@ -7,6 +16,10 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"server_ports": [
|
||||
"2080:3000"
|
||||
],
|
||||
"hop_interval": "",
|
||||
"up": "100 Mbps",
|
||||
"up_mbps": 100,
|
||||
"down": "100 Mbps",
|
||||
@@ -38,6 +51,22 @@
|
||||
|
||||
服务器端口。
|
||||
|
||||
#### server_ports
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
服务器端口范围列表。
|
||||
|
||||
与 `server_port` 冲突。
|
||||
|
||||
#### hop_interval
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
端口跳跃间隔。
|
||||
|
||||
默认使用 `30s`。
|
||||
|
||||
#### up, down
|
||||
|
||||
==必填==
|
||||
|
||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
WireGuard 出站已被启用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
|
||||
WireGuard 出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.8.0"
|
||||
!!! failure "Removed in sing-box 1.12.0"
|
||||
|
||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||
GeoIP is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||
|
||||
### Structure
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||
!!! failure "已在 sing-box 1.12.0 中被移除"
|
||||
|
||||
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||
|
||||
### 结构
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.8.0"
|
||||
!!! failure "Removed in sing-box 1.12.0"
|
||||
|
||||
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||
Geosite is deprecated in sing-box 1.8.0 and removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||
|
||||
### Structure
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
icon: material/note-remove
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||
!!! failure "已在 sing-box 1.12.0 中被移除"
|
||||
|
||||
Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
||||
Geosite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
||||
|
||||
### 结构
|
||||
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# Route
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||
:material-note-remove: [geoip](#geoip)
|
||||
:material-note-remove: [geosite](#geosite)
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [default_network_strategy](#default_network_strategy)
|
||||
@@ -22,8 +28,6 @@ icon: material/new-box
|
||||
```json
|
||||
{
|
||||
"route": {
|
||||
"geoip": {},
|
||||
"geosite": {},
|
||||
"rules": [],
|
||||
"rule_set": [],
|
||||
"final": "",
|
||||
@@ -31,10 +35,16 @@ icon: material/new-box
|
||||
"override_android_vpn": false,
|
||||
"default_interface": "",
|
||||
"default_mark": 0,
|
||||
"default_domain_resolver": "", // or {}
|
||||
"default_network_strategy": "",
|
||||
"default_network_type": [],
|
||||
"default_fallback_network_type": [],
|
||||
"default_fallback_delay": ""
|
||||
"default_fallback_delay": "",
|
||||
|
||||
// Removed
|
||||
|
||||
"geoip": {},
|
||||
"geosite": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -97,6 +107,14 @@ Set routing mark by default.
|
||||
|
||||
Takes no effect if `outbound.routing_mark` is set.
|
||||
|
||||
#### default_domain_resolver
|
||||
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) for details.
|
||||
|
||||
Can be overrides by `outbound.domain_resolver`.
|
||||
|
||||
#### default_network_strategy
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# 路由
|
||||
|
||||
!!! quote "sing-box 1.12.0 中的更改"
|
||||
|
||||
:material-plus: [default_domain_resolver](#default_domain_resolver)
|
||||
:material-note-remove: [geoip](#geoip)
|
||||
:material-note-remove: [geosite](#geosite)
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [network_strategy](#network_strategy)
|
||||
@@ -100,6 +106,14 @@ icon: material/new-box
|
||||
|
||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||
|
||||
#### default_domain_resolver
|
||||
|
||||
!!! question "自 sing-box 1.12.0 起"
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#domain_resolver)。
|
||||
|
||||
可以被 `outbound.domain_resolver` 覆盖。
|
||||
|
||||
#### network_strategy
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.12.0"
|
||||
|
||||
:material-plus: [tls_fragment](#tls_fragment)
|
||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||
|
||||
## Final actions
|
||||
|
||||
### route
|
||||
@@ -31,6 +36,45 @@ Tag of target outbound.
|
||||
|
||||
See `route-options` fields below.
|
||||
|
||||
### reject
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "reject",
|
||||
"method": "default", // default
|
||||
"no_drop": false
|
||||
}
|
||||
```
|
||||
|
||||
`reject` reject connections
|
||||
|
||||
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
|
||||
|
||||
For non-tun connections and already established connections, will just be closed.
|
||||
|
||||
#### method
|
||||
|
||||
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
|
||||
- `drop`: Drop packets.
|
||||
|
||||
#### no_drop
|
||||
|
||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
||||
|
||||
Not available when `method` is set to drop.
|
||||
|
||||
### hijack-dns
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
```
|
||||
|
||||
`hijack-dns` hijack DNS requests to the sing-box DNS module.
|
||||
|
||||
## Non-final actions
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
@@ -42,7 +86,9 @@ See `route-options` fields below.
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false,
|
||||
"udp_timeout": ""
|
||||
"udp_timeout": "",
|
||||
"tls_fragment": false,
|
||||
"tls_fragment_fallback_delay": ""
|
||||
}
|
||||
```
|
||||
|
||||
@@ -109,44 +155,27 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
|
||||
| 443 | `quic` |
|
||||
| 3478 | `stun` |
|
||||
|
||||
### reject
|
||||
#### tls_fragment
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "reject",
|
||||
"method": "default", // default
|
||||
"no_drop": false
|
||||
}
|
||||
```
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
`reject` reject connections
|
||||
Fragment TLS handshakes to bypass firewalls.
|
||||
|
||||
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
|
||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
|
||||
|
||||
For non-tun connections and already established connections, will just be closed.
|
||||
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked.
|
||||
|
||||
#### method
|
||||
On Linux, Apple platforms, (administrator privileges required) Windows, the wait time can be automatically detected, otherwise it will fall back to waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||
|
||||
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
|
||||
- `drop`: Drop packets.
|
||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, because the target is considered to be local or behind a transparent proxy.
|
||||
|
||||
#### no_drop
|
||||
#### tls_fragment_fallback_delay
|
||||
|
||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
||||
!!! question "Since sing-box 1.12.0"
|
||||
|
||||
Not available when `method` is set to drop.
|
||||
The fallback value used when TLS segmentation cannot automatically determine the wait time.
|
||||
|
||||
### hijack-dns
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
```
|
||||
|
||||
`hijack-dns` hijack DNS requests to the sing-box DNS module.
|
||||
|
||||
## Non-final actions
|
||||
`500ms` is used by default.
|
||||
|
||||
### sniff
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user