mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
48 Commits
v1.12.0-al
...
dev-ts
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbcd4cf312 | ||
|
|
2cb5ff521a | ||
|
|
364a055f77 | ||
|
|
8bc14592d7 | ||
|
|
78084f61c8 | ||
|
|
f740d2a2de | ||
|
|
33b7c11470 | ||
|
|
ad3357c863 | ||
|
|
2cf1bfbdcc | ||
|
|
5d8764f34d | ||
|
|
d7361263ba | ||
|
|
f57171cf91 | ||
|
|
5b8e750944 | ||
|
|
fcdfc4e4b6 | ||
|
|
83563645fb | ||
|
|
6c4081f8f4 | ||
|
|
84101cc69b | ||
|
|
cecc7ea56c | ||
|
|
2a995bc716 | ||
|
|
342c633af4 | ||
|
|
3b3325e15e | ||
|
|
bb6bf22824 | ||
|
|
e264c49d7a | ||
|
|
d572343b20 | ||
|
|
a9e51f3204 | ||
|
|
8de6d7e1df | ||
|
|
0ec4929af6 | ||
|
|
24443f7e2c | ||
|
|
d472229f60 | ||
|
|
e5cdf22ffc | ||
|
|
6f7b1ed899 | ||
|
|
0bda2eb98e | ||
|
|
e5f2263e8a | ||
|
|
6cf08f14cc | ||
|
|
3310d0716e | ||
|
|
0f7ffeed5c | ||
|
|
3378102c37 | ||
|
|
bd4858f627 | ||
|
|
be5a45e942 | ||
|
|
2a6e2feebf | ||
|
|
bda049f6a1 | ||
|
|
b50f65f4ab | ||
|
|
c0b9dd2584 | ||
|
|
b3efa1124e | ||
|
|
007b19d768 | ||
|
|
d286b6e631 | ||
|
|
5ebc86c796 | ||
|
|
59342bd152 |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -144,7 +144,7 @@ jobs:
|
|||||||
~/go/go1.20.14
|
~/go/go1.20.14
|
||||||
key: go120
|
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: |-
|
run: |-
|
||||||
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||||
tar -xzf go1.20.14.linux-amd64.tar.gz
|
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||||
@@ -159,7 +159,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: Extract signing key
|
- name: Extract signing key
|
||||||
run: |-
|
run: |-
|
||||||
@@ -224,7 +224,7 @@ jobs:
|
|||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28-beta3
|
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
|
||||||
@@ -299,7 +299,7 @@ jobs:
|
|||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28-beta3
|
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
|
||||||
@@ -548,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
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ run:
|
|||||||
- with_reality_server
|
- with_reality_server
|
||||||
- with_acme
|
- with_acme
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- badlinkname
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-dirs:
|
exclude-dirs:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ builds:
|
|||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
- -s
|
- -s
|
||||||
- -buildid=
|
- -buildid=
|
||||||
|
- -checklinkname=0
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ builds:
|
|||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
- -s
|
- -s
|
||||||
- -buildid=
|
- -buildid=
|
||||||
|
- -checklinkname=0
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
@@ -52,7 +53,7 @@ builds:
|
|||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
||||||
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ RUN set -ex \
|
|||||||
&& go build -v -trimpath -tags \
|
&& go build -v -trimpath -tags \
|
||||||
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_ech,with_utls,with_reality_server,with_acme,with_clash_api" \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
FROM --platform=$TARGETPLATFORM alpine AS dist
|
FROM --platform=$TARGETPLATFORM alpine AS dist
|
||||||
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
LABEL maintainer="nekohasekai <contact-git@sekai.icu>"
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -2,14 +2,15 @@ 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 ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
TAGS_GO123 = with_tailscale,badlinkname
|
||||||
|
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)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid= -checklinkname=0"
|
||||||
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
MAIN_PARAMS = $(PARAMS) -tags $(TAGS)
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
@@ -61,9 +62,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)
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ type InboundContext struct {
|
|||||||
UDPDisableDomainUnmapping bool
|
UDPDisableDomainUnmapping bool
|
||||||
UDPConnect bool
|
UDPConnect bool
|
||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
|
||||||
TLSFragmentFallbackDelay time.Duration
|
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -10,7 +9,6 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
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"
|
||||||
@@ -68,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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,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
|
||||||
|
|||||||
22
box.go
22
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)
|
||||||
@@ -460,7 +446,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.services {
|
for _, lifecycleService := range s.services {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
|
|||||||
Submodule clients/android updated: 6df2f60c3a...b17fb6d857
Submodule clients/apple updated: 17fed6b9fc...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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
currentTag = "unknown"
|
currentTag = "unknown"
|
||||||
}
|
}
|
||||||
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= -checklinkname=0")
|
||||||
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")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_ech", "with_utls", "with_clash_api", "with_tailscale")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
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.12916984"
|
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
|
||||||
|
|||||||
@@ -1,68 +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(`")
|
|
||||||
generated.WriteString(record[certIndex])
|
|
||||||
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,184 +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, "":
|
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
|
||||||
systemCertificates := platformInterface.SystemCertificates()
|
|
||||||
if len(systemCertificates) > 0 {
|
|
||||||
systemPool = x509.NewCertPool()
|
|
||||||
for _, cert := range systemCertificates {
|
|
||||||
if !systemPool.AppendCertsFromPEM([]byte(cert)) {
|
|
||||||
return nil, E.New("invalid system certificate PEM: ", cert)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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,105 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
net.Conn
|
|
||||||
tcpConn *net.TCPConn
|
|
||||||
ctx context.Context
|
|
||||||
firstPacketWritten bool
|
|
||||||
fallbackDelay time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
|
||||||
return &Conn{
|
|
||||||
Conn: conn,
|
|
||||||
tcpConn: tcpConn,
|
|
||||||
ctx: ctx,
|
|
||||||
fallbackDelay: fallbackDelay,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
|
||||||
if !c.firstPacketWritten {
|
|
||||||
defer func() {
|
|
||||||
c.firstPacketWritten = true
|
|
||||||
}()
|
|
||||||
serverName := indexTLSServerName(b)
|
|
||||||
if serverName != nil {
|
|
||||||
if c.tcpConn != nil {
|
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
splits := strings.Split(serverName.ServerName, ".")
|
|
||||||
currentIndex := serverName.Index
|
|
||||||
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
|
|
||||||
splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")]
|
|
||||||
}
|
|
||||||
if len(splits) > 1 && splits[0] == "..." {
|
|
||||||
currentIndex += len(splits[0]) + 1
|
|
||||||
splits = splits[1:]
|
|
||||||
}
|
|
||||||
var splitIndexes []int
|
|
||||||
for i, split := range splits {
|
|
||||||
splitAt := rand.Intn(len(split))
|
|
||||||
splitIndexes = append(splitIndexes, currentIndex+splitAt)
|
|
||||||
currentIndex += len(split)
|
|
||||||
if i != len(splits)-1 {
|
|
||||||
currentIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
|
||||||
var payload []byte
|
|
||||||
if i == 0 {
|
|
||||||
payload = b[:splitIndexes[i]]
|
|
||||||
} else if i == len(splitIndexes) {
|
|
||||||
payload = b[splitIndexes[i-1]:]
|
|
||||||
} else {
|
|
||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
|
||||||
}
|
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.tcpConn != nil {
|
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.Conn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) ReaderReplaceable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) WriterReplaceable() bool {
|
|
||||||
return c.firstPacketWritten
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
recordLayerHeaderLen int = 5
|
|
||||||
handshakeHeaderLen int = 6
|
|
||||||
randomDataLen int = 32
|
|
||||||
sessionIDHeaderLen int = 1
|
|
||||||
cipherSuiteHeaderLen int = 2
|
|
||||||
compressMethodHeaderLen int = 1
|
|
||||||
extensionsHeaderLen int = 2
|
|
||||||
extensionHeaderLen int = 4
|
|
||||||
sniExtensionHeaderLen int = 5
|
|
||||||
contentType uint8 = 22
|
|
||||||
handshakeType uint8 = 1
|
|
||||||
sniExtensionType uint16 = 0
|
|
||||||
sniNameDNSHostnameType uint8 = 0
|
|
||||||
tlsVersionBitmask uint16 = 0xFFFC
|
|
||||||
tls13 uint16 = 0x0304
|
|
||||||
)
|
|
||||||
|
|
||||||
type myServerName struct {
|
|
||||||
Index int
|
|
||||||
Length int
|
|
||||||
ServerName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexTLSServerName(payload []byte) *myServerName {
|
|
||||||
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
segmentLen := binary.BigEndian.Uint16(payload[3:5])
|
|
||||||
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
|
||||||
if serverName == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
serverName.Length += recordLayerHeaderLen
|
|
||||||
return serverName
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
|
||||||
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if hs[0] != handshakeType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
|
||||||
if len(hs[4:]) != int(handshakeLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
|
||||||
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sessionIDLen := hs[38]
|
|
||||||
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
|
||||||
if len(cs) < cipherSuiteHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
|
||||||
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
|
||||||
if serverName == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
serverName.Index += currentIndex
|
|
||||||
return serverName
|
|
||||||
}
|
|
||||||
|
|
||||||
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
|
||||||
if len(exs) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(exs) < extensionsHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exsLen := uint16(exs[0])<<8 | uint16(exs[1])
|
|
||||||
exs = exs[extensionsHeaderLen:]
|
|
||||||
if len(exs) < int(exsLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for currentIndex := extensionsHeaderLen; len(exs) > 0; {
|
|
||||||
if len(exs) < extensionHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exType := uint16(exs[0])<<8 | uint16(exs[1])
|
|
||||||
exLen := uint16(exs[2])<<8 | uint16(exs[3])
|
|
||||||
if len(exs) < extensionHeaderLen+int(exLen) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)]
|
|
||||||
|
|
||||||
switch exType {
|
|
||||||
case sniExtensionType:
|
|
||||||
if len(sex) < sniExtensionHeaderLen {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sniType := sex[2]
|
|
||||||
if sniType != sniNameDNSHostnameType {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
|
||||||
return &myServerName{
|
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
|
||||||
Length: int(sniLen),
|
|
||||||
ServerName: string(sex),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exs = exs[4+exLen:]
|
|
||||||
currentIndex += 4 + int(exLen)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
const tcpMaxNotifyAck = 10
|
|
||||||
|
|
||||||
type tcpNotifyAckID uint32
|
|
||||||
|
|
||||||
type tcpNotifyAckComplete struct {
|
|
||||||
NotifyPending uint32
|
|
||||||
NotifyCompleteCount uint32
|
|
||||||
NotifyCompleteID [tcpMaxNotifyAck]tcpNotifyAckID
|
|
||||||
}
|
|
||||||
|
|
||||||
var sizeOfTCPNotifyAckComplete = int(unsafe.Sizeof(tcpNotifyAckComplete{}))
|
|
||||||
|
|
||||||
func getsockoptTCPNotifyAckComplete(fd, level, opt int) (*tcpNotifyAckComplete, error) {
|
|
||||||
var value tcpNotifyAckComplete
|
|
||||||
vallen := uint32(sizeOfTCPNotifyAckComplete)
|
|
||||||
err := getsockopt(fd, level, opt, unsafe.Pointer(&value), &vallen)
|
|
||||||
return &value, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname getsockopt golang.org/x/sys/unix.getsockopt
|
|
||||||
func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32) error
|
|
||||||
|
|
||||||
func waitAck(ctx context.Context, conn *net.TCPConn, _ time.Duration) error {
|
|
||||||
const TCP_NOTIFY_ACKNOWLEDGEMENT = 0x212
|
|
||||||
return control.Conn(conn, func(fd uintptr) error {
|
|
||||||
err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT, 1)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, unix.EINVAL) {
|
|
||||||
return waitAckFallback(ctx, conn, 0)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
var ackComplete *tcpNotifyAckComplete
|
|
||||||
ackComplete, err = getsockoptTCPNotifyAckComplete(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ackComplete.NotifyPending == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
|
||||||
_, err := conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return control.Conn(conn, func(fd uintptr) error {
|
|
||||||
start := time.Now()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
unacked, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_NWRITE)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if unacked == 0 {
|
|
||||||
if time.Since(start) <= 20*time.Millisecond {
|
|
||||||
// under transparent proxy
|
|
||||||
time.Sleep(fallbackDelay)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
|
||||||
_, err := conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return control.Conn(conn, func(fd uintptr) error {
|
|
||||||
start := time.Now()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
tcpInfo, err := unix.GetsockoptTCPInfo(int(fd), unix.IPPROTO_TCP, unix.TCP_INFO)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if tcpInfo.Unacked == 0 {
|
|
||||||
if time.Since(start) <= 20*time.Millisecond {
|
|
||||||
// under transparent proxy
|
|
||||||
time.Sleep(fallbackDelay)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build !(linux || darwin || windows)
|
|
||||||
|
|
||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
|
||||||
time.Sleep(fallbackDelay)
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
@@ -22,11 +22,11 @@ const (
|
|||||||
DNSTypeHTTPS = "https"
|
DNSTypeHTTPS = "https"
|
||||||
DNSTypeQUIC = "quic"
|
DNSTypeQUIC = "quic"
|
||||||
DNSTypeHTTP3 = "h3"
|
DNSTypeHTTP3 = "h3"
|
||||||
DNSTypeHosts = "hosts"
|
|
||||||
DNSTypeLocal = "local"
|
DNSTypeLocal = "local"
|
||||||
DNSTypePreDefined = "predefined"
|
DNSTypePreDefined = "predefined"
|
||||||
DNSTypeFakeIP = "fakeip"
|
DNSTypeFakeIP = "fakeip"
|
||||||
DNSTypeDHCP = "dhcp"
|
DNSTypeDHCP = "dhcp"
|
||||||
|
DNSTypeTailscale = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const (
|
|||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
|
TypeTailscale = "tailscale"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ const (
|
|||||||
StopTimeout = 5 * time.Second
|
StopTimeout = 5 * time.Second
|
||||||
FatalStopTimeout = 10 * time.Second
|
FatalStopTimeout = 10 * time.Second
|
||||||
FakeIPMetadataSaveInterval = 10 * time.Second
|
FakeIPMetadataSaveInterval = 10 * time.Second
|
||||||
TLSFragmentFallbackDelay = 500 * time.Millisecond
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var PortProtocols = map[uint16]string{
|
var PortProtocols = map[uint16]string{
|
||||||
|
|||||||
@@ -154,9 +154,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
|||||||
if isFakeIP && !allowFakeIP {
|
if isFakeIP && !allowFakeIP {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if action.Strategy != C.DomainStrategyAsIS {
|
|
||||||
options.Strategy = action.Strategy
|
|
||||||
}
|
|
||||||
if isFakeIP || action.DisableCache {
|
if isFakeIP || action.DisableCache {
|
||||||
options.DisableCache = true
|
options.DisableCache = true
|
||||||
}
|
}
|
||||||
@@ -177,9 +174,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
|||||||
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
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 {
|
|
||||||
options.Strategy = action.Strategy
|
|
||||||
}
|
|
||||||
if action.DisableCache {
|
if action.DisableCache {
|
||||||
options.DisableCache = true
|
options.DisableCache = true
|
||||||
}
|
}
|
||||||
@@ -252,8 +246,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:
|
||||||
@@ -272,10 +265,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) {
|
||||||
@@ -301,7 +294,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:
|
||||||
|
|||||||
@@ -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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
package hosts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RegisterTransport(registry *dns.TransportRegistry) {
|
|
||||||
dns.RegisterTransport[option.HostsDNSServerOptions](registry, C.DNSTypeHosts, NewTransport)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.DNSTransport = (*Transport)(nil)
|
|
||||||
|
|
||||||
type Transport struct {
|
|
||||||
dns.TransportAdapter
|
|
||||||
files []*File
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, options option.HostsDNSServerOptions) (adapter.DNSTransport, error) {
|
|
||||||
var files []*File
|
|
||||||
if len(options.Path) == 0 {
|
|
||||||
files = append(files, NewFile(DefaultPath))
|
|
||||||
} else {
|
|
||||||
for _, path := range options.Path {
|
|
||||||
files = append(files, NewFile(path))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &Transport{
|
|
||||||
TransportAdapter: dns.NewTransportAdapter(C.DNSTypeHosts, tag, nil),
|
|
||||||
files: files,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
question := message.Question[0]
|
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
|
||||||
for _, file := range t.files {
|
|
||||||
addresses := file.Lookup(domain)
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &mDNS.Msg{
|
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeNameError,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{question},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package hosts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
const cacheMaxAge = 5 * time.Second
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
path string
|
|
||||||
access sync.Mutex
|
|
||||||
byName map[string][]netip.Addr
|
|
||||||
expire time.Time
|
|
||||||
modTime time.Time
|
|
||||||
size int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFile(path string) *File {
|
|
||||||
return &File{
|
|
||||||
path: path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) Lookup(name string) []netip.Addr {
|
|
||||||
f.access.Lock()
|
|
||||||
defer f.access.Unlock()
|
|
||||||
f.update()
|
|
||||||
return f.byName[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *File) update() {
|
|
||||||
now := time.Now()
|
|
||||||
if now.Before(f.expire) && len(f.byName) > 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
stat, err := os.Stat(f.path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if f.modTime.Equal(stat.ModTime()) && f.size == stat.Size() {
|
|
||||||
f.expire = now.Add(cacheMaxAge)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
byName := make(map[string][]netip.Addr)
|
|
||||||
file, err := os.Open(f.path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
var (
|
|
||||||
prefix []byte
|
|
||||||
line []byte
|
|
||||||
isPrefix bool
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
line, isPrefix, err = reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isPrefix {
|
|
||||||
prefix = append(prefix, line...)
|
|
||||||
continue
|
|
||||||
} else if len(prefix) > 0 {
|
|
||||||
line = append(prefix, line...)
|
|
||||||
prefix = nil
|
|
||||||
}
|
|
||||||
commentIndex := strings.IndexRune(string(line), '#')
|
|
||||||
if commentIndex != -1 {
|
|
||||||
line = line[:commentIndex]
|
|
||||||
}
|
|
||||||
fields := strings.Fields(string(line))
|
|
||||||
if len(fields) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var addr netip.Addr
|
|
||||||
addr, err = netip.ParseAddr(fields[0])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for index := 1; index < len(fields); index++ {
|
|
||||||
canonicalName := dns.CanonicalName(fields[index])
|
|
||||||
byName[canonicalName] = append(byName[canonicalName], addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.expire = now.Add(cacheMaxAge)
|
|
||||||
f.modTime = stat.ModTime()
|
|
||||||
f.size = stat.Size()
|
|
||||||
f.byName = byName
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package hosts_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/netip"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
|
|
||||||
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package hosts
|
|
||||||
|
|
||||||
var DefaultPath = "/etc/hosts"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package hosts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var DefaultPath string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
systemDirectory, err := windows.GetSystemDirectory()
|
|
||||||
if err != nil {
|
|
||||||
systemDirectory = "C:\\Windows\\System32"
|
|
||||||
}
|
|
||||||
DefaultPath = filepath.Join(systemDirectory, "Drivers/etc/hosts")
|
|
||||||
}
|
|
||||||
2
dns/transport/hosts/testdata/hosts
vendored
2
dns/transport/hosts/testdata/hosts
vendored
@@ -1,2 +0,0 @@
|
|||||||
127.0.0.1 localhost
|
|
||||||
::1 localhost
|
|
||||||
@@ -62,20 +62,9 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if !common.Contains(tlsConfig.NextProtos(), "http/1.1") {
|
if !common.Contains(tlsConfig.NextProtos(), "http/1.1") {
|
||||||
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), "http/1.1"))
|
tlsConfig.SetNextProtos(append(tlsConfig.NextProtos(), "http/1.1"))
|
||||||
}
|
}
|
||||||
headers := options.Headers.Build()
|
|
||||||
host := headers.Get("Host")
|
|
||||||
if host != "" {
|
|
||||||
headers.Del("Host")
|
|
||||||
} else {
|
|
||||||
if tlsConfig.ServerName() != "" {
|
|
||||||
host = tlsConfig.ServerName()
|
|
||||||
} else {
|
|
||||||
host = options.Server
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destinationURL := url.URL{
|
destinationURL := url.URL{
|
||||||
Scheme: "https",
|
Scheme: "https",
|
||||||
Host: host,
|
Host: options.Host,
|
||||||
}
|
}
|
||||||
if destinationURL.Host == "" {
|
if destinationURL.Host == "" {
|
||||||
destinationURL.Host = options.Server
|
destinationURL.Host = options.Server
|
||||||
@@ -100,7 +89,7 @@ func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
logger,
|
logger,
|
||||||
transportDialer,
|
transportDialer,
|
||||||
&destinationURL,
|
&destinationURL,
|
||||||
headers,
|
options.Headers.Build(),
|
||||||
serverAddr,
|
serverAddr,
|
||||||
tlsConfig,
|
tlsConfig,
|
||||||
), nil
|
), nil
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
|
||||||
"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/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport/hosts"
|
|
||||||
"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/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -27,7 +26,6 @@ var _ adapter.DNSTransport = (*Transport)(nil)
|
|||||||
|
|
||||||
type Transport struct {
|
type Transport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
hosts *hosts.File
|
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +35,7 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Transport{
|
return &Transport{
|
||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeLocal, tag, options),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeTCP, tag, options),
|
||||||
hosts: hosts.NewFile(hosts.DefaultPath),
|
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -50,9 +47,9 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
domain := dns.FqdnToDomain(question.Name)
|
domain := dns.FqdnToDomain(question.Name)
|
||||||
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
if question.Qtype == mDNS.TypeA || question.Qtype == mDNS.TypeAAAA {
|
||||||
addresses := t.hosts.Lookup(domain)
|
addressStrings, _ := lookupStaticHost(domain)
|
||||||
if len(addresses) > 0 {
|
if len(addressStrings) > 0 {
|
||||||
return dns.FixedResponse(message.Id, question, addresses, C.DefaultDNSTTL), nil
|
return dns.FixedResponse(message.Id, question, common.Map(addressStrings, M.ParseAddr), C.DefaultDNSTTL), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
systemConfig := getSystemDNSConfig()
|
systemConfig := getSystemDNSConfig()
|
||||||
@@ -65,7 +62,7 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
|
|
||||||
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
func (t *Transport) exchangeSingleRequest(ctx context.Context, systemConfig *dnsConfig, message *mDNS.Msg, domain string) (*mDNS.Msg, error) {
|
||||||
var lastErr error
|
var lastErr error
|
||||||
for _, fqdn := range systemConfig.nameList(domain) {
|
for _, fqdn := range nameList(systemConfig, domain) {
|
||||||
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
response, err := t.tryOneName(ctx, systemConfig, fqdn, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
@@ -86,10 +83,6 @@ 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)
|
||||||
addresses, _ := dns.MessageToAddresses(response)
|
|
||||||
if len(addresses) == 0 {
|
|
||||||
err = E.New(fqdn, ": empty result")
|
|
||||||
}
|
|
||||||
select {
|
select {
|
||||||
case results <- queryResult{response, err}:
|
case results <- queryResult{response, err}:
|
||||||
case <-returned:
|
case <-returned:
|
||||||
@@ -97,25 +90,14 @@ func (t *Transport) exchangeParallel(ctx context.Context, systemConfig *dnsConfi
|
|||||||
}
|
}
|
||||||
queryCtx, queryCancel := context.WithCancel(ctx)
|
queryCtx, queryCancel := context.WithCancel(ctx)
|
||||||
defer queryCancel()
|
defer queryCancel()
|
||||||
var nameCount int
|
for _, fqdn := range nameList(systemConfig, domain) {
|
||||||
for _, fqdn := range systemConfig.nameList(domain) {
|
|
||||||
nameCount++
|
|
||||||
go startRacer(queryCtx, fqdn)
|
go startRacer(queryCtx, fqdn)
|
||||||
}
|
}
|
||||||
var errors []error
|
select {
|
||||||
for {
|
case <-ctx.Done():
|
||||||
select {
|
return nil, ctx.Err()
|
||||||
case <-ctx.Done():
|
case result := <-results:
|
||||||
return nil, ctx.Err()
|
return result.response, result.err
|
||||||
case result := <-results:
|
|
||||||
if result.err == nil {
|
|
||||||
return result.response, nil
|
|
||||||
}
|
|
||||||
errors = append(errors, result.err)
|
|
||||||
if len(errors) == nameCount {
|
|
||||||
return nil, E.Errors(errors...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +118,7 @@ func (t *Transport) tryOneName(ctx context.Context, config *dnsConfig, fqdn stri
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, E.Cause(lastErr, fqdn)
|
return nil, lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, question mDNS.Question, timeout time.Duration, useTCP, ad bool) (*mDNS.Msg, error) {
|
||||||
@@ -148,7 +130,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,
|
||||||
},
|
},
|
||||||
|
|||||||
19
dns/transport/local/local_badlinkname.go
Normal file
19
dns/transport/local/local_badlinkname.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build badlinkname
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:linkname getSystemDNSConfig net.getSystemDNSConfig
|
||||||
|
func getSystemDNSConfig() *dnsConfig
|
||||||
|
|
||||||
|
//go:linkname nameList net.(*dnsConfig).nameList
|
||||||
|
func nameList(c *dnsConfig, name string) []string
|
||||||
|
|
||||||
|
//go:linkname lookupStaticHost net.lookupStaticHost
|
||||||
|
func lookupStaticHost(host string) ([]string, string)
|
||||||
|
|
||||||
|
//go:linkname splitHostZone net.splitHostZone
|
||||||
|
func splitHostZone(s string) (host, zone string)
|
||||||
44
dns/transport/local/local_linkname.go
Normal file
44
dns/transport/local/local_linkname.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// net.maxDNSPacketSize
|
||||||
|
maxDNSPacketSize = 1232
|
||||||
|
)
|
||||||
|
|
||||||
|
type dnsConfig struct {
|
||||||
|
servers []string // server addresses (in host:port form) to use
|
||||||
|
search []string // rooted suffixes to append to local name
|
||||||
|
ndots int // number of dots in name to trigger absolute lookup
|
||||||
|
timeout time.Duration // wait before giving up on a query, including retries
|
||||||
|
attempts int // lost packets before giving up on server
|
||||||
|
rotate bool // round robin among servers
|
||||||
|
unknownOpt bool // anything unknown was encountered
|
||||||
|
lookup []string // OpenBSD top-level database "lookup" order
|
||||||
|
err error // any error that occurs during open of resolv.conf
|
||||||
|
mtime time.Time // time of resolv.conf modification
|
||||||
|
soffset uint32 // used by serverOffset
|
||||||
|
singleRequest bool // use sequential A and AAAA queries instead of parallel queries
|
||||||
|
useTCP bool // force usage of TCP for DNS resolutions
|
||||||
|
trustAD bool // add AD flag to queries
|
||||||
|
noReload bool // do not check for config file updates
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dnsConfig) serverOffset() uint32 {
|
||||||
|
if c.rotate {
|
||||||
|
return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname runtime_rand runtime.rand
|
||||||
|
func runtime_rand() uint64
|
||||||
|
|
||||||
|
func randInt() int {
|
||||||
|
return int(uint(runtime_rand()) >> 1) // clear sign bit
|
||||||
|
}
|
||||||
19
dns/transport/local/local_notbadlinkname.go
Normal file
19
dns/transport/local/local_notbadlinkname.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !badlinkname
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
func getSystemDNSConfig() *dnsConfig {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
|
|
||||||
|
func nameList(c *dnsConfig, name string) []string {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupStaticHost(host string) ([]string, string) {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitHostZone(s string) (host, zone string) {
|
||||||
|
panic("stub")
|
||||||
|
}
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// net.maxDNSPacketSize
|
|
||||||
maxDNSPacketSize = 1232
|
|
||||||
)
|
|
||||||
|
|
||||||
type resolverConfig struct {
|
|
||||||
initOnce sync.Once
|
|
||||||
ch chan struct{}
|
|
||||||
lastChecked time.Time
|
|
||||||
dnsConfig atomic.Pointer[dnsConfig]
|
|
||||||
}
|
|
||||||
|
|
||||||
var resolvConf resolverConfig
|
|
||||||
|
|
||||||
func getSystemDNSConfig() *dnsConfig {
|
|
||||||
resolvConf.tryUpdate("/etc/resolv.conf")
|
|
||||||
return resolvConf.dnsConfig.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *resolverConfig) init() {
|
|
||||||
conf.dnsConfig.Store(dnsReadConfig("/etc/resolv.conf"))
|
|
||||||
conf.lastChecked = time.Now()
|
|
||||||
conf.ch = make(chan struct{}, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *resolverConfig) tryUpdate(name string) {
|
|
||||||
conf.initOnce.Do(conf.init)
|
|
||||||
|
|
||||||
if conf.dnsConfig.Load().noReload {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !conf.tryAcquireSema() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conf.releaseSema()
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
if conf.lastChecked.After(now.Add(-5 * time.Second)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
conf.lastChecked = now
|
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
var mtime time.Time
|
|
||||||
if fi, err := os.Stat(name); err == nil {
|
|
||||||
mtime = fi.ModTime()
|
|
||||||
}
|
|
||||||
if mtime.Equal(conf.dnsConfig.Load().mtime) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dnsConf := dnsReadConfig(name)
|
|
||||||
conf.dnsConfig.Store(dnsConf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *resolverConfig) tryAcquireSema() bool {
|
|
||||||
select {
|
|
||||||
case conf.ch <- struct{}{}:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *resolverConfig) releaseSema() {
|
|
||||||
<-conf.ch
|
|
||||||
}
|
|
||||||
|
|
||||||
type dnsConfig struct {
|
|
||||||
servers []string
|
|
||||||
search []string
|
|
||||||
ndots int
|
|
||||||
timeout time.Duration
|
|
||||||
attempts int
|
|
||||||
rotate bool
|
|
||||||
unknownOpt bool
|
|
||||||
lookup []string
|
|
||||||
err error
|
|
||||||
mtime time.Time
|
|
||||||
soffset uint32
|
|
||||||
singleRequest bool
|
|
||||||
useTCP bool
|
|
||||||
trustAD bool
|
|
||||||
noReload bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *dnsConfig) serverOffset() uint32 {
|
|
||||||
if c.rotate {
|
|
||||||
return atomic.AddUint32(&c.soffset, 1) - 1 // return 0 to start
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conf *dnsConfig) nameList(name string) []string {
|
|
||||||
l := len(name)
|
|
||||||
rooted := l > 0 && name[l-1] == '.'
|
|
||||||
if l > 254 || l == 254 && !rooted {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if rooted {
|
|
||||||
if avoidDNS(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []string{name}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasNdots := strings.Count(name, ".") >= conf.ndots
|
|
||||||
name += "."
|
|
||||||
// l++
|
|
||||||
|
|
||||||
names := make([]string, 0, 1+len(conf.search))
|
|
||||||
if hasNdots && !avoidDNS(name) {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
for _, suffix := range conf.search {
|
|
||||||
fqdn := name + suffix
|
|
||||||
if !avoidDNS(fqdn) && len(fqdn) <= 254 {
|
|
||||||
names = append(names, fqdn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasNdots && !avoidDNS(name) {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
func avoidDNS(name string) bool {
|
|
||||||
if name == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if name[len(name)-1] == '.' {
|
|
||||||
name = name[:len(name)-1]
|
|
||||||
}
|
|
||||||
return strings.HasSuffix(name, ".onion")
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
_ "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func dnsReadConfig(name string) *dnsConfig {
|
|
||||||
conf := &dnsConfig{
|
|
||||||
ndots: 1,
|
|
||||||
timeout: 5 * time.Second,
|
|
||||||
attempts: 2,
|
|
||||||
}
|
|
||||||
file, err := os.Open(name)
|
|
||||||
if err != nil {
|
|
||||||
conf.servers = defaultNS
|
|
||||||
conf.search = dnsDefaultSearch()
|
|
||||||
conf.err = err
|
|
||||||
return conf
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
fi, err := file.Stat()
|
|
||||||
if err == nil {
|
|
||||||
conf.mtime = fi.ModTime()
|
|
||||||
} else {
|
|
||||||
conf.servers = defaultNS
|
|
||||||
conf.search = dnsDefaultSearch()
|
|
||||||
conf.err = err
|
|
||||||
return conf
|
|
||||||
}
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
var (
|
|
||||||
prefix []byte
|
|
||||||
line []byte
|
|
||||||
isPrefix bool
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
line, isPrefix, err = reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if isPrefix {
|
|
||||||
prefix = append(prefix, line...)
|
|
||||||
continue
|
|
||||||
} else if len(prefix) > 0 {
|
|
||||||
line = append(prefix, line...)
|
|
||||||
prefix = nil
|
|
||||||
}
|
|
||||||
if len(line) > 0 && (line[0] == ';' || line[0] == '#') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f := strings.Fields(string(line))
|
|
||||||
if len(f) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch f[0] {
|
|
||||||
case "nameserver":
|
|
||||||
if len(f) > 1 && len(conf.servers) < 3 {
|
|
||||||
if _, err := netip.ParseAddr(f[1]); err == nil {
|
|
||||||
conf.servers = append(conf.servers, net.JoinHostPort(f[1], "53"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "domain":
|
|
||||||
if len(f) > 1 {
|
|
||||||
conf.search = []string{ensureRooted(f[1])}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "search":
|
|
||||||
conf.search = make([]string, 0, len(f)-1)
|
|
||||||
for i := 1; i < len(f); i++ {
|
|
||||||
name := ensureRooted(f[i])
|
|
||||||
if name == "." {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
conf.search = append(conf.search, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
case "options":
|
|
||||||
for _, s := range f[1:] {
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(s, "ndots:"):
|
|
||||||
n, _, _ := dtoi(s[6:])
|
|
||||||
if n < 0 {
|
|
||||||
n = 0
|
|
||||||
} else if n > 15 {
|
|
||||||
n = 15
|
|
||||||
}
|
|
||||||
conf.ndots = n
|
|
||||||
case strings.HasPrefix(s, "timeout:"):
|
|
||||||
n, _, _ := dtoi(s[8:])
|
|
||||||
if n < 1 {
|
|
||||||
n = 1
|
|
||||||
}
|
|
||||||
conf.timeout = time.Duration(n) * time.Second
|
|
||||||
case strings.HasPrefix(s, "attempts:"):
|
|
||||||
n, _, _ := dtoi(s[9:])
|
|
||||||
if n < 1 {
|
|
||||||
n = 1
|
|
||||||
}
|
|
||||||
conf.attempts = n
|
|
||||||
case s == "rotate":
|
|
||||||
conf.rotate = true
|
|
||||||
case s == "single-request" || s == "single-request-reopen":
|
|
||||||
conf.singleRequest = true
|
|
||||||
case s == "use-vc" || s == "usevc" || s == "tcp":
|
|
||||||
conf.useTCP = true
|
|
||||||
case s == "trust-ad":
|
|
||||||
conf.trustAD = true
|
|
||||||
case s == "edns0":
|
|
||||||
case s == "no-reload":
|
|
||||||
conf.noReload = true
|
|
||||||
default:
|
|
||||||
conf.unknownOpt = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "lookup":
|
|
||||||
conf.lookup = f[1:]
|
|
||||||
|
|
||||||
default:
|
|
||||||
conf.unknownOpt = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(conf.servers) == 0 {
|
|
||||||
conf.servers = defaultNS
|
|
||||||
}
|
|
||||||
if len(conf.search) == 0 {
|
|
||||||
conf.search = dnsDefaultSearch()
|
|
||||||
}
|
|
||||||
return conf
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname defaultNS net.defaultNS
|
|
||||||
var defaultNS []string
|
|
||||||
|
|
||||||
func dnsDefaultSearch() []string {
|
|
||||||
hn, err := os.Hostname()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if i := strings.IndexRune(hn, '.'); i >= 0 && i < len(hn)-1 {
|
|
||||||
return []string{ensureRooted(hn[i+1:])}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureRooted(s string) string {
|
|
||||||
if len(s) > 0 && s[len(s)-1] == '.' {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
const big = 0xFFFFFF
|
|
||||||
|
|
||||||
func dtoi(s string) (n int, i int, ok bool) {
|
|
||||||
n = 0
|
|
||||||
for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
|
|
||||||
n = n*10 + int(s[i]-'0')
|
|
||||||
if n >= big {
|
|
||||||
return big, i, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
return n, i, true
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func dnsReadConfig(_ string) *dnsConfig {
|
|
||||||
conf := &dnsConfig{
|
|
||||||
ndots: 1,
|
|
||||||
timeout: 5 * time.Second,
|
|
||||||
attempts: 2,
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if len(conf.servers) == 0 {
|
|
||||||
conf.servers = defaultNS
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
aas, err := adapterAddresses()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, aa := range aas {
|
|
||||||
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
|
|
||||||
if aa.OperStatus != windows.IfOperStatusUp {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only take interfaces which have at least one gateway
|
|
||||||
if aa.FirstGatewayAddress == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
|
||||||
sa, err := dns.Address.Sockaddr.Sockaddr()
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var ip netip.Addr
|
|
||||||
switch sa := sa.(type) {
|
|
||||||
case *syscall.SockaddrInet4:
|
|
||||||
ip = netip.AddrFrom4([4]byte{sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3]})
|
|
||||||
case *syscall.SockaddrInet6:
|
|
||||||
var addr16 [16]byte
|
|
||||||
copy(addr16[:], sa.Addr[:])
|
|
||||||
if addr16[0] == 0xfe && addr16[1] == 0xc0 {
|
|
||||||
// fec0/10 IPv6 addresses are site local anycast DNS
|
|
||||||
// addresses Microsoft sets by default if no other
|
|
||||||
// IPv6 DNS address is set. Site local anycast is
|
|
||||||
// deprecated since 2004, see
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc3879
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ip = netip.AddrFrom16(addr16)
|
|
||||||
default:
|
|
||||||
// Unexpected type.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
conf.servers = append(conf.servers, net.JoinHostPort(ip.String(), "53"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return conf
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname defaultNS net.defaultNS
|
|
||||||
var defaultNS []string
|
|
||||||
|
|
||||||
func adapterAddresses() ([]*windows.IpAdapterAddresses, error) {
|
|
||||||
var b []byte
|
|
||||||
l := uint32(15000) // recommended initial size
|
|
||||||
for {
|
|
||||||
b = make([]byte, l)
|
|
||||||
const flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS
|
|
||||||
err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
|
|
||||||
if err == nil {
|
|
||||||
if l == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err.(syscall.Errno) != syscall.ERROR_BUFFER_OVERFLOW {
|
|
||||||
return nil, os.NewSyscallError("getadaptersaddresses", err)
|
|
||||||
}
|
|
||||||
if l <= uint32(len(b)) {
|
|
||||||
return nil, os.NewSyscallError("getadaptersaddresses", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var aas []*windows.IpAdapterAddresses
|
|
||||||
for aa := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); aa != nil; aa = aa.Next {
|
|
||||||
aas = append(aas, aa)
|
|
||||||
}
|
|
||||||
return aas, nil
|
|
||||||
}
|
|
||||||
@@ -60,20 +60,9 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
headers := options.Headers.Build()
|
|
||||||
host := headers.Get("Host")
|
|
||||||
if host != "" {
|
|
||||||
headers.Del("Host")
|
|
||||||
} else {
|
|
||||||
if tlsConfig.ServerName() != "" {
|
|
||||||
host = tlsConfig.ServerName()
|
|
||||||
} else {
|
|
||||||
host = options.Server
|
|
||||||
}
|
|
||||||
}
|
|
||||||
destinationURL := url.URL{
|
destinationURL := url.URL{
|
||||||
Scheme: "HTTP3",
|
Scheme: "HTTP3",
|
||||||
Host: host,
|
Host: options.Host,
|
||||||
}
|
}
|
||||||
if destinationURL.Host == "" {
|
if destinationURL.Host == "" {
|
||||||
destinationURL.Host = options.Server
|
destinationURL.Host = options.Server
|
||||||
@@ -98,7 +87,7 @@ func NewHTTP3(ctx context.Context, logger log.ContextLogger, tag string, options
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
destination: &destinationURL,
|
destination: &destinationURL,
|
||||||
headers: headers,
|
headers: options.Headers.Build(),
|
||||||
transport: &http3.Transport{
|
transport: &http3.Transport{
|
||||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
|
Dial: func(ctx context.Context, addr string, tlsCfg *tls.STDConfig, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||||
destinationAddr := M.ParseSocksaddr(addr)
|
destinationAddr := M.ParseSocksaddr(addr)
|
||||||
|
|||||||
@@ -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,186 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.0-alpha.3
|
#### 1.11.0-beta.23
|
||||||
|
|
||||||
* Fixes and improvements
|
* 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
|
||||||
@@ -15,7 +11,7 @@ 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,33 +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,69 +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,69 +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,56 +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,50 +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,56 +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,50 +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,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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
|
||||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
|
||||||
|
|
||||||
## 最终动作
|
## 最终动作
|
||||||
|
|
||||||
### route
|
### route
|
||||||
@@ -32,45 +27,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
参阅下方的 `route-options` 字段。
|
参阅下方的 `route-options` 字段。
|
||||||
|
|
||||||
### reject
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // 默认
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` 拒绝连接。
|
|
||||||
|
|
||||||
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
|
|
||||||
|
|
||||||
对于非 tun 连接和已建立的连接,将直接关闭。
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。
|
|
||||||
- `drop`: 丢弃数据包。
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
|
||||||
|
|
||||||
当 `method` 设为 `drop` 时不可用。
|
|
||||||
|
|
||||||
### hijack-dns
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "hijack-dns"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
|
|
||||||
|
|
||||||
## 非最终动作
|
|
||||||
|
|
||||||
### route-options
|
### route-options
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -151,27 +107,44 @@ UDP 连接超时时间。
|
|||||||
| 443 | `quic` |
|
| 443 | `quic` |
|
||||||
| 3478 | `stun` |
|
| 3478 | `stun` |
|
||||||
|
|
||||||
#### tls_fragment
|
### reject
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
```json
|
||||||
|
{
|
||||||
|
"action": "reject",
|
||||||
|
"method": "default", // 默认
|
||||||
|
"no_drop": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
通过分段 TLS 握手数据包来绕过防火墙检测。
|
`reject` 拒绝连接。
|
||||||
|
|
||||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
|
||||||
|
|
||||||
由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称。
|
对于非 tun 连接和已建立的连接,将直接关闭。
|
||||||
|
|
||||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
#### method
|
||||||
|
|
||||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。
|
||||||
|
- `drop`: 丢弃数据包。
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### no_drop
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
||||||
|
|
||||||
当 TLS 分片功能无法自动判定等待时间时使用的回退值。
|
当 `method` 设为 `drop` 时不可用。
|
||||||
|
|
||||||
默认使用 `500ms`。
|
### hijack-dns
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "hijack-dns"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
|
||||||
|
|
||||||
|
## 非最终动作
|
||||||
|
|
||||||
### 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