Compare commits
11 Commits
v1.11.0-al
...
dev-next-w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47736b27ba | ||
|
|
0b2c7ec35c | ||
|
|
e537c56b6b | ||
|
|
b456aff4ac | ||
|
|
4da652ff02 | ||
|
|
262727ec6c | ||
|
|
81dc9e7698 | ||
|
|
d876077281 | ||
|
|
0df81c297b | ||
|
|
b460484e43 | ||
|
|
0e511791e8 |
@@ -4,28 +4,28 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
LifecycleService
|
Service
|
||||||
ConnectionTracker
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() *urltest.HistoryStorage
|
||||||
}
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
||||||
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
||||||
type V2RayServer interface {
|
|
||||||
LifecycleService
|
|
||||||
StatsService() ConnectionTracker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheFile interface {
|
type CacheFile interface {
|
||||||
LifecycleService
|
Service
|
||||||
|
PreStarter
|
||||||
|
|
||||||
StoreFakeIP() bool
|
StoreFakeIP() bool
|
||||||
FakeIPStorage
|
FakeIPStorage
|
||||||
@@ -94,6 +94,10 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Tracker interface {
|
||||||
|
Leave()
|
||||||
|
}
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
Outbound
|
Outbound
|
||||||
Now() string
|
Now() string
|
||||||
@@ -111,3 +115,13 @@ func OutboundTag(detour Outbound) string {
|
|||||||
}
|
}
|
||||||
return detour.Tag()
|
return detour.Tag()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V2RayServer interface {
|
||||||
|
Service
|
||||||
|
StatsService() V2RayStatsService
|
||||||
|
}
|
||||||
|
|
||||||
|
type V2RayStatsService interface {
|
||||||
|
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||||
|
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -30,15 +28,7 @@ type UDPInjectableInbound interface {
|
|||||||
|
|
||||||
type InboundRegistry interface {
|
type InboundRegistry interface {
|
||||||
option.InboundOptionsRegistry
|
option.InboundOptionsRegistry
|
||||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error)
|
||||||
}
|
|
||||||
|
|
||||||
type InboundManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Inbounds() []Inbound
|
|
||||||
Get(tag string) (Inbound, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
@@ -67,13 +57,7 @@ type InboundContext struct {
|
|||||||
// Deprecated
|
// Deprecated
|
||||||
InboundOptions option.InboundOptions
|
InboundOptions option.InboundOptions
|
||||||
UDPDisableDomainUnmapping bool
|
UDPDisableDomainUnmapping bool
|
||||||
UDPConnect bool
|
DNSServer string
|
||||||
NetworkStrategy C.NetworkStrategy
|
|
||||||
NetworkType []C.InterfaceType
|
|
||||||
FallbackNetworkType []C.InterfaceType
|
|
||||||
FallbackDelay time.Duration
|
|
||||||
|
|
||||||
DNSServer string
|
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
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.InboundManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.InboundRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
inbounds []adapter.Inbound
|
|
||||||
inboundByTag map[string]adapter.Inbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
inboundByTag: make(map[string]adapter.Inbound),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
for _, inbound := range m.inbounds {
|
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if !m.started {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
inbounds := m.inbounds
|
|
||||||
m.inbounds = nil
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
var err error
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
|
||||||
err = E.Append(err, inbound.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Inbounds() []adapter.Inbound {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.inbounds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
inbound, found := m.inboundByTag[tag]
|
|
||||||
return inbound, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
inbound, found := m.inboundByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.inboundByTag, tag)
|
|
||||||
index := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
|
||||||
return it == inbound
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid inbound index")
|
|
||||||
}
|
|
||||||
m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return inbound.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
|
||||||
inbound, 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(inbound, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = existsInbound.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
|
||||||
return it == existsInbound
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid inbound index")
|
|
||||||
}
|
|
||||||
m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.inbounds = append(m.inbounds, inbound)
|
|
||||||
m.inboundByTag[tag] = inbound
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -28,41 +28,41 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
optionsType map[string]optionsConstructorFunc
|
optionsType map[string]optionsConstructorFunc
|
||||||
constructor map[string]constructorFunc
|
constructors map[string]constructorFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
func NewRegistry() *Registry {
|
||||||
return &Registry{
|
return &Registry{
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
constructor: make(map[string]constructorFunc),
|
constructors: make(map[string]constructorFunc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
m.access.Lock()
|
r.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer r.access.Unlock()
|
||||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
return optionsConstructor(), true
|
return optionsConstructor(), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
m.access.Lock()
|
r.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer r.access.Unlock()
|
||||||
constructor, loaded := m.constructor[outboundType]
|
constructor, loaded := r.constructors[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
}
|
}
|
||||||
return constructor(ctx, router, logger, tag, options)
|
return constructor(ctx, router, logger, tag, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
m.access.Lock()
|
r.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer r.access.Unlock()
|
||||||
m.optionsType[outboundType] = optionsConstructor
|
r.optionsType[outboundType] = optionsConstructor
|
||||||
m.constructor[outboundType] = constructor
|
r.constructors[outboundType] = constructor
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
|
|
||||||
type StartStage uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
StartStateInitialize StartStage = iota
|
|
||||||
StartStateStart
|
|
||||||
StartStatePostStart
|
|
||||||
StartStateStarted
|
|
||||||
)
|
|
||||||
|
|
||||||
var ListStartStages = []StartStage{
|
|
||||||
StartStateInitialize,
|
|
||||||
StartStateStart,
|
|
||||||
StartStatePostStart,
|
|
||||||
StartStateStarted,
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s StartStage) String() string {
|
|
||||||
switch s {
|
|
||||||
case StartStateInitialize:
|
|
||||||
return "initialize"
|
|
||||||
case StartStateStart:
|
|
||||||
return "start"
|
|
||||||
case StartStatePostStart:
|
|
||||||
return "post-start"
|
|
||||||
case StartStateStarted:
|
|
||||||
return "finish-start"
|
|
||||||
default:
|
|
||||||
panic("unknown stage")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Lifecycle interface {
|
|
||||||
Start(stage StartStage) error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
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,49 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
func LegacyStart(starter any, stage StartStage) error {
|
|
||||||
switch stage {
|
|
||||||
case StartStateInitialize:
|
|
||||||
if preStarter, isPreStarter := starter.(interface {
|
|
||||||
PreStart() error
|
|
||||||
}); isPreStarter {
|
|
||||||
return preStarter.PreStart()
|
|
||||||
}
|
|
||||||
case StartStateStart:
|
|
||||||
if starter, isStarter := starter.(interface {
|
|
||||||
Start() error
|
|
||||||
}); isStarter {
|
|
||||||
return starter.Start()
|
|
||||||
}
|
|
||||||
case StartStateStarted:
|
|
||||||
if postStarter, isPostStarter := starter.(interface {
|
|
||||||
PostStart() error
|
|
||||||
}); isPostStarter {
|
|
||||||
return postStarter.PostStart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type lifecycleServiceWrapper struct {
|
|
||||||
Service
|
|
||||||
name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLifecycleService(service Service, name string) LifecycleService {
|
|
||||||
return &lifecycleServiceWrapper{
|
|
||||||
Service: service,
|
|
||||||
name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Name() string {
|
|
||||||
return l.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
|
||||||
return LegacyStart(l.Service, stage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Close() error {
|
|
||||||
return l.Service.Close()
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NetworkManager interface {
|
|
||||||
Lifecycle
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
|
||||||
UpdateInterfaces() error
|
|
||||||
DefaultNetworkInterface() *NetworkInterface
|
|
||||||
NetworkInterfaces() []NetworkInterface
|
|
||||||
AutoDetectInterface() bool
|
|
||||||
AutoDetectInterfaceFunc() control.Func
|
|
||||||
ProtectFunc() control.Func
|
|
||||||
DefaultOptions() NetworkOptions
|
|
||||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
|
||||||
AutoRedirectOutputMark() uint32
|
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
|
||||||
PackageManager() tun.PackageManager
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -22,12 +22,3 @@ type OutboundRegistry interface {
|
|||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Outbounds() []Outbound
|
|
||||||
Outbound(tag string) (Outbound, bool)
|
|
||||||
Default() Outbound
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
@@ -20,16 +20,38 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
defer conn.Close()
|
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var outConn net.Conn
|
var outConn net.Conn
|
||||||
var err error
|
var err error
|
||||||
if len(metadata.DestinationAddresses) > 0 {
|
if len(metadata.DestinationAddresses) > 0 {
|
||||||
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
|
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
||||||
outConn, err = dialer.DialSerialNetwork(ctx, parallelDialer, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
} else {
|
||||||
} else {
|
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
||||||
outConn, err = N.DialSerial(ctx, this, N.NetworkTCP, metadata.Destination, metadata.DestinationAddresses)
|
}
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
} else {
|
} else {
|
||||||
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
outConn, err = this.DialContext(ctx, N.NetworkTCP, metadata.Destination)
|
||||||
}
|
}
|
||||||
@@ -45,57 +67,29 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
defer conn.Close()
|
|
||||||
ctx = adapter.WithContext(ctx, &metadata)
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
var (
|
var outConn net.PacketConn
|
||||||
outPacketConn net.PacketConn
|
var destinationAddress netip.Addr
|
||||||
outConn net.Conn
|
var err error
|
||||||
destinationAddress netip.Addr
|
if len(metadata.DestinationAddresses) > 0 {
|
||||||
err error
|
outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
|
||||||
)
|
|
||||||
if metadata.UDPConnect {
|
|
||||||
if len(metadata.DestinationAddresses) > 0 {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return N.ReportHandshakeFailure(conn, err)
|
|
||||||
}
|
|
||||||
outPacketConn = bufio.NewUnbindPacketConn(outConn)
|
|
||||||
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
|
|
||||||
if connRemoteAddr != metadata.Destination.Addr {
|
|
||||||
destinationAddress = connRemoteAddr
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if len(metadata.DestinationAddresses) > 0 {
|
outConn, err = this.ListenPacket(ctx, metadata.Destination)
|
||||||
if parallelDialer, isParallelDialer := this.(dialer.ParallelInterfaceDialer); isParallelDialer {
|
|
||||||
outPacketConn, destinationAddress, err = dialer.ListenSerialNetworkPacket(ctx, parallelDialer, metadata.Destination, metadata.DestinationAddresses, metadata.NetworkStrategy, metadata.NetworkType, metadata.FallbackNetworkType, metadata.FallbackDelay)
|
|
||||||
} else {
|
|
||||||
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.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 {
|
if err != nil {
|
||||||
outPacketConn.Close()
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
|
}
|
||||||
|
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||||
|
if err != nil {
|
||||||
|
outConn.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if destinationAddress.IsValid() {
|
if destinationAddress.IsValid() {
|
||||||
if metadata.Destination.IsFqdn() {
|
if metadata.Destination.IsFqdn() {
|
||||||
if metadata.UDPDisableDomainUnmapping {
|
if metadata.UDPDisableDomainUnmapping {
|
||||||
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||||
} else {
|
} else {
|
||||||
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
|
||||||
@@ -110,7 +104,51 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
|
|||||||
case C.ProtocolDNS:
|
case C.ProtocolDNS:
|
||||||
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
|
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
|
||||||
}
|
}
|
||||||
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
|
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
|
||||||
|
ctx = adapter.WithContext(ctx, &metadata)
|
||||||
|
var outConn net.PacketConn
|
||||||
|
var destinationAddress netip.Addr
|
||||||
|
var err error
|
||||||
|
if len(metadata.DestinationAddresses) > 0 {
|
||||||
|
outConn, 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)
|
||||||
|
}
|
||||||
|
outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses)
|
||||||
|
} else {
|
||||||
|
outConn, err = this.ListenPacket(ctx, metadata.Destination)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return N.ReportHandshakeFailure(conn, err)
|
||||||
|
}
|
||||||
|
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
|
||||||
|
if err != nil {
|
||||||
|
outConn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if destinationAddress.IsValid() {
|
||||||
|
if metadata.Destination.IsFqdn() {
|
||||||
|
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), 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(outConn))
|
||||||
}
|
}
|
||||||
|
|
||||||
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
|
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {
|
||||||
|
|||||||
@@ -1,269 +0,0 @@
|
|||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"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"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.OutboundManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.OutboundRegistry
|
|
||||||
defaultTag string
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
outbounds []adapter.Outbound
|
|
||||||
outboundByTag map[string]adapter.Outbound
|
|
||||||
dependByTag map[string][]string
|
|
||||||
defaultOutbound adapter.Outbound
|
|
||||||
defaultOutboundFallback adapter.Outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, defaultTag string) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
defaultTag: defaultTag,
|
|
||||||
outboundByTag: make(map[string]adapter.Outbound),
|
|
||||||
dependByTag: make(map[string][]string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
|
||||||
m.defaultOutboundFallback = defaultOutboundFallback
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
|
||||||
m.access.Lock()
|
|
||||||
if m.started && m.stage >= stage {
|
|
||||||
panic("already started")
|
|
||||||
}
|
|
||||||
m.started = true
|
|
||||||
m.stage = stage
|
|
||||||
outbounds := m.outbounds
|
|
||||||
m.access.Unlock()
|
|
||||||
if stage == adapter.StartStateStart {
|
|
||||||
return m.startOutbounds(outbounds)
|
|
||||||
} else {
|
|
||||||
for _, outbound := range outbounds {
|
|
||||||
err := adapter.LegacyStart(outbound, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StartTimeout)
|
|
||||||
started := make(map[string]bool)
|
|
||||||
for {
|
|
||||||
canContinue := false
|
|
||||||
startOne:
|
|
||||||
for _, outboundToStart := range outbounds {
|
|
||||||
outboundTag := outboundToStart.Tag()
|
|
||||||
if started[outboundTag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dependencies := outboundToStart.Dependencies()
|
|
||||||
for _, dependency := range dependencies {
|
|
||||||
if !started[dependency] {
|
|
||||||
continue startOne
|
|
||||||
}
|
|
||||||
}
|
|
||||||
started[outboundTag] = true
|
|
||||||
canContinue = true
|
|
||||||
if starter, isStarter := outboundToStart.(interface {
|
|
||||||
Start() error
|
|
||||||
}); isStarter {
|
|
||||||
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
err := starter.Start()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(started) == len(outbounds) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if canContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {
|
|
||||||
return !started[it.Tag()]
|
|
||||||
})
|
|
||||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
|
||||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
|
||||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
|
||||||
return !started[it]
|
|
||||||
})
|
|
||||||
if common.Contains(oTree, problemOutboundTag) {
|
|
||||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
problemOutbound := m.outboundByTag[problemOutboundTag]
|
|
||||||
m.access.Unlock()
|
|
||||||
if problemOutbound == nil {
|
|
||||||
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]")
|
|
||||||
}
|
|
||||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
|
||||||
}
|
|
||||||
return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
m.access.Lock()
|
|
||||||
if !m.started {
|
|
||||||
m.access.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
outbounds := m.outbounds
|
|
||||||
m.outbounds = nil
|
|
||||||
m.access.Unlock()
|
|
||||||
var err error
|
|
||||||
for _, outbound := range outbounds {
|
|
||||||
if closer, isCloser := outbound.(io.Closer); isCloser {
|
|
||||||
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
|
||||||
err = E.Append(err, closer.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Outbounds() []adapter.Outbound {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.outbounds
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
outbound, found := m.outboundByTag[tag]
|
|
||||||
return outbound, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Default() adapter.Outbound {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.defaultOutbound != nil {
|
|
||||||
return m.defaultOutbound
|
|
||||||
} else {
|
|
||||||
return m.defaultOutboundFallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
outbound, found := m.outboundByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.outboundByTag, tag)
|
|
||||||
index := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
|
||||||
return it == outbound
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid inbound index")
|
|
||||||
}
|
|
||||||
m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
if m.defaultOutbound == outbound {
|
|
||||||
if len(m.outbounds) > 0 {
|
|
||||||
m.defaultOutbound = m.outbounds[0]
|
|
||||||
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
|
|
||||||
} else {
|
|
||||||
m.defaultOutbound = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependBy := m.dependByTag[tag]
|
|
||||||
if len(dependBy) > 0 {
|
|
||||||
return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", "))
|
|
||||||
}
|
|
||||||
dependencies := outbound.Dependencies()
|
|
||||||
for _, dependency := range dependencies {
|
|
||||||
if len(m.dependByTag[dependency]) == 1 {
|
|
||||||
delete(m.dependByTag, dependency)
|
|
||||||
} else {
|
|
||||||
m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {
|
|
||||||
return it != tag
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return common.Close(outbound)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {
|
|
||||||
if tag == "" {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
err = adapter.LegacyStart(outbound, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = common.Close(existsOutbound)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
|
||||||
return it == existsOutbound
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid inbound index")
|
|
||||||
}
|
|
||||||
m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.outbounds = append(m.outbounds, outbound)
|
|
||||||
m.outboundByTag[tag] = outbound
|
|
||||||
dependencies := outbound.Dependencies()
|
|
||||||
for _, dependency := range dependencies {
|
|
||||||
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
|
|
||||||
}
|
|
||||||
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
|
||||||
m.defaultOutbound = outbound
|
|
||||||
if m.started {
|
|
||||||
m.logger.Info("updated default outbound to ", outbound.Tag())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1 +1,9 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
|
type PreStarter interface {
|
||||||
|
PreStart() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostStarter interface {
|
||||||
|
PostStart() error
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,16 +10,26 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
mdns "github.com/miekg/dns"
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Lifecycle
|
Service
|
||||||
|
PreStarter
|
||||||
|
PostStarter
|
||||||
|
Cleanup() error
|
||||||
|
|
||||||
|
Outbounds() []Outbound
|
||||||
|
Outbound(tag string) (Outbound, bool)
|
||||||
|
DefaultOutbound(network string) (Outbound, error)
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
@@ -29,23 +39,37 @@ type Router interface {
|
|||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
|
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
|
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
|
|
||||||
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
|
|
||||||
|
InterfaceFinder() control.InterfaceFinder
|
||||||
|
UpdateInterfaces() error
|
||||||
|
DefaultInterface() string
|
||||||
|
AutoDetectInterface() bool
|
||||||
|
AutoDetectInterfaceFunc() control.Func
|
||||||
|
DefaultMark() uint32
|
||||||
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
|
AutoRedirectOutputMark() uint32
|
||||||
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
|
PackageManager() tun.PackageManager
|
||||||
|
WIFIState() WIFIState
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
|
||||||
SetTracker(tracker ConnectionTracker)
|
ClashServer() ClashServer
|
||||||
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
ResetNetwork()
|
V2RayServer() V2RayServer
|
||||||
}
|
SetV2RayServer(server V2RayServer)
|
||||||
|
|
||||||
type ConnectionTracker interface {
|
ResetNetwork() error
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
|
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use ConnectionRouterEx instead.
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
@@ -60,6 +84,14 @@ type ConnectionRouterEx interface {
|
|||||||
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||||
|
return service.ContextWith(ctx, router)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RouterFromContext(ctx context.Context) Router {
|
||||||
|
return service.FromContext[Router](ctx)
|
||||||
|
}
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
Name() string
|
Name() string
|
||||||
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
@@ -119,3 +151,12 @@ func (c *HTTPStartContext) Close() {
|
|||||||
client.CloseIdleConnections()
|
client.CloseIdleConnections()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InterfaceUpdateListener interface {
|
||||||
|
InterfaceUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
type WIFIState struct {
|
||||||
|
SSID string
|
||||||
|
BSSID string
|
||||||
|
}
|
||||||
|
|||||||
334
box.go
334
box.go
@@ -9,9 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
@@ -24,7 +21,6 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
@@ -32,15 +28,16 @@ import (
|
|||||||
var _ adapter.Service = (*Box)(nil)
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
router adapter.Router
|
||||||
logger log.ContextLogger
|
inbounds []adapter.Inbound
|
||||||
network *route.NetworkManager
|
outbounds []adapter.Outbound
|
||||||
router *route.Router
|
logFactory log.Factory
|
||||||
inbound *inbound.Manager
|
logger log.ContextLogger
|
||||||
outbound *outbound.Manager
|
preServices1 map[string]adapter.Service
|
||||||
services []adapter.LifecycleService
|
preServices2 map[string]adapter.Service
|
||||||
done chan struct{}
|
postServices map[string]adapter.Service
|
||||||
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -49,11 +46,7 @@ type Options struct {
|
|||||||
PlatformLogWriter log.PlatformWriter
|
PlatformLogWriter log.PlatformWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
func Context(
|
func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context {
|
||||||
ctx context.Context,
|
|
||||||
inboundRegistry adapter.InboundRegistry,
|
|
||||||
outboundRegistry adapter.OutboundRegistry,
|
|
||||||
) context.Context {
|
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||||
@@ -73,18 +66,15 @@ func New(options Options) (*Box, error) {
|
|||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
|
||||||
|
|
||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
if inboundRegistry == nil {
|
if inboundRegistry == nil {
|
||||||
return nil, E.New("missing inbound registry in context")
|
return nil, E.New("missing inbound registry in context")
|
||||||
}
|
}
|
||||||
|
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, E.New("missing outbound registry in context")
|
return nil, E.New("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
@@ -116,21 +106,16 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
router, err := route.NewRouter(
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
ctx,
|
||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry)
|
logFactory,
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final)
|
common.PtrValueOrDefault(options.Route),
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
common.PtrValueOrDefault(options.DNS),
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
common.PtrValueOrDefault(options.NTP),
|
||||||
|
options.Inbounds,
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize network manager")
|
return nil, E.Cause(err, "parse route options")
|
||||||
}
|
|
||||||
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
|
||||||
router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize router")
|
|
||||||
}
|
}
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if len(options.LegacyInbounds) > 0 {
|
if len(options.LegacyInbounds) > 0 {
|
||||||
@@ -142,6 +127,7 @@ func New(options Options) (*Box, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if len(options.LegacyOutbounds) > 0 {
|
if len(options.LegacyOutbounds) > 0 {
|
||||||
for _, legacyOutbound := range options.LegacyOutbounds {
|
for _, legacyOutbound := range options.LegacyOutbounds {
|
||||||
@@ -152,14 +138,17 @@ func New(options Options) (*Box, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
|
var currentInbound adapter.Inbound
|
||||||
var tag string
|
var tag string
|
||||||
if inboundOptions.Tag != "" {
|
if inboundOptions.Tag != "" {
|
||||||
tag = inboundOptions.Tag
|
tag = inboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
err = inboundManager.Create(ctx,
|
currentInbound, err = inboundRegistry.CreateInbound(
|
||||||
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
@@ -167,10 +156,12 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundOptions.Options,
|
inboundOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
}
|
}
|
||||||
|
inbounds = append(inbounds, currentInbound)
|
||||||
}
|
}
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
|
var currentOutbound adapter.Outbound
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
tag = outboundOptions.Tag
|
tag = outboundOptions.Tag
|
||||||
@@ -184,7 +175,7 @@ func New(options Options) (*Box, error) {
|
|||||||
Outbound: tag,
|
Outbound: tag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
err = outboundManager.Create(
|
currentOutbound, err = outboundRegistry.CreateOutbound(
|
||||||
outboundCtx,
|
outboundCtx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
@@ -193,79 +184,65 @@ func New(options Options) (*Box, error) {
|
|||||||
outboundOptions.Options,
|
outboundOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||||
}
|
}
|
||||||
|
outbounds = append(outbounds, currentOutbound)
|
||||||
|
}
|
||||||
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
|
defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
|
||||||
|
common.Must(cErr)
|
||||||
|
outbounds = append(outbounds, defaultOutbound)
|
||||||
|
return defaultOutbound
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
outboundManager.Initialize(common.Must1(
|
|
||||||
direct.NewOutbound(
|
|
||||||
ctx,
|
|
||||||
router,
|
|
||||||
logFactory.NewLogger("outbound/direct"),
|
|
||||||
"direct",
|
|
||||||
option.DirectOutboundOptions{},
|
|
||||||
),
|
|
||||||
))
|
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
err = platformInterface.Initialize(networkManager)
|
err = platformInterface.Initialize(ctx, router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var services []adapter.LifecycleService
|
preServices1 := make(map[string]adapter.Service)
|
||||||
|
preServices2 := make(map[string]adapter.Service)
|
||||||
|
postServices := make(map[string]adapter.Service)
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
if cacheFile == nil {
|
||||||
services = append(services, cacheFile)
|
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
|
}
|
||||||
|
preServices1["cache file"] = cacheFile
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
||||||
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
|
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash-server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
router.SetTracker(clashServer)
|
router.SetClashServer(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
preServices2["clash api"] = clashServer
|
||||||
services = append(services, clashServer)
|
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create v2ray-server")
|
return nil, E.Cause(err, "create v2ray api server")
|
||||||
}
|
}
|
||||||
if v2rayServer.StatsService() != nil {
|
router.SetV2RayServer(v2rayServer)
|
||||||
router.SetTracker(v2rayServer.StatsService())
|
preServices2["v2ray api"] = v2rayServer
|
||||||
services = append(services, v2rayServer)
|
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
|
||||||
if ntpOptions.Enabled {
|
|
||||||
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create NTP service")
|
|
||||||
}
|
|
||||||
timeService := ntp.NewService(ntp.Options{
|
|
||||||
Context: ctx,
|
|
||||||
Dialer: ntpDialer,
|
|
||||||
Logger: logFactory.NewLogger("ntp"),
|
|
||||||
Server: ntpOptions.ServerOptions.Build(),
|
|
||||||
Interval: time.Duration(ntpOptions.Interval),
|
|
||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
|
||||||
})
|
|
||||||
service.MustRegister[ntp.TimeService](ctx, timeService)
|
|
||||||
services = append(services, adapter.NewLifecycleService(timeService, "ntp service"))
|
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
router: router,
|
||||||
router: router,
|
inbounds: inbounds,
|
||||||
inbound: inboundManager,
|
outbounds: outbounds,
|
||||||
outbound: outboundManager,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
logFactory: logFactory,
|
||||||
logFactory: logFactory,
|
logger: logFactory.Logger(),
|
||||||
logger: logFactory.Logger(),
|
preServices1: preServices1,
|
||||||
services: services,
|
preServices2: preServices2,
|
||||||
done: make(chan struct{}),
|
postServices: postServices,
|
||||||
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,19 +292,35 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
for serviceName, service := range s.preServices1 {
|
||||||
|
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
||||||
|
monitor.Start("pre-start ", serviceName)
|
||||||
|
err := preService.PreStart()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "pre-start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for serviceName, service := range s.preServices2 {
|
||||||
|
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
||||||
|
monitor.Start("pre-start ", serviceName)
|
||||||
|
err := preService.PreStart()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "pre-start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = s.router.PreStart()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "pre-start router")
|
||||||
|
}
|
||||||
|
err = s.startOutbounds()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.router, s.outbound, s.inbound)
|
return s.router.Start()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.router)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) start() error {
|
func (s *Box) start() error {
|
||||||
@@ -335,29 +328,64 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
for serviceName, service := range s.preServices1 {
|
||||||
|
err = service.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for serviceName, service := range s.preServices2 {
|
||||||
|
err = service.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, in := range s.inbounds {
|
||||||
|
var tag string
|
||||||
|
if in.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = in.Tag()
|
||||||
|
}
|
||||||
|
err = in.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = s.postStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.inbound.Start(adapter.StartStateStart)
|
return s.router.Cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) postStart() error {
|
||||||
|
for serviceName, service := range s.postServices {
|
||||||
|
err := service.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: reorganize ALL start order
|
||||||
|
for _, out := range s.outbounds {
|
||||||
|
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
|
||||||
|
err := lateOutbound.PostStart()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "post-start outbound/", out.Tag())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := s.router.PostStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.router, s.inbound)
|
for _, in := range s.inbounds {
|
||||||
if err != nil {
|
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
|
||||||
return err
|
err = lateInbound.PostStart()
|
||||||
}
|
if err != nil {
|
||||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
return E.Cause(err, "post-start inbound/", in.Tag())
|
||||||
if err != nil {
|
}
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.router, s.outbound, s.inbound)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -369,32 +397,58 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
monitor := taskmonitor.New(s.logger, C.StopTimeout)
|
||||||
s.inbound, s.outbound, s.router, s.network,
|
var errors error
|
||||||
)
|
for serviceName, service := range s.postServices {
|
||||||
for _, lifecycleService := range s.services {
|
monitor.Start("close ", serviceName)
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
for i, in := range s.inbounds {
|
||||||
|
monitor.Start("close inbound/", in.Type(), "[", i, "]")
|
||||||
|
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
for i, out := range s.outbounds {
|
||||||
|
monitor.Start("close outbound/", out.Type(), "[", i, "]")
|
||||||
|
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||||
|
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
monitor.Start("close router")
|
||||||
|
if err := common.Close(s.router); err != nil {
|
||||||
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
|
return E.Cause(err, "close router")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
monitor.Finish()
|
||||||
return E.Cause(err, "close logger")
|
for serviceName, service := range s.preServices1 {
|
||||||
})
|
monitor.Start("close ", serviceName)
|
||||||
return err
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
}
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
})
|
||||||
func (s *Box) Network() adapter.NetworkManager {
|
monitor.Finish()
|
||||||
return s.network
|
}
|
||||||
|
for serviceName, service := range s.preServices2 {
|
||||||
|
monitor.Start("close ", serviceName)
|
||||||
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
if err := common.Close(s.logFactory); err != nil {
|
||||||
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
|
return E.Cause(err, "close logger")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Router() adapter.Router {
|
func (s *Box) Router() adapter.Router {
|
||||||
return s.router
|
return s.router
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Inbound() adapter.InboundManager {
|
|
||||||
return s.inbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) Outbound() adapter.OutboundManager {
|
|
||||||
return s.outbound
|
|
||||||
}
|
|
||||||
|
|||||||
85
box_outbound.go
Normal file
85
box_outbound.go
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
package box
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Box) startOutbounds() error {
|
||||||
|
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
||||||
|
outboundTags := make(map[adapter.Outbound]string)
|
||||||
|
outbounds := make(map[string]adapter.Outbound)
|
||||||
|
for i, outboundToStart := range s.outbounds {
|
||||||
|
var outboundTag string
|
||||||
|
if outboundToStart.Tag() == "" {
|
||||||
|
outboundTag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
outboundTag = outboundToStart.Tag()
|
||||||
|
}
|
||||||
|
if _, exists := outbounds[outboundTag]; exists {
|
||||||
|
return E.New("outbound tag ", outboundTag, " duplicated")
|
||||||
|
}
|
||||||
|
outboundTags[outboundToStart] = outboundTag
|
||||||
|
outbounds[outboundTag] = outboundToStart
|
||||||
|
}
|
||||||
|
started := make(map[string]bool)
|
||||||
|
for {
|
||||||
|
canContinue := false
|
||||||
|
startOne:
|
||||||
|
for _, outboundToStart := range s.outbounds {
|
||||||
|
outboundTag := outboundTags[outboundToStart]
|
||||||
|
if started[outboundTag] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dependencies := outboundToStart.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if !started[dependency] {
|
||||||
|
continue startOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
started[outboundTag] = true
|
||||||
|
canContinue = true
|
||||||
|
if starter, isStarter := outboundToStart.(interface {
|
||||||
|
Start() error
|
||||||
|
}); isStarter {
|
||||||
|
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
err := starter.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(started) == len(s.outbounds) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if canContinue {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
|
||||||
|
return !started[outboundTags[it]]
|
||||||
|
})
|
||||||
|
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
||||||
|
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
||||||
|
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
||||||
|
return !started[it]
|
||||||
|
})
|
||||||
|
if common.Contains(oTree, problemOutboundTag) {
|
||||||
|
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
||||||
|
}
|
||||||
|
problemOutbound := outbounds[problemOutboundTag]
|
||||||
|
if problemOutbound == nil {
|
||||||
|
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
|
||||||
|
}
|
||||||
|
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||||
|
}
|
||||||
|
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -58,7 +58,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "26.2.11394342"
|
const fixedVersion = "27.2.12479018"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
|
|||||||
@@ -68,6 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger()))
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func check() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(globalCtx)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ func format() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, optionsEntry := range optionsList {
|
for _, optionsEntry := range optionsList {
|
||||||
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)
|
optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -55,6 +56,10 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ruleSet, err := plainRuleSet.Upgrade()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".json") {
|
if strings.HasSuffix(sourcePath, ".json") {
|
||||||
@@ -69,7 +74,7 @@ func compileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = srs.Write(outputFile, plainRuleSet.Options, plainRuleSet.Version)
|
err = srs.Write(outputFile, ruleSet, plainRuleSet.Version == C.RuleSetVersion2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -78,7 +77,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer outputFile.Close()
|
defer outputFile.Close()
|
||||||
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, C.RuleSetVersion2)
|
err = srs.Write(outputFile, option.PlainRuleSet{Rules: rules}, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"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/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -46,10 +48,14 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ruleSet, err := srs.Read(reader, true)
|
plainRuleSet, err := srs.Read(reader, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ruleSet := option.PlainRuleSetCompat{
|
||||||
|
Version: C.RuleSetVersion1,
|
||||||
|
Options: plainRuleSet,
|
||||||
|
}
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -56,25 +55,26 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read rule-set")
|
return E.Cause(err, "read rule-set")
|
||||||
}
|
}
|
||||||
var ruleSet option.PlainRuleSetCompat
|
var plainRuleSet option.PlainRuleSet
|
||||||
switch flagRuleSetMatchFormat {
|
switch flagRuleSetMatchFormat {
|
||||||
case C.RuleSetFormatSource:
|
case C.RuleSetFormatSource:
|
||||||
ruleSet, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
var compat option.PlainRuleSetCompat
|
||||||
|
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
plainRuleSet, err = compat.Upgrade()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case C.RuleSetFormatBinary:
|
case C.RuleSetFormatBinary:
|
||||||
ruleSet, err = srs.Read(bytes.NewReader(content), false)
|
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
||||||
}
|
}
|
||||||
plainRuleSet, err := ruleSet.Upgrade()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ipAddress := M.ParseAddr(domain)
|
ipAddress := M.ParseAddr(domain)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
if ipAddress.IsValid() {
|
if ipAddress.IsValid() {
|
||||||
@@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
}
|
}
|
||||||
for i, ruleOptions := range plainRuleSet.Rules {
|
for i, ruleOptions := range plainRuleSet.Rules {
|
||||||
var currentRule adapter.HeadlessRule
|
var currentRule adapter.HeadlessRule
|
||||||
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)
|
currentRule, err = rule.NewHeadlessRule(nil, ruleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) {
|
|||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
|
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
||||||
if outboundTag == "" {
|
if outboundTag == "" {
|
||||||
return instance.Outbound().Default(), nil
|
return instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||||
} else {
|
} else {
|
||||||
outbound, loaded := instance.Outbound().Outbound(outboundTag)
|
outbound, loaded := instance.Router().Outbound(outboundTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound not found: ", outboundTag)
|
return nil, E.New("outbound not found: ", outboundTag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func connect(address string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer instance.Close()
|
defer instance.Close()
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func fetch(args []string) error {
|
|||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initializeHTTP3Client(instance *box.Box) error {
|
func initializeHTTP3Client(instance *box.Box) error {
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -44,7 +45,7 @@ func syncTime() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package adguard
|
package adguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ example.arpa
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.org",
|
"example.org",
|
||||||
@@ -86,7 +85,7 @@ func TestHosts(t *testing.T) {
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"google.com",
|
"google.com",
|
||||||
@@ -116,7 +115,7 @@ www.example.org
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|||||||
@@ -3,109 +3,72 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/atomic"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var _ WireGuardListener = (*DefaultDialer)(nil)
|
||||||
_ ParallelInterfaceDialer = (*DefaultDialer)(nil)
|
|
||||||
_ WireGuardListener = (*DefaultDialer)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tcpDialer
|
dialer4 tcpDialer
|
||||||
dialer6 tcpDialer
|
dialer6 tcpDialer
|
||||||
udpDialer4 net.Dialer
|
udpDialer4 net.Dialer
|
||||||
udpDialer6 net.Dialer
|
udpDialer6 net.Dialer
|
||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
isWireGuardListener bool
|
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) {
|
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
var (
|
var dialer net.Dialer
|
||||||
dialer net.Dialer
|
var listener net.ListenConfig
|
||||||
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 != "" {
|
if options.BindInterface != "" {
|
||||||
|
var interfaceFinder control.InterfaceFinder
|
||||||
|
if router != nil {
|
||||||
|
interfaceFinder = router.InterfaceFinder()
|
||||||
|
} else {
|
||||||
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
|
}
|
||||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else if router != nil && router.AutoDetectInterface() {
|
||||||
|
bindFunc := router.AutoDetectInterfaceFunc()
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else if router != nil && router.DefaultInterface() != "" {
|
||||||
|
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
}
|
||||||
|
var autoRedirectOutputMark uint32
|
||||||
|
if router != nil {
|
||||||
|
autoRedirectOutputMark = router.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 {
|
if options.RoutingMark > 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||||
}
|
|
||||||
if networkManager != nil {
|
|
||||||
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
|
||||||
if autoRedirectOutputMark > 0 {
|
if autoRedirectOutputMark > 0 {
|
||||||
if options.RoutingMark > 0 {
|
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
||||||
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 router != nil && router.DefaultMark() > 0 {
|
||||||
if C.NetworkStrategy(options.NetworkStrategy) != C.NetworkStrategyDefault {
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
if options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil {
|
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||||
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address` and `inet6_bind_address`")
|
if autoRedirectOutputMark > 0 {
|
||||||
}
|
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
||||||
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 {
|
if options.ReuseAddr {
|
||||||
@@ -139,7 +102,7 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
|||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
)
|
)
|
||||||
if options.Inet4BindAddress != nil {
|
if options.Inet4BindAddress != nil {
|
||||||
bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())
|
bindAddr := options.Inet4BindAddress.Build()
|
||||||
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
@@ -150,7 +113,7 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
|||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
)
|
)
|
||||||
if options.Inet6BindAddress != nil {
|
if options.Inet6BindAddress != nil {
|
||||||
bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())
|
bindAddr := options.Inet6BindAddress.Build()
|
||||||
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
@@ -166,9 +129,6 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
|||||||
listener.Control = control.Append(listener.Control, controlFn)
|
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)
|
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -178,19 +138,14 @@ func NewDefault(networkManager adapter.NetworkManager, options option.DialerOpti
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &DefaultDialer{
|
return &DefaultDialer{
|
||||||
dialer4: tcpDialer4,
|
tcpDialer4,
|
||||||
dialer6: tcpDialer6,
|
tcpDialer6,
|
||||||
udpDialer4: udpDialer4,
|
udpDialer4,
|
||||||
udpDialer6: udpDialer6,
|
udpDialer6,
|
||||||
udpListener: listener,
|
listener,
|
||||||
udpAddr4: udpAddr4,
|
udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6,
|
||||||
isWireGuardListener: options.IsWireGuardListener,
|
options.IsWireGuardListener,
|
||||||
networkManager: networkManager,
|
|
||||||
networkStrategy: networkStrategy,
|
|
||||||
networkType: networkType,
|
|
||||||
fallbackNetworkType: fallbackNetworkType,
|
|
||||||
networkFallbackDelay: networkFallbackDelay,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,88 +153,33 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
}
|
}
|
||||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
switch N.NetworkName(network) {
|
||||||
switch N.NetworkName(network) {
|
case N.NetworkUDP:
|
||||||
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() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||||
} else {
|
} else {
|
||||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !address.IsIPv6() {
|
||||||
|
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||||
} else {
|
} else {
|
||||||
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == C.NetworkStrategyDefault {
|
if destination.IsIPv6() {
|
||||||
if destination.IsIPv6() {
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
|
||||||
} else {
|
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||||
return trackPacketConn(d.listenSerialInterfacePacket(context.Background(), d.udpListener, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay))
|
return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address))
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
|
|||||||
@@ -13,7 +13,3 @@ type tcpDialer = tfo.Dialer
|
|||||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||||
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
|
||||||
return dialer.Dialer
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,3 @@ func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
|||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
|
||||||
return dialer
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = N.DefaultFallbackDelay
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
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 ParallelInterfaceDialer, 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
|
|
||||||
for _, address := range destinationAddresses {
|
|
||||||
conn, err := dialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
|
||||||
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 ParallelInterfaceDialer, 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
|
|
||||||
for _, address := range destinationAddresses {
|
|
||||||
conn, err := dialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
|
||||||
if err == nil {
|
|
||||||
return conn, address, nil
|
|
||||||
}
|
|
||||||
errors = append(errors, err)
|
|
||||||
}
|
|
||||||
return nil, netip.Addr{}, E.Errors(errors...)
|
|
||||||
}
|
|
||||||
@@ -12,15 +12,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DetourDialer struct {
|
type DetourDialer struct {
|
||||||
outboundManager adapter.OutboundManager
|
router adapter.Router
|
||||||
detour string
|
detour string
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
initErr error
|
initErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
|
func NewDetour(router adapter.Router, detour string) N.Dialer {
|
||||||
return &DetourDialer{outboundManager: outboundManager, detour: detour}
|
return &DetourDialer{router: router, detour: detour}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Start() error {
|
func (d *DetourDialer) Start() error {
|
||||||
@@ -31,7 +31,7 @@ func (d *DetourDialer) Start() error {
|
|||||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||||
d.initOnce.Do(func() {
|
d.initOnce.Do(func() {
|
||||||
var loaded bool
|
var loaded bool
|
||||||
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
d.dialer, loaded = d.router.Outbound(d.detour)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +1,40 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
"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"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
|
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
|
||||||
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
|
||||||
if options.IsWireGuardListener {
|
if options.IsWireGuardListener {
|
||||||
return NewDefault(networkManager, options)
|
return NewDefault(router, options)
|
||||||
|
}
|
||||||
|
if router == nil {
|
||||||
|
return NewDefault(nil, options)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if options.Detour == "" {
|
if options.Detour == "" {
|
||||||
dialer, err = NewDefault(networkManager, options)
|
dialer, err = NewDefault(router, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
|
dialer = NewDetour(router, options.Detour)
|
||||||
if outboundManager == nil {
|
|
||||||
return nil, E.New("missing outbound manager")
|
|
||||||
}
|
|
||||||
dialer = NewDetour(outboundManager, options.Detour)
|
|
||||||
}
|
|
||||||
if networkManager == nil {
|
|
||||||
return NewDefault(networkManager, options)
|
|
||||||
}
|
}
|
||||||
if options.Detour == "" {
|
if options.Detour == "" {
|
||||||
router := service.FromContext[adapter.Router](ctx)
|
dialer = NewResolveDialer(
|
||||||
if router != nil {
|
router,
|
||||||
dialer = NewResolveDialer(
|
dialer,
|
||||||
router,
|
options.Detour == "" && !options.TCPFastOpen,
|
||||||
dialer,
|
dns.DomainStrategy(options.DomainStrategy),
|
||||||
options.Detour == "" && !options.TCPFastOpen,
|
time.Duration(options.FallbackDelay))
|
||||||
dns.DomainStrategy(options.DomainStrategy),
|
|
||||||
time.Duration(options.FallbackDelay))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) {
|
|
||||||
if options.Detour != "" {
|
|
||||||
return nil, E.New("`detour` is not supported in direct context")
|
|
||||||
}
|
|
||||||
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,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
@@ -15,12 +14,7 @@ import (
|
|||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type ResolveDialer struct {
|
||||||
_ N.Dialer = (*resolveDialer)(nil)
|
|
||||||
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type resolveDialer struct {
|
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
parallel bool
|
parallel bool
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
@@ -28,8 +22,8 @@ type resolveDialer struct {
|
|||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer {
|
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
||||||
return &resolveDialer{
|
return &ResolveDialer{
|
||||||
dialer,
|
dialer,
|
||||||
parallel,
|
parallel,
|
||||||
router,
|
router,
|
||||||
@@ -38,25 +32,7 @@ func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolveParallelNetworkDialer struct {
|
func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
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() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
@@ -81,7 +57,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() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
@@ -106,59 +82,6 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
func (d *ResolveDialer) Upstream() any {
|
||||||
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
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,22 +9,30 @@ import (
|
|||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultOutboundDialer struct {
|
type RouterDialer struct {
|
||||||
outboundManager adapter.OutboundManager
|
router adapter.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultOutbound(outboundManager adapter.OutboundManager) N.Dialer {
|
func NewRouter(router adapter.Router) N.Dialer {
|
||||||
return &DefaultOutboundDialer{outboundManager: outboundManager}
|
return &RouterDialer{router: router}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
return d.outboundManager.Default().DialContext(ctx, network, destination)
|
dialer, err := d.router.DefaultOutbound(network)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return d.outboundManager.Default().ListenPacket(ctx, destination)
|
dialer, err := d.router.DefaultOutbound(N.NetworkUDP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultOutboundDialer) Upstream() any {
|
func (d *RouterDialer) Upstream() any {
|
||||||
return d.outboundManager.Default()
|
return d.router
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package listener
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -93,7 +92,7 @@ func (l *Listener) Start() error {
|
|||||||
if l.setSystemProxy {
|
if l.setSystemProxy {
|
||||||
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
|
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
|
||||||
var listenAddrString string
|
var listenAddrString string
|
||||||
listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())
|
listenAddr := l.listenOptions.Listen.Build()
|
||||||
if listenAddr.IsUnspecified() {
|
if listenAddr.IsUnspecified() {
|
||||||
listenAddrString = "127.0.0.1"
|
listenAddrString = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package listener
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -17,7 +16,7 @@ import (
|
|||||||
|
|
||||||
func (l *Listener) ListenTCP() (net.Listener, error) {
|
func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||||
var err error
|
var err error
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
|
||||||
var tcpListener net.Listener
|
var tcpListener net.Listener
|
||||||
var listenConfig net.ListenConfig
|
var listenConfig net.ListenConfig
|
||||||
if l.listenOptions.TCPKeepAlive >= 0 {
|
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package listener
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
|
||||||
var lc net.ListenConfig
|
var lc net.ListenConfig
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if l.listenOptions.UDPFragment != nil {
|
if l.listenOptions.UDPFragment != nil {
|
||||||
@@ -49,9 +48,9 @@ func (l *Listener) loopUDPIn() {
|
|||||||
if !l.threadUnsafePacketWriter {
|
if !l.threadUnsafePacketWriter {
|
||||||
buffer = buf.NewPacket()
|
buffer = buf.NewPacket()
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
buffer.IncRef()
|
|
||||||
defer buffer.DecRef()
|
|
||||||
}
|
}
|
||||||
|
buffer.IncRef()
|
||||||
|
defer buffer.DecRef()
|
||||||
if l.oobPacketHandler != nil {
|
if l.oobPacketHandler != nil {
|
||||||
oob := make([]byte, 1024)
|
oob := make([]byte, 1024)
|
||||||
for {
|
for {
|
||||||
|
|||||||
@@ -2,17 +2,16 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/shell"
|
"github.com/sagernet/sing/common/shell"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DarwinSystemProxy struct {
|
type DarwinSystemProxy struct {
|
||||||
@@ -25,7 +24,7 @@ type DarwinSystemProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
||||||
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
|
interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
|
||||||
if interfaceMonitor == nil {
|
if interfaceMonitor == nil {
|
||||||
return nil, E.New("missing interface monitor")
|
return nil, E.New("missing interface monitor")
|
||||||
}
|
}
|
||||||
@@ -34,7 +33,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo
|
|||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
supportSOCKS: supportSOCKS,
|
supportSOCKS: supportSOCKS,
|
||||||
}
|
}
|
||||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate)
|
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
||||||
return proxy, nil
|
return proxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,22 +65,25 @@ func (p *DarwinSystemProxy) Disable() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) {
|
func (p *DarwinSystemProxy) update(event int) {
|
||||||
if !p.isEnabled || defaultInterface == nil {
|
if event&tun.EventInterfaceUpdate == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !p.isEnabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = p.update0()
|
_ = p.update0()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DarwinSystemProxy) update0() error {
|
func (p *DarwinSystemProxy) update0() error {
|
||||||
newInterface := p.monitor.DefaultInterface()
|
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
||||||
if p.interfaceName == newInterface.Name {
|
if p.interfaceName == newInterfaceName {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if p.interfaceName != "" {
|
if p.interfaceName != "" {
|
||||||
_ = p.Disable()
|
_ = p.Disable()
|
||||||
}
|
}
|
||||||
p.interfaceName = newInterface.Name
|
p.interfaceName = newInterfaceName
|
||||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -38,13 +38,10 @@ const (
|
|||||||
ruleItemWIFIBSSID
|
ruleItemWIFIBSSID
|
||||||
ruleItemAdGuardDomain
|
ruleItemAdGuardDomain
|
||||||
ruleItemProcessPathRegex
|
ruleItemProcessPathRegex
|
||||||
ruleItemNetworkType
|
|
||||||
ruleItemNetworkIsExpensive
|
|
||||||
ruleItemNetworkIsConstrained
|
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetCompat, err error) {
|
func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) {
|
||||||
var magicBytes [3]byte
|
var magicBytes [3]byte
|
||||||
_, err = io.ReadFull(reader, magicBytes[:])
|
_, err = io.ReadFull(reader, magicBytes[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -57,10 +54,10 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
|||||||
var version uint8
|
var version uint8
|
||||||
err = binary.Read(reader, binary.BigEndian, &version)
|
err = binary.Read(reader, binary.BigEndian, &version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruleSetCompat, err
|
return ruleSet, err
|
||||||
}
|
}
|
||||||
if version > C.RuleSetVersionCurrent {
|
if version > C.RuleSetVersion2 {
|
||||||
return ruleSetCompat, E.New("unsupported version: ", version)
|
return ruleSet, E.New("unsupported version: ", version)
|
||||||
}
|
}
|
||||||
compressReader, err := zlib.NewReader(reader)
|
compressReader, err := zlib.NewReader(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,10 +68,9 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ruleSetCompat.Version = version
|
ruleSet.Rules = make([]option.HeadlessRule, length)
|
||||||
ruleSetCompat.Options.Rules = make([]option.HeadlessRule, length)
|
|
||||||
for i := uint64(0); i < length; i++ {
|
for i := uint64(0); i < length; i++ {
|
||||||
ruleSetCompat.Options.Rules[i], err = readRule(bReader, recover)
|
ruleSet.Rules[i], err = readRule(bReader, recover)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = E.Cause(err, "read rule[", i, "]")
|
err = E.Cause(err, "read rule[", i, "]")
|
||||||
return
|
return
|
||||||
@@ -83,12 +79,18 @@ func Read(reader io.Reader, recover bool) (ruleSetCompat option.PlainRuleSetComp
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8) error {
|
func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateUnstable bool) error {
|
||||||
_, err := writer.Write(MagicBytes[:])
|
_, err := writer.Write(MagicBytes[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = binary.Write(writer, binary.BigEndian, generateVersion)
|
var version uint8
|
||||||
|
if generateUnstable {
|
||||||
|
version = C.RuleSetVersion2
|
||||||
|
} else {
|
||||||
|
version = C.RuleSetVersion1
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.BigEndian, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -102,7 +104,7 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet, generateVersion uint8)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, rule := range ruleSet.Rules {
|
for _, rule := range ruleSet.Rules {
|
||||||
err = writeRule(bWriter, rule, generateVersion)
|
err = writeRule(bWriter, rule, generateUnstable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -133,12 +135,12 @@ func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateVersion uint8) error {
|
func writeRule(writer varbin.Writer, rule option.HeadlessRule, generateUnstable bool) error {
|
||||||
switch rule.Type {
|
switch rule.Type {
|
||||||
case C.RuleTypeDefault:
|
case C.RuleTypeDefault:
|
||||||
return writeDefaultRule(writer, rule.DefaultOptions, generateVersion)
|
return writeDefaultRule(writer, rule.DefaultOptions, generateUnstable)
|
||||||
case C.RuleTypeLogical:
|
case C.RuleTypeLogical:
|
||||||
return writeLogicalRule(writer, rule.LogicalOptions, generateVersion)
|
return writeLogicalRule(writer, rule.LogicalOptions, generateUnstable)
|
||||||
default:
|
default:
|
||||||
panic("unknown rule type: " + rule.Type)
|
panic("unknown rule type: " + rule.Type)
|
||||||
}
|
}
|
||||||
@@ -225,12 +227,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
case ruleItemNetworkType:
|
|
||||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
|
||||||
case ruleItemNetworkIsExpensive:
|
|
||||||
rule.NetworkIsExpensive = true
|
|
||||||
case ruleItemNetworkIsConstrained:
|
|
||||||
rule.NetworkIsConstrained = true
|
|
||||||
case ruleItemFinal:
|
case ruleItemFinal:
|
||||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||||
return
|
return
|
||||||
@@ -244,7 +240,7 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateVersion uint8) error {
|
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, generateUnstable bool) error {
|
||||||
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -268,7 +264,7 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, generateVersion == C.RuleSetVersion1).Write(writer)
|
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix, !generateUnstable).Write(writer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -345,27 +341,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
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 {
|
if len(rule.WIFISSID) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -379,9 +354,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(rule.AdGuardDomain) > 0 {
|
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)
|
err = binary.Write(writer, binary.BigEndian, ruleItemAdGuardDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -414,18 +386,6 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
|
|||||||
return varbin.Write(writer, binary.BigEndian, value)
|
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) {
|
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||||
}
|
}
|
||||||
@@ -497,7 +457,7 @@ func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.Lo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateVersion uint8) error {
|
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule, generateUnstable bool) error {
|
||||||
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -518,7 +478,7 @@ func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRu
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, rule := range logicalRule.Rules {
|
for _, rule := range logicalRule.Rules {
|
||||||
err = writeRule(writer, rule, generateVersion)
|
err = writeRule(writer, rule, generateUnstable)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -214,7 +213,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message)
|
response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/reality"
|
"github.com/sagernet/reality"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -101,7 +102,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
tlsConfig.ShortIds[shortID] = true
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions)
|
handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
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 IsAndroid = goos.IsAndroid == 1
|
||||||
|
|
||||||
const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1
|
const IsDarwin = goos.IsDarwin == 1
|
||||||
|
|
||||||
const IsDragonfly = goos.IsDragonfly == 1
|
const IsDragonfly = goos.IsDragonfly == 1
|
||||||
|
|
||||||
|
|||||||
@@ -21,21 +21,22 @@ const (
|
|||||||
const (
|
const (
|
||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
RuleSetVersion3
|
|
||||||
RuleSetVersionCurrent = RuleSetVersion3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RuleActionTypeRoute = "route"
|
RuleActionTypeRoute = "route"
|
||||||
RuleActionTypeRouteOptions = "route-options"
|
RuleActionTypeReturn = "return"
|
||||||
RuleActionTypeDirect = "direct"
|
RuleActionTypeReject = "reject"
|
||||||
RuleActionTypeReject = "reject"
|
RuleActionTypeHijackDNS = "hijack-dns"
|
||||||
RuleActionTypeHijackDNS = "hijack-dns"
|
RuleActionTypeSniff = "sniff"
|
||||||
RuleActionTypeSniff = "sniff"
|
RuleActionTypeResolve = "resolve"
|
||||||
RuleActionTypeResolve = "resolve"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RuleActionRejectMethodDefault = "default"
|
RuleActionRejectMethodDefault = "default"
|
||||||
RuleActionRejectMethodDrop = "drop"
|
RuleActionRejectMethodReset = "reset"
|
||||||
|
RuleActionRejectMethodNetworkUnreachable = "network-unreachable"
|
||||||
|
RuleActionRejectMethodHostUnreachable = "host-unreachable"
|
||||||
|
RuleActionRejectMethodPortUnreachable = "port-unreachable"
|
||||||
|
RuleActionRejectMethodDrop = "drop"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,106 +2,8 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.11.0-alpha.15
|
#### 1.11.0-alpha.5
|
||||||
|
|
||||||
* 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**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
When `gvisor` tun stack is enabled, even if the request passes routing,
|
|
||||||
if the outbound connection establishment fails,
|
|
||||||
the connection still does not need to be established and a TCP RST is replied.
|
|
||||||
|
|
||||||
#### 1.11.0-alpha.7
|
|
||||||
|
|
||||||
* Introducing rule actions **1**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
New rule actions replace legacy inbound fields and special outbound fields,
|
|
||||||
and can be used for pre-matching **2**.
|
|
||||||
|
|
||||||
See [Rule](/configuration/route/rule/),
|
|
||||||
[Rule Action](/configuration/route/rule_action/),
|
|
||||||
[DNS Rule](/configuration/dns/rule/) and
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/).
|
|
||||||
|
|
||||||
For migration, see
|
|
||||||
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
|
|
||||||
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
|
|
||||||
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Similar to Surge's pre-matching.
|
|
||||||
|
|
||||||
Specifically, new rule actions allow you to reject connections with
|
|
||||||
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
|
|
||||||
before connection established to improve tun's compatibility.
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/rule_action/).
|
|
||||||
|
|
||||||
#### 1.11.0-alpha.6
|
|
||||||
|
|
||||||
* Update quic-go to v0.48.1
|
|
||||||
* Set gateway for tun correctly
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
#### 1.11.0-alpha.2
|
#### 1.11.0-alpha.2
|
||||||
@@ -145,7 +47,7 @@ Important changes since 1.9:
|
|||||||
The new auto-redirect feature allows TUN to automatically
|
The new auto-redirect feature allows TUN to automatically
|
||||||
configure connection redirection to improve proxy performance.
|
configure connection redirection to improve proxy performance.
|
||||||
|
|
||||||
When auto-redirect is enabled, new route address set options will allow you to
|
When auto-redirect is enabled, new route address set options will allow you to
|
||||||
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
||||||
|
|
||||||
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||||
@@ -189,7 +91,7 @@ allows you to write headless rules directly without creating a rule-set file.
|
|||||||
|
|
||||||
**8**:
|
**8**:
|
||||||
|
|
||||||
With new access control options, not only can you allow Clash dashboards
|
With the new access control options, not only can you allow Clash dashboards
|
||||||
to access the Clash API on your local network,
|
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.
|
you can also manually limit the websites that can access the API instead of allowing everyone.
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# FakeIP
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# FakeIP
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -2,17 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in 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 "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
@@ -25,7 +14,7 @@ icon: material/new-box
|
|||||||
:material-plus: [geoip](#geoip)
|
:material-plus: [geoip](#geoip)
|
||||||
:material-plus: [ip_cidr](#ip_cidr)
|
:material-plus: [ip_cidr](#ip_cidr)
|
||||||
:material-plus: [ip_is_private](#ip_is_private)
|
:material-plus: [ip_is_private](#ip_is_private)
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
@@ -128,11 +117,6 @@ icon: material/new-box
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
"network_type": [
|
|
||||||
"wifi"
|
|
||||||
],
|
|
||||||
"network_is_expensive": false,
|
|
||||||
"network_is_constrained": false,
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -151,15 +135,19 @@ icon: material/new-box
|
|||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
],
|
],
|
||||||
"action": "route",
|
"server": "local",
|
||||||
"server": "local"
|
"disable_cache": false,
|
||||||
|
"rewrite_ttl": 100,
|
||||||
|
"client_subnet": "127.0.0.1/24"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"action": "route",
|
"server": "local",
|
||||||
"server": "local"
|
"disable_cache": false,
|
||||||
|
"rewrite_ttl": 100,
|
||||||
|
"client_subnet": "127.0.0.1/24"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -230,7 +218,7 @@ Match domain using regular expression.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
Match geosite.
|
Match geosite.
|
||||||
|
|
||||||
@@ -238,7 +226,7 @@ Match geosite.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
Match source geoip.
|
Match source geoip.
|
||||||
|
|
||||||
@@ -318,39 +306,6 @@ Match user id.
|
|||||||
|
|
||||||
Match Clash mode.
|
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
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -399,35 +354,29 @@ Match outbound.
|
|||||||
|
|
||||||
`any` can be used as a value to match any outbound.
|
`any` can be used as a value to match any outbound.
|
||||||
|
|
||||||
#### action
|
#### server
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
See [DNS Rule Actions](../rule_action/) for details.
|
Tag of the target dns server.
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
Rewrite TTL in DNS responses.
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
#### client_subnet
|
#### client_subnet
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
!!! question "Since sing-box 1.9.0"
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||||
|
|
||||||
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
|
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
|
||||||
|
|
||||||
### Address Filter Fields
|
### Address Filter Fields
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,6 @@
|
|||||||
icon: material/new-box
|
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 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
@@ -25,7 +14,7 @@ icon: material/new-box
|
|||||||
:material-plus: [geoip](#geoip)
|
:material-plus: [geoip](#geoip)
|
||||||
:material-plus: [ip_cidr](#ip_cidr)
|
:material-plus: [ip_cidr](#ip_cidr)
|
||||||
:material-plus: [ip_is_private](#ip_is_private)
|
:material-plus: [ip_is_private](#ip_is_private)
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
@@ -128,11 +117,6 @@ icon: material/new-box
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
"network_type": [
|
|
||||||
"wifi"
|
|
||||||
],
|
|
||||||
"network_is_expensive": false,
|
|
||||||
"network_is_constrained": false,
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -151,15 +135,17 @@ icon: material/new-box
|
|||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
],
|
],
|
||||||
"action": "route",
|
"server": "local",
|
||||||
"server": "local"
|
"disable_cache": false,
|
||||||
|
"client_subnet": "127.0.0.1/24"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"action": "route",
|
"server": "local",
|
||||||
"server": "local"
|
"disable_cache": false,
|
||||||
|
"client_subnet": "127.0.0.1/24"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -318,39 +304,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
|||||||
|
|
||||||
匹配 Clash 模式。
|
匹配 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
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -399,35 +352,29 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
`any` 可作为值用于匹配任意出站。
|
`any` 可作为值用于匹配任意出站。
|
||||||
|
|
||||||
#### action
|
#### server
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
参阅 [规则动作](../rule_action/)。
|
目标 DNS 服务器的标签。
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
已移动到 [DNS 规则动作](../rule_action#route).
|
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
在此查询中禁用缓存。
|
||||||
|
|
||||||
已移动到 [DNS 规则动作](../rule_action#route).
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
重写 DNS 回应中的 TTL。
|
||||||
|
|
||||||
已移动到 [DNS 规则动作](../rule_action#route).
|
|
||||||
|
|
||||||
#### client_subnet
|
#### client_subnet
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
!!! question "自 sing-box 1.9.0 起"
|
||||||
|
|
||||||
已移动到 [DNS 规则动作](../rule_action#route).
|
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||||
|
|
||||||
|
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||||
|
|
||||||
|
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
||||||
|
|
||||||
### 地址筛选字段
|
### 地址筛选字段
|
||||||
|
|
||||||
@@ -473,12 +420,8 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
`and` 或 `or`
|
`and` 或 `or`
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
==必填==
|
包括的规则。
|
||||||
|
|
||||||
包括的规则。
|
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // default
|
|
||||||
"server": "",
|
|
||||||
"disable_cache": false,
|
|
||||||
"rewrite_ttl": 0,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route` inherits the classic rule behavior of routing DNS requests to the specified server.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Tag of target server.
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
Disable cache and save cache in this query.
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
|
||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // default
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` reject DNS requests.
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: Reply with NXDOMAIN.
|
|
||||||
- `drop`: Drop the request.
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
|
||||||
|
|
||||||
Not available when `method` is set to drop.
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // 默认
|
|
||||||
"server": "",
|
|
||||||
|
|
||||||
// 兼容性
|
|
||||||
"disable_cache": false,
|
|
||||||
"rewrite_ttl": 0,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route` 继承了将 DNS 请求 路由到指定服务器的经典规则动作。
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
目标 DNS 服务器的标签。
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
在此查询中禁用缓存。
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
|
||||||
|
|
||||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
|
||||||
|
|
||||||
将覆盖 `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
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // default
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` 拒绝 DNS 请求。
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: 返回 NXDOMAIN。
|
|
||||||
- `drop`: 丢弃请求。
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
|
||||||
|
|
||||||
当 `method` 设为 `drop` 时不可用。
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|||||||
@@ -66,11 +66,11 @@ Only available in the ShadowTLS protocol 3.
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Handshake server address and [Dial Fields](/configuration/shared/dial/).
|
Handshake server address and [Dial options](/configuration/shared/dial/).
|
||||||
|
|
||||||
#### handshake_for_server_name
|
#### handshake_for_server_name
|
||||||
|
|
||||||
Handshake server address and [Dial Fields](/configuration/shared/dial/) for specific server name.
|
Handshake server address and [Dial options](/configuration/shared/dial/) for specific server name.
|
||||||
|
|
||||||
Only available in the ShadowTLS protocol 2/3.
|
Only available in the ShadowTLS protocol 2/3.
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
---
|
`block` outbound closes all incoming requests.
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json F
|
```json
|
||||||
{
|
{
|
||||||
"type": "block",
|
"type": "block",
|
||||||
"tag": "block"
|
"tag": "block"
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
`block` 出站关闭所有传入请求。
|
`block` 出站关闭所有传入请求。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
@@ -19,4 +11,4 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
无字段。
|
无字段。
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
`dns` outbound is a internal DNS server.
|
`dns` outbound is a internal DNS server.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除, 参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
`dns` 出站是一个内部 DNS 服务器。
|
`dns` 出站是一个内部 DNS 服务器。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,5 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
# Route
|
# 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-alert: [default_fallback_delay](#default_fallback_delay)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
|
|
||||||
:material-plus: [rule_set](#rule_set)
|
:material-plus: [rule_set](#rule_set)
|
||||||
@@ -29,22 +18,19 @@ icon: material/new-box
|
|||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "en0",
|
||||||
"default_mark": 0,
|
"default_mark": 233
|
||||||
"default_network_strategy": "",
|
|
||||||
"default_network_type": [],
|
|
||||||
"default_fallback_network_type": [],
|
|
||||||
"default_fallback_delay": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
You can ignore the JSON Array [] tag when the content is only one item
|
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
| Key | Format |
|
||||||
|
|-----------|----------------------|
|
||||||
|
| `geoip` | [GeoIP](./geoip/) |
|
||||||
|
| `geosite` | [Geosite](./geosite/) |
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
List of [Route Rule](./rule/)
|
List of [Route Rule](./rule/)
|
||||||
@@ -95,34 +81,4 @@ Takes no effect if `auto_detect_interface` is set.
|
|||||||
|
|
||||||
Set routing mark by default.
|
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,16 +1,5 @@
|
|||||||
---
|
|
||||||
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-alert: [default_fallback_delay](#default_fallback_delay)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [rule_set](#rule_set)
|
:material-plus: [rule_set](#rule_set)
|
||||||
@@ -29,18 +18,12 @@ icon: material/new-box
|
|||||||
"final": "",
|
"final": "",
|
||||||
"auto_detect_interface": false,
|
"auto_detect_interface": false,
|
||||||
"override_android_vpn": false,
|
"override_android_vpn": false,
|
||||||
"default_interface": "",
|
"default_interface": "en0",
|
||||||
"default_mark": 0,
|
"default_mark": 233
|
||||||
"default_network_strategy": "",
|
|
||||||
"default_fallback_delay": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
|
||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
| 键 | 格式 |
|
| 键 | 格式 |
|
||||||
@@ -99,33 +82,3 @@ icon: material/new-box
|
|||||||
默认为出站连接设置路由标记。
|
默认为出站连接设置路由标记。
|
||||||
|
|
||||||
如果设置了 `outbound.routing_mark` 设置,则不生效。
|
如果设置了 `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)。
|
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
|
||||||
: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"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
@@ -123,11 +115,6 @@ icon: material/new-box
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
"network_type": [
|
|
||||||
"wifi"
|
|
||||||
],
|
|
||||||
"network_is_expensive": false,
|
|
||||||
"network_is_constrained": false,
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -142,7 +129,6 @@ icon: material/new-box
|
|||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -150,7 +136,6 @@ icon: material/new-box
|
|||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -224,7 +209,7 @@ Match domain using regular expression.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
Match geosite.
|
Match geosite.
|
||||||
|
|
||||||
@@ -232,7 +217,7 @@ Match geosite.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
Match source geoip.
|
Match source geoip.
|
||||||
|
|
||||||
@@ -240,7 +225,7 @@ Match source geoip.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
Match geoip.
|
Match geoip.
|
||||||
|
|
||||||
@@ -330,39 +315,6 @@ Match user id.
|
|||||||
|
|
||||||
Match Clash mode.
|
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
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -405,17 +357,11 @@ Make `ip_cidr` in rule-sets match the source IP.
|
|||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
|
|
||||||
#### action
|
#### outbound
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
See [Rule Actions](../rule_action/) for details.
|
Tag of the target outbound.
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Moved to [Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
|
||||||
: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 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
:material-plus: [process_path_regex](#process_path_regex)
|
:material-plus: [process_path_regex](#process_path_regex)
|
||||||
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [rule_set](#rule_set)
|
:material-plus: [rule_set](#rule_set)
|
||||||
@@ -120,11 +113,6 @@ icon: material/new-box
|
|||||||
1000
|
1000
|
||||||
],
|
],
|
||||||
"clash_mode": "direct",
|
"clash_mode": "direct",
|
||||||
"network_type": [
|
|
||||||
"wifi"
|
|
||||||
],
|
|
||||||
"network_is_expensive": false,
|
|
||||||
"network_is_constrained": false,
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -139,7 +127,6 @@ icon: material/new-box
|
|||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -147,7 +134,6 @@ icon: material/new-box
|
|||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -160,7 +146,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
当内容只有一项时,可以忽略 JSON 数组 [] 标签。
|
||||||
|
|
||||||
### 默认字段
|
### Default Fields
|
||||||
|
|
||||||
!!! note ""
|
!!! note ""
|
||||||
|
|
||||||
@@ -327,39 +313,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
匹配 Clash 模式。
|
匹配 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
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
@@ -402,17 +355,11 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
|
|||||||
|
|
||||||
反选匹配结果。
|
反选匹配结果。
|
||||||
|
|
||||||
#### action
|
#### outbound
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
参阅 [规则动作](../rule_action/)。
|
目标出站的标签。
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
已移动到 [规则动作](../rule_action#route).
|
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
## Final actions
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // default
|
|
||||||
"outbound": "",
|
|
||||||
"network_strategy": "",
|
|
||||||
"network_type": [],
|
|
||||||
"fallback_network_type": [],
|
|
||||||
"fallback_delay": "",
|
|
||||||
"udp_disable_domain_unmapping": false,
|
|
||||||
"udp_connect": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! 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
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Tag of target outbound.
|
|
||||||
|
|
||||||
#### 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,
|
|
||||||
the original packet address will be sent in the response instead of the mapped domain.
|
|
||||||
|
|
||||||
This option is used for compatibility with clients that
|
|
||||||
do not support receiving UDP packets with domain addresses, such as Surge.
|
|
||||||
|
|
||||||
#### udp_connect
|
|
||||||
|
|
||||||
If enabled, attempts to connect UDP connection to the destination instead of listen.
|
|
||||||
|
|
||||||
### route-options
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route-options",
|
|
||||||
"network_strategy": "",
|
|
||||||
"fallback_delay": "",
|
|
||||||
"udp_disable_domain_unmapping": false,
|
|
||||||
"udp_connect": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route-options` set options for routing.
|
|
||||||
|
|
||||||
### reject
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // default
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` reject connections
|
|
||||||
|
|
||||||
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
|
|
||||||
|
|
||||||
For non-tun connections and already established connections, will just be closed.
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
|
|
||||||
- `drop`: Drop packets.
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
|
||||||
|
|
||||||
Not available when `method` is set to drop.
|
|
||||||
|
|
||||||
### hijack-dns
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "hijack-dns"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`hijack-dns` hijack DNS requests to the sing-box DNS module.
|
|
||||||
|
|
||||||
## Non-final actions
|
|
||||||
|
|
||||||
### sniff
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "sniff",
|
|
||||||
"sniffer": [],
|
|
||||||
"timeout": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`sniff` performs protocol sniffing on connections.
|
|
||||||
|
|
||||||
For deprecated `inbound.sniff` options, it is considered to `sniff()` performed before routing.
|
|
||||||
|
|
||||||
#### sniffer
|
|
||||||
|
|
||||||
Enabled sniffers.
|
|
||||||
|
|
||||||
All sniffers enabled by default.
|
|
||||||
|
|
||||||
Available protocol values an be found on in [Protocol Sniff](../sniff/)
|
|
||||||
|
|
||||||
#### timeout
|
|
||||||
|
|
||||||
Timeout for sniffing.
|
|
||||||
|
|
||||||
`300ms` is used by default.
|
|
||||||
|
|
||||||
### resolve
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "",
|
|
||||||
"server": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`resolve` resolve request destination from domain to IP addresses.
|
|
||||||
|
|
||||||
#### strategy
|
|
||||||
|
|
||||||
DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
|
||||||
|
|
||||||
`dns.strategy` will be used by default.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
Specifies DNS server tag to use instead of selecting through DNS routing.
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
## 最终动作
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // 默认
|
|
||||||
"outbound": "",
|
|
||||||
"network_strategy": "",
|
|
||||||
"fallback_delay": "",
|
|
||||||
"network_type": [],
|
|
||||||
"fallback_network_type": [],
|
|
||||||
"udp_disable_domain_unmapping": false,
|
|
||||||
"udp_connect": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route` 继承了将连接路由到指定出站的经典规则动作。
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
目标出站的标签。
|
|
||||||
|
|
||||||
#### 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 代理请求,将在响应中发送原始包地址而不是映射的域。
|
|
||||||
|
|
||||||
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
|
||||||
|
|
||||||
#### udp_connect
|
|
||||||
|
|
||||||
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。
|
|
||||||
|
|
||||||
### route-options
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route-options",
|
|
||||||
"network_strategy": "",
|
|
||||||
"fallback_delay": "",
|
|
||||||
"udp_disable_domain_unmapping": false,
|
|
||||||
"udp_connect": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
当内容只有一项时,可以忽略 JSON 数组 [] 标签
|
|
||||||
|
|
||||||
`route-options` 为路由设置选项。
|
|
||||||
|
|
||||||
### reject
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // 默认
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` 拒绝连接。
|
|
||||||
|
|
||||||
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
|
|
||||||
|
|
||||||
对于非 tun 连接和已建立的连接,将直接关闭。
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。
|
|
||||||
- `drop`: 丢弃数据包。
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
|
||||||
|
|
||||||
当 `method` 设为 `drop` 时不可用。
|
|
||||||
|
|
||||||
### hijack-dns
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "hijack-dns"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
|
|
||||||
|
|
||||||
## 非最终动作
|
|
||||||
|
|
||||||
### sniff
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "sniff",
|
|
||||||
"sniffer": [],
|
|
||||||
"timeout": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`sniff` 对连接执行协议嗅探。
|
|
||||||
|
|
||||||
对于已弃用的 `inbound.sniff` 选项,被视为在路由之前执行的 `sniff`。
|
|
||||||
|
|
||||||
#### sniffer
|
|
||||||
|
|
||||||
启用的探测器。
|
|
||||||
|
|
||||||
默认启用所有探测器。
|
|
||||||
|
|
||||||
可用的协议值可以在 [协议嗅探](../sniff/) 中找到。
|
|
||||||
|
|
||||||
#### timeout
|
|
||||||
|
|
||||||
探测超时时间。
|
|
||||||
|
|
||||||
默认使用 300ms。
|
|
||||||
|
|
||||||
### resolve
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "",
|
|
||||||
"server": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`resolve` 将请求的目标从域名解析为 IP 地址。
|
|
||||||
|
|
||||||
#### strategy
|
|
||||||
|
|
||||||
DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、`ipv6_only`。
|
|
||||||
|
|
||||||
默认使用 `dns.strategy`。
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
指定要使用的 DNS 服务器的标签,而不是通过 DNS 路由进行选择。
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# AdGuard DNS Filter
|
||||||
|
|
||||||
!!! question "Since sing-box 1.10.0"
|
!!! question "Since sing-box 1.10.0"
|
||||||
|
|
||||||
sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box,
|
sing-box supports some rule-set formats from other projects which cannot be fully translated to sing-box,
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
---
|
|
||||||
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,13 +1,3 @@
|
|||||||
---
|
|
||||||
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
|
### Structure
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
@@ -73,11 +63,6 @@ icon: material/new-box
|
|||||||
"package_name": [
|
"package_name": [
|
||||||
"com.termux"
|
"com.termux"
|
||||||
],
|
],
|
||||||
"network_type": [
|
|
||||||
"wifi"
|
|
||||||
],
|
|
||||||
"network_is_expensive": false,
|
|
||||||
"network_is_constrained": false,
|
|
||||||
"wifi_ssid": [
|
"wifi_ssid": [
|
||||||
"My WIFI"
|
"My WIFI"
|
||||||
],
|
],
|
||||||
@@ -192,39 +177,6 @@ Match process path using regular expression.
|
|||||||
|
|
||||||
Match android package name.
|
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
|
#### wifi_ssid
|
||||||
|
|
||||||
!!! quote ""
|
!!! quote ""
|
||||||
|
|||||||
@@ -1,258 +0,0 @@
|
|||||||
---
|
|
||||||
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==
|
==Required==
|
||||||
|
|
||||||
List of [Headless Rule](../headless-rule/).
|
List of [Headless Rule](./headless-rule.md/).
|
||||||
|
|
||||||
### Local or Remote Fields
|
### Local or Remote Fields
|
||||||
|
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
---
|
|
||||||
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,9 +2,7 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
# Source Format
|
||||||
|
|
||||||
:material-plus: version `3`
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
@@ -16,7 +14,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"rules": []
|
"rules": []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -31,14 +29,19 @@ Use `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` to c
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Version of rule-set.
|
Version of rule-set, one of `1` or `2`.
|
||||||
|
|
||||||
* 1: sing-box 1.8.0: Initial rule-set version.
|
* 1: Initial rule-set version, since sing-box 1.8.0.
|
||||||
* 2: sing-box 1.10.0: Optimized memory usages of `domain_suffix` rules in binary rule-sets.
|
* 2: Optimized memory usages of `domain_suffix` rules.
|
||||||
* 3: sing-box 1.11.0: Added `network_type`, `network_is_expensive` and `network_is_constrainted` rule items.
|
|
||||||
|
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.
|
||||||
|
|
||||||
#### rules
|
#### rules
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
List of [Headless Rule](../headless-rule/).
|
List of [Headless Rule](./headless-rule.md/).
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
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,14 +1,3 @@
|
|||||||
---
|
|
||||||
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
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -24,25 +13,20 @@ icon: material/new-box
|
|||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"domain_strategy": "prefer_ipv6",
|
"domain_strategy": "prefer_ipv6",
|
||||||
"network_strategy": "default",
|
|
||||||
"network_type": [],
|
|
||||||
"fallback_network_type": [],
|
|
||||||
"fallback_delay": "300ms"
|
"fallback_delay": "300ms"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note ""
|
|
||||||
|
|
||||||
You can ignore the JSON Array [] tag when the content is only one item
|
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
|
| Field | Available Context |
|
||||||
|
|------------------------------------------------------------------------------------------------------------------------------------------|-------------------|
|
||||||
|
| `bind_interface` /`*bind_address` /`routing_mark` /`reuse_addr` / `tcp_fast_open` / `tcp_multi_path` / `udp_fragment` /`connect_timeout` | `detour` not set |
|
||||||
|
|
||||||
#### detour
|
#### detour
|
||||||
|
|
||||||
The tag of the upstream outbound.
|
The tag of the upstream outbound.
|
||||||
|
|
||||||
If enabled, all other fields will be ignored.
|
|
||||||
|
|
||||||
#### bind_interface
|
#### bind_interface
|
||||||
|
|
||||||
The network interface to bind to.
|
The network interface to bind to.
|
||||||
@@ -94,7 +78,7 @@ Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
|
|||||||
|
|
||||||
#### domain_strategy
|
#### domain_strategy
|
||||||
|
|
||||||
Available values: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before connect.
|
If set, the requested domain name will be resolved to IP before connect.
|
||||||
|
|
||||||
@@ -103,71 +87,11 @@ If set, the requested domain name will be resolved to IP before connect.
|
|||||||
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
|
| `direct` | Domain in request | Take `inbound.domain_strategy` if not set |
|
||||||
| others | Domain in server address | / |
|
| 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
|
#### fallback_delay
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
|
||||||
|
|
||||||
!!! 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.
|
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
|
||||||
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.
|
that IPv4/IPv6 is misconfigured and falling back to other type of addresses.
|
||||||
|
If zero, a default delay of 300ms is used.
|
||||||
|
|
||||||
For `network_strategy`, is the amount of time to wait for connection to succeed before falling
|
Only take effect when `domain_strategy` is set.
|
||||||
back to other interfaces.
|
|
||||||
|
|
||||||
Only take effect when `domain_strategy` or `network_strategy` is set.
|
|
||||||
|
|
||||||
`300ms` is used by default.
|
|
||||||
@@ -1,14 +1,3 @@
|
|||||||
---
|
|
||||||
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
|
```json
|
||||||
@@ -24,19 +13,17 @@ icon: material/new-box
|
|||||||
"tcp_multi_path": false,
|
"tcp_multi_path": false,
|
||||||
"udp_fragment": false,
|
"udp_fragment": false,
|
||||||
"domain_strategy": "prefer_ipv6",
|
"domain_strategy": "prefer_ipv6",
|
||||||
"network_strategy": "",
|
|
||||||
"network_type": [],
|
|
||||||
"fallback_network_type": [],
|
|
||||||
"fallback_delay": "300ms"
|
"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
|
#### detour
|
||||||
|
|
||||||
上游出站的标签。
|
上游出站的标签。
|
||||||
@@ -96,67 +83,15 @@ icon: material/new-box
|
|||||||
|
|
||||||
如果设置,域名将在请求发出之前解析为 IP。
|
如果设置,域名将在请求发出之前解析为 IP。
|
||||||
|
|
||||||
| 出站 | 受影响的域名 | 默认回退值 |
|
| 出站 | 受影响的域名 | 默认回退值 |
|
||||||
|----------|-----------|---------------------------|
|
|----------|--------------------------|-------------------------------------------|
|
||||||
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
| `direct` | 请求中的域名 | `inbound.domain_strategy` |
|
||||||
| others | 服务器地址中的域名 | / |
|
| 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
|
#### fallback_delay
|
||||||
|
|
||||||
在生成 RFC 6555 快速回退连接之前等待的时间长度。
|
在生成 RFC 6555 快速回退连接之前等待的时间长度。
|
||||||
|
也就是说,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。
|
||||||
|
如果为零,则使用 300 毫秒的默认延迟。
|
||||||
|
|
||||||
对于 `domain_strategy`,是在假设之前等待 IPv6 成功的时间量如果设置了 "prefer_ipv4",则 IPv6 配置错误并回退到 IPv4。
|
仅当 `domain_strategy` 为 `prefer_ipv4` 或 `prefer_ipv6` 时生效。
|
||||||
|
|
||||||
对于 `network_strategy`,对于 `network_strategy`,是在回退到其他接口之前等待连接成功的时间。
|
|
||||||
|
|
||||||
仅当 `domain_strategy` 或 `network_strategy` 已设置时生效。
|
|
||||||
|
|
||||||
默认使用 `300ms`。
|
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [sniff](#sniff)
|
|
||||||
:material-delete-clock: [sniff_override_destination](#sniff_override_destination)
|
|
||||||
:material-delete-clock: [sniff_timeout](#sniff_timeout)
|
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
|
||||||
:material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping)
|
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -80,40 +68,24 @@ Requires target inbound support, see [Injectable](/configuration/inbound/#fields
|
|||||||
|
|
||||||
#### sniff
|
#### sniff
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
Enable sniffing.
|
Enable sniffing.
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
||||||
|
|
||||||
#### sniff_override_destination
|
#### sniff_override_destination
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
Override the connection destination address with the sniffed domain.
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
If the domain name is invalid (like tor), this will not work.
|
||||||
|
|
||||||
#### sniff_timeout
|
#### sniff_timeout
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
Timeout for sniffing.
|
Timeout for sniffing.
|
||||||
|
|
||||||
`300ms` is used by default.
|
300ms is used by default.
|
||||||
|
|
||||||
#### domain_strategy
|
#### domain_strategy
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
If set, the requested domain name will be resolved to IP before routing.
|
||||||
@@ -122,10 +94,6 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb
|
|||||||
|
|
||||||
#### udp_disable_domain_unmapping
|
#### udp_disable_domain_unmapping
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
If enabled, for UDP proxy requests addressed to a domain,
|
If enabled, for UDP proxy requests addressed to a domain,
|
||||||
the original packet address will be sent in the response instead of the mapped domain.
|
the original packet address will be sent in the response instead of the mapped domain.
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [sniff](#sniff)
|
|
||||||
:material-delete-clock: [sniff_override_destination](#sniff_override_destination)
|
|
||||||
:material-delete-clock: [sniff_timeout](#sniff_timeout)
|
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
|
||||||
:material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping)
|
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -81,40 +69,24 @@ UDP NAT 过期时间,以秒为单位。
|
|||||||
|
|
||||||
#### sniff
|
#### sniff
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
启用协议探测。
|
启用协议探测。
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)
|
参阅 [协议探测](/zh/configuration/route/sniff/)
|
||||||
|
|
||||||
#### sniff_override_destination
|
#### sniff_override_destination
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除。
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
用探测出的域名覆盖连接目标地址。
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
如果域名无效(如 Tor),将不生效。
|
||||||
|
|
||||||
#### sniff_timeout
|
#### sniff_timeout
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
探测超时时间。
|
探测超时时间。
|
||||||
|
|
||||||
默认使用 300ms。
|
默认使用 300ms。
|
||||||
|
|
||||||
#### domain_strategy
|
#### domain_strategy
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
如果设置,请求的域名将在路由之前解析为 IP。
|
||||||
@@ -123,10 +95,6 @@ UDP NAT 过期时间,以秒为单位。
|
|||||||
|
|
||||||
#### udp_disable_domain_unmapping
|
#### udp_disable_domain_unmapping
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
||||||
|
|
||||||
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/) for details
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Handshake server address and [Dial Fields](/configuration/shared/dial/).
|
Handshake server address and [Dial options](/configuration/shared/dial/).
|
||||||
|
|
||||||
#### private_key
|
#### private_key
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# UDP over TCP
|
||||||
|
|
||||||
!!! warning ""
|
!!! warning ""
|
||||||
|
|
||||||
It's a proprietary protocol created by SagerNet, not part of shadowsocks.
|
It's a proprietary protocol created by SagerNet, not part of shadowsocks.
|
||||||
|
|||||||
@@ -4,24 +4,6 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# Deprecated Feature List
|
# Deprecated Feature List
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
#### Legacy special outbounds
|
|
||||||
|
|
||||||
Legacy special outbounds (`block` / `dns`) are deprecated
|
|
||||||
and can be replaced by rule actions,
|
|
||||||
check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
#### Legacy inbound fields
|
|
||||||
|
|
||||||
Legacy inbound fields (`inbound.<sniff/domain_strategy/...>` are deprecated
|
|
||||||
and can be replaced by rule actions,
|
|
||||||
check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
#### TUN address fields are merged
|
#### TUN address fields are merged
|
||||||
@@ -30,7 +12,7 @@ Old fields will be removed in sing-box 1.13.0.
|
|||||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.12.0.
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
#### Match source rule items are renamed
|
#### Match source rule items are renamed
|
||||||
|
|
||||||
@@ -50,7 +32,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o
|
|||||||
|
|
||||||
#### GeoIP
|
#### GeoIP
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0.
|
GeoIP is deprecated and may be removed in the future.
|
||||||
|
|
||||||
The maxmind GeoIP National Database, as an IP classification database,
|
The maxmind GeoIP National Database, as an IP classification database,
|
||||||
is not entirely suitable for traffic bypassing,
|
is not entirely suitable for traffic bypassing,
|
||||||
@@ -61,7 +43,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
|||||||
|
|
||||||
#### Geosite
|
#### Geosite
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0.
|
Geosite is deprecated and may be removed in the future.
|
||||||
|
|
||||||
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
|
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
|
||||||
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
|
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
|
||||||
|
|||||||
@@ -4,22 +4,6 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# 废弃功能列表
|
# 废弃功能列表
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
#### 旧的特殊出站
|
|
||||||
|
|
||||||
旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代,
|
|
||||||
参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.13.0 中被移除。
|
|
||||||
|
|
||||||
#### 旧的入站字段
|
|
||||||
|
|
||||||
旧的入站字段(`inbound.<sniff/domain_strategy/...>`)已废弃且可以通过规则动作替代,
|
|
||||||
参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.13.0 中被移除。
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
#### Match source 规则项已重命名
|
#### Match source 规则项已重命名
|
||||||
@@ -33,7 +17,7 @@ icon: material/delete-alert
|
|||||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||||
|
|
||||||
旧字段将在 sing-box 1.11.0 中被移除。
|
旧字段已废弃,且将在 sing-box 1.11.0 中被移除。
|
||||||
|
|
||||||
#### 移除对 go1.18 和 go1.19 的支持
|
#### 移除对 go1.18 和 go1.19 的支持
|
||||||
|
|
||||||
@@ -48,7 +32,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `
|
|||||||
|
|
||||||
#### GeoIP
|
#### GeoIP
|
||||||
|
|
||||||
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。
|
GeoIP 已废弃且可能在不久的将来移除。
|
||||||
|
|
||||||
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
|
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
|
||||||
且现有的实现均存在内存使用大与管理困难的问题。
|
且现有的实现均存在内存使用大与管理困难的问题。
|
||||||
@@ -58,7 +42,7 @@ sing-box 1.8.0 引入了[规则集](/configuration/rule-set/),
|
|||||||
|
|
||||||
#### Geosite
|
#### Geosite
|
||||||
|
|
||||||
Geosite 已废弃且将在 sing-box 1.12.0 中被移除。
|
Geosite 已废弃且可能在不久的将来移除。
|
||||||
|
|
||||||
Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
|
Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
|
||||||
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
|
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
|
||||||
|
|||||||
@@ -2,160 +2,6 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
### Migrate legacy special outbounds to rule actions
|
|
||||||
|
|
||||||
Legacy special outbounds are deprecated and can be replaced by rule actions.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[Rule Action](/configuration/route/rule_action/) /
|
|
||||||
[Block](/configuration/outbound/block/) /
|
|
||||||
[DNS](/configuration/outbound/dns)
|
|
||||||
|
|
||||||
=== "Block"
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"outbound": "block"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"action": "reject"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "DNS"
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"sniff": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "dns",
|
|
||||||
"type": "dns"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"action": "hijack-dns"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrate legacy inbound fields to rule actions
|
|
||||||
|
|
||||||
Inbound fields are deprecated and can be replaced by rule actions.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[Listen Fields](/configuration/inbound/listen/) /
|
|
||||||
[Rule](/configuration/route/rule/) /
|
|
||||||
[Rule Action](/configuration/route/rule_action/) /
|
|
||||||
[DNS Rule](/configuration/dns/rule/) /
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"sniff": true,
|
|
||||||
"sniff_timeout": "1s",
|
|
||||||
"domain_strategy": "prefer_ipv4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"tag": "in"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "prefer_ipv4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "sniff",
|
|
||||||
"timeout": "1s"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
### TUN address fields are merged
|
### TUN address fields are merged
|
||||||
@@ -164,6 +10,8 @@ Inbound fields are deprecated and can be replaced by rule actions.
|
|||||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||||
|
|
||||||
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
!!! info "References"
|
!!! info "References"
|
||||||
|
|
||||||
[TUN](/configuration/inbound/tun/)
|
[TUN](/configuration/inbound/tun/)
|
||||||
|
|||||||
@@ -2,160 +2,6 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
### 迁移旧的特殊出站到规则动作
|
|
||||||
|
|
||||||
旧的特殊出站已被弃用,且可以被规则动作替代。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[规则动作](/zh/configuration/route/rule_action/) /
|
|
||||||
[Block](/zh/configuration/outbound/block/) /
|
|
||||||
[DNS](/zh/configuration/outbound/dns)
|
|
||||||
|
|
||||||
=== "Block"
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"outbound": "block"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"action": "reject"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "DNS"
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"sniff": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "dns",
|
|
||||||
"type": "dns"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"action": "hijack-dns"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 迁移旧的入站字段到规则动作
|
|
||||||
|
|
||||||
入站选项已被弃用,且可以被规则动作替代。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[监听字段](/zh/configuration/shared/listen/) /
|
|
||||||
[规则](/zh/configuration/route/rule/) /
|
|
||||||
[规则动作](/zh/configuration/route/rule_action/) /
|
|
||||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
|
||||||
[DNS 规则动作](/zh/configuration/dns/rule_action/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"sniff": true,
|
|
||||||
"sniff_timeout": "1s",
|
|
||||||
"domain_strategy": "prefer_ipv4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"tag": "in"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "prefer_ipv4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "sniff",
|
|
||||||
"timeout": "1s"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
### TUN 地址字段已合并
|
### TUN 地址字段已合并
|
||||||
|
|||||||
@@ -93,18 +93,7 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) Name() string {
|
func (c *CacheFile) start() error {
|
||||||
return "cache-file"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) Dependencies() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CacheFile) Start(stage adapter.StartStage) error {
|
|
||||||
if stage != adapter.StartStateInitialize {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
const fileMode = 0o666
|
const fileMode = 0o666
|
||||||
options := bbolt.Options{Timeout: time.Second}
|
options := bbolt.Options{Timeout: time.Second}
|
||||||
var (
|
var (
|
||||||
@@ -162,6 +151,14 @@ func (c *CacheFile) Start(stage adapter.StartStage) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) PreStart() error {
|
||||||
|
return c.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CacheFile) Start() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CacheFile) Close() error {
|
func (c *CacheFile) Close() error {
|
||||||
if c.DB == nil {
|
if c.DB == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
|
type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
|
||||||
|
|
||||||
var clashServerConstructor ClashServerConstructor
|
var clashServerConstructor ClashServerConstructor
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) {
|
|||||||
clashServerConstructor = constructor
|
clashServerConstructor = constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
if clashServerConstructor == nil {
|
if clashServerConstructor == nil {
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
return clashServerConstructor(ctx, logFactory, options)
|
return clashServerConstructor(ctx, router, logFactory, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalculateClashModeList(options option.Options) []string {
|
func CalculateClashModeList(options option.Options) []string {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func groupRouter(server *Server) http.Handler {
|
|||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getGroups(server))
|
r.Get("/", getGroups(server))
|
||||||
r.Route("/{name}", func(r chi.Router) {
|
r.Route("/{name}", func(r chi.Router) {
|
||||||
r.Use(parseProxyName, findProxyByName(server))
|
r.Use(parseProxyName, findProxyByName(server.router))
|
||||||
r.Get("/", getGroup(server))
|
r.Get("/", getGroup(server))
|
||||||
r.Get("/delay", getGroupDelay(server))
|
r.Get("/delay", getGroupDelay(server))
|
||||||
})
|
})
|
||||||
@@ -32,7 +32,7 @@ func groupRouter(server *Server) http.Handler {
|
|||||||
|
|
||||||
func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
func getGroups(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
groups := common.Map(common.Filter(server.outboundManager.Outbounds(), func(it adapter.Outbound) bool {
|
groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool {
|
||||||
_, isGroup := it.(adapter.OutboundGroup)
|
_, isGroup := it.(adapter.OutboundGroup)
|
||||||
return isGroup
|
return isGroup
|
||||||
}), func(it adapter.Outbound) *badjson.JSONObject {
|
}), func(it adapter.Outbound) *badjson.JSONObject {
|
||||||
@@ -86,7 +86,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
result, err = urlTestGroup.URLTest(ctx)
|
result, err = urlTestGroup.URLTest(ctx)
|
||||||
} else {
|
} else {
|
||||||
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
||||||
itOutbound, _ := server.outboundManager.Outbound(it)
|
itOutbound, _ := server.router.Outbound(it)
|
||||||
return itOutbound
|
return itOutbound
|
||||||
}))
|
}))
|
||||||
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
|
||||||
@@ -100,7 +100,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
checked[realTag] = true
|
checked[realTag] = true
|
||||||
p, loaded := server.outboundManager.Outbound(realTag)
|
p, loaded := server.router.Outbound(realTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ import (
|
|||||||
|
|
||||||
func proxyRouter(server *Server, router adapter.Router) http.Handler {
|
func proxyRouter(server *Server, router adapter.Router) http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getProxies(server))
|
r.Get("/", getProxies(server, router))
|
||||||
|
|
||||||
r.Route("/{name}", func(r chi.Router) {
|
r.Route("/{name}", func(r chi.Router) {
|
||||||
r.Use(parseProxyName, findProxyByName(server))
|
r.Use(parseProxyName, findProxyByName(router))
|
||||||
r.Get("/", getProxy(server))
|
r.Get("/", getProxy(server))
|
||||||
r.Get("/delay", getProxyDelay(server))
|
r.Get("/delay", getProxyDelay(server))
|
||||||
r.Put("/", updateProxy)
|
r.Put("/", updateProxy)
|
||||||
@@ -42,11 +42,11 @@ func parseProxyName(next http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func findProxyByName(server *Server) func(next http.Handler) http.Handler {
|
func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
name := r.Context().Value(CtxKeyProxyName).(string)
|
name := r.Context().Value(CtxKeyProxyName).(string)
|
||||||
proxy, exist := server.outboundManager.Outbound(name)
|
proxy, exist := router.Outbound(name)
|
||||||
if !exist {
|
if !exist {
|
||||||
render.Status(r, http.StatusNotFound)
|
render.Status(r, http.StatusNotFound)
|
||||||
render.JSON(w, r, ErrNotFound)
|
render.JSON(w, r, ErrNotFound)
|
||||||
@@ -83,10 +83,10 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
|
|||||||
return &info
|
return &info
|
||||||
}
|
}
|
||||||
|
|
||||||
func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var proxyMap badjson.JSONObject
|
var proxyMap badjson.JSONObject
|
||||||
outbounds := common.Filter(server.outboundManager.Outbounds(), func(detour adapter.Outbound) bool {
|
outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool {
|
||||||
return detour.Tag() != ""
|
return detour.Tag() != ""
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -100,7 +100,12 @@ func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
|||||||
allProxies = append(allProxies, detour.Tag())
|
allProxies = append(allProxies, detour.Tag())
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultTag := server.outboundManager.Default().Tag()
|
var defaultTag string
|
||||||
|
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||||
|
defaultTag = defaultOutbound.Tag()
|
||||||
|
} else {
|
||||||
|
defaultTag = allProxies[0]
|
||||||
|
}
|
||||||
|
|
||||||
sort.SliceStable(allProxies, func(i, j int) bool {
|
sort.SliceStable(allProxies, func(i, j int) bool {
|
||||||
return allProxies[i] == defaultTag
|
return allProxies[i] == defaultTag
|
||||||
|
|||||||
@@ -40,16 +40,15 @@ func init() {
|
|||||||
var _ adapter.ClashServer = (*Server)(nil)
|
var _ adapter.ClashServer = (*Server)(nil)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
outboundManager adapter.OutboundManager
|
logger log.Logger
|
||||||
logger log.Logger
|
httpServer *http.Server
|
||||||
httpServer *http.Server
|
trafficManager *trafficontrol.Manager
|
||||||
trafficManager *trafficontrol.Manager
|
urlTestHistory *urltest.HistoryStorage
|
||||||
urlTestHistory *urltest.HistoryStorage
|
mode string
|
||||||
mode string
|
modeList []string
|
||||||
modeList []string
|
modeUpdateHook chan<- struct{}
|
||||||
modeUpdateHook chan<- struct{}
|
|
||||||
|
|
||||||
externalController bool
|
externalController bool
|
||||||
externalUI string
|
externalUI string
|
||||||
@@ -57,14 +56,13 @@ type Server struct {
|
|||||||
externalUIDownloadDetour string
|
externalUIDownloadDetour string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
|
||||||
trafficManager := trafficontrol.NewManager()
|
trafficManager := trafficontrol.NewManager()
|
||||||
chiRouter := chi.NewRouter()
|
chiRouter := chi.NewRouter()
|
||||||
s := &Server{
|
server := &Server{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
router: service.FromContext[adapter.Router](ctx),
|
router: router,
|
||||||
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
|
logger: logFactory.NewLogger("clash-api"),
|
||||||
logger: logFactory.NewLogger("clash-api"),
|
|
||||||
httpServer: &http.Server{
|
httpServer: &http.Server{
|
||||||
Addr: options.ExternalController,
|
Addr: options.ExternalController,
|
||||||
Handler: chiRouter,
|
Handler: chiRouter,
|
||||||
@@ -75,18 +73,18 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
|||||||
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
externalUIDownloadURL: options.ExternalUIDownloadURL,
|
||||||
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
|
||||||
}
|
}
|
||||||
s.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
|
server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
|
||||||
if s.urlTestHistory == nil {
|
if server.urlTestHistory == nil {
|
||||||
s.urlTestHistory = urltest.NewHistoryStorage()
|
server.urlTestHistory = urltest.NewHistoryStorage()
|
||||||
}
|
}
|
||||||
defaultMode := "Rule"
|
defaultMode := "Rule"
|
||||||
if options.DefaultMode != "" {
|
if options.DefaultMode != "" {
|
||||||
defaultMode = options.DefaultMode
|
defaultMode = options.DefaultMode
|
||||||
}
|
}
|
||||||
if !common.Contains(s.modeList, defaultMode) {
|
if !common.Contains(server.modeList, defaultMode) {
|
||||||
s.modeList = append([]string{defaultMode}, s.modeList...)
|
server.modeList = append([]string{defaultMode}, server.modeList...)
|
||||||
}
|
}
|
||||||
s.mode = defaultMode
|
server.mode = defaultMode
|
||||||
//goland:noinspection GoDeprecation
|
//goland:noinspection GoDeprecation
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" {
|
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" {
|
||||||
@@ -110,76 +108,71 @@ func NewServer(ctx context.Context, logFactory log.ObservableFactory, options op
|
|||||||
r.Get("/logs", getLogs(logFactory))
|
r.Get("/logs", getLogs(logFactory))
|
||||||
r.Get("/traffic", traffic(trafficManager))
|
r.Get("/traffic", traffic(trafficManager))
|
||||||
r.Get("/version", version)
|
r.Get("/version", version)
|
||||||
r.Mount("/configs", configRouter(s, logFactory))
|
r.Mount("/configs", configRouter(server, logFactory))
|
||||||
r.Mount("/proxies", proxyRouter(s, s.router))
|
r.Mount("/proxies", proxyRouter(server, router))
|
||||||
r.Mount("/rules", ruleRouter(s.router))
|
r.Mount("/rules", ruleRouter(router))
|
||||||
r.Mount("/connections", connectionRouter(s.router, trafficManager))
|
r.Mount("/connections", connectionRouter(router, trafficManager))
|
||||||
r.Mount("/providers/proxies", proxyProviderRouter())
|
r.Mount("/providers/proxies", proxyProviderRouter())
|
||||||
r.Mount("/providers/rules", ruleProviderRouter())
|
r.Mount("/providers/rules", ruleProviderRouter())
|
||||||
r.Mount("/script", scriptRouter())
|
r.Mount("/script", scriptRouter())
|
||||||
r.Mount("/profile", profileRouter())
|
r.Mount("/profile", profileRouter())
|
||||||
r.Mount("/cache", cacheRouter(ctx))
|
r.Mount("/cache", cacheRouter(ctx))
|
||||||
r.Mount("/dns", dnsRouter(s.router))
|
r.Mount("/dns", dnsRouter(router))
|
||||||
|
|
||||||
s.setupMetaAPI(r)
|
server.setupMetaAPI(r)
|
||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(s.externalUI)))
|
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fs.ServeHTTP(w, r)
|
fs.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return s, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Name() string {
|
func (s *Server) PreStart() error {
|
||||||
return "clash server"
|
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
||||||
}
|
if cacheFile != nil {
|
||||||
|
mode := cacheFile.LoadMode()
|
||||||
func (s *Server) Start(stage adapter.StartStage) error {
|
if common.Any(s.modeList, func(it string) bool {
|
||||||
switch stage {
|
return strings.EqualFold(it, mode)
|
||||||
case adapter.StartStateStart:
|
}) {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
|
s.mode = mode
|
||||||
if cacheFile != nil {
|
|
||||||
mode := cacheFile.LoadMode()
|
|
||||||
if common.Any(s.modeList, func(it string) bool {
|
|
||||||
return strings.EqualFold(it, mode)
|
|
||||||
}) {
|
|
||||||
s.mode = mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case adapter.StartStateStarted:
|
|
||||||
if s.externalController {
|
|
||||||
s.checkAndDownloadExternalUI()
|
|
||||||
var (
|
|
||||||
listener net.Listener
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
listener, err = net.Listen("tcp", s.httpServer.Addr)
|
|
||||||
if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "external controller listen error")
|
|
||||||
}
|
|
||||||
s.logger.Info("restful api listening at ", listener.Addr())
|
|
||||||
go func() {
|
|
||||||
err = s.httpServer.Serve(listener)
|
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
s.logger.Error("external controller serve error: ", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
if s.externalController {
|
||||||
|
s.checkAndDownloadExternalUI()
|
||||||
|
var (
|
||||||
|
listener net.Listener
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
listener, err = net.Listen("tcp", s.httpServer.Addr)
|
||||||
|
if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) {
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "external controller listen error")
|
||||||
|
}
|
||||||
|
s.logger.Info("restful api listening at ", listener.Addr())
|
||||||
|
go func() {
|
||||||
|
err = s.httpServer.Serve(listener)
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
s.logger.Error("external controller serve error: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,12 +234,14 @@ func (s *Server) TrafficManager() *trafficontrol.Manager {
|
|||||||
return s.trafficManager
|
return s.trafficManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
|
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||||
return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound)
|
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||||
|
return tracker, tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn {
|
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
|
||||||
return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound)
|
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||||
|
return tracker, tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,13 +45,16 @@ func (s *Server) downloadExternalUI() error {
|
|||||||
s.logger.Info("downloading external ui")
|
s.logger.Info("downloading external ui")
|
||||||
var detour adapter.Outbound
|
var detour adapter.Outbound
|
||||||
if s.externalUIDownloadDetour != "" {
|
if s.externalUIDownloadDetour != "" {
|
||||||
outbound, loaded := s.outboundManager.Outbound(s.externalUIDownloadDetour)
|
outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
|
return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
|
||||||
}
|
}
|
||||||
detour = outbound
|
detour = outbound
|
||||||
} else {
|
} else {
|
||||||
outbound := s.outboundManager.Default()
|
outbound, err := s.router.DefaultOutbound(N.NetworkTCP)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
detour = outbound
|
detour = outbound
|
||||||
}
|
}
|
||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
@@ -87,6 +88,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
type Tracker interface {
|
||||||
|
adapter.Tracker
|
||||||
Metadata() TrackerMetadata
|
Metadata() TrackerMetadata
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
@@ -106,6 +108,10 @@ func (tt *TCPConn) Close() error {
|
|||||||
return tt.ExtendedConn.Close()
|
return tt.ExtendedConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tt *TCPConn) Leave() {
|
||||||
|
tt.manager.Leave(tt)
|
||||||
|
}
|
||||||
|
|
||||||
func (tt *TCPConn) Upstream() any {
|
func (tt *TCPConn) Upstream() any {
|
||||||
return tt.ExtendedConn
|
return tt.ExtendedConn
|
||||||
}
|
}
|
||||||
@@ -118,7 +124,7 @@ func (tt *TCPConn) WriterReplaceable() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *TCPConn {
|
func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn {
|
||||||
id, _ := uuid.NewV4()
|
id, _ := uuid.NewV4()
|
||||||
var (
|
var (
|
||||||
chain []string
|
chain []string
|
||||||
@@ -126,13 +132,17 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
|
|||||||
outbound string
|
outbound string
|
||||||
outboundType string
|
outboundType string
|
||||||
)
|
)
|
||||||
if matchOutbound != nil {
|
var action adapter.RuleAction
|
||||||
next = matchOutbound.Tag()
|
if rule != nil {
|
||||||
} else {
|
action = rule.Action()
|
||||||
next = outboundManager.Default().Tag()
|
}
|
||||||
|
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
||||||
|
next = routeAction.Outbound
|
||||||
|
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||||
|
next = defaultOutbound.Tag()
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
detour, loaded := outboundManager.Outbound(next)
|
detour, loaded := router.Outbound(next)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -162,7 +172,7 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
|
|||||||
Upload: upload,
|
Upload: upload,
|
||||||
Download: download,
|
Download: download,
|
||||||
Chain: common.Reverse(chain),
|
Chain: common.Reverse(chain),
|
||||||
Rule: matchRule,
|
Rule: rule,
|
||||||
Outbound: outbound,
|
Outbound: outbound,
|
||||||
OutboundType: outboundType,
|
OutboundType: outboundType,
|
||||||
},
|
},
|
||||||
@@ -187,6 +197,10 @@ func (ut *UDPConn) Close() error {
|
|||||||
return ut.PacketConn.Close()
|
return ut.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ut *UDPConn) Leave() {
|
||||||
|
ut.manager.Leave(ut)
|
||||||
|
}
|
||||||
|
|
||||||
func (ut *UDPConn) Upstream() any {
|
func (ut *UDPConn) Upstream() any {
|
||||||
return ut.PacketConn
|
return ut.PacketConn
|
||||||
}
|
}
|
||||||
@@ -199,7 +213,7 @@ func (ut *UDPConn) WriterReplaceable() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *UDPConn {
|
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn {
|
||||||
id, _ := uuid.NewV4()
|
id, _ := uuid.NewV4()
|
||||||
var (
|
var (
|
||||||
chain []string
|
chain []string
|
||||||
@@ -207,13 +221,17 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
|
|||||||
outbound string
|
outbound string
|
||||||
outboundType string
|
outboundType string
|
||||||
)
|
)
|
||||||
if matchOutbound != nil {
|
var action adapter.RuleAction
|
||||||
next = matchOutbound.Tag()
|
if rule != nil {
|
||||||
} else {
|
action = rule.Action()
|
||||||
next = outboundManager.Default().Tag()
|
}
|
||||||
|
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
||||||
|
next = routeAction.Outbound
|
||||||
|
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||||
|
next = defaultOutbound.Tag()
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
detour, loaded := outboundManager.Outbound(next)
|
detour, loaded := router.Outbound(next)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -243,7 +261,7 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
|
|||||||
Upload: upload,
|
Upload: upload,
|
||||||
Download: download,
|
Download: download,
|
||||||
Chain: common.Reverse(chain),
|
Chain: common.Reverse(chain),
|
||||||
Rule: matchRule,
|
Rule: rule,
|
||||||
Outbound: outbound,
|
Outbound: outbound,
|
||||||
OutboundType: outboundType,
|
OutboundType: outboundType,
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user