mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-04 23:57:27 +03:00
Compare commits
12 Commits
dev-mitm
...
dev-tls-fr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
420d8d144a | ||
|
|
7ecc62f2e0 | ||
|
|
3fa417f283 | ||
|
|
314b25bccf | ||
|
|
1c56213cbe | ||
|
|
1947a90878 | ||
|
|
63ef6f972f | ||
|
|
eae2631208 | ||
|
|
8cc7734a92 | ||
|
|
d74a05fdff | ||
|
|
4f453e81ac | ||
|
|
bd2e052815 |
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
|
||||||
|
|||||||
@@ -52,7 +52,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
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -61,12 +61,6 @@ proto_install:
|
|||||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
update_public_suffix:
|
|
||||||
go generate common/tlsfragment/public_suffix.go
|
|
||||||
|
|
||||||
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,21 +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
|
||||||
LoadScript(tag string) *SavedBinary
|
|
||||||
SaveScript(tag string, script *SavedBinary) error
|
|
||||||
SurgePersistentStoreRead(key string) string
|
|
||||||
SurgePersistentStoreWrite(key string, value string) 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 {
|
||||||
@@ -85,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)
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -59,8 +57,6 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
HTTPRequest *http.Request
|
|
||||||
ClientHello *tls.ClientHelloInfo
|
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
@@ -77,7 +73,6 @@ type InboundContext struct {
|
|||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
MITM *option.MITMRouteOptions
|
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
@@ -47,9 +45,6 @@ type LifecycleService interface {
|
|||||||
|
|
||||||
func Start(stage StartStage, services ...Lifecycle) error {
|
func Start(stage StartStage, services ...Lifecycle) error {
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
if service == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
err := service.Start(stage)
|
err := service.Start(stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/x509"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MITMEngine interface {
|
|
||||||
Lifecycle
|
|
||||||
ExportCertificate() *x509.Certificate
|
|
||||||
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ScriptManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Scripts() []Script
|
|
||||||
Script(name string) (Script, bool)
|
|
||||||
SurgeCache() *SurgeInMemoryCache
|
|
||||||
}
|
|
||||||
|
|
||||||
type SurgeInMemoryCache struct {
|
|
||||||
sync.RWMutex
|
|
||||||
Data map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Script interface {
|
|
||||||
Type() string
|
|
||||||
Tag() string
|
|
||||||
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
|
||||||
PostStart() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SurgeScript interface {
|
|
||||||
Script
|
|
||||||
ExecuteGeneric(ctx context.Context, scriptType string, timeout time.Duration, arguments []string) error
|
|
||||||
ExecuteHTTPRequest(ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool, arguments []string) (*HTTPRequestScriptResult, error)
|
|
||||||
ExecuteHTTPResponse(ctx context.Context, timeout time.Duration, request *http.Request, response *http.Response, body []byte, binaryBody bool, arguments []string) (*HTTPResponseScriptResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPRequestScriptResult struct {
|
|
||||||
URL string
|
|
||||||
Headers http.Header
|
|
||||||
Body []byte
|
|
||||||
Response *HTTPRequestScriptResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPRequestScriptResponse struct {
|
|
||||||
Status int
|
|
||||||
Headers http.Header
|
|
||||||
Body []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTPResponseScriptResult struct {
|
|
||||||
Status int
|
|
||||||
Headers http.Header
|
|
||||||
Body []byte
|
|
||||||
}
|
|
||||||
55
box.go
55
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"
|
||||||
@@ -23,11 +22,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/mitm"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
"github.com/sagernet/sing-box/script"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
@@ -50,8 +47,6 @@ type Box struct {
|
|||||||
dnsRouter *dns.Router
|
dnsRouter *dns.Router
|
||||||
connection *route.ConnectionManager
|
connection *route.ConnectionManager
|
||||||
router *route.Router
|
router *route.Router
|
||||||
script *script.Manager
|
|
||||||
mitm adapter.MITMEngine //*mitm.Engine
|
|
||||||
services []adapter.LifecycleService
|
services []adapter.LifecycleService
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
@@ -146,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)
|
||||||
@@ -177,7 +158,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
|
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
service.MustRegister[adapter.Router](ctx, router)
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
@@ -185,8 +166,8 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize router")
|
return nil, E.Cause(err, "initialize router")
|
||||||
}
|
}
|
||||||
var timeService *tls.TimeServiceWrapper
|
|
||||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||||
|
var timeService *tls.TimeServiceWrapper
|
||||||
if ntpOptions.Enabled {
|
if ntpOptions.Enabled {
|
||||||
timeService = new(tls.TimeServiceWrapper)
|
timeService = new(tls.TimeServiceWrapper)
|
||||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
@@ -206,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)
|
||||||
@@ -229,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 {
|
||||||
@@ -293,17 +274,13 @@ func New(options Options) (*Box, error) {
|
|||||||
"local",
|
"local",
|
||||||
option.LocalDNSServerOptions{},
|
option.LocalDNSServerOptions{},
|
||||||
)))
|
)))
|
||||||
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize script manager")
|
|
||||||
}
|
|
||||||
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
|
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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)
|
||||||
@@ -347,16 +324,6 @@ func New(options Options) (*Box, error) {
|
|||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
mitmOptions := common.PtrValueOrDefault(options.MITM)
|
|
||||||
var mitmEngine adapter.MITMEngine
|
|
||||||
if mitmOptions.Enabled {
|
|
||||||
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create MITM engine")
|
|
||||||
}
|
|
||||||
service.MustRegister[adapter.MITMEngine](ctx, engine)
|
|
||||||
mitmEngine = engine
|
|
||||||
}
|
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
@@ -366,8 +333,6 @@ func New(options Options) (*Box, error) {
|
|||||||
dnsRouter: dnsRouter,
|
dnsRouter: dnsRouter,
|
||||||
connection: connectionManager,
|
connection: connectionManager,
|
||||||
router: router,
|
router: router,
|
||||||
script: scriptManager,
|
|
||||||
mitm: mitmEngine,
|
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
@@ -426,11 +391,11 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm)
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -454,7 +419,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -462,7 +427,7 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -481,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.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, 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: 599d8cecac...b17fb6d857
Submodule clients/apple updated: e43e3a3f64...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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,5 @@ func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Rea
|
|||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolHTTP
|
metadata.Protocol = C.ProtocolHTTP
|
||||||
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
||||||
metadata.HTTPRequest = request
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
|
|||||||
if clientHello != nil {
|
if clientHello != nil {
|
||||||
metadata.Protocol = C.ProtocolTLS
|
metadata.Protocol = C.ProtocolTLS
|
||||||
metadata.Domain = clientHello.ServerName
|
metadata.Domain = clientHello.ServerName
|
||||||
metadata.ClientHello = clientHello
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,14 +8,11 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -26,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
|
||||||
}
|
}
|
||||||
@@ -38,36 +35,19 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var template *x509.Certificate
|
template := &x509.Certificate{
|
||||||
if serverAddress := M.ParseAddr(serverName); serverAddress.IsValid() {
|
SerialNumber: serialNumber,
|
||||||
template = &x509.Certificate{
|
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||||
SerialNumber: serialNumber,
|
NotAfter: expire,
|
||||||
IPAddresses: []net.IP{serverAddress.AsSlice()},
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
NotAfter: expire,
|
BasicConstraintsValid: true,
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
Subject: pkix.Name{
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
CommonName: serverName,
|
||||||
BasicConstraintsValid: true,
|
},
|
||||||
}
|
DNSNames: []string{serverName},
|
||||||
} else {
|
|
||||||
template = &x509.Certificate{
|
|
||||||
SerialNumber: serialNumber,
|
|
||||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
|
||||||
NotAfter: expire,
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
||||||
BasicConstraintsValid: true,
|
|
||||||
Subject: pkix.Name{
|
|
||||||
CommonName: serverName,
|
|
||||||
},
|
|
||||||
DNSNames: []string{serverName},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if parent == nil {
|
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||||
parent = template
|
|
||||||
parentKey = key
|
|
||||||
}
|
|
||||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
@@ -42,7 +40,6 @@ import (
|
|||||||
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
||||||
|
|
||||||
type RealityClientConfig struct {
|
type RealityClientConfig struct {
|
||||||
ctx context.Context
|
|
||||||
uClient *UTLSClientConfig
|
uClient *UTLSClientConfig
|
||||||
publicKey []byte
|
publicKey []byte
|
||||||
shortID [8]byte
|
shortID [8]byte
|
||||||
@@ -73,7 +70,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
@@ -183,24 +180,20 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !verifier.verified {
|
if !verifier.verified {
|
||||||
go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)
|
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &realityClientConnWrapper{uConn}, nil
|
return &realityClientConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
defer uConn.Close()
|
defer uConn.Close()
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http2.Transport{
|
Transport: &http2.Transport{
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
return uConn, nil
|
return uConn, nil
|
||||||
},
|
},
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
Time: ntp.TimeFuncFromContext(ctx),
|
|
||||||
RootCAs: adapter.RootPoolFromContext(ctx),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||||
@@ -220,7 +213,6 @@ func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello [
|
|||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
return &RealityClientConfig{
|
return &RealityClientConfig{
|
||||||
e.ctx,
|
|
||||||
e.uClient.Clone().(*UTLSClientConfig),
|
e.uClient.Clone().(*UTLSClientConfig),
|
||||||
e.publicKey,
|
e.publicKey,
|
||||||
e.shortID,
|
e.shortID,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -59,7 +58,6 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (c *STDServerConfig) startWatcher() error {
|
|||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
err := c.certificateUpdated(path)
|
err := c.certificateUpdated(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(E.Cause(err, "reload certificate"))
|
c.logger.Error(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -131,7 +130,6 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,26 +5,26 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
tcpConn *net.TCPConn
|
syscallConn syscall.Conn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
syscallConn, _ := N.UnwrapReader(conn).(syscall.Conn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
syscallConn: syscallConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -37,18 +37,39 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}()
|
}()
|
||||||
serverName := indexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.tcpConn != nil {
|
tcpConn, isTCPConn := c.syscallConn.(interface {
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
SetNoDelay(bool) error
|
||||||
|
})
|
||||||
|
if isTCPConn {
|
||||||
|
err = tcpConn.SetNoDelay(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splits := strings.Split(serverName.ServerName, ".")
|
splits := strings.Split(string(b[serverName.Index:serverName.Index+serverName.Length]), ".")
|
||||||
currentIndex := serverName.Index
|
currentIndex := serverName.Index
|
||||||
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
|
var striped bool
|
||||||
splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")]
|
if len(splits) > 3 {
|
||||||
|
suffix := splits[len(splits)-3] + "." + splits[len(splits)-2] + "." + splits[len(splits)-1]
|
||||||
|
if publicSuffixMatcher().Match(suffix) {
|
||||||
|
splits = splits[:len(splits)-3]
|
||||||
|
}
|
||||||
|
striped = true
|
||||||
}
|
}
|
||||||
if len(splits) > 1 && splits[0] == "..." {
|
if !striped && len(splits) > 2 {
|
||||||
|
suffix := splits[len(splits)-2] + "." + splits[len(splits)-1]
|
||||||
|
if publicSuffixMatcher().Match(suffix) {
|
||||||
|
splits = splits[:len(splits)-2]
|
||||||
|
}
|
||||||
|
striped = true
|
||||||
|
}
|
||||||
|
if !striped && len(splits) > 1 {
|
||||||
|
suffix := splits[len(splits)-1]
|
||||||
|
if publicSuffixMatcher().Match(suffix) {
|
||||||
|
splits = splits[:len(splits)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(splits) > 1 && common.Contains(publicPrefix, splits[0]) {
|
||||||
currentIndex += len(splits[0]) + 1
|
currentIndex += len(splits[0]) + 1
|
||||||
splits = splits[1:]
|
splits = splits[1:]
|
||||||
}
|
}
|
||||||
@@ -62,28 +83,25 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
for i := 0; i <= len(splitIndexes); i++ {
|
||||||
var payload []byte
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
payload = b[:splitIndexes[i]]
|
_, err = c.Conn.Write(b[:splitIndexes[i]])
|
||||||
} else if i == len(splitIndexes) {
|
} else if i == len(splitIndexes) {
|
||||||
payload = b[splitIndexes[i-1]:]
|
_, err = c.Conn.Write(b[splitIndexes[i-1]:])
|
||||||
} else {
|
} else {
|
||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
_, err = c.Conn.Write(b[splitIndexes[i-1]:splitIndexes[i]])
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
if err != nil {
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
return
|
||||||
if err != nil {
|
}
|
||||||
return
|
if c.syscallConn != nil && i != len(splitIndexes) {
|
||||||
}
|
err = waitAck(c.ctx, c.syscallConn, c.fallbackDelay)
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil {
|
if isTCPConn {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = tcpConn.SetNoDelay(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type myServerName struct {
|
type myServerName struct {
|
||||||
Index int
|
Index int
|
||||||
Length int
|
Length int
|
||||||
ServerName string
|
sex []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func indexTLSServerName(payload []byte) *myServerName {
|
func indexTLSServerName(payload []byte) *myServerName {
|
||||||
@@ -68,9 +68,15 @@ func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||||
|
numCiphers := int(csLen / 2)
|
||||||
|
cipherSuites := make([]uint16, 0, numCiphers)
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
for i := 0; i < numCiphers; i++ {
|
||||||
|
cipherSuite := uint16(cs[2+i<<1])<<8 | uint16(cs[3+i<<1])
|
||||||
|
cipherSuites = append(cipherSuites, cipherSuite)
|
||||||
|
}
|
||||||
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||||
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||||
return nil
|
return nil
|
||||||
@@ -119,9 +125,9 @@ func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
|||||||
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||||
sex = sex[sniExtensionHeaderLen:]
|
sex = sex[sniExtensionHeaderLen:]
|
||||||
return &myServerName{
|
return &myServerName{
|
||||||
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||||
Length: int(sniLen),
|
Length: int(sniLen),
|
||||||
ServerName: string(sex),
|
sex: sex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exs = exs[4+exLen:]
|
exs = exs[4+exLen:]
|
||||||
|
|||||||
55
common/tlsfragment/public_suffix.go
Normal file
55
common/tlsfragment/public_suffix.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package tf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/domain"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var publicPrefix = []string{
|
||||||
|
"www",
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:generate wget -O public_suffix_list.dat https://publicsuffix.org/list/public_suffix_list.dat
|
||||||
|
|
||||||
|
//go:embed public_suffix_list.dat
|
||||||
|
var publicSuffix []byte
|
||||||
|
|
||||||
|
var publicSuffixMatcher = sync.OnceValue(func() *domain.Matcher {
|
||||||
|
matcher, err := initPublicSuffixMatcher()
|
||||||
|
if err != nil {
|
||||||
|
panic(F.ToString("error in initialize public suffix matcher"))
|
||||||
|
}
|
||||||
|
return matcher
|
||||||
|
})
|
||||||
|
|
||||||
|
func initPublicSuffixMatcher() (*domain.Matcher, error) {
|
||||||
|
reader := bufio.NewReader(bytes.NewReader(publicSuffix))
|
||||||
|
var domainList []string
|
||||||
|
for {
|
||||||
|
line, isPrefix, err := reader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isPrefix {
|
||||||
|
return nil, E.New("unexpected prefix line")
|
||||||
|
}
|
||||||
|
lineStr := string(line)
|
||||||
|
lineStr = strings.TrimSpace(lineStr)
|
||||||
|
if lineStr == "" || strings.HasPrefix(lineStr, "//") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
domainList = append(domainList, lineStr)
|
||||||
|
}
|
||||||
|
return domain.NewMatcher(domainList, nil, false), nil
|
||||||
|
}
|
||||||
15692
common/tlsfragment/public_suffix_list.dat
Normal file
15692
common/tlsfragment/public_suffix_list.dat
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ package tf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -63,11 +63,7 @@ func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32)
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||||
_, err := conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return control.Conn(conn, func(fd uintptr) error {
|
return control.Conn(conn, func(fd uintptr) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package tf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -10,11 +10,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||||
_, err := conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return control.Conn(conn, func(fd uintptr) error {
|
return control.Conn(conn, func(fd uintptr) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
//go:build !(linux || darwin || windows)
|
//go:build !(linux || darwin)
|
||||||
|
|
||||||
package tf
|
package tf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
func waitAck(ctx context.Context, conn syscall.Conn, fallbackDelay time.Duration) error {
|
||||||
time.Sleep(fallbackDelay)
|
time.Sleep(fallbackDelay)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package tf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/winiphlpapi"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
|
||||||
start := time.Now()
|
|
||||||
err := winiphlpapi.WriteAndWaitAck(ctx, conn, payload)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, windows.ERROR_ACCESS_DENIED) {
|
|
||||||
time.Sleep(fallbackDelay)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if time.Since(start) <= 20*time.Millisecond {
|
|
||||||
time.Sleep(fallbackDelay)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -2,32 +2,32 @@ package urltest
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
|
type History struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
type HistoryStorage struct {
|
type HistoryStorage struct {
|
||||||
access sync.RWMutex
|
access sync.RWMutex
|
||||||
delayHistory map[string]*adapter.URLTestHistory
|
delayHistory map[string]*History
|
||||||
updateHook chan<- struct{}
|
updateHook chan<- struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHistoryStorage() *HistoryStorage {
|
func NewHistoryStorage() *HistoryStorage {
|
||||||
return &HistoryStorage{
|
return &HistoryStorage{
|
||||||
delayHistory: make(map[string]*adapter.URLTestHistory),
|
delayHistory: make(map[string]*History),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
|
|||||||
s.updateHook = hook
|
s.updateHook = hook
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
|
func (s *HistoryStorage) LoadURLTestHistory(tag string) *History {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
|
|||||||
s.notifyUpdated()
|
s.notifyUpdated()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
|
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *History) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
s.delayHistory[tag] = history
|
s.delayHistory[tag] = history
|
||||||
s.access.Unlock()
|
s.access.Unlock()
|
||||||
@@ -110,10 +110,6 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return instance, nil
|
return instance, nil
|
||||||
},
|
},
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
Time: ntp.TimeFuncFromContext(ctx),
|
|
||||||
RootCAs: adapter.RootPoolFromContext(ctx),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package constant
|
|
||||||
|
|
||||||
const (
|
|
||||||
CertificateStoreSystem = "system"
|
|
||||||
CertificateStoreMozilla = "mozilla"
|
|
||||||
CertificateStoreNone = "none"
|
|
||||||
)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package constant
|
|
||||||
|
|
||||||
const (
|
|
||||||
ScriptTypeSurge = "surge"
|
|
||||||
ScriptSourceTypeLocal = "local"
|
|
||||||
ScriptSourceTypeRemote = "remote"
|
|
||||||
)
|
|
||||||
@@ -252,8 +252,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
ruleIndex = -1
|
ruleIndex = -1
|
||||||
for {
|
for {
|
||||||
dnsCtx := adapter.OverrideContext(ctx)
|
dnsCtx := adapter.OverrideContext(ctx)
|
||||||
dnsOptions := options
|
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &options)
|
||||||
transport, rule, ruleIndex = r.matchDNS(ctx, true, ruleIndex, isAddressQuery(message), &dnsOptions)
|
|
||||||
if rule != nil {
|
if rule != nil {
|
||||||
switch action := rule.Action().(type) {
|
switch action := rule.Action().(type) {
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
@@ -272,10 +271,10 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
return rule.MatchAddressLimit(metadata)
|
return rule.MatchAddressLimit(metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dnsOptions.Strategy == C.DomainStrategyAsIS {
|
if options.Strategy == C.DomainStrategyAsIS {
|
||||||
dnsOptions.Strategy = r.defaultDomainStrategy
|
options.Strategy = r.defaultDomainStrategy
|
||||||
}
|
}
|
||||||
response, err = r.client.Exchange(dnsCtx, transport, message, dnsOptions, responseCheck)
|
response, err = r.client.Exchange(dnsCtx, transport, message, options, responseCheck)
|
||||||
var rejected bool
|
var rejected bool
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ErrResponseRejectedCached) {
|
if errors.Is(err, ErrResponseRejectedCached) {
|
||||||
|
|||||||
@@ -36,14 +36,6 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) Start() error {
|
|
||||||
return t.store.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Close() error {
|
|
||||||
return t.store.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Reset() {
|
func (t *Transport) Reset() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
|
||||||
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
|
require.Equal(t, []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1}), netip.IPv6Loopback()}, hosts.NewFile("testdata/hosts").Lookup("localhost."))
|
||||||
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
|
require.NotEmpty(t, hosts.NewFile(hosts.DefaultPath).Lookup("localhost."))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
package hosts
|
package hosts
|
||||||
|
|
||||||
import (
|
import _ "unsafe"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
var DefaultPath = getSystemDirectory() + "/Drivers/etc/hosts"
|
||||||
)
|
|
||||||
|
|
||||||
var DefaultPath string
|
//go:linkname getSystemDirectory internal/syscall/windows.GetSystemDirectory
|
||||||
|
func getSystemDirectory() string
|
||||||
func init() {
|
|
||||||
systemDirectory, err := windows.GetSystemDirectory()
|
|
||||||
if err != nil {
|
|
||||||
systemDirectory = "C:\\Windows\\System32"
|
|
||||||
}
|
|
||||||
DefaultPath = filepath.Join(systemDirectory, "Drivers/etc/hosts")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"math/rand"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -148,7 +147,7 @@ func (t *Transport) exchangeOne(ctx context.Context, server M.Socksaddr, questio
|
|||||||
}
|
}
|
||||||
request := &mDNS.Msg{
|
request := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
Id: uint16(rand.Uint32()),
|
Id: uint16(randInt()),
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
AuthenticatedData: ad,
|
AuthenticatedData: ad,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -117,7 +118,7 @@ func (conf *dnsConfig) nameList(name string) []string {
|
|||||||
|
|
||||||
hasNdots := strings.Count(name, ".") >= conf.ndots
|
hasNdots := strings.Count(name, ".") >= conf.ndots
|
||||||
name += "."
|
name += "."
|
||||||
// l++
|
l++
|
||||||
|
|
||||||
names := make([]string, 0, 1+len(conf.search))
|
names := make([]string, 0, 1+len(conf.search))
|
||||||
if hasNdots && !avoidDNS(name) {
|
if hasNdots && !avoidDNS(name) {
|
||||||
@@ -135,6 +136,13 @@ func (conf *dnsConfig) nameList(name string) []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname runtime_rand runtime.rand
|
||||||
|
func runtime_rand() uint64
|
||||||
|
|
||||||
|
func randInt() int {
|
||||||
|
return int(uint(runtime_rand()) >> 1) // clear sign bit
|
||||||
|
}
|
||||||
|
|
||||||
func avoidDNS(name string) bool {
|
func avoidDNS(name string) bool {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -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,182 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.0-alpha.3
|
#### 1.11.0-beta.24
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.12.0-alpha.2
|
|
||||||
|
|
||||||
* Update quic-go to v0.49.0
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.12.0-alpha.1
|
|
||||||
|
|
||||||
* Refactor DNS servers **1**
|
|
||||||
* Add domain resolver options**2**
|
|
||||||
* Add TLS fragment route options **3**
|
|
||||||
* Add certificate options **4**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
DNS servers are refactored for better performance and scalability.
|
|
||||||
|
|
||||||
See [DNS server](/configuration/dns/server/).
|
|
||||||
|
|
||||||
For migration, see [Migrate to new DNS server formats](/migration/#migrate-to-new-dns-servers).
|
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Legacy `outbound` DNS rules are deprecated
|
|
||||||
and can be replaced by the new `domain_resolver` option.
|
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#domain_resolver) and
|
|
||||||
[Route](/configuration/route/#default_domain_resolver).
|
|
||||||
|
|
||||||
For migration, see [Migrate outbound DNS rule items to domain resolver](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
The new TLS fragment route options allow you to fragment TLS handshakes to bypass firewalls.
|
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
|
|
||||||
|
|
||||||
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked.
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#tls_fragment).
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
New certificate options allow you to manage the default list of trusted X509 CA certificates.
|
|
||||||
|
|
||||||
For the system certificate list, fixed Go not reading Android trusted certificates correctly.
|
|
||||||
|
|
||||||
You can also use the Mozilla Included List instead, or add trusted certificates yourself.
|
|
||||||
|
|
||||||
See [Certificate](/configuration/certificate/).
|
|
||||||
|
|
||||||
### 1.11.0
|
|
||||||
|
|
||||||
Important changes since 1.10:
|
|
||||||
|
|
||||||
* Introducing rule actions **1**
|
|
||||||
* Improve tun compatibility **3**
|
|
||||||
* Merge route options to route actions **4**
|
|
||||||
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**
|
|
||||||
* Add multi network dialing **6**
|
|
||||||
* Add `cache_capacity` DNS option **7**
|
|
||||||
* Add `override_address` and `override_port` route options **8**
|
|
||||||
* Upgrade WireGuard outbound to endpoint **9**
|
|
||||||
* Add UDP GSO support for WireGuard
|
|
||||||
* Make GSO adaptive **10**
|
|
||||||
* Add UDP timeout route option **11**
|
|
||||||
* Add more masquerade options for hysteria2 **12**
|
|
||||||
* Add `rule-set merge` command
|
|
||||||
* Add port hopping support for Hysteria2 **13**
|
|
||||||
* Hysteria2 `ignore_client_bandwidth` behavior update **14**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
New rule actions replace legacy inbound fields and special outbound fields,
|
|
||||||
and can be used for pre-matching **2**.
|
|
||||||
|
|
||||||
See [Rule](/configuration/route/rule/),
|
|
||||||
[Rule Action](/configuration/route/rule_action/),
|
|
||||||
[DNS Rule](/configuration/dns/rule/) and
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/).
|
|
||||||
|
|
||||||
For migration, see
|
|
||||||
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
|
|
||||||
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
|
|
||||||
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Similar to Surge's pre-matching.
|
|
||||||
|
|
||||||
Specifically, new rule actions allow you to reject connections with
|
|
||||||
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
|
|
||||||
before connection established to improve tun's compatibility.
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/rule_action/).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
When `gvisor` tun stack is enabled, even if the request passes routing,
|
|
||||||
if the outbound connection establishment fails,
|
|
||||||
the connection still does not need to be established and a TCP RST is replied.
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
Route options in DNS route actions will no longer be considered deprecated,
|
|
||||||
see [DNS Route Action](/configuration/dns/rule_action/).
|
|
||||||
|
|
||||||
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
|
|
||||||
see [Route Action](/configuration/route/rule_action/).
|
|
||||||
|
|
||||||
**5**:
|
|
||||||
|
|
||||||
When using in graphical clients, new routing rule items allow you to match on
|
|
||||||
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
|
||||||
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
|
||||||
|
|
||||||
**6**:
|
|
||||||
|
|
||||||
Similar to Surge's strategy.
|
|
||||||
|
|
||||||
New options allow you to connect using multiple network interfaces,
|
|
||||||
prefer or only use one type of interface,
|
|
||||||
and configure a timeout to fallback to other interfaces.
|
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#network_strategy),
|
|
||||||
[Rule Action](/configuration/route/rule_action/#network_strategy)
|
|
||||||
and [Route](/configuration/route/#default_network_strategy).
|
|
||||||
|
|
||||||
**7**:
|
|
||||||
|
|
||||||
See [DNS](/configuration/dns/#cache_capacity).
|
|
||||||
|
|
||||||
**8**:
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/#override_address) and
|
|
||||||
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
|
|
||||||
|
|
||||||
**9**:
|
|
||||||
|
|
||||||
The new WireGuard endpoint combines inbound and outbound capabilities,
|
|
||||||
and the old outbound will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
|
||||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
|
||||||
|
|
||||||
**10**:
|
|
||||||
|
|
||||||
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
|
|
||||||
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
|
|
||||||
|
|
||||||
For TUN, GSO has been removed,
|
|
||||||
see [Deprecated](/deprecated/#gso-option-in-tun).
|
|
||||||
|
|
||||||
**11**:
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
|
|
||||||
|
|
||||||
**12**:
|
|
||||||
|
|
||||||
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
|
|
||||||
|
|
||||||
**13**:
|
|
||||||
|
|
||||||
See [Hysteria2](/configuration/outbound/hysteria2/).
|
|
||||||
|
|
||||||
**14**:
|
|
||||||
|
|
||||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
|
||||||
|
|
||||||
### 1.10.7
|
### 1.10.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
# Certificate
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"store": "",
|
|
||||||
"certificate": [],
|
|
||||||
"certificate_path": [],
|
|
||||||
"certificate_directory_path": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
You can ignore the JSON Array [] tag when the content is only one item
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### store
|
|
||||||
|
|
||||||
The default X509 trusted CA certificate list.
|
|
||||||
|
|
||||||
| Type | Description |
|
|
||||||
|--------------------|---------------------------------------------------------------------------------------------------------------|
|
|
||||||
| `system` (default) | System trusted CA certificates |
|
|
||||||
| `mozilla` | [Mozilla Included List](https://wiki.mozilla.org/CA/Included_Certificates) with China CA certificates removed |
|
|
||||||
| `none` | Empty list |
|
|
||||||
|
|
||||||
#### certificate
|
|
||||||
|
|
||||||
The certificate line array to trust, in PEM format.
|
|
||||||
|
|
||||||
#### certificate_path
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
Will be automatically reloaded if file modified.
|
|
||||||
|
|
||||||
The paths to certificates to trust, in PEM format.
|
|
||||||
|
|
||||||
#### certificate_directory_path
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
Will be automatically reloaded if file modified.
|
|
||||||
|
|
||||||
The directory path to search for certificates to trust,in PEM format.
|
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
|
||||||
|
|
||||||
Legacy fake-ip configuration is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
|
||||||
|
|
||||||
旧的 fake-ip 配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
|
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ Default domain strategy for resolving the domain names.
|
|||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
|
||||||
|
Take no effect if `server.strategy` is set.
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
Disable dns cache.
|
Disable dns cache.
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ icon: material/new-box
|
|||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
|
|
||||||
|
如果设置了 `server.strategy`,则不生效。
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
禁用 DNS 缓存。
|
禁用 DNS 缓存。
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [outbound](#outbound)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@@ -399,10 +395,6 @@ Invert match result.
|
|||||||
|
|
||||||
#### outbound
|
#### outbound
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
|
||||||
|
|
||||||
`outbound` rule items are deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
|
||||||
|
|
||||||
Match outbound.
|
Match outbound.
|
||||||
|
|
||||||
`any` can be used as a value to match any outbound.
|
`any` can be used as a value to match any outbound.
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [outbound](#outbound)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
:material-plus: [action](#action)
|
||||||
@@ -399,10 +395,6 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
#### outbound
|
#### outbound
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.12.0 废弃"
|
|
||||||
|
|
||||||
`outbound` 规则项已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-outbound-dns-rule-items-to-domain-resolver)。
|
|
||||||
|
|
||||||
匹配出站。
|
匹配出站。
|
||||||
|
|
||||||
`any` 可作为值用于匹配任意出站。
|
`any` 可作为值用于匹配任意出站。
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
!!! question "Since sing-box 1.11.0"
|
||||||
|
|
||||||
### route
|
### route
|
||||||
@@ -14,7 +10,6 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"action": "route", // default
|
"action": "route", // default
|
||||||
"server": "",
|
"server": "",
|
||||||
"strategy": "",
|
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"rewrite_ttl": 0,
|
"rewrite_ttl": 0,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
@@ -29,12 +24,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
Tag of target server.
|
Tag of target server.
|
||||||
|
|
||||||
#### strategy
|
|
||||||
|
|
||||||
Set domain strategy for this query.
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
Disable cache and save cache in this query.
|
Disable cache and save cache in this query.
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [strategy](#strategy)
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
!!! question "自 sing-box 1.11.0 起"
|
||||||
|
|
||||||
### route
|
### route
|
||||||
@@ -14,8 +10,8 @@ icon: material/new-box
|
|||||||
{
|
{
|
||||||
"action": "route", // 默认
|
"action": "route", // 默认
|
||||||
"server": "",
|
"server": "",
|
||||||
|
|
||||||
"strategy": "",
|
// 兼容性
|
||||||
"disable_cache": false,
|
"disable_cache": false,
|
||||||
"rewrite_ttl": 0,
|
"rewrite_ttl": 0,
|
||||||
"client_subnet": null
|
"client_subnet": null
|
||||||
@@ -30,12 +26,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
目标 DNS 服务器的标签。
|
目标 DNS 服务器的标签。
|
||||||
|
|
||||||
#### strategy
|
|
||||||
|
|
||||||
为此查询设置域名策略。
|
|
||||||
|
|
||||||
可选项:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
在此查询中禁用缓存。
|
在此查询中禁用缓存。
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
|
||||||
|
|
||||||
Legacy DNS servers is deprecated and will be removed in sing-box 1.14.0, check [Migration](/migration/#migrate-to-new-dns-servers).
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
|
||||||
|
|
||||||
旧的 DNS 服务器配置已废弃且将在 sing-box 1.14.0 中被移除,参阅 [迁移指南](/migration/#migrate-to-new-dns-servers)。
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
# DHCP
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "dhcp",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"interface": "",
|
|
||||||
|
|
||||||
// Dial Fields
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### interface
|
|
||||||
|
|
||||||
Interface name to listen on.
|
|
||||||
|
|
||||||
Tge default interface will be used by default.
|
|
||||||
|
|
||||||
### Dial Fields
|
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
|
||||||
@@ -1,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
|
||||||
|
|
||||||
|
|||||||
@@ -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: QUIC client type detect support for QUIC
|
:material-plus: QUIC client type detect support for QUIC
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.10.0 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-plus: QUIC 的 客户端类型探测支持
|
:material-plus: QUIC 的 客户端类型探测支持
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box,
|
sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box,
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "自 sing-box 1.10.0 起"
|
!!! question "自 sing-box 1.10.0 起"
|
||||||
|
|
||||||
sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box,
|
sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box,
|
||||||
|
|||||||
@@ -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: `type: inline`
|
:material-plus: `type: inline`
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.10.0 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-plus: `type: inline`
|
:material-plus: `type: inline`
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-plus: [domain_resolver](#domain_resolver)
|
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [network_strategy](#network_strategy)
|
:material-plus: [network_strategy](#network_strategy)
|
||||||
@@ -28,14 +23,11 @@ icon: material/new-box
|
|||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"domain_resolver": "", // or {}
|
"domain_strategy": "prefer_ipv6",
|
||||||
"network_strategy": "default",
|
"network_strategy": "default",
|
||||||
"network_type": [],
|
"network_type": [],
|
||||||
"fallback_network_type": [],
|
"fallback_network_type": [],
|
||||||
"fallback_delay": "300ms",
|
"fallback_delay": "300ms"
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
"domain_strategy": "prefer_ipv6"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -100,22 +92,16 @@ decimal numbers, each with optional fraction and a unit suffix,
|
|||||||
such as "300ms", "-1.5h" or "2h45m".
|
such as "300ms", "-1.5h" or "2h45m".
|
||||||
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||||
|
|
||||||
#### domain_resolver
|
#### domain_strategy
|
||||||
|
|
||||||
!!! warning ""
|
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
||||||
|
|
||||||
`outbound` DNS rule items are deprecated and will be removed in sing-box 1.14.0, so this item will be required for outbound/endpoints using domain name in server address since sing-box 1.14.0.
|
If set, the requested domain name will be resolved to IP before connect.
|
||||||
|
|
||||||
Set domain resolver to use for resolving domain names.
|
| Outbound | Effected domains | Fallback Value |
|
||||||
|
|----------|--------------------------|-------------------------------------------|
|
||||||
This option uses the same format as the [route DNS rule action](/configuration/dns/rule_action/#route) without the `action` field.
|
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
|
||||||
|
| others | Domain in server address | / |
|
||||||
Setting this option directly to a string is equivalent to setting `server` of this options.
|
|
||||||
|
|
||||||
| Outbound/Endpoints | Effected domains |
|
|
||||||
|--------------------|--------------------------|
|
|
||||||
| `direct` | Domain in request |
|
|
||||||
| others | Domain in server address |
|
|
||||||
|
|
||||||
#### network_strategy
|
#### network_strategy
|
||||||
|
|
||||||
@@ -185,19 +171,3 @@ back to other interfaces.
|
|||||||
Only take effect when `domain_strategy` or `network_strategy` is set.
|
Only take effect when `domain_strategy` or `network_strategy` is set.
|
||||||
|
|
||||||
`300ms` is used by default.
|
`300ms` is used by default.
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.12.0"
|
|
||||||
|
|
||||||
`domain_strategy` is merged to [domain_resolver](#domain_resolver) in sing-box 1.12.0.
|
|
||||||
|
|
||||||
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before connect.
|
|
||||||
|
|
||||||
| Outbound | Effected domains | Fallback Value |
|
|
||||||
|----------|--------------------------|-------------------------------------------|
|
|
||||||
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
|
|
||||||
| others | Domain in server address | / |
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [domain_resolver](#domain_resolver)
|
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [network_strategy](#network_strategy)
|
:material-plus: [network_strategy](#network_strategy)
|
||||||
@@ -28,15 +23,11 @@ icon: material/new-box
|
|||||||
"tcp_fast_open": false,
|
"tcp_fast_open": false,
|
||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"domain_resolver": "", // 或 {}
|
"domain_strategy": "prefer_ipv6",
|
||||||
"network_strategy": "",
|
"network_strategy": "",
|
||||||
"network_type": [],
|
"network_type": [],
|
||||||
"fallback_network_type": [],
|
"fallback_network_type": [],
|
||||||
"fallback_delay": "300ms",
|
"fallback_delay": "300ms"
|
||||||
|
|
||||||
// 废弃的
|
|
||||||
|
|
||||||
"domain_strategy": "prefer_ipv6"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -99,22 +90,16 @@ icon: material/new-box
|
|||||||
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
|
持续时间字符串是一个可能有符号的序列十进制数,每个都有可选的分数和单位后缀, 例如 "300ms"、"-1.5h" 或 "2h45m"。
|
||||||
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
|
有效时间单位为 "ns"、"us"(或 "µs")、"ms"、"s"、"m"、"h"。
|
||||||
|
|
||||||
#### domain_resolver
|
#### domain_strategy
|
||||||
|
|
||||||
!!! warning ""
|
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
|
|
||||||
`outbound` DNS 规则项已弃用,且将在 sing-box 1.14.0 中被移除。因此,从 sing-box 1.14.0 版本开始,所有在服务器地址中使用域名的出站/端点均需配置此项。
|
如果设置,域名将在请求发出之前解析为 IP。
|
||||||
|
|
||||||
用于设置解析域名的域名解析器。
|
| 出站 | 受影响的域名 | 默认回退值 |
|
||||||
|
|----------|-----------|---------------------------|
|
||||||
此选项的格式与 [路由 DNS 规则动作](/configuration/dns/rule_action/#route) 相同,但不包含 `action` 字段。
|
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
||||||
|
| others | 服务器地址中的域名 | / |
|
||||||
若直接将此选项设置为字符串,则等同于设置该选项的 `server` 字段。
|
|
||||||
|
|
||||||
| 出站/端点 | 受影响的域名 |
|
|
||||||
|----------------|---------------------------|
|
|
||||||
| `direct` | 请求中的域名 |
|
|
||||||
| 其他类型 | 服务器地址中的域名 |
|
|
||||||
|
|
||||||
#### network_strategy
|
#### network_strategy
|
||||||
|
|
||||||
@@ -175,14 +160,3 @@ icon: material/new-box
|
|||||||
仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。
|
仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。
|
||||||
|
|
||||||
默认使用 `300ms`。
|
默认使用 `300ms`。
|
||||||
|
|
||||||
#### domain_strategy
|
|
||||||
|
|
||||||
可选值:`prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
|
||||||
|
|
||||||
如果设置,域名将在请求发出之前解析为 IP。
|
|
||||||
|
|
||||||
| 出站 | 受影响的域名 | 默认回退值 |
|
|
||||||
|----------|-----------|---------------------------|
|
|
||||||
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
|
||||||
| others | 服务器地址中的域名 | / |
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/alert-decagram
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.10.0 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-alert-decagram: [utls](#utls)
|
:material-alert-decagram: [utls](#utls)
|
||||||
|
|||||||
@@ -4,21 +4,6 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# Deprecated Feature List
|
# Deprecated Feature List
|
||||||
|
|
||||||
## 1.12.0
|
|
||||||
|
|
||||||
#### Legacy DNS server formats
|
|
||||||
|
|
||||||
DNS servers are refactored,
|
|
||||||
check [Migration](../migration/#migrate-to-new-dns-servers).
|
|
||||||
|
|
||||||
Compatibility for old formats will be removed in sing-box 1.14.0.
|
|
||||||
|
|
||||||
#### `outbound` DNS rule item
|
|
||||||
|
|
||||||
Legacy `outbound` DNS rules are deprecated
|
|
||||||
and can be replaced by dial fields,
|
|
||||||
check [Migration](../migration/#migrate-outbound-dns-rule-items-to-domain-resolver).
|
|
||||||
|
|
||||||
## 1.11.0
|
## 1.11.0
|
||||||
|
|
||||||
#### Legacy special outbounds
|
#### Legacy special outbounds
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user