mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-15 05:08:33 +10:00
Compare commits
12 Commits
dev-ping
...
dev-tls-fr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420d8d144a | ||
|
|
7ecc62f2e0 | ||
|
|
3fa417f283 | ||
|
|
314b25bccf | ||
|
|
1c56213cbe | ||
|
|
1947a90878 | ||
|
|
63ef6f972f | ||
|
|
eae2631208 | ||
|
|
8cc7734a92 | ||
|
|
d74a05fdff | ||
|
|
4f453e81ac | ||
|
|
bd2e052815 |
22
.github/setup_legacy_go.sh
vendored
22
.github/setup_legacy_go.sh
vendored
@@ -1,22 +0,0 @@
|
|||||||
#!/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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.23
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -112,6 +112,7 @@ jobs:
|
|||||||
- name: darwin_amd64
|
- name: darwin_amd64
|
||||||
goos: darwin
|
goos: darwin
|
||||||
goarch: amd64
|
goarch: amd64
|
||||||
|
require_legacy_go: true
|
||||||
- name: android_arm64
|
- name: android_arm64
|
||||||
goos: android
|
goos: android
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
@@ -133,29 +134,32 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.23
|
||||||
- name: Cache legacy Go
|
- name: Cache legacy Go
|
||||||
if: matrix.require_legacy_go
|
if: matrix.require_legacy_go
|
||||||
id: cache-legacy-go
|
id: cache-legacy-go
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_legacy
|
~/go/go1.20.14
|
||||||
key: go_legacy_1236
|
key: go120
|
||||||
- name: Setup legacy Go
|
- name: Setup legacy Go
|
||||||
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
run: bash .github/setup_legacy_go.sh
|
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
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.goos == 'android'
|
if: matrix.goos == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28-beta2
|
||||||
local-cache: true
|
local-cache: true
|
||||||
- name: Setup Goreleaser
|
- name: Setup Goreleaser
|
||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
distribution: goreleaser-pro
|
||||||
version: 2.5.1
|
version: latest
|
||||||
install-only: true
|
install-only: true
|
||||||
- name: Extract signing key
|
- name: Extract signing key
|
||||||
run: |-
|
run: |-
|
||||||
@@ -215,12 +219,12 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.23
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28-beta2
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -252,16 +256,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ~/.gradle
|
path: ~/.gradle
|
||||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
- 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
|
- name: Build
|
||||||
run: |-
|
run: |-
|
||||||
|
go run -v ./cmd/internal/update_android_version --ci
|
||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp libbox.aar clients/android/app/libs
|
cp libbox.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
@@ -297,12 +294,12 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.23
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28
|
ndk-version: r28-beta2
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -395,7 +392,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.23
|
||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -551,7 +548,7 @@ jobs:
|
|||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
distribution: goreleaser-pro
|
||||||
version: 2.5.1
|
version: latest
|
||||||
install-only: true
|
install-only: true
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.23
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v6
|
uses: golangci/golangci-lint-action@v6
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.24
|
go-version: ^1.23
|
||||||
- name: Extract signing key
|
- name: Extract signing key
|
||||||
run: |-
|
run: |-
|
||||||
mkdir -p $HOME/.gnupg
|
mkdir -p $HOME/.gnupg
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ linters-settings:
|
|||||||
- -SA1003
|
- -SA1003
|
||||||
|
|
||||||
run:
|
run:
|
||||||
go: "1.24"
|
go: "1.23"
|
||||||
build-tags:
|
build-tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ builds:
|
|||||||
- with_reality_server
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ builds:
|
|||||||
- with_reality_server
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
@@ -50,14 +49,14 @@ builds:
|
|||||||
- with_reality_server
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
- with_tailscale
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
||||||
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
|
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
|
- darwin_amd64_v1
|
||||||
- id: android
|
- id: android
|
||||||
<<: *template
|
<<: *template
|
||||||
env:
|
env:
|
||||||
@@ -123,8 +122,8 @@ nfpms:
|
|||||||
- deb
|
- deb
|
||||||
- rpm
|
- rpm
|
||||||
- archlinux
|
- archlinux
|
||||||
# - apk
|
# - apk
|
||||||
# - ipk
|
# - ipk
|
||||||
priority: extra
|
priority: extra
|
||||||
contents:
|
contents:
|
||||||
- src: release/config/config.json
|
- src: release/config/config.json
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS builder
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -2,8 +2,7 @@ NAME = sing-box
|
|||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
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_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||||
TAGS_GO121 = with_ech
|
TAGS_GO121 = with_ech
|
||||||
TAGS_GO123 = with_tailscale
|
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||||
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
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
@@ -62,9 +61,6 @@ proto_install:
|
|||||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
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
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
update_certificates:
|
|
||||||
go run ./cmd/internal/update_certificates
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
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,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,20 +15,7 @@ type ClashServer interface {
|
|||||||
ConnectionTracker
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
HistoryStorage() URLTestHistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
type V2RayServer interface {
|
||||||
@@ -50,17 +38,17 @@ type CacheFile interface {
|
|||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedRuleSet
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedRuleSet) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedRuleSet struct {
|
||||||
Content []byte
|
Content []byte
|
||||||
LastUpdated time.Time
|
LastUpdated time.Time
|
||||||
LastEtag string
|
LastEtag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,7 +69,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
var version uint8
|
var version uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &version)
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
|||||||
@@ -28,14 +28,12 @@ type NetworkManager interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type NetworkOptions struct {
|
type NetworkOptions struct {
|
||||||
BindInterface string
|
NetworkStrategy *C.NetworkStrategy
|
||||||
RoutingMark uint32
|
NetworkType []C.InterfaceType
|
||||||
DomainResolver string
|
FallbackNetworkType []C.InterfaceType
|
||||||
DomainResolveOptions DNSQueryOptions
|
FallbackDelay time.Duration
|
||||||
NetworkStrategy *C.NetworkStrategy
|
BindInterface string
|
||||||
NetworkType []C.InterfaceType
|
RoutingMark uint32
|
||||||
FallbackNetworkType []C.InterfaceType
|
|
||||||
FallbackDelay time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,11 +18,6 @@ type Outbound interface {
|
|||||||
N.Dialer
|
N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
type DirectRouteOutbound interface {
|
|
||||||
Outbound
|
|
||||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext) (tun.DirectRouteDestination, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
|||||||
@@ -2,16 +2,13 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
@@ -20,7 +17,7 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Lifecycle
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext, context tun.DirectRouteContext) (tun.DirectRouteDestination, error)
|
PreMatch(metadata InboundContext) error
|
||||||
ConnectionRouterEx
|
ConnectionRouterEx
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
@@ -69,14 +66,12 @@ type RuleSetMetadata struct {
|
|||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
type HTTPStartContext struct {
|
||||||
ctx context.Context
|
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
httpClientCache map[string]*http.Client
|
httpClientCache map[string]*http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
|
func NewHTTPStartContext() *HTTPStartContext {
|
||||||
return &HTTPStartContext{
|
return &HTTPStartContext{
|
||||||
ctx: ctx,
|
|
||||||
httpClientCache: make(map[string]*http.Client),
|
httpClientCache: make(map[string]*http.Client),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,10 +89,6 @@ func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Clie
|
|||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
},
|
},
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
Time: ntp.TimeFuncFromContext(c.ctx),
|
|
||||||
RootCAs: RootPoolFromContext(c.ctx),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.httpClientCache[detour] = httpClient
|
c.httpClientCache[detour] = httpClient
|
||||||
|
|||||||
20
box.go
20
box.go
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"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/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
@@ -142,20 +141,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create log factory")
|
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)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
@@ -202,7 +187,7 @@ func New(options Options) (*Box, error) {
|
|||||||
transportOptions.Options,
|
transportOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize DNS server[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = dnsRouter.Initialize(dnsOptions.Rules)
|
err = dnsRouter.Initialize(dnsOptions.Rules)
|
||||||
@@ -225,7 +210,7 @@ func New(options Options) (*Box, error) {
|
|||||||
endpointOptions.Options,
|
endpointOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize endpoint[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
@@ -295,6 +280,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var services []adapter.LifecycleService
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
|
|||||||
Submodule clients/android updated: 3a2fc9c880...b17fb6d857
Submodule clients/apple updated: 3d5d7343fb...64a4614aca
@@ -5,7 +5,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/asc-go/asc"
|
"github.com/sagernet/asc-go/asc"
|
||||||
@@ -195,10 +194,6 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
log.Info(string(platform), " ", tag, " create submission")
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
|
||||||
log.Error(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
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)
|
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", "with_tailscale")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "28.0.13004108"
|
const fixedVersion = "28.0.12674087"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
|
|||||||
@@ -13,14 +13,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var flagRunInCI bool
|
||||||
flagRunInCI bool
|
|
||||||
flagRunNightly bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
flag.BoolVar(&flagRunInCI, "ci", false, "Run in CI")
|
||||||
flag.BoolVar(&flagRunNightly, "nightly", false, "Run nightly")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -50,23 +46,21 @@ func main() {
|
|||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
case "VERSION_NAME":
|
case "VERSION_NAME":
|
||||||
if propPair[1] != newVersion {
|
if propPair[1] != newVersion {
|
||||||
log.Info("updated version from ", propPair[1], " to ", newVersion)
|
|
||||||
versionUpdated = true
|
versionUpdated = true
|
||||||
propPair[1] = newVersion
|
propPair[1] = newVersion
|
||||||
|
log.Info("updated version to ", newVersion)
|
||||||
}
|
}
|
||||||
case "GO_VERSION":
|
case "GO_VERSION":
|
||||||
if propPair[1] != runtime.Version() {
|
if propPair[1] != runtime.Version() {
|
||||||
log.Info("updated Go version from ", propPair[1], " to ", runtime.Version())
|
|
||||||
goVersionUpdated = true
|
goVersionUpdated = true
|
||||||
propPair[1] = runtime.Version()
|
propPair[1] = runtime.Version()
|
||||||
|
log.Info("updated Go version to ", runtime.Version())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !(versionUpdated || goVersionUpdated) {
|
if !(versionUpdated || goVersionUpdated) {
|
||||||
log.Info("version not changed")
|
log.Info("version not changed")
|
||||||
return
|
return
|
||||||
} else if flagRunInCI && !flagRunNightly {
|
|
||||||
log.Fatal("version changed, commit changes first.")
|
|
||||||
}
|
}
|
||||||
for _, propPair := range propsList {
|
for _, propPair := range propsList {
|
||||||
switch propPair[0] {
|
switch propPair[0] {
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
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 {
|
func generateTLSKeyPair(serverName string) error {
|
||||||
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,15 +61,14 @@ func upgradeRuleSet(sourcePath string) error {
|
|||||||
log.Info("already up-to-date")
|
log.Info("already up-to-date")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
|
plainRuleSet, err := plainRuleSetCompat.Upgrade()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
encoder := json.NewEncoder(buffer)
|
encoder := json.NewEncoder(buffer)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
err = encoder.Encode(plainRuleSetCompat)
|
err = encoder.Encode(plainRuleSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "encode config")
|
return E.Cause(err, "encode config")
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,182 +0,0 @@
|
|||||||
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,6 +35,7 @@ type DefaultDialer struct {
|
|||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
|
isWireGuardListener bool
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy *C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
defaultNetworkStrategy bool
|
defaultNetworkStrategy bool
|
||||||
@@ -182,6 +183,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
}
|
}
|
||||||
setMultiPathTCP(&dialer4)
|
setMultiPathTCP(&dialer4)
|
||||||
}
|
}
|
||||||
|
if options.IsWireGuardListener {
|
||||||
|
for _, controlFn := range WgControlFns {
|
||||||
|
listener.Control = control.Append(listener.Control, controlFn)
|
||||||
|
}
|
||||||
|
}
|
||||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -198,6 +204,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpListener: listener,
|
udpListener: listener,
|
||||||
udpAddr4: udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
|
isWireGuardListener: options.IsWireGuardListener,
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
networkStrategy: networkStrategy,
|
||||||
defaultNetworkStrategy: defaultNetworkStrategy,
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
|
|||||||
@@ -29,18 +29,16 @@ func (d *DetourDialer) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||||
d.initOnce.Do(d.init)
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
return d.dialer, d.initErr
|
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) {
|
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
dialer, err := d.Dialer()
|
dialer, err := d.Dialer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -16,108 +16,81 @@ import (
|
|||||||
"github.com/sagernet/sing/service"
|
"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) {
|
func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {
|
||||||
return NewWithOptions(Options{
|
if options.IsWireGuardListener {
|
||||||
Context: ctx,
|
return NewDefault(ctx, options)
|
||||||
Options: options,
|
}
|
||||||
RemoteIsDomain: remoteIsDomain,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWithOptions(options Options) (N.Dialer, error) {
|
|
||||||
dialOptions := options.Options
|
|
||||||
var (
|
var (
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if dialOptions.Detour != "" {
|
if options.Detour == "" {
|
||||||
outboundManager := service.FromContext[adapter.OutboundManager](options.Context)
|
dialer, err = NewDefault(ctx, options)
|
||||||
if outboundManager == nil {
|
|
||||||
return nil, E.New("missing outbound manager")
|
|
||||||
}
|
|
||||||
dialer = NewDetour(outboundManager, dialOptions.Detour)
|
|
||||||
} else {
|
|
||||||
dialer, err = NewDefault(options.Context, dialOptions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
|
||||||
|
if outboundManager == nil {
|
||||||
|
return nil, E.New("missing outbound manager")
|
||||||
|
}
|
||||||
|
dialer = NewDetour(outboundManager, options.Detour)
|
||||||
}
|
}
|
||||||
if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) {
|
if remoteIsDomain && options.Detour == "" && options.DomainResolver == "" {
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](options.Context)
|
deprecated.Report(ctx, deprecated.OptionMissingDomainResolverInDialOptions)
|
||||||
dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)
|
}
|
||||||
var defaultOptions adapter.NetworkOptions
|
if (options.Detour == "" && remoteIsDomain) || options.DomainResolver != "" {
|
||||||
if networkManager != nil {
|
router := service.FromContext[adapter.DNSRouter](ctx)
|
||||||
defaultOptions = networkManager.DefaultOptions()
|
if router != nil {
|
||||||
}
|
var resolveTransport adapter.DNSTransport
|
||||||
var (
|
if options.DomainResolver != "" {
|
||||||
server string
|
transport, loaded := service.FromContext[adapter.DNSTransportManager](ctx).Transport(options.DomainResolver)
|
||||||
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 {
|
if !loaded {
|
||||||
return nil, E.New("domain resolver not found: " + dialOptions.DomainResolver.Server)
|
return nil, E.New("DNS server not found: " + options.DomainResolver)
|
||||||
}
|
}
|
||||||
|
resolveTransport = transport
|
||||||
}
|
}
|
||||||
var strategy C.DomainStrategy
|
dialer = NewResolveDialer(
|
||||||
if dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
router,
|
||||||
strategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy)
|
dialer,
|
||||||
} else if
|
options.Detour == "" && !options.TCPFastOpen,
|
||||||
//nolint:staticcheck
|
resolveTransport,
|
||||||
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
C.DomainStrategy(options.DomainStrategy),
|
||||||
//nolint:staticcheck
|
time.Duration(options.FallbackDelay))
|
||||||
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
|
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 {
|
type ParallelInterfaceDialer interface {
|
||||||
N.Dialer
|
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)
|
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,17 +3,14 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -21,37 +18,23 @@ var (
|
|||||||
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResolveDialer interface {
|
|
||||||
N.Dialer
|
|
||||||
QueryOptions() adapter.DNSQueryOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ParallelInterfaceResolveDialer interface {
|
|
||||||
ParallelInterfaceDialer
|
|
||||||
QueryOptions() adapter.DNSQueryOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
type resolveDialer struct {
|
type resolveDialer struct {
|
||||||
transport adapter.DNSTransportManager
|
|
||||||
router adapter.DNSRouter
|
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
parallel bool
|
parallel bool
|
||||||
server string
|
router adapter.DNSRouter
|
||||||
initOnce sync.Once
|
transport adapter.DNSTransport
|
||||||
initErr error
|
strategy C.DomainStrategy
|
||||||
queryOptions adapter.DNSQueryOptions
|
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
|
func NewResolveDialer(router adapter.DNSRouter, dialer N.Dialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
||||||
return &resolveDialer{
|
return &resolveDialer{
|
||||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
dialer,
|
||||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
parallel,
|
||||||
dialer: dialer,
|
router,
|
||||||
parallel: parallel,
|
transport,
|
||||||
server: server,
|
strategy,
|
||||||
queryOptions: queryOptions,
|
fallbackDelay,
|
||||||
fallbackDelay: fallbackDelay,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,68 +43,42 @@ type resolveParallelNetworkDialer struct {
|
|||||||
dialer ParallelInterfaceDialer
|
dialer ParallelInterfaceDialer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
|
func NewResolveParallelInterfaceDialer(router adapter.DNSRouter, dialer ParallelInterfaceDialer, parallel bool, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
|
||||||
return &resolveParallelNetworkDialer{
|
return &resolveParallelNetworkDialer{
|
||||||
resolveDialer{
|
resolveDialer{
|
||||||
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
dialer,
|
||||||
router: service.FromContext[adapter.DNSRouter](ctx),
|
parallel,
|
||||||
dialer: dialer,
|
router,
|
||||||
parallel: parallel,
|
transport,
|
||||||
server: server,
|
strategy,
|
||||||
queryOptions: queryOptions,
|
fallbackDelay,
|
||||||
fallbackDelay: fallbackDelay,
|
|
||||||
},
|
},
|
||||||
dialer,
|
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) {
|
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() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if d.parallel {
|
if d.parallel {
|
||||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
} else {
|
} else {
|
||||||
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
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() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -132,24 +89,12 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
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) {
|
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() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -157,28 +102,21 @@ func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context
|
|||||||
fallbackDelay = d.fallbackDelay
|
fallbackDelay = d.fallbackDelay
|
||||||
}
|
}
|
||||||
if d.parallel {
|
if d.parallel {
|
||||||
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
} else {
|
} else {
|
||||||
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
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) {
|
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() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, adapter.DNSQueryOptions{Transport: d.transport, Strategy: d.strategy})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = d.fallbackDelay
|
|
||||||
}
|
|
||||||
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -186,10 +124,6 @@ func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.C
|
|||||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions {
|
func (d *resolveDialer) Upstream() any {
|
||||||
return d.queryOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *resolveParallelNetworkDialer) Upstream() any {
|
|
||||||
return d.dialer
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
ProcessID uint32
|
|
||||||
ProcessPath string
|
ProcessPath string
|
||||||
PackageName string
|
PackageName string
|
||||||
User string
|
User string
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ package process
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/winiphlpapi"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
@@ -23,39 +26,209 @@ func NewSearcher(_ Config) (Searcher, error) {
|
|||||||
return &windowsSearcher{}, nil
|
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 {
|
func initWin32API() error {
|
||||||
return winiphlpapi.LoadExtendedTable()
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
pid, err := winiphlpapi.FindPid(network, source)
|
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
path, err := getProcessPath(pid)
|
return &Info{ProcessPath: processName, UserId: -1}, nil
|
||||||
if err != nil {
|
|
||||||
return &Info{ProcessID: pid, UserId: -1}, err
|
|
||||||
}
|
|
||||||
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProcessPath(pid uint32) (string, error) {
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return "", 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
switch pid {
|
switch pid {
|
||||||
case 0:
|
case 0:
|
||||||
|
// reserved pid for system idle process
|
||||||
return ":System Idle Process", nil
|
return ":System Idle Process", nil
|
||||||
case 4:
|
case 4:
|
||||||
|
// reserved pid for windows kernel image
|
||||||
return ":System", nil
|
return ":System", nil
|
||||||
}
|
}
|
||||||
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer windows.CloseHandle(handle)
|
defer windows.CloseHandle(h)
|
||||||
size := uint32(syscall.MAX_LONG_PATH)
|
|
||||||
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
||||||
err = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size)
|
size := uint32(len(buf))
|
||||||
if err != nil {
|
r1, _, err := syscall.SyscallN(
|
||||||
|
procQueryFullProcessImageNameW.Addr(),
|
||||||
|
uintptr(h),
|
||||||
|
uintptr(0),
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
)
|
||||||
|
if r1 == 0 {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return windows.UTF16ToString(buf[:size]), nil
|
return syscall.UTF16ToString(buf[:size]), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
"github.com/libdns/cloudflare"
|
"github.com/libdns/cloudflare"
|
||||||
"github.com/mholt/acmez/v3/acme"
|
"github.com/mholt/acmez/acme"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func (c *echServerConfig) startWatcher() error {
|
|||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
err := c.credentialsUpdated(path)
|
err := c.credentialsUpdated(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(E.Cause(err, "reload credentials"))
|
c.logger.Error(E.Cause(err, "reload credentials from ", path))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||||
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))
|
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() ti
|
|||||||
return &certificate, err
|
return &certificate, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||||
if timeFunc == nil {
|
if timeFunc == nil {
|
||||||
timeFunc = time.Now
|
timeFunc = time.Now
|
||||||
}
|
}
|
||||||
@@ -47,11 +47,7 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
|
|||||||
},
|
},
|
||||||
DNSNames: []string{serverName},
|
DNSNames: []string{serverName},
|
||||||
}
|
}
|
||||||
if parent == nil {
|
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||||
parent = template
|
|
||||||
parentKey = key
|
|
||||||
}
|
|
||||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
@@ -42,7 +40,6 @@ import (
|
|||||||
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
||||||
|
|
||||||
type RealityClientConfig struct {
|
type RealityClientConfig struct {
|
||||||
ctx context.Context
|
|
||||||
uClient *UTLSClientConfig
|
uClient *UTLSClientConfig
|
||||||
publicKey []byte
|
publicKey []byte
|
||||||
shortID [8]byte
|
shortID [8]byte
|
||||||
@@ -73,7 +70,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
@@ -183,24 +180,20 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !verifier.verified {
|
if !verifier.verified {
|
||||||
go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)
|
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &realityClientConnWrapper{uConn}, nil
|
return &realityClientConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
defer uConn.Close()
|
defer uConn.Close()
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http2.Transport{
|
Transport: &http2.Transport{
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
return uConn, nil
|
return uConn, nil
|
||||||
},
|
},
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
Time: ntp.TimeFuncFromContext(ctx),
|
|
||||||
RootCAs: adapter.RootPoolFromContext(ctx),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||||
@@ -220,7 +213,6 @@ func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello [
|
|||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
return &RealityClientConfig{
|
return &RealityClientConfig{
|
||||||
e.ctx,
|
|
||||||
e.uClient.Clone().(*UTLSClientConfig),
|
e.uClient.Clone().(*UTLSClientConfig),
|
||||||
e.publicKey,
|
e.publicKey,
|
||||||
e.shortID,
|
e.shortID,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -59,7 +58,6 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (c *STDServerConfig) startWatcher() error {
|
|||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
err := c.certificateUpdated(path)
|
err := c.certificateUpdated(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(E.Cause(err, "reload certificate"))
|
c.logger.Error(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -131,7 +130,6 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,26 +5,26 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
tcpConn *net.TCPConn
|
syscallConn syscall.Conn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
syscallConn, _ := N.UnwrapReader(conn).(syscall.Conn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
syscallConn: syscallConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -37,18 +37,39 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}()
|
}()
|
||||||
serverName := indexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.tcpConn != nil {
|
tcpConn, isTCPConn := c.syscallConn.(interface {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
SetNoDelay(bool) error
|
||||||
|
})
|
||||||
|
if isTCPConn {
|
||||||
|
err = tcpConn.SetNoDelay(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splits := strings.Split(serverName.ServerName, ".")
|
splits := strings.Split(string(b[serverName.Index:serverName.Index+serverName.Length]), ".")
|
||||||
currentIndex := serverName.Index
|
currentIndex := serverName.Index
|
||||||
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
|
var striped bool
|
||||||
splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")]
|
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 len(splits) > 1 && splits[0] == "..." {
|
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]) {
|
||||||
currentIndex += len(splits[0]) + 1
|
currentIndex += len(splits[0]) + 1
|
||||||
splits = splits[1:]
|
splits = splits[1:]
|
||||||
}
|
}
|
||||||
@@ -62,28 +83,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
for i := 0; i <= len(splitIndexes); i++ {
|
||||||
var payload []byte
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
payload = b[:splitIndexes[i]]
|
_, err = c.Conn.Write(b[:splitIndexes[i]])
|
||||||
} else if i == len(splitIndexes) {
|
} else if i == len(splitIndexes) {
|
||||||
payload = b[splitIndexes[i-1]:]
|
_, err = c.Conn.Write(b[splitIndexes[i-1]:])
|
||||||
} else {
|
} else {
|
||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
_, err = c.Conn.Write(b[splitIndexes[i-1]:splitIndexes[i]])
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
if err != nil {
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
return
|
||||||
if err != nil {
|
}
|
||||||
return
|
if c.syscallConn != nil && i != len(splitIndexes) {
|
||||||
}
|
err = waitAck(c.ctx, c.syscallConn, c.fallbackDelay)
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil {
|
if isTCPConn {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type myServerName struct {
|
type myServerName struct {
|
||||||
Index int
|
Index int
|
||||||
Length int
|
Length int
|
||||||
ServerName string
|
sex []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerName(payload []byte) *myServerName {
|
func indexTLSServerName(payload []byte) *myServerName {
|
||||||
@@ -68,9 +68,15 @@ func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||||
|
numCiphers := int(csLen / 2)
|
||||||
|
cipherSuites := make([]uint16, 0, numCiphers)
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||||
return nil
|
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)])
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||||
return nil
|
return nil
|
||||||
@@ -119,9 +125,9 @@ func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
|||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
sex = sex[sniExtensionHeaderLen:]
|
||||||
return &myServerName{
|
return &myServerName{
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||||
Length: int(sniLen),
|
Length: int(sniLen),
|
||||||
ServerName: string(sex),
|
sex: sex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exs = exs[4+exLen:]
|
exs = exs[4+exLen:]
|
||||||
|
|||||||
55
common/tlsfragment/public_suffix.go
Normal file
55
common/tlsfragment/public_suffix.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
15692
common/tlsfragment/public_suffix_list.dat
Normal file
15692
common/tlsfragment/public_suffix_list.dat
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ package tf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -63,11 +63,7 @@ func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32)
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||||
_, err := conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return control.Conn(conn, func(fd uintptr) error {
|
return control.Conn(conn, func(fd uintptr) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package tf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -10,11 +10,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||||
_, err := conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return control.Conn(conn, func(fd uintptr) error {
|
return control.Conn(conn, func(fd uintptr) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
//go:build !(linux || darwin || windows)
|
//go:build !(linux || darwin)
|
||||||
|
|
||||||
package tf
|
package tf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||||
time.Sleep(fallbackDelay)
|
time.Sleep(fallbackDelay)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
type History struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
type HistoryStorage struct {
|
type HistoryStorage struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
delayHistory map[string]*adapter.URLTestHistory
|
delayHistory map[string]*History
|
||||||
updateHook chan<- struct{}
|
updateHook chan<- struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistoryStorage() *HistoryStorage {
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
return &HistoryStorage{
|
return &HistoryStorage{
|
||||||
delayHistory: make(map[string]*adapter.URLTestHistory),
|
delayHistory: make(map[string]*History),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
|
|||||||
s.updateHook = hook
|
s.updateHook = hook
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
|
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
|||||||
s.notifyUpdated()
|
s.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
s.delayHistory[tag] = history
|
s.delayHistory[tag] = history
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
@@ -110,10 +110,6 @@ 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) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return instance, nil
|
return instance, nil
|
||||||
},
|
},
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
Time: ntp.TimeFuncFromContext(ctx),
|
|
||||||
RootCAs: adapter.RootPoolFromContext(ctx),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package constant
|
|
||||||
|
|
||||||
const (
|
|
||||||
CertificateStoreSystem = "system"
|
|
||||||
CertificateStoreMozilla = "mozilla"
|
|
||||||
CertificateStoreNone = "none"
|
|
||||||
)
|
|
||||||
@@ -27,7 +27,6 @@ const (
|
|||||||
DNSTypePreDefined = "predefined"
|
DNSTypePreDefined = "predefined"
|
||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeDHCP = "dhcp"
|
||||||
DNSTypeTailscale = "tailscale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const (
|
|||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
|||||||
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
options.ClientSubnet = legacyTransport.LegacyClientSubnet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
return transport, currentRule, currentRuleIndex
|
return transport, currentRule, currentRuleIndex
|
||||||
case *R.RuleActionDNSRouteOptions:
|
case *R.RuleActionDNSRouteOptions:
|
||||||
if action.Strategy != C.DomainStrategyAsIS {
|
if action.Strategy != C.DomainStrategyAsIS {
|
||||||
@@ -188,7 +189,9 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
|||||||
if action.ClientSubnet.IsValid() {
|
if action.ClientSubnet.IsValid() {
|
||||||
options.ClientSubnet = action.ClientSubnet
|
options.ClientSubnet = action.ClientSubnet
|
||||||
}
|
}
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
return nil, currentRule, currentRuleIndex
|
return nil, currentRule, currentRuleIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -249,8 +252,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
ruleIndex = -1
|
ruleIndex = -1
|
||||||
for {
|
for {
|
||||||
dnsCtx := adapter.OverrideContext(ctx)
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
dnsOptions := options
|
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &options)
|
||||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
switch action := rule.Action().(type) {
|
switch action := rule.Action().(type) {
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
@@ -269,10 +271,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
return rule.MatchAddressLimit(metadata)
|
return rule.MatchAddressLimit(metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
options.Strategy = r.defaultDomainStrategy
|
||||||
}
|
}
|
||||||
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
|
response, err = r.client.Exchange(dnsCtx, transport, message, options, responseCheck)
|
||||||
var rejected bool
|
var rejected bool
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrResponseRejectedCached) {
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
@@ -298,7 +300,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
|
if r.dnsReverseMapping != nil && len(message.Question) > 0 && response != nil && len(response.Answer) > 0 {
|
||||||
if transport == nil || transport.Type() != C.DNSTypeFakeIP {
|
if transport.Type() != C.DNSTypeFakeIP {
|
||||||
for _, answer := range response.Answer {
|
for _, answer := range response.Answer {
|
||||||
switch record := answer.(type) {
|
switch record := answer.(type) {
|
||||||
case *mDNS.A:
|
case *mDNS.A:
|
||||||
@@ -342,7 +344,7 @@ func (r *Router) Lookup(ctx context.Context, domain string, options adapter.DNSQ
|
|||||||
r.logger.DebugContext(ctx, "lookup domain ", domain)
|
r.logger.DebugContext(ctx, "lookup domain ", domain)
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
ctx, metadata := adapter.ExtendContext(ctx)
|
||||||
metadata.Destination = M.Socksaddr{}
|
metadata.Destination = M.Socksaddr{}
|
||||||
metadata.Domain = FqdnToDomain(domain)
|
metadata.Domain = domain
|
||||||
if options.Transport != nil {
|
if options.Transport != nil {
|
||||||
transport := options.Transport
|
transport := options.Transport
|
||||||
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
if legacyTransport, isLegacy := transport.(adapter.LegacyDNSTransport); isLegacy {
|
||||||
|
|||||||
@@ -36,17 +36,6 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
}, nil
|
}, 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() {
|
func (t *Transport) Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
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.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."))
|
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
package hosts
|
package hosts
|
||||||
|
|
||||||
import (
|
import _ "unsafe"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
var DefaultPath = getSystemDirectory() + "/Drivers/etc/hosts"
|
||||||
)
|
|
||||||
|
|
||||||
var DefaultPath string
|
//go:linkname getSystemDirectory internal/syscall/windows.GetSystemDirectory
|
||||||
|
func getSystemDirectory() string
|
||||||
func init() {
|
|
||||||
systemDirectory, err := windows.GetSystemDirectory()
|
|
||||||
if err != nil {
|
|
||||||
systemDirectory = "C:\\Windows\\System32"
|
|
||||||
}
|
|
||||||
DefaultPath = filepath.Join(systemDirectory, "Drivers/etc/hosts")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -86,11 +85,9 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
|||||||
results := make(chan queryResult)
|
results := make(chan queryResult)
|
||||||
startRacer := func(ctx context.Context, fqdn string) {
|
startRacer := func(ctx context.Context, fqdn string) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err == nil {
|
addresses, _ := dns.MessageToAddresses(response)
|
||||||
addresses, _ := dns.MessageToAddresses(response)
|
if len(addresses) == 0 {
|
||||||
if len(addresses) == 0 {
|
err = E.New(fqdn, ": empty result")
|
||||||
err = E.New(fqdn, ": empty result")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case results <- queryResult{response, err}:
|
case results <- queryResult{response, err}:
|
||||||
@@ -150,7 +147,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
|||||||
}
|
}
|
||||||
request := &mDNS.Msg{
|
request := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
Id: uint16(rand.Uint32()),
|
Id: uint16(randInt()),
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
AuthenticatedData: ad,
|
AuthenticatedData: ad,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -117,7 +118,7 @@ func (conf *dnsConfig) nameList(name string) []string {
|
|||||||
|
|
||||||
hasNdots := strings.Count(name, ".") >= conf.ndots
|
hasNdots := strings.Count(name, ".") >= conf.ndots
|
||||||
name += "."
|
name += "."
|
||||||
// l++
|
l++
|
||||||
|
|
||||||
names := make([]string, 0, 1+len(conf.search))
|
names := make([]string, 0, 1+len(conf.search))
|
||||||
if hasNdots && !avoidDNS(name) {
|
if hasNdots && !avoidDNS(name) {
|
||||||
@@ -135,6 +136,13 @@ func (conf *dnsConfig) nameList(name string) []string {
|
|||||||
return names
|
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 {
|
func avoidDNS(name string) bool {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -144,13 +144,6 @@ func (t *UDPTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.M
|
|||||||
func (t *UDPTransport) open(ctx context.Context) (*dnsConnection, error) {
|
func (t *UDPTransport) open(ctx context.Context) (*dnsConnection, error) {
|
||||||
t.access.Lock()
|
t.access.Lock()
|
||||||
defer t.access.Unlock()
|
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)
|
conn, err := t.dialer.DialContext(ctx, N.NetworkUDP, t.serverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -161,7 +154,6 @@ func (t *UDPTransport) open(ctx context.Context) (*dnsConnection, error) {
|
|||||||
callbacks: make(map[uint16]*dnsCallback),
|
callbacks: make(map[uint16]*dnsCallback),
|
||||||
}
|
}
|
||||||
go t.recvLoop(dnsConn)
|
go t.recvLoop(dnsConn)
|
||||||
t.conn = dnsConn
|
|
||||||
return dnsConn, nil
|
return dnsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,6 +201,8 @@ type dnsConnection struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *dnsConnection) Close(err error) {
|
func (c *dnsConnection) Close(err error) {
|
||||||
|
c.access.Lock()
|
||||||
|
defer c.access.Unlock()
|
||||||
c.closeOnce.Do(func() {
|
c.closeOnce.Do(func() {
|
||||||
close(c.done)
|
close(c.done)
|
||||||
c.err = err
|
c.err = err
|
||||||
|
|||||||
@@ -27,14 +27,9 @@ func NewTransportAdapter(transportType string, transportTag string, dependencies
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTransportAdapterWithLocalOptions(transportType string, transportTag string, localOptions option.LocalDNSServerOptions) TransportAdapter {
|
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{
|
return TransportAdapter{
|
||||||
transportType: transportType,
|
transportType: transportType,
|
||||||
transportTag: transportTag,
|
transportTag: transportTag,
|
||||||
dependencies: dependencies,
|
|
||||||
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||||
clientSubnet: localOptions.LegacyClientSubnet,
|
clientSubnet: localOptions.LegacyClientSubnet,
|
||||||
}
|
}
|
||||||
@@ -42,11 +37,8 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
|||||||
|
|
||||||
func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter {
|
func NewTransportAdapterWithRemoteOptions(transportType string, transportTag string, remoteOptions option.RemoteDNSServerOptions) TransportAdapter {
|
||||||
var dependencies []string
|
var dependencies []string
|
||||||
if remoteOptions.DomainResolver != nil && remoteOptions.DomainResolver.Server != "" {
|
if remoteOptions.AddressResolver != "" {
|
||||||
dependencies = append(dependencies, remoteOptions.DomainResolver.Server)
|
dependencies = []string{remoteOptions.AddressResolver}
|
||||||
}
|
|
||||||
if remoteOptions.LegacyAddressResolver != "" {
|
|
||||||
dependencies = append(dependencies, remoteOptions.LegacyAddressResolver)
|
|
||||||
}
|
}
|
||||||
return TransportAdapter{
|
return TransportAdapter{
|
||||||
transportType: transportType,
|
transportType: transportType,
|
||||||
|
|||||||
@@ -19,39 +19,37 @@ func NewLocalDialer(ctx context.Context, options option.LocalDNSServerOptions) (
|
|||||||
if options.LegacyDefaultDialer {
|
if options.LegacyDefaultDialer {
|
||||||
return dialer.NewDefaultOutbound(ctx), nil
|
return dialer.NewDefaultOutbound(ctx), nil
|
||||||
} else {
|
} else {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
return dialer.New(ctx, options.DialerOptions, false)
|
||||||
Context: ctx,
|
|
||||||
Options: options.DialerOptions,
|
|
||||||
DirectResolver: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
func NewRemoteDialer(ctx context.Context, options option.RemoteDNSServerOptions) (N.Dialer, error) {
|
||||||
|
var (
|
||||||
|
transportDialer N.Dialer
|
||||||
|
err error
|
||||||
|
)
|
||||||
if options.LegacyDefaultDialer {
|
if options.LegacyDefaultDialer {
|
||||||
transportDialer := dialer.NewDefaultOutbound(ctx)
|
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)
|
|
||||||
}
|
|
||||||
return transportDialer, nil
|
|
||||||
} else {
|
} else {
|
||||||
return dialer.NewWithOptions(dialer.Options{
|
transportDialer, err = dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
|
||||||
Context: ctx,
|
|
||||||
Options: options.DialerOptions,
|
|
||||||
RemoteIsDomain: options.ServerIsDomain(),
|
|
||||||
DirectResolver: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
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 = 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
|
||||||
}
|
}
|
||||||
|
|
||||||
type legacyTransportDialer struct {
|
type TransportDialer struct {
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
transport adapter.DNSTransport
|
transport adapter.DNSTransport
|
||||||
@@ -59,8 +57,8 @@ type legacyTransportDialer struct {
|
|||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *legacyTransportDialer {
|
func NewTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport adapter.DNSTransport, strategy C.DomainStrategy, fallbackDelay time.Duration) *TransportDialer {
|
||||||
return &legacyTransportDialer{
|
return &TransportDialer{
|
||||||
dialer,
|
dialer,
|
||||||
dnsRouter,
|
dnsRouter,
|
||||||
transport,
|
transport,
|
||||||
@@ -69,7 +67,7 @@ func newTransportDialer(dialer N.Dialer, dnsRouter adapter.DNSRouter, transport
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *legacyTransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *TransportDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if destination.IsIP() {
|
if destination.IsIP() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
@@ -83,7 +81,7 @@ func (d *legacyTransportDialer) DialContext(ctx context.Context, network string,
|
|||||||
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.strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *TransportDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if destination.IsIP() {
|
if destination.IsIP() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
@@ -98,6 +96,6 @@ func (d *legacyTransportDialer) ListenPacket(ctx context.Context, destination M.
|
|||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *legacyTransportDialer) Upstream() any {
|
func (d *TransportDialer) Upstream() any {
|
||||||
return d.dialer
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,224 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.0-alpha.8
|
#### 1.11.0-beta.24
|
||||||
|
|
||||||
* Fixes and improvements
|
* 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
|
### 1.10.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
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,11 +1,3 @@
|
|||||||
---
|
|
||||||
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
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
|
||||||
|
|
||||||
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
|
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ Default domain strategy for resolving the domain names.
|
|||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
|
||||||
|
Take no effect if `server.strategy` is set.
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
Disable dns cache.
|
Disable dns cache.
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
|
|
||||||
|
如果设置了 `server.strategy`,则不生效。
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
禁用 DNS 缓存。
|
禁用 DNS 缓存。
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [outbound](#outbound)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@@ -399,10 +395,6 @@ Invert match result.
|
|||||||
|
|
||||||
#### outbound
|
#### 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.
|
Match outbound.
|
||||||
|
|
||||||
`any` can be used as a value to match any outbound.
|
`any` can be used as a value to match any outbound.
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [outbound](#outbound)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@@ -399,10 +395,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
#### outbound
|
#### outbound
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
|
||||||
|
|
||||||
`outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver)。
|
|
||||||
|
|
||||||
匹配出站。
|
匹配出站。
|
||||||
|
|
||||||
`any` 可作为值用于匹配任意出站。
|
`any` 可作为值用于匹配任意出站。
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
### route
|
### route
|
||||||
@@ -14,7 +10,6 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"action": "route", // default
|
"action": "route", // default
|
||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"rewrite_ttl": 0,
|
"rewrite_ttl": 0,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
@@ -29,12 +24,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
Tag of target server.
|
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
|
||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
### route
|
### route
|
||||||
@@ -14,8 +10,8 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"action": "route", // 默认
|
"action": "route", // 默认
|
||||||
"server": "",
|
"server": "",
|
||||||
|
|
||||||
"strategy": "",
|
// 兼容性
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"rewrite_ttl": 0,
|
"rewrite_ttl": 0,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
@@ -30,12 +26,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
目标 DNS 服务器的标签。
|
目标 DNS 服务器的标签。
|
||||||
|
|
||||||
#### strategy
|
|
||||||
|
|
||||||
为此查询设置域名策略。
|
|
||||||
|
|
||||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
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"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
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 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
---
|
|
||||||
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,33 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
---
|
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
---
|
|
||||||
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.
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
---
|
|
||||||
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,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.10.0 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
:material-plus: [access_control_allow_origin](#access_control_allow_origin)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
{
|
{
|
||||||
"log": {},
|
"log": {},
|
||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
|
||||||
"endpoints": [],
|
"endpoints": [],
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
@@ -23,7 +22,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
|----------------|------------------------|
|
|----------------|------------------------|
|
||||||
| `log` | [日志](./log/) |
|
| `log` | [日志](./log/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
| `ntp` | [NTP](./ntp/) |
|
|
||||||
| `endpoints` | [端点](./endpoint/) |
|
| `endpoints` | [端点](./endpoint/) |
|
||||||
| `inbounds` | [入站](./inbound/) |
|
| `inbounds` | [入站](./inbound/) |
|
||||||
| `outbounds` | [出站](./outbound/) |
|
| `outbounds` | [出站](./outbound/) |
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json F
|
||||||
{
|
{
|
||||||
"type": "block",
|
"type": "block",
|
||||||
"tag": "block"
|
"tag": "block"
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
---
|
|
||||||
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
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -16,10 +7,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"server_ports": [
|
|
||||||
"2080:3000"
|
|
||||||
],
|
|
||||||
"hop_interval": "",
|
|
||||||
"up": "100 Mbps",
|
"up": "100 Mbps",
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down": "100 Mbps",
|
"down": "100 Mbps",
|
||||||
@@ -51,22 +38,6 @@ The server address.
|
|||||||
|
|
||||||
The server port.
|
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
|
#### up, down
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|||||||
@@ -1,12 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [server_ports](#server_ports)
|
|
||||||
:material-plus: [hop_interval](#hop_interval)
|
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -16,10 +7,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"server_ports": [
|
|
||||||
"2080:3000"
|
|
||||||
],
|
|
||||||
"hop_interval": "",
|
|
||||||
"up": "100 Mbps",
|
"up": "100 Mbps",
|
||||||
"up_mbps": 100,
|
"up_mbps": 100,
|
||||||
"down": "100 Mbps",
|
"down": "100 Mbps",
|
||||||
@@ -51,22 +38,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
服务器端口。
|
服务器端口。
|
||||||
|
|
||||||
#### server_ports
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
服务器端口范围列表。
|
|
||||||
|
|
||||||
与 `server_port` 冲突。
|
|
||||||
|
|
||||||
#### hop_interval
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
端口跳跃间隔。
|
|
||||||
|
|
||||||
默认使用 `30s`。
|
|
||||||
|
|
||||||
#### up, down
|
#### up, down
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
!!! 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 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Removed in sing-box 1.12.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
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).
|
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 中被移除"
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
GeoIP 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "Removed in sing-box 1.12.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
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).
|
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
icon: material/note-remove
|
icon: material/delete-clock
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 中被移除"
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
Geosite 已在 sing-box 1.8.0 废弃且在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
# Route
|
# 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"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [default_network_strategy](#default_network_strategy)
|
:material-plus: [default_network_strategy](#default_network_strategy)
|
||||||
@@ -28,6 +22,8 @@ icon: material/alert-decagram
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"route": {
|
"route": {
|
||||||
|
"geoip": {},
|
||||||
|
"geosite": {},
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"rule_set": [],
|
"rule_set": [],
|
||||||
"final": "",
|
"final": "",
|
||||||
@@ -35,16 +31,10 @@ icon: material/alert-decagram
|
|||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "",
|
||||||
"default_mark": 0,
|
"default_mark": 0,
|
||||||
"default_domain_resolver": "", // or {}
|
|
||||||
"default_network_strategy": "",
|
"default_network_strategy": "",
|
||||||
"default_network_type": [],
|
"default_network_type": [],
|
||||||
"default_fallback_network_type": [],
|
"default_fallback_network_type": [],
|
||||||
"default_fallback_delay": "",
|
"default_fallback_delay": ""
|
||||||
|
|
||||||
// Removed
|
|
||||||
|
|
||||||
"geoip": {},
|
|
||||||
"geosite": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -107,14 +97,6 @@ Set routing mark by default.
|
|||||||
|
|
||||||
Takes no effect if `outbound.routing_mark` is set.
|
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
|
#### default_network_strategy
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|||||||
@@ -1,15 +1,9 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
# 路由
|
# 路由
|
||||||
|
|
||||||
!!! 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 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [network_strategy](#network_strategy)
|
:material-plus: [network_strategy](#network_strategy)
|
||||||
@@ -106,14 +100,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||||
|
|
||||||
#### default_domain_resolver
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
详情参阅 [拨号字段](/configuration/shared/dial/#domain_resolver)。
|
|
||||||
|
|
||||||
可以被 `outbound.domain_resolver` 覆盖。
|
|
||||||
|
|
||||||
#### network_strategy
|
#### network_strategy
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
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
|
## Final actions
|
||||||
|
|
||||||
### route
|
### route
|
||||||
@@ -36,45 +31,6 @@ Tag of target outbound.
|
|||||||
|
|
||||||
See `route-options` fields below.
|
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
|
### route-options
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -86,9 +42,7 @@ Not available when `method` is set to drop.
|
|||||||
"fallback_delay": "",
|
"fallback_delay": "",
|
||||||
"udp_disable_domain_unmapping": false,
|
"udp_disable_domain_unmapping": false,
|
||||||
"udp_connect": false,
|
"udp_connect": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": ""
|
||||||
"tls_fragment": false,
|
|
||||||
"tls_fragment_fallback_delay": ""
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -155,27 +109,44 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
|
|||||||
| 443 | `quic` |
|
| 443 | `quic` |
|
||||||
| 3478 | `stun` |
|
| 3478 | `stun` |
|
||||||
|
|
||||||
#### tls_fragment
|
### reject
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
```json
|
||||||
|
{
|
||||||
|
"action": "reject",
|
||||||
|
"method": "default", // default
|
||||||
|
"no_drop": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Fragment TLS handshakes to bypass firewalls.
|
`reject` reject connections
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
|
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
|
||||||
|
|
||||||
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.
|
For non-tun connections and already established connections, will just be closed.
|
||||||
|
|
||||||
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`.
|
#### method
|
||||||
|
|
||||||
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.
|
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
|
||||||
|
- `drop`: Drop packets.
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### no_drop
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
||||||
|
|
||||||
The fallback value used when TLS segmentation cannot automatically determine the wait time.
|
Not available when `method` is set to drop.
|
||||||
|
|
||||||
`500ms` is used by default.
|
### hijack-dns
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "hijack-dns"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`hijack-dns` hijack DNS requests to the sing-box DNS module.
|
||||||
|
|
||||||
|
## Non-final actions
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user