mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
51 Commits
v1.11.0-al
...
v1.11.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ddcf40f5d | ||
|
|
3c89cddb6e | ||
|
|
0b75e31ecc | ||
|
|
3a02b982a0 | ||
|
|
6f8c59b838 | ||
|
|
d91350975b | ||
|
|
e3d9f56d7a | ||
|
|
8610018f3b | ||
|
|
7ff4dd541f | ||
|
|
0751506441 | ||
|
|
762418ac16 | ||
|
|
cba8fa185f | ||
|
|
2a1ce5b6ec | ||
|
|
012b0af2ab | ||
|
|
1dd21c9b2b | ||
|
|
f476657ef6 | ||
|
|
da4e199bd5 | ||
|
|
05bda2ef0b | ||
|
|
d68acbc353 | ||
|
|
d34dda9672 | ||
|
|
9aceb133f1 | ||
|
|
e926d15973 | ||
|
|
f1f5a1c60a | ||
|
|
9229769d86 | ||
|
|
6f2af49154 | ||
|
|
ed75e44989 | ||
|
|
ac87be3651 | ||
|
|
303e0f0b0f | ||
|
|
76fdcaa73a | ||
|
|
355e5eebb7 | ||
|
|
05367f3f24 | ||
|
|
d61c622474 | ||
|
|
3ddfc7028b | ||
|
|
153172799d | ||
|
|
aa2ac3e020 | ||
|
|
ae2346b5fc | ||
|
|
2a4bcddbcb | ||
|
|
08d8378a1c | ||
|
|
1086d5e665 | ||
|
|
d9102ba599 | ||
|
|
17019f1729 | ||
|
|
6be07ed51f | ||
|
|
af58e3bec0 | ||
|
|
e58b549d0f | ||
|
|
1d81996ceb | ||
|
|
97c47e72c4 | ||
|
|
122be275b0 | ||
|
|
0bb1132034 | ||
|
|
de14337b4b | ||
|
|
1e07633914 | ||
|
|
e3e203844e |
2
.github/workflows/debug.yml
vendored
2
.github/workflows/debug.yml
vendored
@@ -204,7 +204,7 @@ jobs:
|
||||
GOARM: ${{ matrix.goarm }}
|
||||
GOMIPS: ${{ matrix.gomips }}
|
||||
CGO_ENABLED: 0
|
||||
TAGS: with_clash_api,with_quic
|
||||
TAGS: with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_ech
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
|
||||
14
Makefile
14
Makefile
@@ -96,11 +96,7 @@ upload_android:
|
||||
release_android: lib_android update_android_version build_android upload_android
|
||||
|
||||
publish_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle
|
||||
|
||||
publish_android_appcenter:
|
||||
cd ../sing-box-for-android && ./gradlew :app:appCenterAssembleAndUploadPlayRelease
|
||||
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||
|
||||
# TODO: find why and remove `-destination 'generic/platform=iOS'`
|
||||
build_ios:
|
||||
@@ -147,9 +143,13 @@ build_macos_dmg:
|
||||
--hide-extension "SFM.app" \
|
||||
--app-drop-link 0 0 \
|
||||
--skip-jenkins \
|
||||
--notarize "notarytool-password" \
|
||||
"../sing-box/dist/SFM/SFM.dmg" "build/SFM.System/SFM.app"
|
||||
|
||||
notarize_macos_dmg:
|
||||
xcrun notarytool submit "dist/SFM/SFM.dmg" --wait \
|
||||
--keychain-profile "notarytool-password" \
|
||||
--no-s3-acceleration
|
||||
|
||||
upload_macos_dmg:
|
||||
cd dist/SFM && \
|
||||
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||
@@ -164,7 +164,7 @@ upload_macos_dsyms:
|
||||
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
||||
|
||||
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||
release_macos_standalone: build_macos_standalone build_macos_dmg notarize_macos_dmg upload_macos_dmg upload_macos_dsyms
|
||||
|
||||
build_tvos:
|
||||
cd ../sing-box-for-apple && \
|
||||
|
||||
15
adapter/connections.go
Normal file
15
adapter/connections.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ConnectionManager interface {
|
||||
Start() error
|
||||
Close() error
|
||||
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
28
adapter/endpoint.go
Normal file
28
adapter/endpoint.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type Endpoint interface {
|
||||
Lifecycle
|
||||
Type() string
|
||||
Tag() string
|
||||
Outbound
|
||||
}
|
||||
|
||||
type EndpointRegistry interface {
|
||||
option.EndpointOptionsRegistry
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
|
||||
}
|
||||
|
||||
type EndpointManager interface {
|
||||
Lifecycle
|
||||
Endpoints() []Endpoint
|
||||
Get(tag string) (Endpoint, bool)
|
||||
Remove(tag string) error
|
||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
|
||||
}
|
||||
43
adapter/endpoint/adapter.go
Normal file
43
adapter/endpoint/adapter.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package endpoint
|
||||
|
||||
import "github.com/sagernet/sing-box/option"
|
||||
|
||||
type Adapter struct {
|
||||
endpointType string
|
||||
endpointTag string
|
||||
network []string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
|
||||
return Adapter{
|
||||
endpointType: endpointType,
|
||||
endpointTag: endpointTag,
|
||||
network: network,
|
||||
dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||
var dependencies []string
|
||||
if dialOptions.Detour != "" {
|
||||
dependencies = []string{dialOptions.Detour}
|
||||
}
|
||||
return NewAdapter(endpointType, endpointTag, network, dependencies)
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.endpointType
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.endpointTag
|
||||
}
|
||||
|
||||
func (a *Adapter) Network() []string {
|
||||
return a.network
|
||||
}
|
||||
|
||||
func (a *Adapter) Dependencies() []string {
|
||||
return a.dependencies
|
||||
}
|
||||
147
adapter/endpoint/manager.go
Normal file
147
adapter/endpoint/manager.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.EndpointRegistry
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
endpoints []adapter.Endpoint
|
||||
endpointByTag map[string]adapter.Endpoint
|
||||
}
|
||||
|
||||
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpointByTag: make(map[string]adapter.Endpoint),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started && m.stage >= stage {
|
||||
panic("already started")
|
||||
}
|
||||
m.started = true
|
||||
m.stage = stage
|
||||
if stage == adapter.StartStateStart {
|
||||
// started with outbound manager
|
||||
return nil
|
||||
}
|
||||
for _, endpoint := range m.endpoints {
|
||||
err := adapter.LegacyStart(endpoint, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Close() error {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if !m.started {
|
||||
return nil
|
||||
}
|
||||
m.started = false
|
||||
endpoints := m.endpoints
|
||||
m.endpoints = nil
|
||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||
var err error
|
||||
for _, endpoint := range endpoints {
|
||||
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
})
|
||||
monitor.Finish()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Endpoints() []adapter.Endpoint {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
return m.endpoints
|
||||
}
|
||||
|
||||
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
endpoint, found := m.endpointByTag[tag]
|
||||
return endpoint, found
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
m.access.Lock()
|
||||
endpoint, found := m.endpointByTag[tag]
|
||||
if !found {
|
||||
m.access.Unlock()
|
||||
return os.ErrInvalid
|
||||
}
|
||||
delete(m.endpointByTag, tag)
|
||||
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||
return it == endpoint
|
||||
})
|
||||
if index == -1 {
|
||||
panic("invalid endpoint index")
|
||||
}
|
||||
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
|
||||
started := m.started
|
||||
m.access.Unlock()
|
||||
if started {
|
||||
return endpoint.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
if m.started {
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(endpoint, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||
if m.started {
|
||||
err = existsEndpoint.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
|
||||
}
|
||||
}
|
||||
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||
return it == existsEndpoint
|
||||
})
|
||||
if existsIndex == -1 {
|
||||
panic("invalid endpoint index")
|
||||
}
|
||||
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
|
||||
}
|
||||
m.endpoints = append(m.endpoints, endpoint)
|
||||
m.endpointByTag[tag] = endpoint
|
||||
return nil
|
||||
}
|
||||
72
adapter/endpoint/registry.go
Normal file
72
adapter/endpoint/registry.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package endpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
|
||||
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
var _ adapter.EndpointRegistry = (*Registry)(nil)
|
||||
|
||||
type (
|
||||
optionsConstructorFunc func() any
|
||||
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
|
||||
)
|
||||
|
||||
type Registry struct {
|
||||
access sync.Mutex
|
||||
optionsType map[string]optionsConstructorFunc
|
||||
constructor map[string]constructorFunc
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
optionsType: make(map[string]optionsConstructorFunc),
|
||||
constructor: make(map[string]constructorFunc),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
return optionsConstructor(), true
|
||||
}
|
||||
|
||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
constructor, loaded := m.constructor[outboundType]
|
||||
if !loaded {
|
||||
return nil, E.New("outbound type not found: " + outboundType)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, options)
|
||||
}
|
||||
|
||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.optionsType[outboundType] = optionsConstructor
|
||||
m.constructor[outboundType] = constructor
|
||||
}
|
||||
@@ -46,6 +46,9 @@ type PacketConnectionHandlerEx interface {
|
||||
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||
}
|
||||
|
||||
// Deprecated: use TCPConnectionHandlerEx instead
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type UpstreamHandlerAdapter interface {
|
||||
N.TCPConnectionHandler
|
||||
N.UDPConnectionHandler
|
||||
|
||||
@@ -3,15 +3,17 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type Inbound interface {
|
||||
Service
|
||||
Lifecycle
|
||||
Type() string
|
||||
Tag() string
|
||||
}
|
||||
@@ -59,13 +61,19 @@ type InboundContext struct {
|
||||
// cache
|
||||
|
||||
// Deprecated: implement in rule action
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
// Deprecated
|
||||
InboundDetour string
|
||||
LastInbound string
|
||||
OriginDestination M.Socksaddr
|
||||
RouteOriginalDestination M.Socksaddr
|
||||
// Deprecated: to be removed
|
||||
//nolint:staticcheck
|
||||
InboundOptions option.InboundOptions
|
||||
UDPDisableDomainUnmapping bool
|
||||
UDPConnect bool
|
||||
NetworkStrategy C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
|
||||
DNSServer string
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ var _ adapter.InboundManager = (*Manager)(nil)
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.InboundRegistry
|
||||
endpoint adapter.EndpointManager
|
||||
access sync.Mutex
|
||||
started bool
|
||||
stage adapter.StartStage
|
||||
@@ -25,10 +26,11 @@ type Manager struct {
|
||||
inboundByTag map[string]adapter.Inbound
|
||||
}
|
||||
|
||||
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry) *Manager {
|
||||
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpoint: endpoint,
|
||||
inboundByTag: make(map[string]adapter.Inbound),
|
||||
}
|
||||
}
|
||||
@@ -44,7 +46,7 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
for _, inbound := range m.inbounds {
|
||||
err := adapter.LegacyStart(inbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -79,9 +81,12 @@ func (m *Manager) Inbounds() []adapter.Inbound {
|
||||
|
||||
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
inbound, found := m.inboundByTag[tag]
|
||||
return inbound, found
|
||||
m.access.Unlock()
|
||||
if found {
|
||||
return inbound, true
|
||||
}
|
||||
return m.endpoint.Get(tag)
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(tag string) error {
|
||||
@@ -118,7 +123,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(inbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage.Action(), " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,12 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package adapter
|
||||
|
||||
import E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
type StartStage uint8
|
||||
|
||||
const (
|
||||
@@ -16,7 +18,7 @@ var ListStartStages = []StartStage{
|
||||
StartStateStarted,
|
||||
}
|
||||
|
||||
func (s StartStage) Action() string {
|
||||
func (s StartStage) String() string {
|
||||
switch s {
|
||||
case StartStateInitialize:
|
||||
return "initialize"
|
||||
@@ -25,7 +27,7 @@ func (s StartStage) Action() string {
|
||||
case StartStatePostStart:
|
||||
return "post-start"
|
||||
case StartStateStarted:
|
||||
return "start-after-started"
|
||||
return "finish-start"
|
||||
default:
|
||||
panic("unknown stage")
|
||||
}
|
||||
@@ -40,3 +42,23 @@ type LifecycleService interface {
|
||||
Name() string
|
||||
Lifecycle
|
||||
}
|
||||
|
||||
func Start(stage StartStage, services ...Lifecycle) error {
|
||||
for _, service := range services {
|
||||
err := service.Start(stage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartNamed(stage StartStage, services []LifecycleService) error {
|
||||
for _, service := range services {
|
||||
err := service.Start(stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage.String(), " ", service.Name())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package adapter
|
||||
|
||||
func LegacyStart(starter any, stage StartStage) error {
|
||||
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
|
||||
return lifecycle.Start(stage)
|
||||
}
|
||||
switch stage {
|
||||
case StartStateInitialize:
|
||||
if preStarter, isPreStarter := starter.(interface {
|
||||
@@ -14,7 +17,7 @@ func LegacyStart(starter any, stage StartStage) error {
|
||||
}); isStarter {
|
||||
return starter.Start()
|
||||
}
|
||||
case StartStatePostStart:
|
||||
case StartStateStarted:
|
||||
if postStarter, isPostStarter := starter.(interface {
|
||||
PostStart() error
|
||||
}); isPostStarter {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
)
|
||||
@@ -9,10 +12,12 @@ type NetworkManager interface {
|
||||
Lifecycle
|
||||
InterfaceFinder() control.InterfaceFinder
|
||||
UpdateInterfaces() error
|
||||
DefaultInterface() string
|
||||
DefaultNetworkInterface() *NetworkInterface
|
||||
NetworkInterfaces() []NetworkInterface
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
DefaultMark() uint32
|
||||
ProtectFunc() control.Func
|
||||
DefaultOptions() NetworkOptions
|
||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||
AutoRedirectOutputMark() uint32
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
@@ -21,3 +26,29 @@ type NetworkManager interface {
|
||||
WIFIState() WIFIState
|
||||
ResetNetwork()
|
||||
}
|
||||
|
||||
type NetworkOptions struct {
|
||||
NetworkStrategy C.NetworkStrategy
|
||||
NetworkType []C.InterfaceType
|
||||
FallbackNetworkType []C.InterfaceType
|
||||
FallbackDelay time.Duration
|
||||
BindInterface string
|
||||
RoutingMark uint32
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
InterfaceUpdated()
|
||||
}
|
||||
|
||||
type WIFIState struct {
|
||||
SSID string
|
||||
BSSID string
|
||||
}
|
||||
|
||||
type NetworkInterface struct {
|
||||
control.Interface
|
||||
Type C.InterfaceType
|
||||
DNSServers []string
|
||||
Expensive bool
|
||||
Constrained bool
|
||||
}
|
||||
|
||||
@@ -5,35 +5,35 @@ import (
|
||||
)
|
||||
|
||||
type Adapter struct {
|
||||
protocol string
|
||||
outboundType string
|
||||
outboundTag string
|
||||
network []string
|
||||
tag string
|
||||
dependencies []string
|
||||
}
|
||||
|
||||
func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter {
|
||||
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
|
||||
return Adapter{
|
||||
protocol: protocol,
|
||||
outboundType: outboundType,
|
||||
outboundTag: outboundTag,
|
||||
network: network,
|
||||
tag: tag,
|
||||
dependencies: dependencies,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter {
|
||||
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||
var dependencies []string
|
||||
if dialOptions.Detour != "" {
|
||||
dependencies = []string{dialOptions.Detour}
|
||||
}
|
||||
return NewAdapter(protocol, network, tag, dependencies)
|
||||
return NewAdapter(outboundType, outboundTag, network, dependencies)
|
||||
}
|
||||
|
||||
func (a *Adapter) Type() string {
|
||||
return a.protocol
|
||||
return a.outboundType
|
||||
}
|
||||
|
||||
func (a *Adapter) Tag() string {
|
||||
return a.tag
|
||||
return a.outboundTag
|
||||
}
|
||||
|
||||
func (a *Adapter) Network() []string {
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@@ -25,35 +25,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
|
||||
var outConn net.Conn
|
||||
var err error
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
err = N.ReportConnHandshakeSuccess(conn, outConn)
|
||||
if err != nil {
|
||||
outConn.Close()
|
||||
return err
|
||||
}
|
||||
return CopyEarlyConn(ctx, conn, outConn)
|
||||
}
|
||||
|
||||
func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var outConn net.Conn
|
||||
var err error
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else if metadata.Destination.IsFqdn() {
|
||||
var destinationAddresses []netip.Addr
|
||||
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, destinationAddresses)
|
||||
outConn, err = dialer.DialSerialNetwork(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
||||
}
|
||||
@@ -79,7 +51,11 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
||||
)
|
||||
if metadata.UDPConnect {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
|
||||
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
|
||||
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
||||
} else {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
|
||||
}
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
|
||||
}
|
||||
@@ -93,7 +69,7 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
||||
}
|
||||
} else {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
|
||||
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, this, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
||||
} else {
|
||||
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
|
||||
}
|
||||
@@ -107,11 +83,17 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
||||
return err
|
||||
}
|
||||
if destinationAddress.IsValid() {
|
||||
if metadata.Destination.IsFqdn() {
|
||||
var originDestination M.Socksaddr
|
||||
if metadata.RouteOriginalDestination.IsValid() {
|
||||
originDestination = metadata.RouteOriginalDestination
|
||||
} else {
|
||||
originDestination = metadata.Destination
|
||||
}
|
||||
if metadata.Destination != M.SocksaddrFrom(destinationAddress, metadata.Destination.Port) {
|
||||
if metadata.UDPDisableDomainUnmapping {
|
||||
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
|
||||
} else {
|
||||
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), originDestination)
|
||||
}
|
||||
}
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
@@ -129,76 +111,6 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
|
||||
}
|
||||
|
||||
func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
|
||||
defer conn.Close()
|
||||
ctx = adapter.WithContext(ctx, &metadata)
|
||||
var (
|
||||
outPacketConn net.PacketConn
|
||||
outConn net.Conn
|
||||
destinationAddress netip.Addr
|
||||
err error
|
||||
)
|
||||
if metadata.UDPConnect {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else if metadata.Destination.IsFqdn() {
|
||||
var destinationAddresses []netip.Addr
|
||||
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, destinationAddresses)
|
||||
} else {
|
||||
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
|
||||
if connRemoteAddr != metadata.Destination.Addr {
|
||||
destinationAddress = connRemoteAddr
|
||||
}
|
||||
} else {
|
||||
if len(metadata.DestinationAddresses) > 0 {
|
||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
|
||||
} else if metadata.Destination.IsFqdn() {
|
||||
var destinationAddresses []netip.Addr
|
||||
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses)
|
||||
} else {
|
||||
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
|
||||
}
|
||||
if err != nil {
|
||||
return N.ReportHandshakeFailure(conn, err)
|
||||
}
|
||||
}
|
||||
err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn)
|
||||
if err != nil {
|
||||
outPacketConn.Close()
|
||||
return err
|
||||
}
|
||||
if destinationAddress.IsValid() {
|
||||
if metadata.Destination.IsFqdn() {
|
||||
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||
}
|
||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||
natConn.UpdateDestination(destinationAddress)
|
||||
}
|
||||
}
|
||||
switch metadata.Protocol {
|
||||
case C.ProtocolSTUN:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.STUNTimeout)
|
||||
case C.ProtocolQUIC:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.QUICTimeout)
|
||||
case C.ProtocolDNS:
|
||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
|
||||
}
|
||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
|
||||
}
|
||||
|
||||
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
|
||||
if cachedReader, isCached := conn.(N.CachedReader); isCached {
|
||||
payload := cachedReader.ReadCached()
|
||||
|
||||
@@ -21,6 +21,7 @@ var _ adapter.OutboundManager = (*Manager)(nil)
|
||||
type Manager struct {
|
||||
logger log.ContextLogger
|
||||
registry adapter.OutboundRegistry
|
||||
endpoint adapter.EndpointManager
|
||||
defaultTag string
|
||||
access sync.Mutex
|
||||
started bool
|
||||
@@ -32,10 +33,11 @@ type Manager struct {
|
||||
defaultOutboundFallback adapter.Outbound
|
||||
}
|
||||
|
||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, defaultTag string) *Manager {
|
||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
registry: registry,
|
||||
endpoint: endpoint,
|
||||
defaultTag: defaultTag,
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
dependByTag: make(map[string][]string),
|
||||
@@ -56,12 +58,19 @@ func (m *Manager) Start(stage adapter.StartStage) error {
|
||||
outbounds := m.outbounds
|
||||
m.access.Unlock()
|
||||
if stage == adapter.StartStateStart {
|
||||
return m.startOutbounds(outbounds)
|
||||
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||
if !loaded {
|
||||
return E.New("default outbound not found: ", m.defaultTag)
|
||||
}
|
||||
m.defaultOutbound = defaultEndpoint
|
||||
}
|
||||
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||
} else {
|
||||
for _, outbound := range outbounds {
|
||||
err := adapter.LegacyStart(outbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +96,14 @@ func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
||||
}
|
||||
started[outboundTag] = true
|
||||
canContinue = true
|
||||
if starter, isStarter := outboundToStart.(interface {
|
||||
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
err := starter.Start(adapter.StartStateStart)
|
||||
monitor.Finish()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
}
|
||||
} else if starter, isStarter := outboundToStart.(interface {
|
||||
Start() error
|
||||
}); isStarter {
|
||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
@@ -160,9 +176,12 @@ func (m *Manager) Outbounds() []adapter.Outbound {
|
||||
|
||||
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
outbound, found := m.outboundByTag[tag]
|
||||
return outbound, found
|
||||
m.access.Unlock()
|
||||
if found {
|
||||
return outbound, true
|
||||
}
|
||||
return m.endpoint.Get(tag)
|
||||
}
|
||||
|
||||
func (m *Manager) Default() adapter.Outbound {
|
||||
@@ -234,7 +253,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
||||
for _, stage := range adapter.ListStartStages {
|
||||
err = adapter.LegacyStart(outbound, stage)
|
||||
if err != nil {
|
||||
return E.Cause(err, stage.Action(), " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,12 @@ type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, log
|
||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||
registry.register(outboundType, func() any {
|
||||
return new(Options)
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) {
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
|
||||
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
|
||||
var options *Options
|
||||
if rawOptions != nil {
|
||||
options = rawOptions.(*Options)
|
||||
}
|
||||
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -119,12 +119,3 @@ func (c *HTTPStartContext) Close() {
|
||||
client.CloseIdleConnections()
|
||||
}
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
InterfaceUpdated()
|
||||
}
|
||||
|
||||
type WIFIState struct {
|
||||
SSID string
|
||||
BSSID string
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ type (
|
||||
)
|
||||
|
||||
// Deprecated
|
||||
//
|
||||
//nolint:staticcheck
|
||||
func NewUpstreamHandler(
|
||||
metadata InboundContext,
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
@@ -34,7 +36,9 @@ func NewUpstreamHandler(
|
||||
|
||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type myUpstreamHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
@@ -42,6 +46,7 @@ type myUpstreamHandlerWrapper struct {
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -53,6 +58,7 @@ func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.C
|
||||
return w.connectionHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -64,11 +70,12 @@ func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn
|
||||
return w.packetHandler(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: removed
|
||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
return M.Metadata{
|
||||
Source: metadata.Source,
|
||||
@@ -76,14 +83,14 @@ func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
type myUpstreamContextHandlerWrapper struct {
|
||||
connectionHandler ConnectionHandlerFunc
|
||||
packetHandler PacketConnectionHandlerFunc
|
||||
errorHandler E.Handler
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func NewUpstreamContextHandler(
|
||||
connectionHandler ConnectionHandlerFunc,
|
||||
packetHandler PacketConnectionHandlerFunc,
|
||||
@@ -96,6 +103,7 @@ func NewUpstreamContextHandler(
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -107,6 +115,7 @@ func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, con
|
||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -118,6 +127,7 @@ func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Contex
|
||||
return w.packetHandler(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.errorHandler.NewError(ctx, err)
|
||||
}
|
||||
@@ -149,12 +159,15 @@ func NewRouteContextHandler(
|
||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
//
|
||||
//nolint:staticcheck
|
||||
type routeHandlerWrapper struct {
|
||||
metadata InboundContext
|
||||
router ConnectionRouter
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -166,6 +179,7 @@ func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn,
|
||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := w.metadata
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -177,6 +191,7 @@ func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.Pa
|
||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
@@ -189,6 +204,7 @@ type routeContextHandlerWrapper struct {
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -200,6 +216,7 @@ func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net
|
||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||
myMetadata := ContextFrom(ctx)
|
||||
if metadata.Source.IsValid() {
|
||||
@@ -211,6 +228,7 @@ func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, co
|
||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||
}
|
||||
|
||||
// Deprecated: Use ConnectionRouterEx instead.
|
||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||
w.logger.ErrorContext(ctx, err)
|
||||
}
|
||||
|
||||
131
box.go
131
box.go
@@ -9,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||
"github.com/sagernet/sing-box/adapter/inbound"
|
||||
"github.com/sagernet/sing-box/adapter/outbound"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
@@ -36,9 +37,11 @@ type Box struct {
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
network *route.NetworkManager
|
||||
router *route.Router
|
||||
endpoint *endpoint.Manager
|
||||
inbound *inbound.Manager
|
||||
outbound *outbound.Manager
|
||||
connection *route.ConnectionManager
|
||||
router *route.Router
|
||||
services []adapter.LifecycleService
|
||||
done chan struct{}
|
||||
}
|
||||
@@ -53,6 +56,7 @@ func Context(
|
||||
ctx context.Context,
|
||||
inboundRegistry adapter.InboundRegistry,
|
||||
outboundRegistry adapter.OutboundRegistry,
|
||||
endpointRegistry adapter.EndpointRegistry,
|
||||
) context.Context {
|
||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||
@@ -64,6 +68,11 @@ func Context(
|
||||
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||
}
|
||||
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -75,12 +84,16 @@ func New(options Options) (*Box, error) {
|
||||
}
|
||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||
|
||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
|
||||
if endpointRegistry == nil {
|
||||
return nil, E.New("missing endpoint registry in context")
|
||||
}
|
||||
if inboundRegistry == nil {
|
||||
return nil, E.New("missing inbound registry in context")
|
||||
}
|
||||
|
||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||
if outboundRegistry == nil {
|
||||
return nil, E.New("missing outbound registry in context")
|
||||
}
|
||||
@@ -118,8 +131,10 @@ func New(options Options) (*Box, error) {
|
||||
}
|
||||
|
||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry)
|
||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final)
|
||||
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||
|
||||
@@ -128,28 +143,28 @@ func New(options Options) (*Box, error) {
|
||||
return nil, E.Cause(err, "initialize network manager")
|
||||
}
|
||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
|
||||
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||
router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize router")
|
||||
}
|
||||
//nolint:staticcheck
|
||||
if len(options.LegacyInbounds) > 0 {
|
||||
for _, legacyInbound := range options.LegacyInbounds {
|
||||
options.Inbounds = append(options.Inbounds, option.Inbound{
|
||||
Type: legacyInbound.Type,
|
||||
Tag: legacyInbound.Tag,
|
||||
Options: common.Must1(legacyInbound.RawOptions()),
|
||||
})
|
||||
for i, endpointOptions := range options.Endpoints {
|
||||
var tag string
|
||||
if endpointOptions.Tag != "" {
|
||||
tag = endpointOptions.Tag
|
||||
} else {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
}
|
||||
//nolint:staticcheck
|
||||
if len(options.LegacyOutbounds) > 0 {
|
||||
for _, legacyOutbound := range options.LegacyOutbounds {
|
||||
options.Outbounds = append(options.Outbounds, option.Outbound{
|
||||
Type: legacyOutbound.Type,
|
||||
Tag: legacyOutbound.Tag,
|
||||
Options: common.Must1(legacyOutbound.RawOptions()),
|
||||
})
|
||||
err = endpointManager.Create(ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
endpointOptions.Type,
|
||||
endpointOptions.Options,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||
}
|
||||
}
|
||||
for i, inboundOptions := range options.Inbounds {
|
||||
@@ -258,9 +273,11 @@ func New(options Options) (*Box, error) {
|
||||
}
|
||||
return &Box{
|
||||
network: networkManager,
|
||||
router: router,
|
||||
endpoint: endpointManager,
|
||||
inbound: inboundManager,
|
||||
outbound: outboundManager,
|
||||
connection: connectionManager,
|
||||
router: router,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
@@ -315,27 +332,17 @@ func (s *Box) preStart() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "start logger")
|
||||
}
|
||||
for _, lifecycleService := range s.services {
|
||||
err = lifecycleService.Start(adapter.StartStateInitialize) // cache-file
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize ", lifecycleService.Name())
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, lifecycle := range []adapter.Lifecycle{
|
||||
s.network, s.router, s.outbound, s.inbound,
|
||||
} {
|
||||
err = lifecycle.Start(adapter.StartStateInitialize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, lifecycle := range []adapter.Lifecycle{
|
||||
s.outbound, s.network, s.router,
|
||||
} {
|
||||
err = lifecycle.Start(adapter.StartStateStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -345,31 +352,33 @@ func (s *Box) start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, lifecycleService := range s.services {
|
||||
err = lifecycleService.Start(adapter.StartStateStart)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize ", lifecycleService.Name())
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.inbound.Start(adapter.StartStateStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, lifecycleService := range []adapter.Lifecycle{
|
||||
s.outbound, s.network, s.router, s.inbound,
|
||||
} {
|
||||
err = lifecycleService.Start(adapter.StartStatePostStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, lifecycleService := range []adapter.Lifecycle{
|
||||
s.network, s.router, s.outbound, s.inbound,
|
||||
} {
|
||||
err = lifecycleService.Start(adapter.StartStateStarted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound, s.endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Submodule clients/android updated: 45a1f5f0aa...ea460ea5d1
Submodule clients/apple updated: c7d9b49de7...286f9717cb
@@ -58,7 +58,7 @@ func FindSDK() {
|
||||
}
|
||||
|
||||
func findNDK() bool {
|
||||
const fixedVersion = "26.3.11579264"
|
||||
const fixedVersion = "26.2.11394342"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
|
||||
@@ -69,5 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
@@ -56,10 +55,6 @@ func compileRuleSet(sourcePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleSet, err := plainRuleSet.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var outputPath string
|
||||
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||
if strings.HasSuffix(sourcePath, ".json") {
|
||||
@@ -74,7 +69,7 @@ func compileRuleSet(sourcePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2)
|
||||
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputPath)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -77,7 +78,7 @@ func convertRuleSet(sourcePath string) error {
|
||||
return err
|
||||
}
|
||||
defer outputFile.Close()
|
||||
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true)
|
||||
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputPath)
|
||||
|
||||
@@ -6,9 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -48,14 +46,10 @@ func decompileRuleSet(sourcePath string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
plainRuleSet, err := srs.Read(reader, true)
|
||||
ruleSet, err := srs.Read(reader, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleSet := option.PlainRuleSetCompat{
|
||||
Version: C.RuleSetVersion1,
|
||||
Options: plainRuleSet,
|
||||
}
|
||||
var outputPath string
|
||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||
if strings.HasSuffix(sourcePath, ".srs") {
|
||||
|
||||
@@ -56,26 +56,25 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
var plainRuleSet option.PlainRuleSet
|
||||
var ruleSet option.PlainRuleSetCompat
|
||||
switch flagRuleSetMatchFormat {
|
||||
case C.RuleSetFormatSource:
|
||||
var compat option.PlainRuleSetCompat
|
||||
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plainRuleSet, err = compat.Upgrade()
|
||||
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case C.RuleSetFormatBinary:
|
||||
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||
ruleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
||||
}
|
||||
plainRuleSet, err := ruleSet.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipAddress := M.ParseAddr(domain)
|
||||
var metadata adapter.InboundContext
|
||||
if ipAddress.IsValid() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package adguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -26,7 +27,7 @@ example.arpa
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"example.org",
|
||||
@@ -85,7 +86,7 @@ func TestHosts(t *testing.T) {
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"google.com",
|
||||
@@ -115,7 +116,7 @@ www.example.org
|
||||
`))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
require.NoError(t, err)
|
||||
matchDomain := []string{
|
||||
"example.com",
|
||||
|
||||
@@ -10,66 +10,102 @@ import (
|
||||
"github.com/sagernet/sing-box/common/conntrack"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ WireGuardListener = (*DefaultDialer)(nil)
|
||||
var (
|
||||
_ ParallelInterfaceDialer = (*DefaultDialer)(nil)
|
||||
_ WireGuardListener = (*DefaultDialer)(nil)
|
||||
)
|
||||
|
||||
type DefaultDialer struct {
|
||||
dialer4 tcpDialer
|
||||
dialer6 tcpDialer
|
||||
udpDialer4 net.Dialer
|
||||
udpDialer6 net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
isWireGuardListener bool
|
||||
dialer4 tcpDialer
|
||||
dialer6 tcpDialer
|
||||
udpDialer4 net.Dialer
|
||||
udpDialer6 net.Dialer
|
||||
udpListener net.ListenConfig
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
isWireGuardListener bool
|
||||
networkManager adapter.NetworkManager
|
||||
networkStrategy C.NetworkStrategy
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
networkLastFallback atomic.TypedValue[time.Time]
|
||||
}
|
||||
|
||||
func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) {
|
||||
var dialer net.Dialer
|
||||
var listener net.ListenConfig
|
||||
var (
|
||||
dialer net.Dialer
|
||||
listener net.ListenConfig
|
||||
interfaceFinder control.InterfaceFinder
|
||||
networkStrategy C.NetworkStrategy
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
)
|
||||
if networkManager != nil {
|
||||
interfaceFinder = networkManager.InterfaceFinder()
|
||||
} else {
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
if options.BindInterface != "" {
|
||||
var interfaceFinder control.InterfaceFinder
|
||||
if networkManager != nil {
|
||||
interfaceFinder = networkManager.InterfaceFinder()
|
||||
} else {
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager != nil && networkManager.AutoDetectInterface() {
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager != nil && networkManager.DefaultInterface() != "" {
|
||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), networkManager.DefaultInterface(), -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
var autoRedirectOutputMark uint32
|
||||
if networkManager != nil {
|
||||
autoRedirectOutputMark = networkManager.AutoRedirectOutputMark()
|
||||
}
|
||||
if autoRedirectOutputMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
}
|
||||
if options.RoutingMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||
}
|
||||
if networkManager != nil {
|
||||
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
||||
if autoRedirectOutputMark > 0 {
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
||||
if options.RoutingMark > 0 {
|
||||
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
|
||||
}
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
}
|
||||
} else if networkManager != nil && networkManager.DefaultMark() > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(networkManager.DefaultMark()))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(networkManager.DefaultMark()))
|
||||
if autoRedirectOutputMark > 0 {
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
||||
}
|
||||
if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault {
|
||||
if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil {
|
||||
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`")
|
||||
}
|
||||
networkStrategy = C.NetworkStrategy(options.NetworkStrategy)
|
||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||
networkFallbackDelay = time.Duration(options.NetworkFallbackDelay)
|
||||
if networkManager == nil || !networkManager.AutoDetectInterface() {
|
||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
||||
}
|
||||
}
|
||||
if networkManager != nil && options.BindInterface == "" && options.Inet4BindAddress == nil && options.Inet6BindAddress == nil {
|
||||
defaultOptions := networkManager.DefaultOptions()
|
||||
if defaultOptions.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if networkManager.AutoDetectInterface() {
|
||||
if defaultOptions.NetworkStrategy != C.NetworkStrategyDefault && C.NetworkStrategy(options.NetworkStrategy) == C.NetworkStrategyDefault {
|
||||
networkStrategy = defaultOptions.NetworkStrategy
|
||||
networkType = defaultOptions.NetworkType
|
||||
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||
bindFunc := networkManager.ProtectFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
@@ -130,6 +166,9 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
||||
listener.Control = control.Append(listener.Control, controlFn)
|
||||
}
|
||||
}
|
||||
if networkStrategy != C.NetworkStrategyDefault && options.TCPFastOpen {
|
||||
return nil, E.New("`tcp_fast_open` is conflict with `network_strategy` or `route.default_network_strategy`")
|
||||
}
|
||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -139,14 +178,19 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
||||
return nil, err
|
||||
}
|
||||
return &DefaultDialer{
|
||||
tcpDialer4,
|
||||
tcpDialer6,
|
||||
udpDialer4,
|
||||
udpDialer6,
|
||||
listener,
|
||||
udpAddr4,
|
||||
udpAddr6,
|
||||
options.IsWireGuardListener,
|
||||
dialer4: tcpDialer4,
|
||||
dialer6: tcpDialer6,
|
||||
udpDialer4: udpDialer4,
|
||||
udpDialer6: udpDialer6,
|
||||
udpListener: listener,
|
||||
udpAddr4: udpAddr4,
|
||||
udpAddr6: udpAddr6,
|
||||
isWireGuardListener: options.IsWireGuardListener,
|
||||
networkManager: networkManager,
|
||||
networkStrategy: networkStrategy,
|
||||
networkType: networkType,
|
||||
fallbackNetworkType: fallbackNetworkType,
|
||||
networkFallbackDelay: networkFallbackDelay,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -154,29 +198,84 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
if !address.IsValid() {
|
||||
return nil, E.New("invalid address")
|
||||
}
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||
} else {
|
||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||
} else {
|
||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||
}
|
||||
}
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||
} else {
|
||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||
}
|
||||
}
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||
} else {
|
||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if destination.IsIPv6() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||
} else {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if strategy == C.NetworkStrategyDefault {
|
||||
return d.DialContext(ctx, network, address)
|
||||
}
|
||||
if !d.networkManager.AutoDetectInterface() {
|
||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
||||
}
|
||||
var dialer net.Dialer
|
||||
if N.NetworkName(network) == N.NetworkTCP {
|
||||
dialer = dialerFromTCPDialer(d.dialer4)
|
||||
} else {
|
||||
dialer = d.udpDialer4
|
||||
}
|
||||
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||
var (
|
||||
conn net.Conn
|
||||
isPrimary bool
|
||||
err error
|
||||
)
|
||||
if !fastFallback {
|
||||
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
} else {
|
||||
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fastFallback && !isPrimary {
|
||||
d.networkLastFallback.Store(time.Now())
|
||||
}
|
||||
return trackConn(conn, nil)
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
||||
if destination.IsIPv6() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||
} else {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
}
|
||||
} else {
|
||||
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
if strategy == C.NetworkStrategyDefault {
|
||||
return d.ListenPacket(ctx, destination)
|
||||
}
|
||||
if !d.networkManager.AutoDetectInterface() {
|
||||
return nil, E.New("`route.auto_detect_interface` is require by `network_strategy`")
|
||||
}
|
||||
network := N.NetworkUDP
|
||||
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
network += "4"
|
||||
}
|
||||
return trackPacketConn(d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", strategy, interfaceType, fallbackInterfaceType, fallbackDelay))
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||
|
||||
@@ -13,3 +13,7 @@ type tcpDialer = tfo.Dialer
|
||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
||||
}
|
||||
|
||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||
return dialer.Dialer
|
||||
}
|
||||
|
||||
@@ -16,3 +16,7 @@ func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||
}
|
||||
return dialer, nil
|
||||
}
|
||||
|
||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||
return dialer
|
||||
}
|
||||
|
||||
220
common/dialer/default_parallel_interface.go
Normal file
220
common/dialer/default_parallel_interface.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) {
|
||||
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
primary bool
|
||||
}
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
|
||||
case <-returned:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case results <- dialResult{Conn: conn}:
|
||||
case <-returned:
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
primaryCtx, primaryCancel := context.WithCancel(ctx)
|
||||
defer primaryCancel()
|
||||
for _, iif := range primaryInterfaces {
|
||||
go startRacer(primaryCtx, true, iif)
|
||||
}
|
||||
var (
|
||||
fallbackTimer *time.Timer
|
||||
fallbackChan <-chan time.Time
|
||||
)
|
||||
if len(fallbackInterfaces) > 0 {
|
||||
fallbackTimer = time.NewTimer(fallbackDelay)
|
||||
defer fallbackTimer.Stop()
|
||||
fallbackChan = fallbackTimer.C
|
||||
}
|
||||
var errors []error
|
||||
for {
|
||||
select {
|
||||
case <-fallbackChan:
|
||||
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||
defer fallbackCancel()
|
||||
for _, iif := range fallbackInterfaces {
|
||||
go startRacer(fallbackCtx, false, iif)
|
||||
}
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, res.primary, nil
|
||||
}
|
||||
errors = append(errors, res.error)
|
||||
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
||||
return nil, false, E.Errors(errors...)
|
||||
}
|
||||
if res.primary && fallbackTimer != nil && fallbackTimer.Stop() {
|
||||
fallbackTimer.Reset(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {
|
||||
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, false, E.New("no available network interface")
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
primary bool
|
||||
}
|
||||
startAt := time.Now()
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||
perNetDialer := dialer
|
||||
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
select {
|
||||
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Name, ")"), primary: primary}:
|
||||
case <-returned:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case results <- dialResult{Conn: conn}:
|
||||
case <-returned:
|
||||
if primary && time.Since(startAt) <= fallbackDelay {
|
||||
resetFastFallback(time.Time{})
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, iif := range primaryInterfaces {
|
||||
go startRacer(ctx, true, iif)
|
||||
}
|
||||
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||
defer fallbackCancel()
|
||||
for _, iif := range fallbackInterfaces {
|
||||
go startRacer(fallbackCtx, false, iif)
|
||||
}
|
||||
var errors []error
|
||||
for {
|
||||
select {
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, res.primary, nil
|
||||
}
|
||||
errors = append(errors, res.error)
|
||||
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
||||
return nil, false, E.Errors(errors...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||
return nil, E.New("no available network interface")
|
||||
}
|
||||
var errors []error
|
||||
for _, primaryInterface := range primaryInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Name, ")"))
|
||||
}
|
||||
for _, fallbackInterface := range fallbackInterfaces {
|
||||
perNetListener := listener
|
||||
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
|
||||
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Name, ")"))
|
||||
}
|
||||
return nil, E.Errors(errors...)
|
||||
}
|
||||
|
||||
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
|
||||
interfaces := networkManager.NetworkInterfaces()
|
||||
switch strategy {
|
||||
case C.NetworkStrategyDefault:
|
||||
if len(interfaceType) == 0 {
|
||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
} else {
|
||||
fallbackInterfaces = append(fallbackInterfaces, iif)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, iif.Type)
|
||||
})
|
||||
}
|
||||
case C.NetworkStrategyHybrid:
|
||||
if len(interfaceType) == 0 {
|
||||
primaryInterfaces = interfaces
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, iif.Type)
|
||||
})
|
||||
}
|
||||
case C.NetworkStrategyFallback:
|
||||
if len(interfaceType) == 0 {
|
||||
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||
for _, iif := range interfaces {
|
||||
if iif.Index == defaultIf.Index {
|
||||
primaryInterfaces = append(primaryInterfaces, iif)
|
||||
} else {
|
||||
fallbackInterfaces = append(fallbackInterfaces, iif)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
primaryInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(interfaceType, iif.Type)
|
||||
})
|
||||
}
|
||||
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||
return common.Contains(fallbackInterfaceType, iif.Type)
|
||||
})
|
||||
}
|
||||
return primaryInterfaces, fallbackInterfaces
|
||||
}
|
||||
142
common/dialer/default_parallel_network.go
Normal file
142
common/dialer/default_parallel_network.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var errors []error
|
||||
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
} else {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port))
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
return nil, E.Errors(errors...)
|
||||
}
|
||||
|
||||
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = N.DefaultFallbackDelay
|
||||
}
|
||||
|
||||
returned := make(chan struct{})
|
||||
defer close(returned)
|
||||
|
||||
addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
|
||||
return address.Is4() || address.Is4In6()
|
||||
})
|
||||
addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
|
||||
return address.Is6() && !address.Is4In6()
|
||||
})
|
||||
if len(addresses4) == 0 || len(addresses6) == 0 {
|
||||
return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var primaries, fallbacks []netip.Addr
|
||||
if preferIPv6 {
|
||||
primaries = addresses6
|
||||
fallbacks = addresses4
|
||||
} else {
|
||||
primaries = addresses4
|
||||
fallbacks = addresses6
|
||||
}
|
||||
type dialResult struct {
|
||||
net.Conn
|
||||
error
|
||||
primary bool
|
||||
done bool
|
||||
}
|
||||
results := make(chan dialResult) // unbuffered
|
||||
startRacer := func(ctx context.Context, primary bool) {
|
||||
ras := primaries
|
||||
if !primary {
|
||||
ras = fallbacks
|
||||
}
|
||||
c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
select {
|
||||
case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
|
||||
case <-returned:
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
var primary, fallback dialResult
|
||||
primaryCtx, primaryCancel := context.WithCancel(ctx)
|
||||
defer primaryCancel()
|
||||
go startRacer(primaryCtx, true)
|
||||
fallbackTimer := time.NewTimer(fallbackDelay)
|
||||
defer fallbackTimer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-fallbackTimer.C:
|
||||
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||
defer fallbackCancel()
|
||||
go startRacer(fallbackCtx, false)
|
||||
|
||||
case res := <-results:
|
||||
if res.error == nil {
|
||||
return res.Conn, nil
|
||||
}
|
||||
if res.primary {
|
||||
primary = res
|
||||
} else {
|
||||
fallback = res
|
||||
}
|
||||
if primary.done && fallback.done {
|
||||
return nil, primary.error
|
||||
}
|
||||
if res.primary && fallbackTimer.Stop() {
|
||||
fallbackTimer.Reset(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
|
||||
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
var errors []error
|
||||
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err == nil {
|
||||
return conn, address, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
} else {
|
||||
for _, address := range destinationAddresses {
|
||||
conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port))
|
||||
if err == nil {
|
||||
return conn, address, nil
|
||||
}
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
return nil, netip.Addr{}, E.Errors(errors...)
|
||||
}
|
||||
@@ -2,12 +2,16 @@ package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-dns"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
@@ -49,3 +53,35 @@ func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
|
||||
}
|
||||
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")
|
||||
}
|
||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(networkManager, options)
|
||||
}
|
||||
dialer, err := NewDefault(networkManager, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewResolveParallelInterfaceDialer(
|
||||
service.FromContext[adapter.Router](ctx),
|
||||
dialer,
|
||||
true,
|
||||
dns.DomainStrategy(options.DomainStrategy),
|
||||
time.Duration(options.FallbackDelay),
|
||||
), nil
|
||||
}
|
||||
|
||||
type ParallelInterfaceDialer interface {
|
||||
N.Dialer
|
||||
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
|
||||
}
|
||||
|
||||
type ParallelNetworkDialer interface {
|
||||
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
@@ -14,7 +15,12 @@ import (
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ResolveDialer struct {
|
||||
var (
|
||||
_ N.Dialer = (*resolveDialer)(nil)
|
||||
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
||||
)
|
||||
|
||||
type resolveDialer struct {
|
||||
dialer N.Dialer
|
||||
parallel bool
|
||||
router adapter.Router
|
||||
@@ -22,8 +28,8 @@ type ResolveDialer struct {
|
||||
fallbackDelay time.Duration
|
||||
}
|
||||
|
||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
||||
return &ResolveDialer{
|
||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
||||
return &resolveDialer{
|
||||
dialer,
|
||||
parallel,
|
||||
router,
|
||||
@@ -32,7 +38,25 @@ func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, str
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
type resolveParallelNetworkDialer struct {
|
||||
resolveDialer
|
||||
dialer ParallelInterfaceDialer
|
||||
}
|
||||
|
||||
func NewResolveParallelInterfaceDialer(router adapter.Router, dialer ParallelInterfaceDialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
|
||||
return &resolveParallelNetworkDialer{
|
||||
resolveDialer{
|
||||
dialer,
|
||||
parallel,
|
||||
router,
|
||||
strategy,
|
||||
fallbackDelay,
|
||||
},
|
||||
dialer,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
@@ -57,7 +81,7 @@ func (d *ResolveDialer) DialContext(ctx context.Context, network string, destina
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
@@ -82,6 +106,59 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *ResolveDialer) Upstream() any {
|
||||
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) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.DialContext(ctx, network, destination)
|
||||
}
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
metadata.Destination = destination
|
||||
metadata.Domain = ""
|
||||
var addresses []netip.Addr
|
||||
var err error
|
||||
if d.strategy == dns.DomainStrategyAsIS {
|
||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
||||
} else {
|
||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fallbackDelay == 0 {
|
||||
fallbackDelay = d.fallbackDelay
|
||||
}
|
||||
if d.parallel {
|
||||
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
} else {
|
||||
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||
if !destination.IsFqdn() {
|
||||
return d.dialer.ListenPacket(ctx, destination)
|
||||
}
|
||||
ctx, metadata := adapter.ExtendContext(ctx)
|
||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||
metadata.Destination = destination
|
||||
metadata.Domain = ""
|
||||
var addresses []netip.Addr
|
||||
var err error
|
||||
if d.strategy == dns.DomainStrategyAsIS {
|
||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
||||
} else {
|
||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *resolveDialer) Upstream() any {
|
||||
return d.dialer
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -124,7 +123,7 @@ func (l *Listener) loopUDPOut() {
|
||||
case packet := <-l.packetOutbound:
|
||||
packet.Buffer.Release()
|
||||
N.PutPacketBuffer(packet)
|
||||
case <-time.After(time.Second):
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||
return log.ContextWithNewID(ctx)
|
||||
},
|
||||
Logger: logger,
|
||||
Handler: adapter.NewRouteContextHandler(router, logger),
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
Logger: logger,
|
||||
HandlerEx: adapter.NewRouteContextHandlerEx(router),
|
||||
Padding: options.Padding,
|
||||
Brutal: brutalOptions,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -52,6 +52,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
||||
return &Router{router, service}, nil
|
||||
}
|
||||
|
||||
// Deprecated: Use RouteConnectionEx instead.
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
if metadata.Destination == mux.Destination {
|
||||
// TODO: check if WithContext is necessary
|
||||
@@ -61,6 +62,7 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Use RoutePacketConnectionEx instead.
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/shell"
|
||||
@@ -34,7 +34,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo
|
||||
serverAddr: serverAddr,
|
||||
supportSOCKS: supportSOCKS,
|
||||
}
|
||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate)
|
||||
return proxy, nil
|
||||
}
|
||||
|
||||
@@ -66,25 +66,22 @@ func (p *DarwinSystemProxy) Disable() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DarwinSystemProxy) update(event int) {
|
||||
if event&tun.EventInterfaceUpdate == 0 {
|
||||
return
|
||||
}
|
||||
if !p.isEnabled {
|
||||
func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) {
|
||||
if !p.isEnabled || defaultInterface == nil {
|
||||
return
|
||||
}
|
||||
_ = p.update0()
|
||||
}
|
||||
|
||||
func (p *DarwinSystemProxy) update0() error {
|
||||
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||
if p.interfaceName == newInterfaceName {
|
||||
newInterface := p.monitor.DefaultInterface()
|
||||
if p.interfaceName == newInterface.Name {
|
||||
return nil
|
||||
}
|
||||
if p.interfaceName != "" {
|
||||
_ = p.Disable()
|
||||
}
|
||||
p.interfaceName = newInterfaceName
|
||||
p.interfaceName = newInterface.Name
|
||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -38,10 +38,13 @@ const (
|
||||
ruleItemWIFIBSSID
|
||||
ruleItemAdGuardDomain
|
||||
ruleItemProcessPathRegex
|
||||
ruleItemNetworkType
|
||||
ruleItemNetworkIsExpensive
|
||||
ruleItemNetworkIsConstrained
|
||||
ruleItemFinal uint8 = 0xFF
|
||||
)
|
||||
|
||||
func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) {
|
||||
func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) {
|
||||
var magicBytes [3]byte
|
||||
_, err = io.ReadFull(reader, magicBytes[:])
|
||||
if err != nil {
|
||||
@@ -54,10 +57,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
|
||||
var version uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &version)
|
||||
if err != nil {
|
||||
return ruleSet, err
|
||||
return ruleSetCompat, err
|
||||
}
|
||||
if version > C.RuleSetVersion2 {
|
||||
return ruleSet, E.New("unsupported version: ", version)
|
||||
if version > C.RuleSetVersionCurrent {
|
||||
return ruleSetCompat, E.New("unsupported version: ", version)
|
||||
}
|
||||
compressReader, err := zlib.NewReader(reader)
|
||||
if err != nil {
|
||||
@@ -68,9 +71,10 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ruleSet.Rules = make([]option.HeadlessRule, length)
|
||||
ruleSetCompat.Version = version
|
||||
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
|
||||
for i := uint64(0); i < length; i++ {
|
||||
ruleSet.Rules[i], err = readRule(bReader, recover)
|
||||
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "read rule[", i, "]")
|
||||
return
|
||||
@@ -79,18 +83,12 @@ func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err erro
|
||||
return
|
||||
}
|
||||
|
||||
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error {
|
||||
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error {
|
||||
_, err := writer.Write(MagicBytes[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var version uint8
|
||||
if generateUnstable {
|
||||
version = C.RuleSetVersion2
|
||||
} else {
|
||||
version = C.RuleSetVersion1
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, version)
|
||||
err = binary.Write(writer, binary.BigEndian, generateVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -104,7 +102,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool)
|
||||
return err
|
||||
}
|
||||
for _, rule := range ruleSet.Rules {
|
||||
err = writeRule(bWriter, rule, generateUnstable)
|
||||
err = writeRule(bWriter, rule, generateVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,12 +133,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err
|
||||
return
|
||||
}
|
||||
|
||||
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error {
|
||||
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable)
|
||||
return writeDefaultRule(writer, rule.DefaultOptions, generateVersion)
|
||||
case C.RuleTypeLogical:
|
||||
return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable)
|
||||
return writeLogicalRule(writer, rule.LogicalOptions, generateVersion)
|
||||
default:
|
||||
panic("unknown rule type: " + rule.Type)
|
||||
}
|
||||
@@ -227,6 +225,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
||||
return
|
||||
}
|
||||
rule.AdGuardDomainMatcher = matcher
|
||||
case ruleItemNetworkType:
|
||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||
case ruleItemNetworkIsExpensive:
|
||||
rule.NetworkIsExpensive = true
|
||||
case ruleItemNetworkIsConstrained:
|
||||
rule.NetworkIsConstrained = true
|
||||
case ruleItemFinal:
|
||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||
return
|
||||
@@ -240,7 +244,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
||||
}
|
||||
}
|
||||
|
||||
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error {
|
||||
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -264,7 +268,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer)
|
||||
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -341,6 +345,27 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(rule.NetworkType) > 0 {
|
||||
if generateVersion < C.RuleSetVersion3 {
|
||||
return E.New("network_type rule item is only supported in version 3 or later")
|
||||
}
|
||||
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rule.NetworkIsExpensive {
|
||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rule.NetworkIsConstrained {
|
||||
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(rule.WIFISSID) > 0 {
|
||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||
if err != nil {
|
||||
@@ -354,6 +379,9 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
||||
}
|
||||
}
|
||||
if len(rule.AdGuardDomain) > 0 {
|
||||
if generateVersion < C.RuleSetVersion2 {
|
||||
return E.New("AdGuard rule items is only supported in version 2 or later")
|
||||
}
|
||||
err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -386,6 +414,18 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
|
||||
return varbin.Write(writer, binary.BigEndian, value)
|
||||
}
|
||||
|
||||
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
|
||||
return varbin.ReadValue[[]E](reader, binary.BigEndian)
|
||||
}
|
||||
|
||||
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
|
||||
err := writer.WriteByte(itemType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return varbin.Write(writer, binary.BigEndian, value)
|
||||
}
|
||||
|
||||
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||
}
|
||||
@@ -457,7 +497,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
|
||||
return
|
||||
}
|
||||
|
||||
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error {
|
||||
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -478,7 +518,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu
|
||||
return err
|
||||
}
|
||||
for _, rule := range logicalRule.Rules {
|
||||
err = writeRule(writer, rule, generateUnstable)
|
||||
err = writeRule(writer, rule, generateVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -97,6 +97,10 @@ func (c *echServerConfig) startWatcher() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.watcher = watcher
|
||||
return nil
|
||||
}
|
||||
@@ -232,7 +236,7 @@ func NewECHServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
var echKey []byte
|
||||
if len(options.ECH.Key) > 0 {
|
||||
echKey = []byte(strings.Join(options.ECH.Key, "\n"))
|
||||
} else if options.KeyPath != "" {
|
||||
} else if options.ECH.KeyPath != "" {
|
||||
content, err := os.ReadFile(options.ECH.KeyPath)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read ECH key")
|
||||
|
||||
@@ -106,6 +106,10 @@ func (c *STDServerConfig) startWatcher() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.watcher = watcher
|
||||
return nil
|
||||
}
|
||||
|
||||
58
constant/network.go
Normal file
58
constant/network.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
type InterfaceType uint8
|
||||
|
||||
const (
|
||||
InterfaceTypeWIFI InterfaceType = iota
|
||||
InterfaceTypeCellular
|
||||
InterfaceTypeEthernet
|
||||
InterfaceTypeOther
|
||||
)
|
||||
|
||||
var (
|
||||
interfaceTypeToString = map[InterfaceType]string{
|
||||
InterfaceTypeWIFI: "wifi",
|
||||
InterfaceTypeCellular: "cellular",
|
||||
InterfaceTypeEthernet: "ethernet",
|
||||
InterfaceTypeOther: "other",
|
||||
}
|
||||
StringToInterfaceType = common.ReverseMap(interfaceTypeToString)
|
||||
)
|
||||
|
||||
func (t InterfaceType) String() string {
|
||||
name, loaded := interfaceTypeToString[t]
|
||||
if !loaded {
|
||||
return F.ToString(int(t))
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
type NetworkStrategy uint8
|
||||
|
||||
const (
|
||||
NetworkStrategyDefault NetworkStrategy = iota
|
||||
NetworkStrategyFallback
|
||||
NetworkStrategyHybrid
|
||||
)
|
||||
|
||||
var (
|
||||
networkStrategyToString = map[NetworkStrategy]string{
|
||||
NetworkStrategyDefault: "default",
|
||||
NetworkStrategyFallback: "fallback",
|
||||
NetworkStrategyHybrid: "hybrid",
|
||||
}
|
||||
StringToNetworkStrategy = common.ReverseMap(networkStrategyToString)
|
||||
)
|
||||
|
||||
func (s NetworkStrategy) String() string {
|
||||
name, loaded := networkStrategyToString[s]
|
||||
if !loaded {
|
||||
return F.ToString(int(s))
|
||||
}
|
||||
return name
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
const IsAndroid = goos.IsAndroid == 1
|
||||
|
||||
const IsDarwin = goos.IsDarwin == 1
|
||||
const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1
|
||||
|
||||
const IsDragonfly = goos.IsDragonfly == 1
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ const (
|
||||
const (
|
||||
RuleSetVersion1 = 1 + iota
|
||||
RuleSetVersion2
|
||||
RuleSetVersion3
|
||||
RuleSetVersionCurrent = RuleSetVersion3
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -46,7 +46,7 @@ func applyDebugListenOption(options option.DebugOptions) {
|
||||
|
||||
encoder := json.NewEncoder(writer)
|
||||
encoder.SetIndent("", " ")
|
||||
encoder.Encode(memObject)
|
||||
encoder.Encode(&memObject)
|
||||
})
|
||||
r.Route("/pprof", func(r chi.Router) {
|
||||
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
||||
@@ -2,10 +2,117 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.11.0-alpha.11
|
||||
#### 1.11.0-alpha.21
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.20
|
||||
|
||||
* Add UDP GSO support for WireGuard
|
||||
* Make GSO adaptive **1**
|
||||
|
||||
**1**:
|
||||
|
||||
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).
|
||||
|
||||
#### 1.11.0-alpha.19
|
||||
|
||||
* Upgrade WireGuard outbound to endpoint **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
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).
|
||||
|
||||
### 1.10.2
|
||||
|
||||
* Add deprecated warnings
|
||||
* Fix proxying websocket connections in HTTP/mixed inbounds
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.18
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.16
|
||||
|
||||
* Add `cache_capacity` DNS option **1**
|
||||
* Add `override_address` and `override_port` route options **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [DNS](/configuration/dns/#cache_capacity).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Rule Action](/configuration/route/#override_address) and
|
||||
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
#### 1.11.0-alpha.15
|
||||
|
||||
* Improve multi network dialing **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
New options allow you to configure the network strategy flexibly.
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_strategy),
|
||||
[Rule Action](/configuration/route/rule_action/#network_strategy)
|
||||
and [Route](/configuration/route/#default_network_strategy).
|
||||
|
||||
#### 1.11.0-alpha.14
|
||||
|
||||
* Add multi network dialing **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
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).
|
||||
|
||||
#### 1.11.0-alpha.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.11.0-alpha.12
|
||||
|
||||
* Merge route options to route actions **1**
|
||||
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
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/).
|
||||
|
||||
**2**:
|
||||
|
||||
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/).
|
||||
|
||||
#### 1.11.0-alpha.9
|
||||
|
||||
* Improve tun compatibility **1**
|
||||
@@ -32,7 +139,7 @@ See [Rule](/configuration/route/rule/),
|
||||
[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 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).
|
||||
|
||||
@@ -40,7 +147,7 @@ and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legac
|
||||
|
||||
Similar to Surge's pre-matching.
|
||||
|
||||
Specifically, the new rule actions allow you to reject connections with
|
||||
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.
|
||||
|
||||
@@ -137,7 +244,7 @@ allows you to write headless rules directly without creating a rule-set file.
|
||||
|
||||
**8**:
|
||||
|
||||
With the new access control options, not only can you allow Clash dashboards
|
||||
With new access control options, not only can you allow Clash dashboards
|
||||
to access the Clash API on your local network,
|
||||
you can also manually limit the websites that can access the API instead of allowing everyone.
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# FakeIP
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# FakeIP
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
||||
# DNS
|
||||
|
||||
@@ -16,6 +20,7 @@
|
||||
"disable_cache": false,
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"cache_capacity": 0,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
@@ -58,6 +63,14 @@ Disable dns cache expire.
|
||||
|
||||
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
|
||||
|
||||
#### cache_capacity
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
LRU cache capacity.
|
||||
|
||||
Value less than 1024 will be ignored.
|
||||
|
||||
#### reverse_mapping
|
||||
|
||||
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [cache_capacity](#cache_capacity)
|
||||
|
||||
# DNS
|
||||
|
||||
@@ -16,6 +20,7 @@
|
||||
"disable_cache": false,
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"cache_capacity": 0,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
@@ -57,6 +62,14 @@
|
||||
|
||||
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
|
||||
|
||||
#### cache_capacity
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
LRU 缓存容量。
|
||||
|
||||
小于 1024 的值将被忽略。
|
||||
|
||||
#### reverse_mapping
|
||||
|
||||
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。
|
||||
|
||||
@@ -8,7 +8,10 @@ icon: material/new-box
|
||||
:material-alert: [server](#server)
|
||||
:material-alert: [disable_cache](#disable_cache)
|
||||
:material-alert: [rewrite_ttl](#rewrite_ttl)
|
||||
:material-alert: [client_subnet](#client_subnet)
|
||||
:material-alert: [client_subnet](#client_subnet)
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-plus: [network_is_expensive](#network_is_expensive)
|
||||
:material-plus: [network_is_constrained](#network_is_constrained)
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
@@ -125,6 +128,11 @@ icon: material/new-box
|
||||
1000
|
||||
],
|
||||
"clash_mode": "direct",
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -310,6 +318,39 @@ Match user id.
|
||||
|
||||
Match Clash mode.
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match network type.
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match if network is considered Metered (on Android) or considered expensive,
|
||||
such as Cellular or a Personal Hotspot (on Apple platforms).
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Apple platforms.
|
||||
|
||||
Match if network is in Low Data Mode.
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -2,6 +2,17 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [action](#action)
|
||||
:material-alert: [server](#server)
|
||||
:material-alert: [disable_cache](#disable_cache)
|
||||
:material-alert: [rewrite_ttl](#rewrite_ttl)
|
||||
:material-alert: [client_subnet](#client_subnet)
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-plus: [network_is_expensive](#network_is_expensive)
|
||||
:material-plus: [network_is_constrained](#network_is_constrained)
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
@@ -117,6 +128,11 @@ icon: material/new-box
|
||||
1000
|
||||
],
|
||||
"clash_mode": "direct",
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -135,17 +151,15 @@ icon: material/new-box
|
||||
"outbound": [
|
||||
"direct"
|
||||
],
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [],
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
"action": "route",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -304,6 +318,39 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
匹配 Clash 模式。
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配网络类型。
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
|
||||
像蜂窝网络或个人热点 (在 Apple 平台)。
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络在低数据模式下。
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
@@ -332,7 +379,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
@@ -352,29 +399,35 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
`any` 可作为值用于匹配任意出站。
|
||||
|
||||
#### server
|
||||
#### action
|
||||
|
||||
==必填==
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
参阅 [规则动作](../rule_action/)。
|
||||
|
||||
#### server
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
|
||||
#### rewrite_ttl
|
||||
|
||||
重写 DNS 回应中的 TTL。
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
|
||||
#### client_subnet
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
||||
已移动到 [DNS 规则动作](../rule_action#route).
|
||||
|
||||
### 地址筛选字段
|
||||
|
||||
@@ -420,8 +473,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### mode
|
||||
|
||||
==必填==
|
||||
|
||||
`and` 或 `or`
|
||||
|
||||
#### rules
|
||||
|
||||
包括的规则。
|
||||
==必填==
|
||||
|
||||
包括的规则。
|
||||
@@ -2,8 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# DNS Rule Action
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
### route
|
||||
@@ -12,8 +10,6 @@ icon: material/new-box
|
||||
{
|
||||
"action": "route", // default
|
||||
"server": "",
|
||||
|
||||
// for compatibility
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 0,
|
||||
"client_subnet": null
|
||||
@@ -28,23 +24,6 @@ icon: material/new-box
|
||||
|
||||
Tag of target server.
|
||||
|
||||
#### disable_cache/rewrite_ttl/client_subnet
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable cache and save cache in this query.
|
||||
@@ -61,6 +40,19 @@ If value is an IP address instead of prefix, `/32` or `/128` will be appended au
|
||||
|
||||
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
`route-options` set options for routing.
|
||||
|
||||
### reject
|
||||
|
||||
```json
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# DNS 规则动作
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
### route
|
||||
@@ -28,24 +26,6 @@ icon: material/new-box
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache/rewrite_ttl/client_subnet
|
||||
|
||||
!!! failure "自 sing-box 1.11.0 起"
|
||||
|
||||
旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
@@ -62,6 +42,19 @@ icon: material/new-box
|
||||
|
||||
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": null,
|
||||
"client_subnet": null
|
||||
}
|
||||
```
|
||||
|
||||
`route-options` 为路由设置选项。
|
||||
|
||||
### reject
|
||||
|
||||
```json
|
||||
|
||||
32
docs/configuration/endpoint/index.md
Normal file
32
docs/configuration/endpoint/index.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
# Endpoint
|
||||
|
||||
Endpoint is protocols that has both inbound and outbound behavior.
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
| Type | Format |
|
||||
|-------------|---------------------------|
|
||||
| `wireguard` | [WireGuard](./wireguard/) |
|
||||
|
||||
#### tag
|
||||
|
||||
The tag of the endpoint.
|
||||
32
docs/configuration/endpoint/index.zh.md
Normal file
32
docs/configuration/endpoint/index.zh.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
# 端点
|
||||
|
||||
端点是具有入站和出站行为的协议。
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "",
|
||||
"tag": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-------------|---------------------------|
|
||||
| `wireguard` | [WireGuard](./wiregaurd/) |
|
||||
|
||||
#### tag
|
||||
|
||||
端点的标签。
|
||||
133
docs/configuration/endpoint/wireguard.md
Normal file
133
docs/configuration/endpoint/wireguard.md
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
|
||||
"system": false,
|
||||
"name": "",
|
||||
"mtu": 1408,
|
||||
"address": [],
|
||||
"private_key": "",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "",
|
||||
"pre_shared_key": "",
|
||||
"allowed_ips": [],
|
||||
"persistent_keepalive_interval": 0,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
],
|
||||
"udp_timeout": "",
|
||||
"workers": 0,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Fields
|
||||
|
||||
#### system
|
||||
|
||||
Use system interface.
|
||||
|
||||
Requires privilege and cannot conflict with exists system interfaces.
|
||||
|
||||
#### name
|
||||
|
||||
Custom interface name for system interface.
|
||||
|
||||
#### mtu
|
||||
|
||||
WireGuard MTU.
|
||||
|
||||
`1408` will be used by default.
|
||||
|
||||
#### address
|
||||
|
||||
==Required==
|
||||
|
||||
List of IP (v4 or v6) address prefixes to be assigned to the interface.
|
||||
|
||||
#### private_key
|
||||
|
||||
==Required==
|
||||
|
||||
WireGuard requires base64-encoded public and private keys. These can be generated using the wg(8) utility:
|
||||
|
||||
```shell
|
||||
wg genkey
|
||||
echo "private key" || wg pubkey
|
||||
```
|
||||
|
||||
or `sing-box generate wg-keypair`.
|
||||
|
||||
#### peers
|
||||
|
||||
==Required==
|
||||
|
||||
List of WireGuard peers.
|
||||
|
||||
#### peers.address
|
||||
|
||||
WireGuard peer address.
|
||||
|
||||
#### peers.port
|
||||
|
||||
WireGuard peer port.
|
||||
|
||||
#### peers.public_key
|
||||
|
||||
==Required==
|
||||
|
||||
WireGuard peer public key.
|
||||
|
||||
#### peers.pre_shared_key
|
||||
|
||||
WireGuard peer pre-shared key.
|
||||
|
||||
#### peers.allowed_ips
|
||||
|
||||
==Required==
|
||||
|
||||
WireGuard allowed IPs.
|
||||
|
||||
#### peers.persistent_keepalive_interval
|
||||
|
||||
WireGuard persistent keepalive interval, in seconds.
|
||||
|
||||
Disabled by default.
|
||||
|
||||
#### peers.reserved
|
||||
|
||||
WireGuard reserved field bytes.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time.
|
||||
|
||||
`5m` will be used by default.
|
||||
|
||||
#### workers
|
||||
|
||||
WireGuard worker count.
|
||||
|
||||
CPU count is used by default.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
135
docs/configuration/endpoint/wireguard.zh.md
Normal file
135
docs/configuration/endpoint/wireguard.zh.md
Normal file
@@ -0,0 +1,135 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
|
||||
"system": false,
|
||||
"name": "",
|
||||
"mtu": 1408,
|
||||
"address": [],
|
||||
"private_key": "",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "",
|
||||
"pre_shared_key": "",
|
||||
"allowed_ips": [],
|
||||
"persistent_keepalive_interval": 0,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
],
|
||||
"udp_timeout": "",
|
||||
"workers": 0,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
### 字段
|
||||
|
||||
#### system_interface
|
||||
|
||||
使用系统设备。
|
||||
|
||||
需要特权且不能与已有系统接口冲突。
|
||||
|
||||
#### name
|
||||
|
||||
为系统接口自定义设备名称。
|
||||
|
||||
#### mtu
|
||||
|
||||
WireGuard MTU。
|
||||
|
||||
默认使用 1408。
|
||||
|
||||
#### address
|
||||
|
||||
==必填==
|
||||
|
||||
接口的 IPv4/IPv6 地址或地址段的列表您。
|
||||
|
||||
要分配给接口的 IP(v4 或 v6)地址段列表。
|
||||
|
||||
#### private_key
|
||||
|
||||
==必填==
|
||||
|
||||
WireGuard 需要 base64 编码的公钥和私钥。 这些可以使用 wg(8) 实用程序生成:
|
||||
|
||||
```shell
|
||||
wg genkey
|
||||
echo "private key" || wg pubkey
|
||||
```
|
||||
|
||||
或 `sing-box generate wg-keypair`.
|
||||
|
||||
#### peers
|
||||
|
||||
==必填==
|
||||
|
||||
WireGuard 对等方的列表。
|
||||
|
||||
#### peers.address
|
||||
|
||||
对等方的 IP 地址。
|
||||
|
||||
#### peers.port
|
||||
|
||||
对等方的 WireGuard 端口。
|
||||
|
||||
#### peers.public_key
|
||||
|
||||
==必填==
|
||||
|
||||
对等方的 WireGuard 公钥。
|
||||
|
||||
#### peers.pre_shared_key
|
||||
|
||||
对等方的预共享密钥。
|
||||
|
||||
#### peers.allowed_ips
|
||||
|
||||
==必填==
|
||||
|
||||
对等方的允许 IP 地址。
|
||||
|
||||
#### peers.persistent_keepalive_interval
|
||||
|
||||
对等方的持久性保持活动间隔,以秒为单位。
|
||||
|
||||
默认禁用。
|
||||
|
||||
#### peers.reserved
|
||||
|
||||
对等方的保留字段字节。
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间。
|
||||
|
||||
默认使用 `5m`。
|
||||
|
||||
#### workers
|
||||
|
||||
WireGuard worker 数量。
|
||||
|
||||
默认使用 CPU 数量。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
@@ -66,11 +66,11 @@ Only available in the ShadowTLS protocol 3.
|
||||
|
||||
==Required==
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial/).
|
||||
Handshake server address and [Dial Fields](/configuration/shared/dial/).
|
||||
|
||||
#### handshake_for_server_name
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial/) for specific server name.
|
||||
Handshake server address and [Dial Fields](/configuration/shared/dial/) for specific server name.
|
||||
|
||||
Only available in the ShadowTLS protocol 2/3.
|
||||
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
@@ -46,16 +50,7 @@ icon: material/new-box
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
@@ -69,28 +64,11 @@ icon: material/new-box
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
@@ -137,8 +115,31 @@ icon: material/new-box
|
||||
"match_domain": []
|
||||
}
|
||||
},
|
||||
...
|
||||
// Listen Fields
|
||||
|
||||
// Deprecated
|
||||
"gso": false,
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
|
||||
... // Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
@@ -166,7 +167,7 @@ IPv4 and IPv6 prefix for the tun interface.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
`inet4_address` is merged to `address` and will be removed in sing-box 1.12.0.
|
||||
|
||||
IPv4 prefix for the tun interface.
|
||||
|
||||
@@ -174,7 +175,7 @@ IPv4 prefix for the tun interface.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
`inet6_address` is merged to `address` and will be removed in sing-box 1.12.0.
|
||||
|
||||
IPv6 prefix for the tun interface.
|
||||
|
||||
@@ -184,6 +185,10 @@ The maximum transmission unit.
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works, and will be removed in sing-box 1.12.0.
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
!!! quote ""
|
||||
@@ -284,7 +289,7 @@ Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
|
||||
`inet4_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address)
|
||||
instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
@@ -293,7 +298,7 @@ Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
|
||||
`inet6_route_address` is deprecated and will be removed in sing-box 1.12.0, please use [route_address](#route_address)
|
||||
instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
@@ -308,7 +313,7 @@ Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
|
||||
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please
|
||||
use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
@@ -317,7 +322,7 @@ Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
|
||||
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.12.0, please
|
||||
use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
@@ -360,7 +365,9 @@ Performance may degrade slightly, so it is not recommended to enable on when it
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds, default is 300 (5 minutes).
|
||||
UDP NAT expiration time.
|
||||
|
||||
`5m` will be used by default.
|
||||
|
||||
#### stack
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
:material-delete-clock: [inet4_address](#inet4_address)
|
||||
@@ -46,16 +50,7 @@ icon: material/new-box
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
@@ -69,28 +64,11 @@ icon: material/new-box
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
@@ -137,6 +115,29 @@ icon: material/new-box
|
||||
"match_domain": []
|
||||
}
|
||||
},
|
||||
|
||||
// 已弃用
|
||||
"gso": false,
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
|
||||
... // 监听字段
|
||||
}
|
||||
@@ -168,7 +169,7 @@ tun 接口的 IPv4 和 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet4_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
==必填==
|
||||
|
||||
@@ -178,7 +179,7 @@ tun 接口的 IPv4 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet6_address` 已合并到 `address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
tun 接口的 IPv6 前缀。
|
||||
|
||||
@@ -188,6 +189,10 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
GSO 对于透明代理场景没有优势,已废弃和不再生效,且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
!!! quote ""
|
||||
@@ -288,7 +293,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
@@ -296,7 +301,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
@@ -310,7 +315,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
@@ -318,7 +323,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
|
||||
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.12.0 中被移除。
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
@@ -356,7 +361,9 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间,以秒为单位,默认为 300(5 分钟)。
|
||||
UDP NAT 过期时间。
|
||||
|
||||
默认使用 `5m`。
|
||||
|
||||
#### stack
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ sing-box uses JSON for configuration files.
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
@@ -23,6 +24,7 @@ sing-box uses JSON for configuration files.
|
||||
| `log` | [Log](./log/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
| `ntp` | [NTP](./ntp/) |
|
||||
| `endpoints` | [Endpoint](./endpoint/) |
|
||||
| `inbounds` | [Inbound](./inbound/) |
|
||||
| `outbounds` | [Outbound](./outbound/) |
|
||||
| `route` | [Route](./route/) |
|
||||
|
||||
@@ -8,6 +8,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
{
|
||||
"log": {},
|
||||
"dns": {},
|
||||
"endpoints": [],
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
@@ -21,6 +22,7 @@ sing-box 使用 JSON 作为配置文件格式。
|
||||
|----------------|------------------------|
|
||||
| `log` | [日志](./log/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
| `endpoints` | [端点](./endpoint/) |
|
||||
| `inbounds` | [入站](./inbound/) |
|
||||
| `outbounds` | [出站](./outbound/) |
|
||||
| `route` | [路由](./route/) |
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-alert-decagram: [override_address](#override_address)
|
||||
:material-alert-decagram: [override_port](#override_port)
|
||||
|
||||
`direct` outbound send requests directly.
|
||||
|
||||
### Structure
|
||||
@@ -9,7 +18,6 @@
|
||||
|
||||
"override_address": "1.0.0.1",
|
||||
"override_port": 53,
|
||||
"proxy_protocol": 0,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
@@ -19,16 +27,20 @@
|
||||
|
||||
#### override_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
Override the connection destination address.
|
||||
|
||||
#### override_port
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
Destination override fields are deprecated in sing-box 1.11.0 and will be removed in sing-box 1.13.0, see [Migration](/migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
Override the connection destination port.
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
Write [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header.
|
||||
|
||||
Protocol value can be `1` or `2`.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-alert-decagram: [override_address](#override_address)
|
||||
:material-alert-decagram: [override_port](#override_port)
|
||||
|
||||
`direct` 出站直接发送请求。
|
||||
|
||||
### 结构
|
||||
@@ -9,7 +18,6 @@
|
||||
|
||||
"override_address": "1.0.0.1",
|
||||
"override_port": 53,
|
||||
"proxy_protocol": 0,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
@@ -19,18 +27,20 @@
|
||||
|
||||
#### override_address
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。
|
||||
|
||||
覆盖连接目标地址。
|
||||
|
||||
#### override_port
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
目标覆盖字段在 sing-box 1.11.0 中已废弃,并将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。
|
||||
|
||||
覆盖连接目标端口。
|
||||
|
||||
#### proxy_protocol
|
||||
|
||||
写出 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) 到连接头。
|
||||
|
||||
可用协议版本值:`1` 或 `2`。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
WireGuard outbound is deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
@@ -12,10 +24,9 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"system_interface": false,
|
||||
"gso": false,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.2/32"
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||
"peers": [
|
||||
@@ -37,6 +48,10 @@
|
||||
"mtu": 1408,
|
||||
"network": "tcp",
|
||||
|
||||
// Deprecated
|
||||
|
||||
"gso": false,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
```
|
||||
@@ -69,6 +84,10 @@ Custom interface name for system interface.
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.11.0"
|
||||
|
||||
GSO will be automatically enabled when available since sing-box 1.11.0.
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
---
|
||||
icon: material/delete-clock
|
||||
---
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
WireGuard 出站已被启用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-delete-alert: [gso](#gso)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
@@ -12,10 +24,9 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"system_interface": false,
|
||||
"gso": false,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.2/32"
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "YNXtAzepDqRv9H52osJVDQnznT5AM11eCK3ESpwSt04=",
|
||||
"peer_public_key": "Z1XXLsKYkYxuiYjJIkRvtIKFepCYHTgON+GwPq7SOV4=",
|
||||
@@ -24,6 +35,10 @@
|
||||
"workers": 4,
|
||||
"mtu": 1408,
|
||||
"network": "tcp",
|
||||
|
||||
// 废弃的
|
||||
|
||||
"gso": false,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
@@ -57,6 +72,10 @@
|
||||
|
||||
#### gso
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
自 sing-box 1.11.0 起,GSO 将可用时自动启用。
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# Route
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [default_network_strategy](#default_network_strategy)
|
||||
:material-plus: [default_network_type](#default_network_type)
|
||||
:material-plus: [default_fallback_network_type](#default_fallback_network_type)
|
||||
:material-plus: [default_fallback_delay](#default_fallback_delay)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@@ -18,18 +29,21 @@
|
||||
"final": "",
|
||||
"auto_detect_interface": false,
|
||||
"override_android_vpn": false,
|
||||
"default_interface": "en0",
|
||||
"default_mark": 233
|
||||
"default_interface": "",
|
||||
"default_mark": 0,
|
||||
"default_network_strategy": "",
|
||||
"default_network_type": [],
|
||||
"default_fallback_network_type": [],
|
||||
"default_fallback_delay": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
!!! note ""
|
||||
|
||||
| Key | Format |
|
||||
|-----------|----------------------|
|
||||
| `geoip` | [GeoIP](./geoip/) |
|
||||
| `geosite` | [Geosite](./geosite/) |
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Fields
|
||||
|
||||
#### rules
|
||||
|
||||
@@ -81,4 +95,34 @@ Takes no effect if `auto_detect_interface` is set.
|
||||
|
||||
Set routing mark by default.
|
||||
|
||||
Takes no effect if `outbound.routing_mark` is set.
|
||||
Takes no effect if `outbound.routing_mark` is set.
|
||||
|
||||
#### default_network_strategy
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
|
||||
|
||||
Takes no effect if `outbound.bind_interface`, `outbound.inet4_bind_address` or `outbound.inet6_bind_address` is set.
|
||||
|
||||
Can be overrides by `outbound.network_strategy`.
|
||||
|
||||
Conflicts with `default_interface`.
|
||||
|
||||
#### default_network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_type) for details.
|
||||
|
||||
#### default_fallback_network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details.
|
||||
|
||||
#### default_fallback_delay
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details.
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# 路由
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [network_strategy](#network_strategy)
|
||||
:material-plus: [default_network_type](#default_network_type)
|
||||
:material-plus: [default_fallback_network_type](#default_fallback_network_type)
|
||||
:material-plus: [default_fallback_delay](#default_fallback_delay)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@@ -18,12 +29,18 @@
|
||||
"final": "",
|
||||
"auto_detect_interface": false,
|
||||
"override_android_vpn": false,
|
||||
"default_interface": "en0",
|
||||
"default_mark": 233
|
||||
"default_interface": "",
|
||||
"default_mark": 0,
|
||||
"default_network_strategy": "",
|
||||
"default_fallback_delay": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
### 字段
|
||||
|
||||
| 键 | 格式 |
|
||||
@@ -82,3 +99,33 @@
|
||||
默认为出站连接设置路由标记。
|
||||
|
||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
||||
|
||||
#### network_strategy
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。
|
||||
|
||||
当 `outbound.bind_interface`, `outbound.inet4_bind_address` 或 `outbound.inet6_bind_address` 已设置时不生效。
|
||||
|
||||
可以被 `outbound.network_strategy` 覆盖。
|
||||
|
||||
与 `default_interface` 冲突。
|
||||
|
||||
#### default_network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#default_network_type)。
|
||||
|
||||
#### default_fallback_network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#default_fallback_network_type)。
|
||||
|
||||
#### default_fallback_delay
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。
|
||||
|
||||
@@ -5,7 +5,10 @@ icon: material/new-box
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [action](#action)
|
||||
:material-alert: [outbound](#outbound)
|
||||
:material-alert: [outbound](#outbound)
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-plus: [network_is_expensive](#network_is_expensive)
|
||||
:material-plus: [network_is_constrained](#network_is_constrained)
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
@@ -120,6 +123,11 @@ icon: material/new-box
|
||||
1000
|
||||
],
|
||||
"clash_mode": "direct",
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -322,6 +330,39 @@ Match user id.
|
||||
|
||||
Match Clash mode.
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match network type.
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match if network is considered Metered (on Android) or considered expensive,
|
||||
such as Cellular or a Personal Hotspot (on Apple platforms).
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Apple platforms.
|
||||
|
||||
Match if network is in Low Data Mode.
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
@@ -5,7 +5,10 @@ icon: material/new-box
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [action](#action)
|
||||
:material-alert: [outbound](#outbound)
|
||||
:material-alert: [outbound](#outbound)
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-plus: [network_is_expensive](#network_is_expensive)
|
||||
:material-plus: [network_is_constrained](#network_is_constrained)
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
@@ -13,7 +16,6 @@ icon: material/new-box
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [process_path_regex](#process_path_regex)
|
||||
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@@ -118,6 +120,11 @@ icon: material/new-box
|
||||
1000
|
||||
],
|
||||
"clash_mode": "direct",
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -153,7 +160,7 @@ icon: material/new-box
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
||||
|
||||
### Default Fields
|
||||
### 默认字段
|
||||
|
||||
!!! note ""
|
||||
|
||||
@@ -320,6 +327,39 @@ icon: material/new-box
|
||||
|
||||
匹配 Clash 模式。
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配网络类型。
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
|
||||
像蜂窝网络或个人热点 (在 Apple 平台)。
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络在低数据模式下。
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
@@ -348,7 +388,7 @@ icon: material/new-box
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
@@ -366,13 +406,13 @@ icon: material/new-box
|
||||
|
||||
==必填==
|
||||
|
||||
参阅 [规则行动](../rule_action/)。
|
||||
参阅 [规则动作](../rule_action/)。
|
||||
|
||||
#### outbound
|
||||
|
||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
||||
|
||||
已移动到 [规则行动](../rule_action#route).
|
||||
已移动到 [规则动作](../rule_action#route).
|
||||
|
||||
### 逻辑字段
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# Rule Action
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
## Final actions
|
||||
|
||||
### route
|
||||
@@ -13,10 +9,16 @@ icon: material/new-box
|
||||
```json
|
||||
{
|
||||
"action": "route", // default
|
||||
"outbound": ""
|
||||
"outbound": "",
|
||||
|
||||
... // route-options Fields
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
`route` inherits the classic rule behavior of routing connection to the specified outbound.
|
||||
|
||||
#### outbound
|
||||
@@ -25,11 +27,19 @@ icon: material/new-box
|
||||
|
||||
Tag of target outbound.
|
||||
|
||||
#### route-options Fields
|
||||
|
||||
See `route-options` fields below.
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"override_address": "",
|
||||
"override_port": 0,
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false
|
||||
}
|
||||
@@ -37,6 +47,33 @@ Tag of target outbound.
|
||||
|
||||
`route-options` set options for routing.
|
||||
|
||||
#### override_address
|
||||
|
||||
Override the connection destination address.
|
||||
|
||||
#### override_port
|
||||
|
||||
Override the connection destination port.
|
||||
|
||||
#### network_strategy
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_strategy) for details.
|
||||
|
||||
Only take effect if outbound is direct without `outbound.bind_interface`,
|
||||
`outbound.inet4_bind_address` and `outbound.inet6_bind_address` set.
|
||||
|
||||
#### network_type
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#network_type) for details.
|
||||
|
||||
#### fallback_network_type
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#fallback_network_type) for details.
|
||||
|
||||
#### fallback_delay
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/#fallback_delay) for details.
|
||||
|
||||
#### udp_disable_domain_unmapping
|
||||
|
||||
If enabled, for UDP proxy requests addressed to a domain,
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# 规则动作
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
## 最终动作
|
||||
|
||||
### route
|
||||
@@ -14,7 +10,8 @@ icon: material/new-box
|
||||
{
|
||||
"action": "route", // 默认
|
||||
"outbound": "",
|
||||
"udp_disable_domain_unmapping": false
|
||||
|
||||
... // route-options 字段
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,16 +23,57 @@ icon: material/new-box
|
||||
|
||||
目标出站的标签。
|
||||
|
||||
#### route-options 字段
|
||||
|
||||
参阅下方的 `route-options` 字段。
|
||||
|
||||
### route-options
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "route-options",
|
||||
"override_address": "",
|
||||
"override_port": 0,
|
||||
"network_strategy": "",
|
||||
"fallback_delay": "",
|
||||
"udp_disable_domain_unmapping": false,
|
||||
"udp_connect": false
|
||||
"udp_connect": false
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
`route-options` 为路由设置选项。
|
||||
|
||||
#### override_address
|
||||
|
||||
覆盖目标地址。
|
||||
|
||||
#### override_port
|
||||
|
||||
覆盖目标端口。
|
||||
|
||||
#### network_strategy
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#network_strategy)。
|
||||
|
||||
仅当出站为 `direct` 且 `outbound.bind_interface`, `outbound.inet4_bind_address`
|
||||
且 `outbound.inet6_bind_address` 未设置时生效。
|
||||
|
||||
#### network_type
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#network_type)。
|
||||
|
||||
#### fallback_network_type
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#fallback_network_type)。
|
||||
|
||||
#### fallback_delay
|
||||
|
||||
详情参阅 [拨号字段](/configuration/shared/dial/#fallback_delay)。
|
||||
|
||||
#### udp_disable_domain_unmapping
|
||||
|
||||
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# AdGuard DNS Filter
|
||||
|
||||
!!! 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,
|
||||
|
||||
68
docs/configuration/rule-set/adguard.zh.md
Normal file
68
docs/configuration/rule-set/adguard.zh.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
sing-box 支持其他项目的一些规则集格式,这些格式无法完全转换为 sing-box,
|
||||
目前只有 AdGuard DNS Filter。
|
||||
|
||||
这些格式不直接作为源格式支持,
|
||||
而是需要将它们转换为二进制规则集。
|
||||
|
||||
## 转换
|
||||
|
||||
使用 `sing-box rule-set convert --type adguard [--output <file-name>.srs] <file-name>.txt` 以转换为二进制规则集。
|
||||
|
||||
## 性能
|
||||
|
||||
AdGuard 将所有规则保存在内存中并按顺序匹配,
|
||||
而 sing-box 选择高性能和较小的内存使用量。
|
||||
作为权衡,您无法知道匹配了哪个规则项。
|
||||
|
||||
## 兼容性
|
||||
|
||||
[AdGuardSDNSFilter](https://github.com/AdguardTeam/AdGuardSDNSFilter)
|
||||
中的几乎所有规则以及 [adguard-filter-list](https://github.com/ppfeufer/adguard-filter-list)
|
||||
中列出的规则集中的规则均受支持。
|
||||
|
||||
## 支持的格式
|
||||
|
||||
### AdGuard Filter
|
||||
|
||||
#### 基本规则语法
|
||||
|
||||
| 语法 | 支持 |
|
||||
|--------|------------------|
|
||||
| `@@` | :material-check: |
|
||||
| `\|\|` | :material-check: |
|
||||
| `\|` | :material-check: |
|
||||
| `^` | :material-check: |
|
||||
| `*` | :material-check: |
|
||||
|
||||
#### 主机语法
|
||||
|
||||
| 语法 | 示例 | 支持 |
|
||||
|-------------|--------------------------|--------------------------|
|
||||
| Scheme | `https://` | :material-alert: Ignored |
|
||||
| Domain Host | `example.org` | :material-check: |
|
||||
| IP Host | `1.1.1.1`, `10.0.0.` | :material-close: |
|
||||
| Regexp | `/regexp/` | :material-check: |
|
||||
| Port | `example.org:80` | :material-close: |
|
||||
| Path | `example.org/path/ad.js` | :material-close: |
|
||||
|
||||
#### 描述符语法
|
||||
|
||||
| 描述符 | 支持 |
|
||||
|-----------------------|--------------------------|
|
||||
| `$important` | :material-check: |
|
||||
| `$dnsrewrite=0.0.0.0` | :material-alert: Ignored |
|
||||
| 任何其他描述符 | :material-close: |
|
||||
|
||||
### Hosts
|
||||
|
||||
只有 IP 地址为 `0.0.0.0` 的条目将被接受。
|
||||
|
||||
### 简易
|
||||
|
||||
当所有行都是有效域时,它们被视为简单的逐行域规则, 与 hosts 一样,只匹配完全相同的域。
|
||||
@@ -1,3 +1,13 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-plus: [network_is_expensive](#network_is_expensive)
|
||||
:material-plus: [network_is_constrained](#network_is_constrained)
|
||||
|
||||
### Structure
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
@@ -63,6 +73,11 @@
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
@@ -177,6 +192,39 @@ Match process path using regular expression.
|
||||
|
||||
Match android package name.
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match network type.
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match if network is considered Metered (on Android) or considered expensive,
|
||||
such as Cellular or a Personal Hotspot (on Apple platforms).
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Apple platforms.
|
||||
|
||||
Match if network is in Low Data Mode.
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
258
docs/configuration/rule-set/headless-rule.zh.md
Normal file
258
docs/configuration/rule-set/headless-rule.zh.md
Normal file
@@ -0,0 +1,258 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [network_type](#network_type)
|
||||
:material-alert: [network_is_expensive](#network_is_expensive)
|
||||
:material-alert: [network_is_constrained](#network_is_constrained)
|
||||
|
||||
### 结构
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
```json
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"query_type": [
|
||||
"A",
|
||||
"HTTPS",
|
||||
32768
|
||||
],
|
||||
"network": [
|
||||
"tcp"
|
||||
],
|
||||
"domain": [
|
||||
"test.com"
|
||||
],
|
||||
"domain_suffix": [
|
||||
".cn"
|
||||
],
|
||||
"domain_keyword": [
|
||||
"test"
|
||||
],
|
||||
"domain_regex": [
|
||||
"^stun\\..+"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
"source_port_range": [
|
||||
"1000:2000",
|
||||
":3000",
|
||||
"4000:"
|
||||
],
|
||||
"port": [
|
||||
80,
|
||||
443
|
||||
],
|
||||
"port_range": [
|
||||
"1000:2000",
|
||||
":3000",
|
||||
"4000:"
|
||||
],
|
||||
"process_name": [
|
||||
"curl"
|
||||
],
|
||||
"process_path": [
|
||||
"/usr/bin/curl"
|
||||
],
|
||||
"process_path_regex": [
|
||||
"^/usr/bin/.+"
|
||||
],
|
||||
"package_name": [
|
||||
"com.termux"
|
||||
],
|
||||
"network_type": [
|
||||
"wifi"
|
||||
],
|
||||
"network_is_expensive": false,
|
||||
"network_is_constrained": false,
|
||||
"wifi_ssid": [
|
||||
"My WIFI"
|
||||
],
|
||||
"wifi_bssid": [
|
||||
"00:00:00:00:00:00"
|
||||
],
|
||||
"invert": false
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [],
|
||||
"invert": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
||||
|
||||
### Default Fields
|
||||
|
||||
!!! note ""
|
||||
|
||||
默认规则使用以下匹配逻辑:
|
||||
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `ip_cidr`) &&
|
||||
(`port` || `port_range`) &&
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
#### query_type
|
||||
|
||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` 或 `udp`。
|
||||
|
||||
#### domain
|
||||
|
||||
匹配完整域名。
|
||||
|
||||
#### domain_suffix
|
||||
|
||||
匹配域名后缀。
|
||||
|
||||
#### domain_keyword
|
||||
|
||||
匹配域名关键字。
|
||||
|
||||
#### domain_regex
|
||||
|
||||
匹配域名正则表达式。
|
||||
|
||||
#### source_ip_cidr
|
||||
|
||||
匹配源 IP CIDR。
|
||||
|
||||
#### ip_cidr
|
||||
|
||||
匹配 IP CIDR。
|
||||
|
||||
#### source_port
|
||||
|
||||
匹配源端口。
|
||||
|
||||
#### source_port_range
|
||||
|
||||
匹配源端口范围。
|
||||
|
||||
#### port
|
||||
|
||||
匹配端口。
|
||||
|
||||
#### port_range
|
||||
|
||||
匹配端口范围。
|
||||
|
||||
#### process_name
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS。
|
||||
|
||||
匹配进程名称。
|
||||
|
||||
#### process_path
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS.
|
||||
|
||||
匹配进程路径。
|
||||
|
||||
#### process_path_regex
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux、Windows 和 macOS.
|
||||
|
||||
使用正则表达式匹配进程路径。
|
||||
|
||||
#### package_name
|
||||
|
||||
匹配 Android 应用包名。
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配网络类型。
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
||||
|
||||
#### network_is_expensive
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
|
||||
像蜂窝网络或个人热点 (在 Apple 平台)。
|
||||
|
||||
#### network_is_constrained
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配如果网络在低数据模式下。
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配 WiFi SSID。
|
||||
|
||||
#### wifi_bssid
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
#### invert
|
||||
|
||||
反选匹配结果。
|
||||
|
||||
### 逻辑字段
|
||||
|
||||
#### type
|
||||
|
||||
`logical`
|
||||
|
||||
#### mode
|
||||
|
||||
==必填==
|
||||
|
||||
`and` 或 `or`
|
||||
|
||||
#### rules
|
||||
|
||||
==必填==
|
||||
|
||||
包括的规则。
|
||||
@@ -74,7 +74,7 @@ Tag of rule-set.
|
||||
|
||||
==Required==
|
||||
|
||||
List of [Headless Rule](./headless-rule.md/).
|
||||
List of [Headless Rule](../headless-rule/).
|
||||
|
||||
### Local or Remote Fields
|
||||
|
||||
|
||||
117
docs/configuration/rule-set/index.zh.md
Normal file
117
docs/configuration/rule-set/index.zh.md
Normal file
@@ -0,0 +1,117 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: `type: inline`
|
||||
|
||||
# 规则集
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
### 结构
|
||||
|
||||
=== "内联"
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "inline", // 可选
|
||||
"tag": "",
|
||||
"rules": []
|
||||
}
|
||||
```
|
||||
|
||||
=== "本地文件"
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "local",
|
||||
"tag": "",
|
||||
"format": "source", // or binary
|
||||
"path": ""
|
||||
}
|
||||
```
|
||||
|
||||
=== "远程文件"
|
||||
|
||||
!!! info ""
|
||||
|
||||
远程规则集将被缓存如果 `experimental.cache_file.enabled` 已启用。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "",
|
||||
"format": "source", // or binary
|
||||
"url": "",
|
||||
"download_detour": "", // 可选
|
||||
"update_interval": "" // 可选
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
|
||||
#### type
|
||||
|
||||
==必填==
|
||||
|
||||
规则集类型, `local` 或 `remote`。
|
||||
|
||||
#### tag
|
||||
|
||||
==必填==
|
||||
|
||||
规则集的标签。
|
||||
|
||||
### 内联字段
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
#### rules
|
||||
|
||||
==必填==
|
||||
|
||||
一组 [无头规则](../headless-rule/).
|
||||
|
||||
### 本地或远程字段
|
||||
|
||||
#### format
|
||||
|
||||
==必填==
|
||||
|
||||
规则集格式, `source` 或 `binary`。
|
||||
|
||||
### 本地字段
|
||||
|
||||
#### path
|
||||
|
||||
==必填==
|
||||
|
||||
!!! note ""
|
||||
|
||||
自 sing-box 1.10.0 起,文件更改时将自动重新加载。
|
||||
|
||||
规则集的文件路径。
|
||||
|
||||
### 远程字段
|
||||
|
||||
#### url
|
||||
|
||||
==必填==
|
||||
|
||||
规则集的下载 URL。
|
||||
|
||||
#### download_detour
|
||||
|
||||
用于下载规则集的出站的标签。
|
||||
|
||||
如果为空,将使用默认出站。
|
||||
|
||||
#### update_interval
|
||||
|
||||
规则集的更新间隔。
|
||||
|
||||
默认使用 `1d`。
|
||||
@@ -2,7 +2,9 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# Source Format
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: version `3`
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
@@ -14,7 +16,7 @@ icon: material/new-box
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 2,
|
||||
"version": 3,
|
||||
"rules": []
|
||||
}
|
||||
```
|
||||
@@ -29,19 +31,14 @@ Use `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` to c
|
||||
|
||||
==Required==
|
||||
|
||||
Version of rule-set, one of `1` or `2`.
|
||||
Version of rule-set.
|
||||
|
||||
* 1: Initial rule-set version, since sing-box 1.8.0.
|
||||
* 2: Optimized memory usages of `domain_suffix` rules.
|
||||
|
||||
The new rule-set version `2` does not make any changes to the format, only affecting `binary` rule-sets compiled by command `rule-set compile`
|
||||
|
||||
Since 1.10.0, the optimization is always applied to `source` rule-sets even if version is set to `1`.
|
||||
|
||||
It is recommended to upgrade to `2` after sing-box 1.10.0 becomes a stable version.
|
||||
* 1: sing-box 1.8.0: Initial rule-set version.
|
||||
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
||||
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
||||
|
||||
#### rules
|
||||
|
||||
==Required==
|
||||
|
||||
List of [Headless Rule](./headless-rule.md/).
|
||||
List of [Headless Rule](../headless-rule/).
|
||||
|
||||
44
docs/configuration/rule-set/source-format.zh.md
Normal file
44
docs/configuration/rule-set/source-format.zh.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: version `3`
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: version `2`
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 3,
|
||||
"rules": []
|
||||
}
|
||||
```
|
||||
|
||||
### 编译
|
||||
|
||||
使用 `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` 以编译源文件为二进制规则集。
|
||||
|
||||
### 字段
|
||||
|
||||
#### version
|
||||
|
||||
==必填==
|
||||
|
||||
规则集版本。
|
||||
|
||||
* 1: sing-box 1.8.0: 初始规则集版本。
|
||||
* 2: sing-box 1.10.0: 优化了二进制规则集中 `domain_suffix` 规则的内存使用。
|
||||
* 3: sing-box 1.11.0: 添加了 `network_type`、 `network_is_expensive` 和 `network_is_constrainted` 规则项。
|
||||
|
||||
#### rules
|
||||
|
||||
==必填==
|
||||
|
||||
一组 [无头规则](../headless-rule/).
|
||||
@@ -1,3 +1,14 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.11.0"
|
||||
|
||||
:material-plus: [network_strategy](#network_strategy)
|
||||
:material-alert: [fallback_delay](#fallback_delay)
|
||||
:material-alert: [network_type](#network_type)
|
||||
:material-alert: [fallback_network_type](#fallback_network_type)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -13,20 +24,25 @@
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"network_strategy": "default",
|
||||
"network_type": [],
|
||||
"fallback_network_type": [],
|
||||
"fallback_delay": "300ms"
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
!!! note ""
|
||||
|
||||
| Field | Available Context |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------|-------------------|
|
||||
| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_multi_path` / `udp_fragment` /`connect_timeout` | `detour` not set |
|
||||
You can ignore the JSON Array [] tag when the content is only one item
|
||||
|
||||
### Fields
|
||||
|
||||
#### detour
|
||||
|
||||
The tag of the upstream outbound.
|
||||
|
||||
If enabled, all other fields will be ignored.
|
||||
|
||||
#### bind_interface
|
||||
|
||||
The network interface to bind to.
|
||||
@@ -78,7 +94,7 @@ Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
||||
|
||||
#### domain_strategy
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
||||
|
||||
If set, the requested domain name will be resolved to IP before connect.
|
||||
|
||||
@@ -87,11 +103,71 @@ If set, the requested domain name will be resolved to IP before connect.
|
||||
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
|
||||
| others | Domain in server address | / |
|
||||
|
||||
#### network_strategy
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
|
||||
|
||||
Strategy for selecting network interfaces.
|
||||
|
||||
Available values:
|
||||
|
||||
- `default` (default): Connect to default network or networks specified in `network_type` sequentially.
|
||||
- `hybrid`: Connect to all networks or networks specified in `network_type` concurrently.
|
||||
- `fallback`: Connect to default network or preferred networks specified in `network_type` concurrently, and try fallback networks when unavailable or timeout.
|
||||
|
||||
For fallback, when preferred interfaces fails or times out,
|
||||
it will enter a 15s fast fallback state (Connect to all preferred and fallback networks concurrently),
|
||||
and exit immediately if preferred networks recover.
|
||||
|
||||
Conflicts with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`.
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
|
||||
|
||||
Network types to use when using `default` or `hybrid` network strategy or
|
||||
preferred network types to use when using `fallback` network strategy.
|
||||
|
||||
Available values: `wifi`, `cellular`, `ethernet`, `other`.
|
||||
|
||||
Device's default network is used by default.
|
||||
|
||||
#### fallback_network_type
|
||||
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
|
||||
|
||||
Fallback network types when preferred networks are unavailable or timeout when using `fallback` network strategy.
|
||||
|
||||
All other networks expect preferred are used by default.
|
||||
|
||||
#### fallback_delay
|
||||
|
||||
The length of time to wait before spawning a RFC 6555 Fast Fallback connection.
|
||||
That is, is the amount of time to wait for connection to succeed before assuming
|
||||
that IPv4/IPv6 is misconfigured and falling back to other type of addresses.
|
||||
If zero, a default delay of 300ms is used.
|
||||
!!! question "Since sing-box 1.11.0"
|
||||
|
||||
Only take effect when `domain_strategy` is set.
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and Apple platforms with `auto_detect_interface` enabled.
|
||||
|
||||
The length of time to wait before spawning a RFC 6555 Fast Fallback connection.
|
||||
|
||||
For `domain_strategy`, is the amount of time to wait for connection to succeed before assuming
|
||||
that IPv4/IPv6 is misconfigured and falling back to other type of addresses.
|
||||
|
||||
For `network_strategy`, is the amount of time to wait for connection to succeed before falling
|
||||
back to other interfaces.
|
||||
|
||||
Only take effect when `domain_strategy` or `network_strategy` is set.
|
||||
|
||||
`300ms` is used by default.
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.11.0 中的更改"
|
||||
|
||||
:material-plus: [network_strategy](#network_strategy)
|
||||
:material-alert: [fallback_delay](#fallback_delay)
|
||||
:material-alert: [network_type](#network_type)
|
||||
:material-alert: [fallback_network_type](#fallback_network_type)
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
@@ -13,17 +24,19 @@
|
||||
"tcp_multi_path": false,
|
||||
"udp_fragment": false,
|
||||
"domain_strategy": "prefer_ipv6",
|
||||
"network_strategy": "",
|
||||
"network_type": [],
|
||||
"fallback_network_type": [],
|
||||
"fallback_delay": "300ms"
|
||||
}
|
||||
```
|
||||
|
||||
!!! note ""
|
||||
|
||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
||||
|
||||
### 字段
|
||||
|
||||
| 字段 | 可用上下文 |
|
||||
|------------------------------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_mutli_path` / `udp_fragment` /`connect_timeout` | `detour` 未设置 |
|
||||
|
||||
|
||||
#### detour
|
||||
|
||||
上游出站的标签。
|
||||
@@ -83,15 +96,67 @@
|
||||
|
||||
如果设置,域名将在请求发出之前解析为 IP。
|
||||
|
||||
| 出站 | 受影响的域名 | 默认回退值 |
|
||||
|----------|--------------------------|-------------------------------------------|
|
||||
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
||||
| others | 服务器地址中的域名 | / |
|
||||
| 出站 | 受影响的域名 | 默认回退值 |
|
||||
|----------|-----------|---------------------------|
|
||||
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
||||
| others | 服务器地址中的域名 | / |
|
||||
|
||||
#### network_strategy
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。
|
||||
|
||||
用于选择网络接口的策略。
|
||||
|
||||
可用值:
|
||||
|
||||
- `default`(默认值):按顺序连接默认网络或 `network_type` 中指定的网络。
|
||||
- `hybrid`:同时连接所有网络或 `network_type` 中指定的网络。
|
||||
- `fallback`:同时连接默认网络或 `network_type` 中指定的首选网络,当不可用或超时时尝试回退网络。
|
||||
|
||||
对于回退模式,当首选接口失败或超时时,
|
||||
将进入15秒的快速回退状态(同时连接所有首选和回退网络),
|
||||
如果首选网络恢复,则立即退出。
|
||||
|
||||
与 `bind_interface`, `bind_inet4_address` 和 `bind_inet6_address` 冲突。
|
||||
|
||||
#### network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。
|
||||
|
||||
当使用 `default` 或 `hybrid` 网络策略时要使用的网络类型,或当使用 `fallback` 网络策略时要使用的首选网络类型。
|
||||
|
||||
可用值:`wifi`, `cellular`, `ethernet`, `other`。
|
||||
|
||||
默认使用设备默认网络。
|
||||
|
||||
#### fallback_network_type
|
||||
|
||||
!!! question "自 sing-box 1.11.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 iOS 平台图形客户端中支持,并且需要 `route.auto_detect_interface`。
|
||||
|
||||
当使用 `fallback` 网络策略时,在首选网络不可用或超时的情况下要使用的回退网络类型。
|
||||
|
||||
默认使用除首选网络外的所有其他网络。
|
||||
|
||||
#### fallback_delay
|
||||
|
||||
在生成 RFC 6555 快速回退连接之前等待的时间长度。
|
||||
也就是说,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。
|
||||
如果为零,则使用 300 毫秒的默认延迟。
|
||||
|
||||
仅当 `domain_strategy` 为 `prefer_ipv4` 或 `prefer_ipv6` 时生效。
|
||||
对于 `domain_strategy`,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。
|
||||
|
||||
对于 `network_strategy`,对于 `network_strategy`,是在回退到其他接口之前等待连接成功的时间。
|
||||
|
||||
仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。
|
||||
|
||||
默认使用 `300ms`。
|
||||
|
||||
@@ -68,9 +68,9 @@ Enable UDP fragmentation.
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT expiration time in seconds.
|
||||
UDP NAT expiration time.
|
||||
|
||||
`5m` is used by default.
|
||||
`5m` will be used by default.
|
||||
|
||||
#### detour
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ icon: material/delete-clock
|
||||
|
||||
#### udp_timeout
|
||||
|
||||
UDP NAT 过期时间,以秒为单位。
|
||||
UDP NAT 过期时间。
|
||||
|
||||
默认使用 `5m`。
|
||||
|
||||
|
||||
@@ -380,7 +380,7 @@ See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details
|
||||
|
||||
==Required==
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial/).
|
||||
Handshake server address and [Dial Fields](/configuration/shared/dial/).
|
||||
|
||||
#### private_key
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# UDP over TCP
|
||||
|
||||
!!! warning ""
|
||||
|
||||
It's a proprietary protocol created by SagerNet, not part of shadowsocks.
|
||||
|
||||
@@ -22,13 +22,24 @@ check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
||||
|
||||
Old fields will be removed in sing-box 1.13.0.
|
||||
|
||||
#### Legacy DNS route options
|
||||
#### Destination override fields in direct outbound
|
||||
|
||||
Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated
|
||||
Destination override fields (`override_address` / `override_port`) in direct outbound are deprecated
|
||||
and can be replaced by rule actions,
|
||||
check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
||||
check [Migration](../migration/#migrate-destination-override-fields-to-route-options).
|
||||
|
||||
Old fields will be removed in sing-box 1.12.0.
|
||||
#### WireGuard outbound
|
||||
|
||||
WireGuard outbound is deprecated and can be replaced by endpoint,
|
||||
check [Migration](../migration/#migrate-wireguard-outbound-to-endpoint).
|
||||
|
||||
Old outbound will be removed in sing-box 1.13.0.
|
||||
|
||||
#### GSO option in TUN
|
||||
|
||||
GSO has no advantages for transparent proxy scenarios, is deprecated and no longer works in TUN.
|
||||
|
||||
Old fields will be removed in sing-box 1.13.0.
|
||||
|
||||
## 1.10.0
|
||||
|
||||
|
||||
@@ -20,12 +20,25 @@ icon: material/delete-alert
|
||||
|
||||
旧字段将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
#### 旧的 DNS 路由参数
|
||||
#### direct 出站中的目标地址覆盖字段
|
||||
|
||||
旧的 DNS 路由参数(`disable_cache`、`rewrite_ttl`、`client_subnet`)已废弃且可以通过规则动作替代,
|
||||
参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。
|
||||
direct 出站中的目标地址覆盖字段(`override_address` / `override_port`)已废弃且可以通过规则动作替代,
|
||||
参阅 [迁移指南](/migration/#migrate-destination-override-fields-to-route-options)。
|
||||
|
||||
旧字段将在 sing-box 1.12.0 中被移除。
|
||||
旧字段将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
#### WireGuard 出站
|
||||
|
||||
WireGuard 出站已废弃且可以通过端点替代,
|
||||
参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
|
||||
|
||||
旧出站将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
#### TUN 的 GSO 字段
|
||||
|
||||
GSO 对透明代理场景没有优势,已废弃且在 TUN 中不再起作用。
|
||||
|
||||
旧字段将在 sing-box 1.13.0 中被移除。
|
||||
|
||||
## 1.10.0
|
||||
|
||||
|
||||
@@ -156,31 +156,26 @@ Inbound fields are deprecated and can be replaced by rule actions.
|
||||
}
|
||||
```
|
||||
|
||||
### Migrate legacy DNS route options to rule actions
|
||||
### Migrate destination override fields to route options
|
||||
|
||||
Legacy DNS route options are deprecated and can be replaced by rule actions.
|
||||
Destination override fields in direct outbound are deprecated and can be replaced by route options.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[DNS Rule](/configuration/dns/rule/) /
|
||||
[DNS Rule Action](/configuration/dns/rule_action/)
|
||||
[Rule Action](/configuration/route/rule_action/) /
|
||||
[Direct](/configuration/outbound/direct/)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
...,
|
||||
|
||||
"server": "local",
|
||||
"disable_cache": true,
|
||||
"rewrite_ttl": 600,
|
||||
"client_subnet": "1.1.1.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -188,23 +183,85 @@ Legacy DNS route options are deprecated and can be replaced by rule actions.
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
...,
|
||||
|
||||
"action": "route-options",
|
||||
"disable_cache": true,
|
||||
"rewrite_ttl": 600,
|
||||
"client_subnet": "1.1.1.1/24"
|
||||
},
|
||||
{
|
||||
...,
|
||||
|
||||
"server": "local"
|
||||
"action": "route-options", // or route
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Migrate WireGuard outbound to endpoint
|
||||
|
||||
WireGuard outbound is deprecated and can be replaced by endpoint.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[Endpoint](/configuration/endpoint/) /
|
||||
[WireGuard Endpoint](/configuration/endpoint/wireguard/) /
|
||||
[WireGuard Outbound](/configuration/outbound/wireguard/)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 10001,
|
||||
"system_interface": true,
|
||||
"gso": true,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"peer_public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"reserved": [0, 0, 0],
|
||||
"mtu": 1408
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: New"
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
"system": true,
|
||||
"name": "wg0",
|
||||
"mtu": 1408,
|
||||
"address": [
|
||||
"10.0.0.2/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"allowed_ips": [
|
||||
"0.0.0.0/0"
|
||||
],
|
||||
"persistent_keepalive_interval": 30,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -156,31 +156,26 @@ icon: material/arrange-bring-forward
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移旧的 DNS 路由选项到规则动作
|
||||
### 迁移 direct 出站中的目标地址覆盖字段到路由字段
|
||||
|
||||
旧的 DNS 路由选项已被弃用,且可以被规则动作替代。
|
||||
direct 出站中的目标地址覆盖字段已废弃,且可以被路由字段替代。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
||||
[DNS 规则动作](/zh/configuration/dns/rule_action/)
|
||||
[Rule Action](/zh/configuration/route/rule_action/) /
|
||||
[Direct](/zh/configuration/outbound/direct/)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"rules": [
|
||||
{
|
||||
...,
|
||||
|
||||
"server": "local",
|
||||
"disable_cache": true,
|
||||
"rewrite_ttl": 600,
|
||||
"client_subnet": "1.1.1.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "direct",
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -188,26 +183,89 @@ icon: material/arrange-bring-forward
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
...,
|
||||
|
||||
"action": "route-options",
|
||||
"disable_cache": true,
|
||||
"rewrite_ttl": 600,
|
||||
"client_subnet": "1.1.1.1/24"
|
||||
},
|
||||
{
|
||||
...,
|
||||
|
||||
"server": "local"
|
||||
"action": "route-options", // 或 route
|
||||
"override_address": "1.1.1.1",
|
||||
"override_port": 443
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 迁移 WireGuard 出站到端点
|
||||
|
||||
WireGuard 出站已被弃用,且可以被端点替代。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[端点](/zh/configuration/endpoint/) /
|
||||
[WireGuard 端点](/zh/configuration/endpoint/wireguard/) /
|
||||
[WireGuard 出站](/zh/configuration/outbound/wireguard/)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-out",
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 10001,
|
||||
"system_interface": true,
|
||||
"gso": true,
|
||||
"interface_name": "wg0",
|
||||
"local_address": [
|
||||
"10.0.0.1/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"peer_public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"reserved": [0, 0, 0],
|
||||
"mtu": 1408
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"type": "wireguard",
|
||||
"tag": "wg-ep",
|
||||
"system": true,
|
||||
"name": "wg0",
|
||||
"mtu": 1408,
|
||||
"address": [
|
||||
"10.0.0.2/32"
|
||||
],
|
||||
"private_key": "<private_key>",
|
||||
"listen_port": 10000,
|
||||
"peers": [
|
||||
{
|
||||
"address": "127.0.0.1",
|
||||
"port": 10001,
|
||||
"public_key": "<peer_public_key>",
|
||||
"pre_shared_key": "<pre_shared_key>",
|
||||
"allowed_ips": [
|
||||
"0.0.0.0/0"
|
||||
],
|
||||
"persistent_keepalive_interval": 30,
|
||||
"reserved": [0, 0, 0]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### TUN 地址字段已合并
|
||||
@@ -216,8 +274,6 @@ icon: material/arrange-bring-forward
|
||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[TUN](/zh/configuration/inbound/tun/)
|
||||
|
||||
@@ -33,17 +33,31 @@ func (n Note) Impending() bool {
|
||||
}
|
||||
|
||||
func (n Note) Message() string {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
|
||||
)
|
||||
if n.MigrationLink != "" {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
|
||||
)
|
||||
} else {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ".",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (n Note) MessageWithLink() string {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink,
|
||||
)
|
||||
if n.MigrationLink != "" {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink,
|
||||
)
|
||||
} else {
|
||||
return F.ToString(
|
||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
||||
" and will be removed in sing-box ", n.ScheduledVersion, ".",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var OptionBadMatchSource = Note{
|
||||
@@ -100,13 +114,39 @@ var OptionInboundOptions = Note{
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions",
|
||||
}
|
||||
|
||||
var OptionLegacyDNSRouteOptions = Note{
|
||||
Name: "legacy-dns-route-options",
|
||||
Description: "legacy dns route options",
|
||||
var OptionDestinationOverrideFields = Note{
|
||||
Name: "destination-override-fields",
|
||||
Description: "destination override fields in direct outbound",
|
||||
DeprecatedVersion: "1.11.0",
|
||||
ScheduledVersion: "1.13.0",
|
||||
EnvName: "DESTINATION_OVERRIDE_FIELDS",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-destination-override-fields-to-route-options",
|
||||
}
|
||||
|
||||
var OptionWireGuardOutbound = Note{
|
||||
Name: "wireguard-outbound",
|
||||
Description: "legacy wireguard outbound",
|
||||
DeprecatedVersion: "1.11.0",
|
||||
ScheduledVersion: "1.13.0",
|
||||
EnvName: "WIREGUARD_OUTBOUND",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-wireguard-outbound-to-endpoint",
|
||||
}
|
||||
|
||||
var OptionWireGuardGSO = Note{
|
||||
Name: "wireguard-gso",
|
||||
Description: "GSO option in wireguard outbound",
|
||||
DeprecatedVersion: "1.11.0",
|
||||
ScheduledVersion: "1.13.0",
|
||||
EnvName: "WIREGUARD_GSO",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-wireguard-outbound-to-endpoint",
|
||||
}
|
||||
|
||||
var OptionTUNGSO = Note{
|
||||
Name: "tun-gso",
|
||||
Description: "GSO option in tun",
|
||||
DeprecatedVersion: "1.11.0",
|
||||
ScheduledVersion: "1.12.0",
|
||||
EnvName: "LEGACY_DNS_ROUTE_OPTIONS",
|
||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions",
|
||||
EnvName: "TUN_GSO",
|
||||
}
|
||||
|
||||
var Options = []Note{
|
||||
@@ -116,5 +156,8 @@ var Options = []Note{
|
||||
OptionTUNAddressX,
|
||||
OptionSpecialOutbounds,
|
||||
OptionInboundOptions,
|
||||
OptionLegacyDNSRouteOptions,
|
||||
OptionDestinationOverrideFields,
|
||||
OptionWireGuardOutbound,
|
||||
OptionWireGuardGSO,
|
||||
OptionTUNGSO,
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@ func (f *stderrManager) ReportDeprecated(feature Note) {
|
||||
return
|
||||
}
|
||||
f.logger.Error(feature.MessageWithLink())
|
||||
f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true")
|
||||
f.logger.Fatal("to continuing using this feature, set environment variable ENABLE_DEPRECATED_" + feature.EnvName + "=true")
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err
|
||||
}
|
||||
|
||||
func CheckConfig(configContent string) error {
|
||||
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry())
|
||||
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
|
||||
options, err := parseConfig(ctx, configContent)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -74,11 +74,7 @@ func (s *platformInterfaceStub) CreateDefaultInterfaceMonitor(logger logger.Logg
|
||||
return (*interfaceMonitorStub)(nil)
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) UsePlatformInterfaceGetter() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *platformInterfaceStub) Interfaces() ([]control.Interface, error) {
|
||||
func (s *platformInterfaceStub) Interfaces() ([]adapter.NetworkInterface, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
@@ -111,16 +107,8 @@ func (s *interfaceMonitorStub) Close() error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (s *interfaceMonitorStub) DefaultInterfaceName(destination netip.Addr) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *interfaceMonitorStub) DefaultInterfaceIndex(destination netip.Addr) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s *interfaceMonitorStub) DefaultInterface(destination netip.Addr) (string, int) {
|
||||
return "", -1
|
||||
func (s *interfaceMonitorStub) DefaultInterface() *control.Interface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *interfaceMonitorStub) OverrideAndroidVPN() bool {
|
||||
@@ -143,7 +131,7 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica
|
||||
}
|
||||
|
||||
func FormatConfig(configContent string) (string, error) {
|
||||
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
|
||||
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry()), configContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !linux
|
||||
//go:build !unix
|
||||
|
||||
package libbox
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build unix
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
@@ -1,15 +1,10 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
@@ -20,19 +15,9 @@ var (
|
||||
|
||||
type platformDefaultInterfaceMonitor struct {
|
||||
*platformInterfaceWrapper
|
||||
networkAddresses []networkAddress
|
||||
defaultInterfaceName string
|
||||
defaultInterfaceIndex int
|
||||
element *list.Element[tun.NetworkUpdateCallback]
|
||||
access sync.Mutex
|
||||
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
type networkAddress struct {
|
||||
interfaceName string
|
||||
interfaceIndex int
|
||||
addresses []netip.Prefix
|
||||
element *list.Element[tun.NetworkUpdateCallback]
|
||||
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) Start() error {
|
||||
@@ -43,37 +28,10 @@ func (m *platformDefaultInterfaceMonitor) Close() error {
|
||||
return m.iif.CloseDefaultInterfaceMonitor(m)
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.interfaceName
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.defaultInterfaceName
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.interfaceIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.defaultInterfaceIndex
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) DefaultInterface(destination netip.Addr) (string, int) {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.interfaceName, address.interfaceIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.defaultInterfaceName, m.defaultInterfaceIndex
|
||||
func (m *platformDefaultInterfaceMonitor) DefaultInterface() *control.Interface {
|
||||
m.defaultInterfaceAccess.Lock()
|
||||
defer m.defaultInterfaceAccess.Unlock()
|
||||
return m.defaultInterface
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool {
|
||||
@@ -85,96 +43,49 @@ func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool {
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.defaultInterfaceAccess.Lock()
|
||||
defer m.defaultInterfaceAccess.Unlock()
|
||||
return m.callbacks.PushBack(callback)
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.defaultInterfaceAccess.Lock()
|
||||
defer m.defaultInterfaceAccess.Unlock()
|
||||
m.callbacks.Remove(element)
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
||||
if interfaceName == "" || interfaceIndex32 == -1 {
|
||||
m.defaultInterfaceName = ""
|
||||
m.defaultInterfaceIndex = -1
|
||||
m.access.Lock()
|
||||
callbacks := m.callbacks.Array()
|
||||
m.access.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(tun.EventNoRoute)
|
||||
}
|
||||
return
|
||||
}
|
||||
var err error
|
||||
if m.iif.UsePlatformInterfaceGetter() {
|
||||
err = m.updateInterfacesPlatform()
|
||||
} else {
|
||||
err = m.updateInterfaces()
|
||||
}
|
||||
if err == nil {
|
||||
err = m.networkManager.UpdateInterfaces()
|
||||
}
|
||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32, isExpensive bool, isConstrained bool) {
|
||||
m.isExpensive = isExpensive
|
||||
m.isConstrained = isConstrained
|
||||
err := m.networkManager.UpdateInterfaces()
|
||||
if err != nil {
|
||||
m.logger.Error(E.Cause(err, "update interfaces"))
|
||||
}
|
||||
interfaceIndex := int(interfaceIndex32)
|
||||
if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex {
|
||||
m.defaultInterfaceAccess.Lock()
|
||||
if interfaceIndex32 == -1 {
|
||||
m.defaultInterface = nil
|
||||
callbacks := m.callbacks.Array()
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(nil, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
oldInterface := m.defaultInterface
|
||||
newInterface, err := m.networkManager.InterfaceFinder().ByIndex(int(interfaceIndex32))
|
||||
if err != nil {
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
m.logger.Error(E.Cause(err, "find updated interface: ", interfaceName))
|
||||
return
|
||||
}
|
||||
m.defaultInterface = newInterface
|
||||
if oldInterface != nil && oldInterface.Name == m.defaultInterface.Name && oldInterface.Index == m.defaultInterface.Index {
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
return
|
||||
}
|
||||
m.defaultInterfaceName = interfaceName
|
||||
m.defaultInterfaceIndex = interfaceIndex
|
||||
m.access.Lock()
|
||||
callbacks := m.callbacks.Array()
|
||||
m.access.Unlock()
|
||||
m.defaultInterfaceAccess.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
callback(tun.EventInterfaceUpdate)
|
||||
callback(newInterface, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) updateInterfaces() error {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var addresses []networkAddress
|
||||
for _, iif := range interfaces {
|
||||
var netAddresses []net.Addr
|
||||
netAddresses, err = iif.Addrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var address networkAddress
|
||||
address.interfaceName = iif.Name
|
||||
address.interfaceIndex = iif.Index
|
||||
address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) {
|
||||
value, loaded := it.(*net.IPNet)
|
||||
return value, loaded
|
||||
}), func(it *net.IPNet) netip.Prefix {
|
||||
bits, _ := it.Mask.Size()
|
||||
return netip.PrefixFrom(M.AddrFromIP(it.IP), bits)
|
||||
})
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
m.networkAddresses = addresses
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error {
|
||||
interfaces, err := m.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var addresses []networkAddress
|
||||
for _, iif := range interfaces {
|
||||
var address networkAddress
|
||||
address.interfaceName = iif.Name
|
||||
address.interfaceIndex = iif.Index
|
||||
// address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix)
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
m.networkAddresses = addresses
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
@@ -13,10 +14,8 @@ type PlatformInterface interface {
|
||||
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
||||
PackageNameByUid(uid int32) (string, error)
|
||||
UIDByPackageName(packageName string) (int32, error)
|
||||
UsePlatformDefaultInterfaceMonitor() bool
|
||||
StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
||||
CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
||||
UsePlatformInterfaceGetter() bool
|
||||
GetInterfaces() (NetworkInterfaceIterator, error)
|
||||
UnderNetworkExtension() bool
|
||||
IncludeAllNetworks() bool
|
||||
@@ -31,15 +30,26 @@ type TunInterface interface {
|
||||
}
|
||||
|
||||
type InterfaceUpdateListener interface {
|
||||
UpdateDefaultInterface(interfaceName string, interfaceIndex int32)
|
||||
UpdateDefaultInterface(interfaceName string, interfaceIndex int32, isExpensive bool, isConstrained bool)
|
||||
}
|
||||
|
||||
const (
|
||||
InterfaceTypeWIFI = int32(C.InterfaceTypeWIFI)
|
||||
InterfaceTypeCellular = int32(C.InterfaceTypeCellular)
|
||||
InterfaceTypeEthernet = int32(C.InterfaceTypeEthernet)
|
||||
InterfaceTypeOther = int32(C.InterfaceTypeOther)
|
||||
)
|
||||
|
||||
type NetworkInterface struct {
|
||||
Index int32
|
||||
MTU int32
|
||||
Name string
|
||||
Addresses StringIterator
|
||||
Flags int32
|
||||
|
||||
Type int32
|
||||
DNSServer StringIterator
|
||||
Metered bool
|
||||
}
|
||||
|
||||
type WIFIState struct {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user