Compare commits

..

30 Commits

Author SHA1 Message Date
世界
e3ffffc645 documentation: Bump version 2024-11-10 17:33:00 +08:00
世界
7daf2d1716 Fix hijack-dns 2024-11-10 17:33:00 +08:00
世界
b4f1c2a596 refactor: Extract clash/v2ray/time service form router 2024-11-10 17:33:00 +08:00
世界
1df8dfcade refactor: Modular network manager 2024-11-10 17:33:00 +08:00
世界
beaab2e4db refactor: Modular inbound/outbound manager 2024-11-10 12:12:08 +08:00
世界
1ee7a4a272 documentation: Bump version 2024-11-10 12:12:08 +08:00
世界
44560f0c20 documentation: Add rule action 2024-11-10 12:12:08 +08:00
世界
b8613de673 documentation: Update the scheduled removal time of deprecated features 2024-11-10 12:12:08 +08:00
世界
24496d89b1 documentation: Remove outdated icons 2024-11-10 12:12:08 +08:00
世界
1a230bda5d Migrate bad options to library 2024-11-10 12:12:08 +08:00
世界
85f634d0cb Implement udp connect 2024-11-10 12:12:08 +08:00
世界
b75dbc8a26 Implement new deprecated warnings 2024-11-10 12:12:08 +08:00
世界
3a3ad11cb3 Improve rule actions 2024-11-09 18:41:11 +08:00
世界
866be4acbd Remove unused reject methods 2024-11-09 18:41:11 +08:00
世界
776052de20 refactor: Modular inbounds/outbounds 2024-11-09 18:41:11 +08:00
世界
e45763d5ba Implement dns-hijack 2024-11-09 12:35:48 +08:00
世界
5eb8522205 Implement resolve(server) 2024-11-09 12:35:47 +08:00
世界
c2b833a228 Implement TCP and ICMP rejects 2024-11-09 12:35:47 +08:00
世界
7f65ab8166 Crazy sekai overturns the small pond 2024-11-09 12:35:43 +08:00
世界
84a102a6ef Fix mux stream accept 2024-11-09 12:26:49 +08:00
世界
f1c76c4dde Fix deprecated version check 2024-11-08 20:31:29 +08:00
世界
8df0aa5719 Downgrade NDK to r26d 2024-11-07 19:58:53 +08:00
世界
21faadb992 Uniq deprecated notes 2024-11-07 19:58:53 +08:00
世界
88099a304a platform: Add SendNotification 2024-11-06 12:53:32 +08:00
世界
f504fb0d46 Revert "platform: Add openURL event"
This reverts commit 718cffea9a.
2024-11-05 20:03:39 +08:00
世界
1d517b6ca5 platform: Add link flags 2024-11-05 18:28:13 +08:00
世界
b702d0b67a Update dependencies 2024-11-05 18:28:03 +08:00
世界
a001e30d8b platform: Remove SetTraceback("all") 2024-11-05 18:28:02 +08:00
世界
cdb93f0bb2 Fix "Fix metadata context" 2024-11-05 18:27:51 +08:00
世界
718cffea9a platform: Add openURL event 2024-11-05 18:27:51 +08:00
194 changed files with 4093 additions and 2813 deletions

View File

@@ -4,28 +4,28 @@ import (
"bytes"
"context"
"encoding/binary"
"net"
"time"
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-dns"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/varbin"
)
type ClashServer interface {
Service
PreStarter
LifecycleService
ConnectionTracker
Mode() string
ModeList() []string
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 {
Service
PreStarter
LifecycleService
StoreFakeIP() bool
FakeIPStorage
@@ -94,10 +94,6 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
return nil
}
type Tracker interface {
Leave()
}
type OutboundGroup interface {
Outbound
Now() string
@@ -115,13 +111,3 @@ func OutboundTag(detour Outbound) string {
}
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
}

View File

@@ -28,7 +28,15 @@ type UDPInjectableInbound interface {
type InboundRegistry interface {
option.InboundOptionsRegistry
CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error)
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType 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 {
@@ -57,7 +65,9 @@ type InboundContext struct {
// Deprecated
InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool
DNSServer string
UDPConnect bool
DNSServer string
DestinationAddresses []netip.Addr
SourceGeoIPCode string

143
adapter/inbound/manager.go Normal file
View File

@@ -0,0 +1,143 @@
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.Action(), " 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.Action(), " 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
}

View File

@@ -28,41 +28,41 @@ type (
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructors: make(map[string]constructorFunc),
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[outboundType]
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[outboundType]
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
r.access.Lock()
defer r.access.Unlock()
r.optionsType[outboundType] = optionsConstructor
r.constructors[outboundType] = constructor
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

42
adapter/lifecycle.go Normal file
View File

@@ -0,0 +1,42 @@
package adapter
type StartStage uint8
const (
StartStateInitialize StartStage = iota
StartStateStart
StartStatePostStart
StartStateStarted
)
var ListStartStages = []StartStage{
StartStateInitialize,
StartStateStart,
StartStatePostStart,
StartStateStarted,
}
func (s StartStage) Action() string {
switch s {
case StartStateInitialize:
return "initialize"
case StartStateStart:
return "start"
case StartStatePostStart:
return "post-start"
case StartStateStarted:
return "start-after-started"
default:
panic("unknown stage")
}
}
type Lifecycle interface {
Start(stage StartStage) error
Close() error
}
type LifecycleService interface {
Name() string
Lifecycle
}

View File

@@ -0,0 +1,49 @@
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 StartStatePostStart:
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()
}

23
adapter/network.go Normal file
View File

@@ -0,0 +1,23 @@
package adapter
import (
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
)
type NetworkManager interface {
Lifecycle
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
ResetNetwork()
}

View File

@@ -22,3 +22,12 @@ type OutboundRegistry interface {
option.OutboundOptionsRegistry
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
}

View File

@@ -20,6 +20,7 @@ import (
)
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn
var err error
@@ -40,6 +41,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
}
func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn net.Conn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn
var err error
@@ -67,29 +69,49 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial
}
func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext) error {
defer conn.Close()
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)
var (
outPacketConn net.PacketConn
outConn net.Conn
destinationAddress netip.Addr
err error
)
if metadata.UDPConnect {
if len(metadata.DestinationAddresses) > 0 {
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
} else {
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 {
outConn, err = this.ListenPacket(ctx, metadata.Destination)
if len(metadata.DestinationAddresses) > 0 {
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 {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
outPacketConn.Close()
return err
}
if destinationAddress.IsValid() {
if metadata.Destination.IsFqdn() {
if metadata.UDPDisableDomainUnmapping {
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
outPacketConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
} else {
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
}
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
@@ -104,37 +126,63 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
case C.ProtocolDNS:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
}
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
}
func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this N.Dialer, conn N.PacketConn, metadata adapter.InboundContext, domainStrategy dns.DomainStrategy) error {
defer conn.Close()
ctx = adapter.WithContext(ctx, &metadata)
var 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)
var (
outPacketConn net.PacketConn
outConn net.Conn
destinationAddress netip.Addr
err error
)
if metadata.UDPConnect {
if len(metadata.DestinationAddresses) > 0 {
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, metadata.DestinationAddresses)
} else if metadata.Destination.IsFqdn() {
var destinationAddresses []netip.Addr
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
outConn, err = N.DialSerial(ctx, this, N.NetworkUDP, metadata.Destination, destinationAddresses)
} else {
outConn, err = this.DialContext(ctx, N.NetworkUDP, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
outConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses)
connRemoteAddr := M.AddrFromNet(outConn.RemoteAddr())
if connRemoteAddr != metadata.Destination.Addr {
destinationAddress = connRemoteAddr
}
} else {
outConn, err = this.ListenPacket(ctx, metadata.Destination)
if len(metadata.DestinationAddresses) > 0 {
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, metadata.DestinationAddresses)
} else if metadata.Destination.IsFqdn() {
var destinationAddresses []netip.Addr
destinationAddresses, err = router.Lookup(ctx, metadata.Destination.Fqdn, domainStrategy)
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
outPacketConn, destinationAddress, err = N.ListenSerial(ctx, this, metadata.Destination, destinationAddresses)
} else {
outPacketConn, err = this.ListenPacket(ctx, metadata.Destination)
}
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
}
err = N.ReportPacketConnHandshakeSuccess(conn, outPacketConn)
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
outPacketConn.Close()
return err
}
if destinationAddress.IsValid() {
if metadata.Destination.IsFqdn() {
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
outPacketConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outPacketConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
}
if natConn, loaded := common.Cast[bufio.NATPacketConn](conn); loaded {
natConn.UpdateDestination(destinationAddress)
@@ -148,7 +196,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
case C.ProtocolDNS:
ctx, conn = canceler.NewPacketConn(ctx, conn, C.DNSTimeout)
}
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outConn))
return bufio.CopyPacketConn(ctx, conn, bufio.NewPacketConn(outPacketConn))
}
func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) error {

269
adapter/outbound/manager.go Normal file
View File

@@ -0,0 +1,269 @@
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.Action(), " 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.Action(), " 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
}

View File

@@ -1,9 +1 @@
package adapter
type PreStarter interface {
PreStart() error
}
type PostStarter interface {
PostStart() error
}

View File

@@ -10,26 +10,16 @@ import (
"github.com/sagernet/sing-box/common/geoip"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
mdns "github.com/miekg/dns"
"go4.org/netipx"
)
type Router interface {
Service
PreStarter
PostStarter
Cleanup() error
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) (Outbound, error)
Lifecycle
FakeIPStore() FakeIPStore
@@ -39,37 +29,23 @@ type Router interface {
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
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
ClashServer() ClashServer
SetClashServer(server ClashServer)
SetTracker(tracker ConnectionTracker)
V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
ResetNetwork()
}
ResetNetwork() error
type ConnectionTracker interface {
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.
@@ -84,14 +60,6 @@ type ConnectionRouterEx interface {
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 {
Name() string
StartContext(ctx context.Context, startContext *HTTPStartContext) error

334
box.go
View File

@@ -9,6 +9,9 @@ import (
"time"
"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"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental"
@@ -21,6 +24,7 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause"
)
@@ -28,16 +32,15 @@ import (
var _ adapter.Service = (*Box)(nil)
type Box struct {
createdAt time.Time
router adapter.Router
inbounds []adapter.Inbound
outbounds []adapter.Outbound
logFactory log.Factory
logger log.ContextLogger
preServices1 map[string]adapter.Service
preServices2 map[string]adapter.Service
postServices map[string]adapter.Service
done chan struct{}
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
router *route.Router
inbound *inbound.Manager
outbound *outbound.Manager
services []adapter.LifecycleService
done chan struct{}
}
type Options struct {
@@ -46,7 +49,11 @@ type Options struct {
PlatformLogWriter log.PlatformWriter
}
func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context {
func Context(
ctx context.Context,
inboundRegistry adapter.InboundRegistry,
outboundRegistry adapter.OutboundRegistry,
) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
@@ -66,15 +73,18 @@ func New(options Options) (*Box, error) {
if ctx == nil {
ctx = context.Background()
}
ctx = service.ContextWithDefaultRegistry(ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
if inboundRegistry == nil {
return nil, E.New("missing inbound registry in context")
}
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
@@ -106,16 +116,21 @@ func New(options Options) (*Box, error) {
if err != nil {
return nil, E.Cause(err, "create log factory")
}
router, err := route.NewRouter(
ctx,
logFactory,
common.PtrValueOrDefault(options.Route),
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
)
routeOptions := common.PtrValueOrDefault(options.Route)
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, routeOptions.Final)
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
if err != nil {
return nil, E.Cause(err, "parse route options")
return nil, E.Cause(err, "initialize network manager")
}
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
if len(options.LegacyInbounds) > 0 {
@@ -127,7 +142,6 @@ func New(options Options) (*Box, error) {
})
}
}
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
//nolint:staticcheck
if len(options.LegacyOutbounds) > 0 {
for _, legacyOutbound := range options.LegacyOutbounds {
@@ -138,17 +152,14 @@ func New(options Options) (*Box, error) {
})
}
}
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
for i, inboundOptions := range options.Inbounds {
var currentInbound adapter.Inbound
var tag string
if inboundOptions.Tag != "" {
tag = inboundOptions.Tag
} else {
tag = F.ToString(i)
}
currentInbound, err = inboundRegistry.CreateInbound(
ctx,
err = inboundManager.Create(ctx,
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
tag,
@@ -156,12 +167,10 @@ func New(options Options) (*Box, error) {
inboundOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
inbounds = append(inbounds, currentInbound)
}
for i, outboundOptions := range options.Outbounds {
var currentOutbound adapter.Outbound
var tag string
if outboundOptions.Tag != "" {
tag = outboundOptions.Tag
@@ -175,7 +184,7 @@ func New(options Options) (*Box, error) {
Outbound: tag,
})
}
currentOutbound, err = outboundRegistry.CreateOutbound(
err = outboundManager.Create(
outboundCtx,
router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
@@ -184,65 +193,79 @@ func New(options Options) (*Box, error) {
outboundOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]")
return nil, E.Cause(err, "initialize 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 {
err = platformInterface.Initialize(ctx, router)
err = platformInterface.Initialize(networkManager)
if err != nil {
return nil, E.Cause(err, "initialize platform interface")
}
}
preServices1 := make(map[string]adapter.Service)
preServices2 := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
var services []adapter.LifecycleService
if needCacheFile {
cacheFile := service.FromContext[adapter.CacheFile](ctx)
if cacheFile == nil {
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
}
preServices1["cache file"] = cacheFile
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
services = append(services, cacheFile)
}
if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
if err != nil {
return nil, E.Cause(err, "create clash api server")
return nil, E.Cause(err, "create clash-server")
}
router.SetClashServer(clashServer)
preServices2["clash api"] = clashServer
router.SetTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
services = append(services, clashServer)
}
if needV2RayAPI {
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
if err != nil {
return nil, E.Cause(err, "create v2ray api server")
return nil, E.Cause(err, "create v2ray-server")
}
router.SetV2RayServer(v2rayServer)
preServices2["v2ray api"] = v2rayServer
if v2rayServer.StatsService() != nil {
router.SetTracker(v2rayServer.StatsService())
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{
router: router,
inbounds: inbounds,
outbounds: outbounds,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
preServices1: preServices1,
preServices2: preServices2,
postServices: postServices,
done: make(chan struct{}),
network: networkManager,
router: router,
inbound: inboundManager,
outbound: outboundManager,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
services: services,
done: make(chan struct{}),
}, nil
}
@@ -292,35 +315,29 @@ func (s *Box) preStart() error {
if err != nil {
return E.Cause(err, "start logger")
}
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 _, lifecycleService := range s.services {
err = lifecycleService.Start(adapter.StartStateInitialize) // cache-file
if err != nil {
return E.Cause(err, "initialize ", lifecycleService.Name())
}
}
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)
}
for _, lifecycle := range []adapter.Lifecycle{
s.network, s.router, s.outbound, s.inbound,
} {
err = lifecycle.Start(adapter.StartStateInitialize)
if err != nil {
return err
}
}
err = s.router.PreStart()
if err != nil {
return E.Cause(err, "pre-start router")
for _, lifecycle := range []adapter.Lifecycle{
s.outbound, s.network, s.router,
} {
err = lifecycle.Start(adapter.StartStateStart)
if err != nil {
return err
}
}
err = s.startOutbounds()
if err != nil {
return err
}
return s.router.Start()
return nil
}
func (s *Box) start() error {
@@ -328,63 +345,30 @@ func (s *Box) start() error {
if err != nil {
return err
}
for serviceName, service := range s.preServices1 {
err = service.Start()
for _, lifecycleService := range s.services {
err = lifecycleService.Start(adapter.StartStateStart)
if err != nil {
return E.Cause(err, "start ", serviceName)
return E.Cause(err, "initialize ", lifecycleService.Name())
}
}
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()
err = s.inbound.Start(adapter.StartStateStart)
if err != nil {
return err
}
return s.router.Cleanup()
}
func (s *Box) postStart() error {
for serviceName, service := range s.postServices {
err := service.Start()
for _, lifecycleService := range []adapter.Lifecycle{
s.outbound, s.network, s.router, s.inbound,
} {
err = lifecycleService.Start(adapter.StartStatePostStart)
if err != nil {
return E.Cause(err, "start ", serviceName)
return err
}
}
// 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 {
return err
}
for _, in := range s.inbounds {
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
err = lateInbound.PostStart()
if err != nil {
return E.Cause(err, "post-start inbound/", in.Tag())
}
for _, lifecycleService := range []adapter.Lifecycle{
s.network, s.router, s.outbound, s.inbound,
} {
err = lifecycleService.Start(adapter.StartStateStarted)
if err != nil {
return err
}
}
return nil
@@ -397,58 +381,32 @@ func (s *Box) Close() error {
default:
close(s.done)
}
monitor := taskmonitor.New(s.logger, C.StopTimeout)
var errors error
for serviceName, service := range s.postServices {
monitor.Start("close ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
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 := common.Close(
s.inbound, s.outbound, s.router, s.network,
)
for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name())
})
}
monitor.Finish()
for serviceName, service := range s.preServices1 {
monitor.Start("close ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
monitor.Finish()
}
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
err = E.Append(err, s.logFactory.Close(), func(err error) error {
return E.Cause(err, "close logger")
})
return err
}
func (s *Box) Network() adapter.NetworkManager {
return s.network
}
func (s *Box) Router() adapter.Router {
return s.router
}
func (s *Box) Inbound() adapter.InboundManager {
return s.inbound
}
func (s *Box) Outbound() adapter.OutboundManager {
return s.outbound
}

View File

@@ -1,85 +0,0 @@
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
}

View File

@@ -58,7 +58,7 @@ func FindSDK() {
}
func findNDK() bool {
const fixedVersion = "27.2.12479018"
const fixedVersion = "26.3.11579264"
const versionFile = "source.properties"
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
androidNDKPath = fixedPath

View File

@@ -68,6 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
if len(configPaths) == 0 && len(configDirectories) == 0 {
configPaths = append(configPaths, "config.json")
}
globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger()))
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
}

View File

@@ -30,7 +30,7 @@ func check() error {
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(globalCtx)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,

View File

@@ -2,7 +2,6 @@ package main
import (
"bytes"
"context"
"os"
"path/filepath"
@@ -39,7 +38,7 @@ func format() error {
return err
}
for _, optionsEntry := range optionsList {
optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options)
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)
if err != nil {
return err
}

View File

@@ -2,6 +2,7 @@ package main
import (
"bytes"
"context"
"io"
"os"
@@ -84,7 +85,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
}
for i, ruleOptions := range plainRuleSet.Rules {
var currentRule adapter.HeadlessRule
currentRule, err = rule.NewHeadlessRule(nil, ruleOptions)
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}

View File

@@ -41,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) {
return instance, nil
}
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
if outboundTag == "" {
return instance.Router().DefaultOutbound(N.NetworkName(network))
return instance.Outbound().Default(), nil
} else {
outbound, loaded := instance.Router().Outbound(outboundTag)
outbound, loaded := instance.Outbound().Outbound(outboundTag)
if !loaded {
return nil, E.New("outbound not found: ", outboundTag)
}

View File

@@ -45,7 +45,7 @@ func connect(address string) error {
return err
}
defer instance.Close()
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}

View File

@@ -48,7 +48,7 @@ func fetch(args []string) error {
httpClient = &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return nil, err
}

View File

@@ -16,7 +16,7 @@ import (
)
func initializeHTTP3Client(instance *box.Box) error {
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
"github.com/spf13/cobra"
@@ -45,7 +44,7 @@ func syncTime() error {
if err != nil {
return err
}
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
dialer, err := createDialer(instance, commandToolsFlagOutbound)
if err != nil {
return err
}

View File

@@ -3,6 +3,7 @@ package dialer
import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -28,31 +29,31 @@ type DefaultDialer struct {
isWireGuardListener bool
}
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
func NewDefault(networkManager adapter.NetworkManager, options option.DialerOptions) (*DefaultDialer, error) {
var dialer net.Dialer
var listener net.ListenConfig
if options.BindInterface != "" {
var interfaceFinder control.InterfaceFinder
if router != nil {
interfaceFinder = router.InterfaceFinder()
if networkManager != nil {
interfaceFinder = networkManager.InterfaceFinder()
} else {
interfaceFinder = control.NewDefaultInterfaceFinder()
}
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router != nil && router.AutoDetectInterface() {
bindFunc := router.AutoDetectInterfaceFunc()
} else if networkManager != nil && networkManager.AutoDetectInterface() {
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router != nil && router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
} else if networkManager != nil && networkManager.DefaultInterface() != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), networkManager.DefaultInterface(), -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
var autoRedirectOutputMark uint32
if router != nil {
autoRedirectOutputMark = router.AutoRedirectOutputMark()
if networkManager != nil {
autoRedirectOutputMark = networkManager.AutoRedirectOutputMark()
}
if autoRedirectOutputMark > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
@@ -64,9 +65,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
if autoRedirectOutputMark > 0 {
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
}
} else if router != nil && router.DefaultMark() > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
} else if networkManager != nil && networkManager.DefaultMark() > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(networkManager.DefaultMark()))
listener.Control = control.Append(listener.Control, control.RoutingMark(networkManager.DefaultMark()))
if autoRedirectOutputMark > 0 {
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
}
@@ -102,7 +103,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
udpAddr4 string
)
if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build()
bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
@@ -113,7 +114,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
udpAddr6 string
)
if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build()
bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()

View File

@@ -12,15 +12,15 @@ import (
)
type DetourDialer struct {
router adapter.Router
detour string
dialer N.Dialer
initOnce sync.Once
initErr error
outboundManager adapter.OutboundManager
detour string
dialer N.Dialer
initOnce sync.Once
initErr error
}
func NewDetour(router adapter.Router, detour string) N.Dialer {
return &DetourDialer{router: router, detour: detour}
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
return &DetourDialer{outboundManager: outboundManager, detour: detour}
}
func (d *DetourDialer) Start() error {
@@ -31,7 +31,7 @@ func (d *DetourDialer) Start() error {
func (d *DetourDialer) Dialer() (N.Dialer, error) {
d.initOnce.Do(func() {
var loaded bool
d.dialer, loaded = d.router.Outbound(d.detour)
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
if !loaded {
d.initErr = E.New("outbound detour not found: ", d.detour)
}

View File

@@ -1,40 +1,51 @@
package dialer
import (
"context"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
)
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx)
if options.IsWireGuardListener {
return NewDefault(router, options)
}
if router == nil {
return NewDefault(nil, options)
return NewDefault(networkManager, options)
}
var (
dialer N.Dialer
err error
)
if options.Detour == "" {
dialer, err = NewDefault(router, options)
dialer, err = NewDefault(networkManager, options)
if err != nil {
return nil, err
}
} else {
dialer = NewDetour(router, options.Detour)
outboundManager := service.FromContext[adapter.OutboundManager](ctx)
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 == "" {
dialer = NewResolveDialer(
router,
dialer,
options.Detour == "" && !options.TCPFastOpen,
dns.DomainStrategy(options.DomainStrategy),
time.Duration(options.FallbackDelay))
router := service.FromContext[adapter.Router](ctx)
if router != nil {
dialer = NewResolveDialer(
router,
dialer,
options.Detour == "" && !options.TCPFastOpen,
dns.DomainStrategy(options.DomainStrategy),
time.Duration(options.FallbackDelay))
}
}
return dialer, nil
}

View File

@@ -9,30 +9,22 @@ import (
N "github.com/sagernet/sing/common/network"
)
type RouterDialer struct {
router adapter.Router
type DefaultOutboundDialer struct {
outboundManager adapter.OutboundManager
}
func NewRouter(router adapter.Router) N.Dialer {
return &RouterDialer{router: router}
func NewDefaultOutbound(outboundManager adapter.OutboundManager) N.Dialer {
return &DefaultOutboundDialer{outboundManager: outboundManager}
}
func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
dialer, err := d.router.DefaultOutbound(network)
if err != nil {
return nil, err
}
return dialer.DialContext(ctx, network, destination)
func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return d.outboundManager.Default().DialContext(ctx, network, destination)
}
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
dialer, err := d.router.DefaultOutbound(N.NetworkUDP)
if err != nil {
return nil, err
}
return dialer.ListenPacket(ctx, destination)
func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return d.outboundManager.Default().ListenPacket(ctx, destination)
}
func (d *RouterDialer) Upstream() any {
return d.router
func (d *DefaultOutboundDialer) Upstream() any {
return d.outboundManager.Default()
}

View File

@@ -3,6 +3,7 @@ package listener
import (
"context"
"net"
"net/netip"
"sync/atomic"
"github.com/sagernet/sing-box/adapter"
@@ -92,7 +93,7 @@ func (l *Listener) Start() error {
if l.setSystemProxy {
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
var listenAddrString string
listenAddr := l.listenOptions.Listen.Build()
listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())
if listenAddr.IsUnspecified() {
listenAddrString = "127.0.0.1"
} else {

View File

@@ -2,6 +2,7 @@ package listener
import (
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
@@ -16,7 +17,7 @@ import (
func (l *Listener) ListenTCP() (net.Listener, error) {
var err error
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var tcpListener net.Listener
var listenConfig net.ListenConfig
if l.listenOptions.TCPKeepAlive >= 0 {

View File

@@ -2,6 +2,7 @@ package listener
import (
"net"
"net/netip"
"os"
"time"
@@ -13,7 +14,7 @@ import (
)
func (l *Listener) ListenUDP() (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var lc net.ListenConfig
var udpFragment bool
if l.listenOptions.UDPFragment != nil {
@@ -48,9 +49,9 @@ func (l *Listener) loopUDPIn() {
if !l.threadUnsafePacketWriter {
buffer = buf.NewPacket()
defer buffer.Release()
buffer.IncRef()
defer buffer.DecRef()
}
buffer.IncRef()
defer buffer.DecRef()
if l.oobPacketHandler != nil {
oob := make([]byte, 1024)
for {

View File

@@ -12,6 +12,7 @@ import (
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
)
type DarwinSystemProxy struct {
@@ -24,7 +25,7 @@ type DarwinSystemProxy struct {
}
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
if interfaceMonitor == nil {
return nil, E.New("missing interface monitor")
}

View File

@@ -19,6 +19,7 @@ import (
"github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service"
mDNS "github.com/miekg/dns"
)
@@ -213,7 +214,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam
},
},
}
response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message)
if err != nil {
return nil, err
}

View File

@@ -11,7 +11,6 @@ import (
"time"
"github.com/sagernet/reality"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
@@ -102,7 +101,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
tlsConfig.ShortIds[shortID] = true
}
handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions)
if err != nil {
return nil, err
}

View File

@@ -24,19 +24,16 @@ const (
)
const (
RuleActionTypeRoute = "route"
RuleActionTypeReturn = "return"
RuleActionTypeReject = "reject"
RuleActionTypeHijackDNS = "hijack-dns"
RuleActionTypeSniff = "sniff"
RuleActionTypeResolve = "resolve"
RuleActionTypeRoute = "route"
RuleActionTypeRouteOptions = "route-options"
RuleActionTypeDirect = "direct"
RuleActionTypeReject = "reject"
RuleActionTypeHijackDNS = "hijack-dns"
RuleActionTypeSniff = "sniff"
RuleActionTypeResolve = "resolve"
)
const (
RuleActionRejectMethodDefault = "default"
RuleActionRejectMethodReset = "reset"
RuleActionRejectMethodNetworkUnreachable = "network-unreachable"
RuleActionRejectMethodHostUnreachable = "host-unreachable"
RuleActionRejectMethodPortUnreachable = "port-unreachable"
RuleActionRejectMethodDrop = "drop"
RuleActionRejectMethodDefault = "default"
RuleActionRejectMethodDrop = "drop"
)

View File

@@ -2,10 +2,56 @@
icon: material/alert-decagram
---
#### 1.11.0-alpha.5
#### 1.11.0-alpha.11
* Fixes and improvements
#### 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, the 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
#### 1.11.0-alpha.2
* Add warnings for usage of deprecated features
@@ -47,7 +93,7 @@ Important changes since 1.9:
The new auto-redirect feature allows TUN to automatically
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.
Specified or unspecified destinations will bypass the sing-box routes to get better performance

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [client_subnet](#client_subnet)

View File

@@ -2,6 +2,14 @@
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)
!!! quote "Changes in sing-box 1.10.0"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
@@ -14,7 +22,7 @@ icon: material/new-box
:material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr)
: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)
!!! quote "Changes in sing-box 1.8.0"
@@ -135,19 +143,15 @@ icon: material/new-box
"outbound": [
"direct"
],
"server": "local",
"disable_cache": false,
"rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
"action": "route",
"server": "local"
},
{
"type": "logical",
"mode": "and",
"rules": [],
"server": "local",
"disable_cache": false,
"rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
"action": "route",
"server": "local"
}
]
}
@@ -218,7 +222,7 @@ Match domain using regular expression.
!!! failure "Deprecated in sing-box 1.8.0"
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Match geosite.
@@ -226,7 +230,7 @@ Match geosite.
!!! failure "Deprecated in sing-box 1.8.0"
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
Match source geoip.
@@ -354,29 +358,35 @@ Match outbound.
`any` can be used as a value to match any outbound.
#### server
#### action
==Required==
Tag of the target dns server.
See [DNS Rule Actions](../rule_action/) for details.
#### server
!!! failure "Deprecated in sing-box 1.11.0"
Moved to [DNS Rule Action](../rule_action#route).
#### disable_cache
Disable cache and save cache in this query.
!!! failure "Deprecated in sing-box 1.11.0"
Moved to [DNS Rule Action](../rule_action#route).
#### rewrite_ttl
Rewrite TTL in DNS responses.
!!! failure "Deprecated in sing-box 1.11.0"
Moved to [DNS Rule Action](../rule_action#route).
#### client_subnet
!!! question "Since sing-box 1.9.0"
!!! failure "Deprecated in sing-box 1.11.0"
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`.
Moved to [DNS Rule Action](../rule_action#route).
### Address Filter Fields

View File

@@ -14,7 +14,7 @@ icon: material/new-box
:material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr)
: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)
!!! quote "sing-box 1.8.0 中的更改"

View File

@@ -0,0 +1,85 @@
---
icon: material/new-box
---
# DNS Rule Action
!!! question "Since sing-box 1.11.0"
### route
```json
{
"action": "route", // default
"server": "",
// for compatibility
"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/rewrite_ttl/client_subnet
!!! failure "Deprecated in sing-box 1.11.0"
Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
### route-options
```json
{
"action": "route-options",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
#### disable_cache
Disable cache and save cache in this query.
#### 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`.
### 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.

View File

@@ -0,0 +1,86 @@
---
icon: material/new-box
---
# DNS 规则动作
!!! 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/client_subnet
!!! failure "自 sing-box 1.11.0 起"
旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
### route-options
```json
{
"action": "route-options",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
#### disable_cache
在此查询中禁用缓存。
#### rewrite_ttl
重写 DNS 回应中的 TTL。
#### client_subnet
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
将覆盖 `dns.client_subnet``servers.[].client_subnet`
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` 拒绝 DNS 请求。
#### method
- `default`: 返回 NXDOMAIN。
- `drop`: 丢弃请求。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.9.0"
:material-plus: [client_subnet](#client_subnet)

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.9.0 中的更改"
:material-plus: [client_subnet](#client_subnet)

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.8.0"
!!! quote "Changes in sing-box 1.9.0"

View File

@@ -1,7 +1,3 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.8.0 起"
!!! quote "sing-box 1.9.0 中的更改"

View File

@@ -1,8 +1,14 @@
`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
```json
```json F
{
"type": "block",
"tag": "block"

View File

@@ -1,3 +1,11 @@
---
icon: material/delete-clock
---
!!! failure "已在 sing-box 1.11.0 废弃"
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
`block` 出站关闭所有传入请求。
### 结构
@@ -11,4 +19,4 @@
### 字段
无字段。
无字段。

View File

@@ -1,3 +1,11 @@
---
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.
### Structure

View File

@@ -1,3 +1,11 @@
---
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 服务器。
### 结构

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "Deprecated in sing-box 1.8.0"
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
### Structure

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "已在 sing-box 1.8.0 废弃"
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
### 结构

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "Deprecated in sing-box 1.8.0"
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
### Structure

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "已在 sing-box 1.8.0 废弃"
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
### 结构

View File

@@ -1,7 +1,12 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [action](#action)
:material-alert: [outbound](#outbound)
!!! quote "Changes in sing-box 1.10.0"
:material-plus: [client](#client)
@@ -129,6 +134,7 @@ icon: material/alert-decagram
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"invert": false,
"action": "route",
"outbound": "direct"
},
{
@@ -136,6 +142,7 @@ icon: material/alert-decagram
"mode": "and",
"rules": [],
"invert": false,
"action": "route",
"outbound": "direct"
}
]
@@ -209,7 +216,7 @@ Match domain using regular expression.
!!! failure "Deprecated in sing-box 1.8.0"
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Match geosite.
@@ -217,7 +224,7 @@ Match geosite.
!!! failure "Deprecated in sing-box 1.8.0"
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
Match source geoip.
@@ -225,7 +232,7 @@ Match source geoip.
!!! failure "Deprecated in sing-box 1.8.0"
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
Match geoip.
@@ -357,11 +364,17 @@ Make `ip_cidr` in rule-sets match the source IP.
Invert match result.
#### outbound
#### action
==Required==
Tag of the target outbound.
See [Rule Actions](../rule_action/) for details.
#### outbound
!!! failure "Deprecated in sing-box 1.11.0"
Moved to [Rule Action](../rule_action#route).
### Logical Fields

View File

@@ -1,7 +1,12 @@
---
icon: material/alert-decagram
icon: material/new-box
---
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [action](#action)
:material-alert: [outbound](#outbound)
!!! quote "sing-box 1.10.0 中的更改"
:material-plus: [client](#client)
@@ -127,6 +132,7 @@ icon: material/alert-decagram
"rule_set_ipcidr_match_source": false,
"rule_set_ip_cidr_match_source": false,
"invert": false,
"action": "route",
"outbound": "direct"
},
{
@@ -134,6 +140,7 @@ icon: material/alert-decagram
"mode": "and",
"rules": [],
"invert": false,
"action": "route",
"outbound": "direct"
}
]
@@ -355,11 +362,17 @@ icon: material/alert-decagram
反选匹配结果。
#### outbound
#### action
==必填==
目标出站的标签
参阅 [规则行动](../rule_action/)
#### outbound
!!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [规则行动](../rule_action#route).
### 逻辑字段

View File

@@ -0,0 +1,139 @@
---
icon: material/new-box
---
# Rule Action
!!! question "Since sing-box 1.11.0"
## Final actions
### route
```json
{
"action": "route", // default
"outbound": ""
}
```
`route` inherits the classic rule behavior of routing connection to the specified outbound.
#### outbound
==Required==
Tag of target outbound.
### route-options
```json
{
"action": "route-options",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
`route-options` set options for routing.
#### 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.
### 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.

View File

@@ -0,0 +1,136 @@
---
icon: material/new-box
---
# 规则动作
!!! question "自 sing-box 1.11.0 起"
## 最终动作
### route
```json
{
"action": "route", // 默认
"outbound": "",
"udp_disable_domain_unmapping": false
}
```
`route` 继承了将连接路由到指定出站的经典规则动作。
#### outbound
==必填==
目标出站的标签。
### route-options
```json
{
"action": "route-options",
"udp_disable_domain_unmapping": false,
"udp_connect": false
}
```
#### udp_disable_domain_unmapping
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
#### udp_connect
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。
### 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 路由进行选择。

View File

@@ -1,3 +1,15 @@
---
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
```json
@@ -68,24 +80,40 @@ Requires target inbound support, see [Injectable](/configuration/inbound/#fields
#### 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.
See [Protocol Sniff](/configuration/route/sniff/) for details.
#### 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.
If the domain name is invalid (like tor), this will not work.
#### 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.
300ms is used by default.
`300ms` is used by default.
#### 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`.
If set, the requested domain name will be resolved to IP before routing.
@@ -94,6 +122,10 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb
#### 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,
the original packet address will be sent in the response instead of the mapped domain.

View File

@@ -1,3 +1,15 @@
---
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
@@ -69,24 +81,40 @@ UDP NAT 过期时间,以秒为单位。
#### sniff
!!! failure "已在 sing-box 1.11.0 废弃"
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
启用协议探测。
参阅 [协议探测](/zh/configuration/route/sniff/)
#### sniff_override_destination
!!! failure "已在 sing-box 1.11.0 废弃"
入站字段已废弃且将在 sing-box 1.12.0 中被移除。
用探测出的域名覆盖连接目标地址。
如果域名无效(如 Tor将不生效。
#### sniff_timeout
!!! failure "已在 sing-box 1.11.0 废弃"
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
探测超时时间。
默认使用 300ms。
#### 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`
如果设置,请求的域名将在路由之前解析为 IP。
@@ -95,6 +123,10 @@ UDP NAT 过期时间,以秒为单位。
#### 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 包的客户端,如 Surge。

View File

@@ -4,6 +4,32 @@ icon: material/delete-alert
# 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.
#### Legacy DNS route options
Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated
and can be replaced by rule actions,
check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions).
Old fields will be removed in sing-box 1.12.0.
## 1.10.0
#### TUN address fields are merged
@@ -12,7 +38,7 @@ icon: material/delete-alert
`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`.
Old fields are deprecated and will be removed in sing-box 1.11.0.
Old fields will be removed in sing-box 1.12.0.
#### Match source rule items are renamed
@@ -32,7 +58,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o
#### GeoIP
GeoIP is deprecated and may be removed in the future.
GeoIP is deprecated and will be removed in sing-box 1.12.0.
The maxmind GeoIP National Database, as an IP classification database,
is not entirely suitable for traffic bypassing,
@@ -43,7 +69,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets).
#### Geosite
Geosite is deprecated and may be removed in the future.
Geosite is deprecated and will be removed in sing-box 1.12.0.
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.

View File

@@ -4,6 +4,29 @@ 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 中被移除。
#### 旧的 DNS 路由参数
旧的 DNS 路由参数(`disable_cache``rewrite_ttl``client_subnet`)已废弃且可以通过规则动作替代,
参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。
旧字段将在 sing-box 1.12.0 中被移除。
## 1.10.0
#### Match source 规则项已重命名
@@ -17,7 +40,7 @@ icon: material/delete-alert
`inet4_route_address``inet6_route_address` 已合并为 `route_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 的支持
@@ -32,7 +55,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `
#### GeoIP
GeoIP 已废弃且可能在不久的将来移除。
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
且现有的实现均存在内存使用大与管理困难的问题。
@@ -42,7 +65,7 @@ sing-box 1.8.0 引入了[规则集](/configuration/rule-set/)
#### Geosite
Geosite 已废弃且可能在不久的将来移除。
Geosite 已废弃且将在 sing-box 1.12.0 中被移除。
Geosite即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
存在着包括缺少维护、规则不准确和管理困难内的大量问题。

View File

@@ -2,6 +2,212 @@
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"
}
]
}
}
```
### Migrate legacy DNS route options to rule actions
Legacy DNS route options are deprecated and can be replaced by rule actions.
!!! info "References"
[DNS Rule](/configuration/dns/rule/) /
[DNS Rule Action](/configuration/dns/rule_action/)
=== ":material-card-remove: Deprecated"
```json
{
"dns": {
"rules": [
{
...,
"server": "local",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
}
]
}
}
```
=== ":material-card-multiple: New"
```json
{
"dns": {
"rules": [
{
...,
"action": "route-options",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
},
{
...,
"server": "local"
}
]
}
}
```
## 1.10.0
### TUN address fields are merged
@@ -10,8 +216,6 @@ icon: material/arrange-bring-forward
`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`.
Old fields are deprecated and will be removed in sing-box 1.11.0.
!!! info "References"
[TUN](/configuration/inbound/tun/)

View File

@@ -2,6 +2,212 @@
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"
}
]
}
}
```
### 迁移旧的 DNS 路由选项到规则动作
旧的 DNS 路由选项已被弃用,且可以被规则动作替代。
!!! info "参考"
[DNS 规则](/zh/configuration/dns/rule/) /
[DNS 规则动作](/zh/configuration/dns/rule_action/)
=== ":material-card-remove: 弃用的"
```json
{
"dns": {
"rules": [
{
...,
"server": "local",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
}
]
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"dns": {
"rules": [
{
...,
"action": "route-options",
"disable_cache": true,
"rewrite_ttl": 600,
"client_subnet": "1.1.1.1/24"
},
{
...,
"server": "local"
}
]
}
}
```
## 1.10.0
### TUN 地址字段已合并

View File

@@ -93,7 +93,18 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
}
}
func (c *CacheFile) start() error {
func (c *CacheFile) Name() string {
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
options := bbolt.Options{Timeout: time.Second}
var (
@@ -151,14 +162,6 @@ func (c *CacheFile) start() error {
return nil
}
func (c *CacheFile) PreStart() error {
return c.start()
}
func (c *CacheFile) Start() error {
return nil
}
func (c *CacheFile) Close() error {
if c.DB == nil {
return nil

View File

@@ -12,7 +12,7 @@ import (
"github.com/sagernet/sing/common"
)
type ClashServerConstructor = func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
type ClashServerConstructor = func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error)
var clashServerConstructor ClashServerConstructor
@@ -20,11 +20,11 @@ func RegisterClashServerConstructor(constructor ClashServerConstructor) {
clashServerConstructor = constructor
}
func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
func NewClashServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
if clashServerConstructor == nil {
return nil, os.ErrInvalid
}
return clashServerConstructor(ctx, router, logFactory, options)
return clashServerConstructor(ctx, logFactory, options)
}
func CalculateClashModeList(options option.Options) []string {

View File

@@ -23,7 +23,7 @@ func groupRouter(server *Server) http.Handler {
r := chi.NewRouter()
r.Get("/", getGroups(server))
r.Route("/{name}", func(r chi.Router) {
r.Use(parseProxyName, findProxyByName(server.router))
r.Use(parseProxyName, findProxyByName(server))
r.Get("/", getGroup(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) {
return func(w http.ResponseWriter, r *http.Request) {
groups := common.Map(common.Filter(server.router.Outbounds(), func(it adapter.Outbound) bool {
groups := common.Map(common.Filter(server.outboundManager.Outbounds(), func(it adapter.Outbound) bool {
_, isGroup := it.(adapter.OutboundGroup)
return isGroup
}), 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)
} else {
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
itOutbound, _ := server.router.Outbound(it)
itOutbound, _ := server.outboundManager.Outbound(it)
return itOutbound
}))
b, _ := batch.New(ctx, batch.WithConcurrencyNum[any](10))
@@ -100,7 +100,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
continue
}
checked[realTag] = true
p, loaded := server.router.Outbound(realTag)
p, loaded := server.outboundManager.Outbound(realTag)
if !loaded {
continue
}

View File

@@ -23,10 +23,10 @@ import (
func proxyRouter(server *Server, router adapter.Router) http.Handler {
r := chi.NewRouter()
r.Get("/", getProxies(server, router))
r.Get("/", getProxies(server))
r.Route("/{name}", func(r chi.Router) {
r.Use(parseProxyName, findProxyByName(router))
r.Use(parseProxyName, findProxyByName(server))
r.Get("/", getProxy(server))
r.Get("/delay", getProxyDelay(server))
r.Put("/", updateProxy)
@@ -42,11 +42,11 @@ func parseProxyName(next http.Handler) http.Handler {
})
}
func findProxyByName(router adapter.Router) func(next http.Handler) http.Handler {
func findProxyByName(server *Server) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
name := r.Context().Value(CtxKeyProxyName).(string)
proxy, exist := router.Outbound(name)
proxy, exist := server.outboundManager.Outbound(name)
if !exist {
render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound)
@@ -83,10 +83,10 @@ func proxyInfo(server *Server, detour adapter.Outbound) *badjson.JSONObject {
return &info
}
func getProxies(server *Server, router adapter.Router) func(w http.ResponseWriter, r *http.Request) {
func getProxies(server *Server) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var proxyMap badjson.JSONObject
outbounds := common.Filter(router.Outbounds(), func(detour adapter.Outbound) bool {
outbounds := common.Filter(server.outboundManager.Outbounds(), func(detour adapter.Outbound) bool {
return detour.Tag() != ""
})
@@ -100,12 +100,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite
allProxies = append(allProxies, detour.Tag())
}
var defaultTag string
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
defaultTag = defaultOutbound.Tag()
} else {
defaultTag = allProxies[0]
}
defaultTag := server.outboundManager.Default().Tag()
sort.SliceStable(allProxies, func(i, j int) bool {
return allProxies[i] == defaultTag

View File

@@ -40,15 +40,16 @@ func init() {
var _ adapter.ClashServer = (*Server)(nil)
type Server struct {
ctx context.Context
router adapter.Router
logger log.Logger
httpServer *http.Server
trafficManager *trafficontrol.Manager
urlTestHistory *urltest.HistoryStorage
mode string
modeList []string
modeUpdateHook chan<- struct{}
ctx context.Context
router adapter.Router
outboundManager adapter.OutboundManager
logger log.Logger
httpServer *http.Server
trafficManager *trafficontrol.Manager
urlTestHistory *urltest.HistoryStorage
mode string
modeList []string
modeUpdateHook chan<- struct{}
externalController bool
externalUI string
@@ -56,13 +57,14 @@ type Server struct {
externalUIDownloadDetour string
}
func NewServer(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
func NewServer(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
trafficManager := trafficontrol.NewManager()
chiRouter := chi.NewRouter()
server := &Server{
ctx: ctx,
router: router,
logger: logFactory.NewLogger("clash-api"),
s := &Server{
ctx: ctx,
router: service.FromContext[adapter.Router](ctx),
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
logger: logFactory.NewLogger("clash-api"),
httpServer: &http.Server{
Addr: options.ExternalController,
Handler: chiRouter,
@@ -73,18 +75,18 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
externalUIDownloadURL: options.ExternalUIDownloadURL,
externalUIDownloadDetour: options.ExternalUIDownloadDetour,
}
server.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
if server.urlTestHistory == nil {
server.urlTestHistory = urltest.NewHistoryStorage()
s.urlTestHistory = service.PtrFromContext[urltest.HistoryStorage](ctx)
if s.urlTestHistory == nil {
s.urlTestHistory = urltest.NewHistoryStorage()
}
defaultMode := "Rule"
if options.DefaultMode != "" {
defaultMode = options.DefaultMode
}
if !common.Contains(server.modeList, defaultMode) {
server.modeList = append([]string{defaultMode}, server.modeList...)
if !common.Contains(s.modeList, defaultMode) {
s.modeList = append([]string{defaultMode}, s.modeList...)
}
server.mode = defaultMode
s.mode = defaultMode
//goland:noinspection GoDeprecation
//nolint:staticcheck
if options.StoreMode || options.StoreSelected || options.StoreFakeIP || options.CacheFile != "" || options.CacheID != "" {
@@ -108,71 +110,76 @@ func NewServer(ctx context.Context, router adapter.Router, logFactory log.Observ
r.Get("/logs", getLogs(logFactory))
r.Get("/traffic", traffic(trafficManager))
r.Get("/version", version)
r.Mount("/configs", configRouter(server, logFactory))
r.Mount("/proxies", proxyRouter(server, router))
r.Mount("/rules", ruleRouter(router))
r.Mount("/connections", connectionRouter(router, trafficManager))
r.Mount("/configs", configRouter(s, logFactory))
r.Mount("/proxies", proxyRouter(s, s.router))
r.Mount("/rules", ruleRouter(s.router))
r.Mount("/connections", connectionRouter(s.router, trafficManager))
r.Mount("/providers/proxies", proxyProviderRouter())
r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/script", scriptRouter())
r.Mount("/profile", profileRouter())
r.Mount("/cache", cacheRouter(ctx))
r.Mount("/dns", dnsRouter(router))
r.Mount("/dns", dnsRouter(s.router))
server.setupMetaAPI(r)
s.setupMetaAPI(r)
})
if options.ExternalUI != "" {
server.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
s.externalUI = filemanager.BasePath(ctx, os.ExpandEnv(options.ExternalUI))
chiRouter.Group(func(r chi.Router) {
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(server.externalUI)))
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(s.externalUI)))
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
fs.ServeHTTP(w, r)
})
})
}
return server, nil
return s, nil
}
func (s *Server) PreStart() error {
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
if cacheFile != nil {
mode := cacheFile.LoadMode()
if common.Any(s.modeList, func(it string) bool {
return strings.EqualFold(it, mode)
}) {
s.mode = mode
}
}
return nil
func (s *Server) Name() string {
return "clash server"
}
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
func (s *Server) Start(stage adapter.StartStage) error {
switch stage {
case adapter.StartStateStart:
cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
if cacheFile != nil {
mode := cacheFile.LoadMode()
if common.Any(s.modeList, func(it string) bool {
return strings.EqualFold(it, mode)
}) {
s.mode = mode
}
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)
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
}
@@ -234,14 +241,12 @@ func (s *Server) TrafficManager() *trafficontrol.Manager {
return s.trafficManager
}
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
return tracker, tracker
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
return trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound)
}
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
tracker := trafficontrol.NewUDPTracker(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 {
return trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.outboundManager, matchedRule, matchOutbound)
}
func authentication(serverSecret string) func(next http.Handler) http.Handler {

View File

@@ -15,7 +15,6 @@ import (
"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"
"github.com/sagernet/sing/service/filemanager"
)
@@ -45,16 +44,13 @@ func (s *Server) downloadExternalUI() error {
s.logger.Info("downloading external ui")
var detour adapter.Outbound
if s.externalUIDownloadDetour != "" {
outbound, loaded := s.router.Outbound(s.externalUIDownloadDetour)
outbound, loaded := s.outboundManager.Outbound(s.externalUIDownloadDetour)
if !loaded {
return E.New("detour outbound not found: ", s.externalUIDownloadDetour)
}
detour = outbound
} else {
outbound, err := s.router.DefaultOutbound(N.NetworkTCP)
if err != nil {
return err
}
outbound := s.outboundManager.Default()
detour = outbound
}
httpClient := &http.Client{

View File

@@ -5,7 +5,6 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
@@ -88,7 +87,6 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
}
type Tracker interface {
adapter.Tracker
Metadata() TrackerMetadata
Close() error
}
@@ -108,10 +106,6 @@ func (tt *TCPConn) Close() error {
return tt.ExtendedConn.Close()
}
func (tt *TCPConn) Leave() {
tt.manager.Leave(tt)
}
func (tt *TCPConn) Upstream() any {
return tt.ExtendedConn
}
@@ -124,7 +118,7 @@ func (tt *TCPConn) WriterReplaceable() bool {
return true
}
func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn {
func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *TCPConn {
id, _ := uuid.NewV4()
var (
chain []string
@@ -132,17 +126,13 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
outbound string
outboundType string
)
var action adapter.RuleAction
if rule != nil {
action = rule.Action()
}
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
next = routeAction.Outbound
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
next = defaultOutbound.Tag()
if matchOutbound != nil {
next = matchOutbound.Tag()
} else {
next = outboundManager.Default().Tag()
}
for {
detour, loaded := router.Outbound(next)
detour, loaded := outboundManager.Outbound(next)
if !loaded {
break
}
@@ -172,7 +162,7 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
Upload: upload,
Download: download,
Chain: common.Reverse(chain),
Rule: rule,
Rule: matchRule,
Outbound: outbound,
OutboundType: outboundType,
},
@@ -197,10 +187,6 @@ func (ut *UDPConn) Close() error {
return ut.PacketConn.Close()
}
func (ut *UDPConn) Leave() {
ut.manager.Leave(ut)
}
func (ut *UDPConn) Upstream() any {
return ut.PacketConn
}
@@ -213,7 +199,7 @@ func (ut *UDPConn) WriterReplaceable() bool {
return true
}
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn {
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, outboundManager adapter.OutboundManager, matchRule adapter.Rule, matchOutbound adapter.Outbound) *UDPConn {
id, _ := uuid.NewV4()
var (
chain []string
@@ -221,17 +207,13 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
outbound string
outboundType string
)
var action adapter.RuleAction
if rule != nil {
action = rule.Action()
}
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
next = routeAction.Outbound
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
next = defaultOutbound.Tag()
if matchOutbound != nil {
next = matchOutbound.Tag()
} else {
next = outboundManager.Default().Tag()
}
for {
detour, loaded := router.Outbound(next)
detour, loaded := outboundManager.Outbound(next)
if !loaded {
break
}
@@ -261,7 +243,7 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
Upload: upload,
Download: download,
Chain: common.Reverse(chain),
Rule: rule,
Rule: matchRule,
Outbound: outbound,
OutboundType: outboundType,
},

View File

@@ -1,6 +1,7 @@
package deprecated
import (
"github.com/sagernet/sing-box/common/badversion"
C "github.com/sagernet/sing-box/constant"
F "github.com/sagernet/sing/common/format"
@@ -23,8 +24,9 @@ func (n Note) Impending() bool {
if !semver.IsValid("v" + C.Version) {
return false
}
versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion)
if versionMinor < 0 {
versionCurrent := badversion.Parse(C.Version)
versionMinor := badversion.Parse(n.ScheduledVersion).Minor - versionCurrent.Minor
if versionCurrent.PreReleaseIdentifier == "" && versionMinor < 0 {
panic("invalid deprecated note: " + n.Name)
}
return versionMinor <= 1
@@ -49,6 +51,7 @@ var OptionBadMatchSource = Note{
Description: "legacy match source rule item",
DeprecatedVersion: "1.10.0",
ScheduledVersion: "1.11.0",
EnvName: "BAD_MATCH_SOURCE",
MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed",
}
@@ -75,12 +78,43 @@ var OptionTUNAddressX = Note{
Description: "legacy tun address fields",
DeprecatedVersion: "1.10.0",
ScheduledVersion: "1.12.0",
EnvName: "TUN_ADDRESS_X",
MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged",
}
var OptionSpecialOutbounds = Note{
Name: "special-outbounds",
Description: "legacy special outbounds",
DeprecatedVersion: "1.11.0",
ScheduledVersion: "1.13.0",
EnvName: "SPECIAL_OUTBOUNDS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions",
}
var OptionInboundOptions = Note{
Name: "inbound-options",
Description: "legacy inbound fields",
DeprecatedVersion: "1.11.0",
ScheduledVersion: "1.13.0",
EnvName: "INBOUND_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions",
}
var OptionLegacyDNSRouteOptions = Note{
Name: "legacy-dns-route-options",
Description: "legacy dns route options",
DeprecatedVersion: "1.11.0",
ScheduledVersion: "1.12.0",
EnvName: "LEGACY_DNS_ROUTE_OPTIONS",
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions",
}
var Options = []Note{
OptionBadMatchSource,
OptionGEOIP,
OptionGEOSITE,
OptionTUNAddressX,
OptionSpecialOutbounds,
OptionInboundOptions,
OptionLegacyDNSRouteOptions,
}

View File

@@ -7,15 +7,23 @@ import (
"github.com/sagernet/sing/common/logger"
)
type envManager struct {
logger logger.Logger
type stderrManager struct {
logger logger.Logger
reported map[string]bool
}
func NewEnvManager(logger logger.Logger) Manager {
return &envManager{logger: logger}
func NewStderrManager(logger logger.Logger) Manager {
return &stderrManager{
logger: logger,
reported: make(map[string]bool),
}
}
func (f *envManager) ReportDeprecated(feature Note) {
func (f *stderrManager) ReportDeprecated(feature Note) {
if f.reported[feature.Name] {
return
}
f.reported[feature.Name] = true
if !feature.Impending() {
f.logger.Warn(feature.MessageWithLink())
return

View File

@@ -38,11 +38,7 @@ func (s *CommandServer) handleSetClashMode(conn net.Conn) error {
if service == nil {
return writeError(conn, E.New("service not ready"))
}
clashServer := service.instance.Router().ClashServer()
if clashServer == nil {
return writeError(conn, E.New("Clash API disabled"))
}
clashServer.(*clashapi.Server).SetMode(newMode)
service.clashServer.(*clashapi.Server).SetMode(newMode)
return writeError(conn, nil)
}
@@ -69,18 +65,14 @@ func (s *CommandServer) handleModeConn(conn net.Conn) error {
return ctx.Err()
}
}
clashServer := s.service.instance.Router().ClashServer()
if clashServer == nil {
return binary.Write(conn, binary.BigEndian, uint16(0))
}
err := writeClashModeList(conn, clashServer)
err := writeClashModeList(conn, s.service.clashServer)
if err != nil {
return err
}
for {
select {
case <-s.modeUpdate:
err = varbin.Write(conn, binary.BigEndian, clashServer.Mode())
err = varbin.Write(conn, binary.BigEndian, s.service.clashServer.Mode())
if err != nil {
return err
}

View File

@@ -20,7 +20,6 @@ type CommandClient struct {
type CommandClientOptions struct {
Command int32
StatusInterval int64
IsMainClient bool
}
type CommandClientHandler interface {
@@ -29,7 +28,6 @@ type CommandClientHandler interface {
ClearLogs()
WriteLogs(messageList StringIterator)
WriteStatus(message *StatusMessage)
OpenURL(url string)
WriteGroups(message OutboundGroupIterator)
InitializeClashMode(modeList StringIterator, currentMode string)
UpdateClashMode(newMode string)
@@ -93,13 +91,9 @@ func (c *CommandClient) Connect() error {
c.handler.Connected()
go c.handleLogConn(conn)
case CommandStatus:
err = binary.Write(conn, binary.BigEndian, c.options.IsMainClient)
if err != nil {
return E.Cause(err, "write is main client")
}
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
if err != nil {
return E.Cause(err, "write header")
return E.Cause(err, "write interval")
}
c.handler.Connected()
go c.handleStatusConn(conn)

View File

@@ -45,11 +45,7 @@ func (s *CommandServer) handleCloseConnection(conn net.Conn) error {
if service == nil {
return writeError(conn, E.New("service not ready"))
}
clashServer := service.instance.Router().ClashServer()
if clashServer == nil {
return writeError(conn, E.New("Clash API disabled"))
}
targetConn := clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId))
targetConn := service.clashServer.(*clashapi.Server).TrafficManager().Connection(uuid.FromStringOrNil(connId))
if targetConn == nil {
return writeError(conn, E.New("connection already closed"))
}

View File

@@ -49,11 +49,7 @@ func (s *CommandServer) handleConnectionsConn(conn net.Conn) error {
for {
service := s.service
if service != nil {
clashServer := service.instance.Router().ClashServer()
if clashServer == nil {
return E.New("Clash API disabled")
}
trafficManager = clashServer.(*clashapi.Server).TrafficManager()
trafficManager = service.clashServer.(*clashapi.Server).TrafficManager()
break
}
select {

View File

@@ -1,40 +0,0 @@
package libbox
import (
"encoding/binary"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
)
type myEvent interface {
writeTo(writer varbin.Writer)
}
func readEvent(reader varbin.Reader) (myEvent, error) {
eventType, err := reader.ReadByte()
if err != nil {
return nil, err
}
switch eventType {
case eventTypeEmpty:
return nil, nil
case eventTypeOpenURL:
url, err := varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return nil, err
}
return &eventOpenURL{URL: url}, nil
default:
return nil, E.New("unknown event type: ", eventType)
}
}
type eventOpenURL struct {
URL string
}
func (e *eventOpenURL) writeTo(writer varbin.Writer) {
writer.WriteByte(eventTypeOpenURL)
varbin.Write(writer, binary.BigEndian, e.URL)
}

View File

@@ -109,7 +109,7 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
func writeGroups(writer io.Writer, boxService *BoxService) error {
historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
cacheFile := service.FromContext[adapter.CacheFile](boxService.ctx)
outbounds := boxService.instance.Router().Outbounds()
outbounds := boxService.instance.Outbound().Outbounds()
var iGroups []adapter.OutboundGroup
for _, it := range outbounds {
if group, isGroup := it.(adapter.OutboundGroup); isGroup {
@@ -130,7 +130,7 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
}
for _, itemTag := range iGroup.All() {
itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag)
itemOutbound, isLoaded := boxService.instance.Outbound().Outbound(itemTag)
if !isLoaded {
continue
}

View File

@@ -43,7 +43,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
if service == nil {
return writeError(conn, E.New("service not ready"))
}
outboundGroup, isLoaded := service.instance.Router().Outbound(groupTag)
outboundGroup, isLoaded := service.instance.Outbound().Outbound(groupTag)
if !isLoaded {
return writeError(conn, E.New("selector not found: ", groupTag))
}

View File

@@ -33,7 +33,6 @@ type CommandServer struct {
urlTestUpdate chan struct{}
modeUpdate chan struct{}
logReset chan struct{}
events chan myEvent
closedConnections []Connection
}
@@ -53,7 +52,6 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
urlTestUpdate: make(chan struct{}, 1),
modeUpdate: make(chan struct{}, 1),
logReset: make(chan struct{}, 1),
events: make(chan myEvent, 8),
}
server.observer = observable.NewObserver[string](server.subscriber, 64)
return server
@@ -62,13 +60,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
func (s *CommandServer) SetService(newService *BoxService) {
if newService != nil {
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
newService.platformInterface.openURLFunc = func(url string) {
select {
case s.events <- &eventOpenURL{URL: url}:
default:
}
}
newService.clashServer.(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
}
s.service = newService
s.notifyURLTestUpdate()

View File

@@ -1,7 +1,6 @@
package libbox
import (
std_bufio "bufio"
"encoding/binary"
"net"
"runtime"
@@ -10,15 +9,9 @@ import (
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/experimental/clashapi"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/memory"
)
const (
eventTypeEmpty byte = iota
eventTypeOpenURL
)
type StatusMessage struct {
Memory int64
Goroutines int32
@@ -38,86 +31,42 @@ func (s *CommandServer) readStatus() StatusMessage {
message.ConnectionsOut = int32(conntrack.Count())
if s.service != nil {
if clashServer := s.service.instance.Router().ClashServer(); clashServer != nil {
message.TrafficAvailable = true
trafficManager := clashServer.(*clashapi.Server).TrafficManager()
message.Uplink, message.Downlink = trafficManager.Now()
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
}
message.TrafficAvailable = true
trafficManager := s.service.clashServer.(*clashapi.Server).TrafficManager()
message.Uplink, message.Downlink = trafficManager.Now()
message.UplinkTotal, message.DownlinkTotal = trafficManager.Total()
message.ConnectionsIn = int32(trafficManager.ConnectionsLen())
}
return message
}
func (s *CommandServer) handleStatusConn(conn net.Conn) error {
var isMainClient bool
err := binary.Read(conn, binary.BigEndian, &isMainClient)
if err != nil {
return E.Cause(err, "read is main client")
}
var interval int64
err = binary.Read(conn, binary.BigEndian, &interval)
err := binary.Read(conn, binary.BigEndian, &interval)
if err != nil {
return E.Cause(err, "read interval")
}
ticker := time.NewTicker(time.Duration(interval))
defer ticker.Stop()
ctx := connKeepAlive(conn)
writer := std_bufio.NewWriter(conn)
if isMainClient {
for {
writer.WriteByte(eventTypeEmpty)
err = binary.Write(conn, binary.BigEndian, s.readStatus())
if err != nil {
return err
}
writer.Flush()
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
case event := <-s.events:
event.writeTo(writer)
writer.Flush()
}
for {
err = binary.Write(conn, binary.BigEndian, s.readStatus())
if err != nil {
return err
}
} else {
for {
err = binary.Write(conn, binary.BigEndian, s.readStatus())
if err != nil {
return err
}
writer.Flush()
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
func (c *CommandClient) handleStatusConn(conn net.Conn) {
reader := std_bufio.NewReader(conn)
for {
if c.options.IsMainClient {
rawEvent, err := readEvent(reader)
if err != nil {
c.handler.Disconnected(err.Error())
return
}
switch event := rawEvent.(type) {
case *eventOpenURL:
c.handler.OpenURL(event.URL)
continue
case nil:
default:
panic(F.ToString("unexpected event type: ", event))
}
}
var message StatusMessage
err := binary.Read(reader, binary.BigEndian, &message)
err := binary.Read(conn, binary.BigEndian, &message)
if err != nil {
c.handler.Disconnected(err.Error())
return

View File

@@ -41,7 +41,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
if serviceNow == nil {
return nil
}
abstractOutboundGroup, isLoaded := serviceNow.instance.Router().Outbound(groupTag)
abstractOutboundGroup, isLoaded := serviceNow.instance.Outbound().Outbound(groupTag)
if !isLoaded {
return writeError(conn, E.New("outbound group not found: ", groupTag))
}
@@ -55,7 +55,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
} else {
historyStorage := service.PtrFromContext[urltest.HistoryStorage](serviceNow.ctx)
outbounds := common.Filter(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
itOutbound, _ := serviceNow.instance.Router().Outbound(it)
itOutbound, _ := serviceNow.instance.Outbound().Outbound(it)
return itOutbound
}), func(it adapter.Outbound) bool {
if it == nil {

View File

@@ -30,11 +30,12 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err
}
func CheckConfig(configContent string) error {
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry())
options, err := parseConfig(ctx, configContent)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(ctx)
defer cancel()
ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil))
instance, err := box.New(box.Options{
@@ -49,7 +50,7 @@ func CheckConfig(configContent string) error {
type platformInterfaceStub struct{}
func (s *platformInterfaceStub) Initialize(ctx context.Context, router adapter.Router) error {
func (s *platformInterfaceStub) Initialize(networkManager adapter.NetworkManager) error {
return nil
}
@@ -57,7 +58,7 @@ func (s *platformInterfaceStub) UsePlatformAutoDetectInterfaceControl() bool {
return true
}
func (s *platformInterfaceStub) AutoDetectInterfaceControl() control.Func {
func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error {
return nil
}
@@ -137,7 +138,8 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
}
func (s *platformInterfaceStub) OpenURL(url string) {
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
return nil
}
func FormatConfig(configContent string) (string, error) {

View File

@@ -4,27 +4,28 @@ import (
"sync"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing/common"
)
var _ deprecated.Manager = (*deprecatedManager)(nil)
type deprecatedManager struct {
access sync.Mutex
features []deprecated.Note
access sync.Mutex
notes []deprecated.Note
}
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
m.access.Lock()
defer m.access.Unlock()
m.features = append(m.features, feature)
m.notes = common.Uniq(append(m.notes, feature))
}
func (m *deprecatedManager) Get() []deprecated.Note {
m.access.Lock()
defer m.access.Unlock()
features := m.features
m.features = nil
return features
notes := m.notes
m.notes = nil
return notes
}
var _ = deprecated.Note(DeprecatedNote{})

View File

@@ -0,0 +1,30 @@
package libbox
import (
"net"
"syscall"
)
// copied from net.linkFlags
func linkFlags(rawFlags uint32) net.Flags {
var f net.Flags
if rawFlags&syscall.IFF_UP != 0 {
f |= net.FlagUp
}
if rawFlags&syscall.IFF_RUNNING != 0 {
f |= net.FlagRunning
}
if rawFlags&syscall.IFF_BROADCAST != 0 {
f |= net.FlagBroadcast
}
if rawFlags&syscall.IFF_LOOPBACK != 0 {
f |= net.FlagLoopback
}
if rawFlags&syscall.IFF_POINTOPOINT != 0 {
f |= net.FlagPointToPoint
}
if rawFlags&syscall.IFF_MULTICAST != 0 {
f |= net.FlagMulticast
}
return f
}

View File

@@ -0,0 +1,11 @@
//go:build !linux
package libbox
import (
"net"
)
func linkFlags(rawFlags uint32) net.Flags {
panic("stub!")
}

View File

@@ -115,7 +115,7 @@ func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName s
err = m.updateInterfaces()
}
if err == nil {
err = m.router.UpdateInterfaces()
err = m.networkManager.UpdateInterfaces()
}
if err != nil {
m.logger.Error(E.Cause(err, "update interfaces"))

View File

@@ -22,6 +22,7 @@ type PlatformInterface interface {
IncludeAllNetworks() bool
ReadWIFIState() *WIFIState
ClearDNSCache()
SendNotification(notification *Notification) error
}
type TunInterface interface {
@@ -38,6 +39,7 @@ type NetworkInterface struct {
MTU int32
Name string
Addresses StringIterator
Flags int32
}
type WIFIState struct {
@@ -54,6 +56,16 @@ type NetworkInterfaceIterator interface {
HasNext() bool
}
type Notification struct {
Identifier string
TypeName string
TypeID int32
Title string
Subtitle string
Body string
OpenURL string
}
type OnDemandRule interface {
Target() int32
DNSSearchDomainMatch() StringIterator

View File

@@ -1,8 +1,6 @@
package platform
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/option"
@@ -12,9 +10,9 @@ import (
)
type Interface interface {
Initialize(ctx context.Context, router adapter.Router) error
Initialize(networkManager adapter.NetworkManager) error
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl() control.Func
AutoDetectInterfaceControl(fd int) error
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
UsePlatformDefaultInterfaceMonitor() bool
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
@@ -25,5 +23,15 @@ type Interface interface {
ClearDNSCache()
ReadWIFIState() adapter.WIFIState
process.Searcher
OpenURL(url string)
SendNotification(notification *Notification) error
}
type Notification struct {
Identifier string
TypeName string
TypeID int32
Title string
Subtitle string
Body string
OpenURL string
}

View File

@@ -34,27 +34,28 @@ import (
type BoxService struct {
ctx context.Context
cancel context.CancelFunc
instance *box.Box
platformInterface *platformInterfaceWrapper
pauseManager pause.Manager
urlTestHistoryStorage *urltest.HistoryStorage
instance *box.Box
clashServer adapter.ClashServer
pauseManager pause.Manager
servicePauseFields
}
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry())
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
service.MustRegister[deprecated.Manager](ctx, new(deprecatedManager))
options, err := parseConfig(ctx, configContent)
if err != nil {
return nil, err
}
runtimeDebug.FreeOSMemory()
ctx, cancel := context.WithCancel(ctx)
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
urlTestHistoryStorage := urltest.NewHistoryStorage()
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager))
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
ctx = service.ContextWith[platform.Interface](ctx, platformWrapper)
service.MustRegister[platform.Interface](ctx, platformWrapper)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
@@ -69,9 +70,9 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
ctx: ctx,
cancel: cancel,
instance: instance,
platformInterface: platformWrapper,
urlTestHistoryStorage: urlTestHistoryStorage,
pauseManager: service.FromContext[pause.Manager](ctx),
clashServer: service.FromContext[adapter.ClashServer](ctx),
}, nil
}
@@ -105,14 +106,13 @@ var (
)
type platformInterfaceWrapper struct {
iif PlatformInterface
useProcFS bool
router adapter.Router
openURLFunc func(url string)
iif PlatformInterface
useProcFS bool
networkManager adapter.NetworkManager
}
func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error {
w.router = router
func (w *platformInterfaceWrapper) Initialize(networkManager adapter.NetworkManager) error {
w.networkManager = networkManager
return nil
}
@@ -120,12 +120,8 @@ func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool
return w.iif.UsePlatformAutoDetectInterfaceControl()
}
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
return func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return w.iif.AutoDetectInterfaceControl(int32(fd))
})
}
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
return w.iif.AutoDetectInterfaceControl(int32(fd))
}
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
@@ -183,6 +179,7 @@ func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) {
MTU: int(netInterface.MTU),
Name: netInterface.Name,
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
Flags: linkFlags(uint32(netInterface.Flags)),
})
}
return interfaces, nil
@@ -243,8 +240,6 @@ func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string)
w.iif.WriteLog(message)
}
func (w *platformInterfaceWrapper) OpenURL(url string) {
if w.openURLFunc != nil {
w.openURLFunc(url)
}
func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error {
return w.iif.SendNotification((*Notification)(notification))
}

View File

@@ -29,5 +29,5 @@ func (s *BoxService) Wake() {
}
func (s *BoxService) ResetNetwork() {
_ = s.instance.Router().ResetNetwork()
s.instance.Router().ResetNetwork()
}

View File

@@ -23,7 +23,6 @@ var (
func init() {
debug.SetPanicOnFault(true)
debug.SetTraceback("all")
}
func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) {

View File

@@ -44,7 +44,14 @@ func NewServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2Ray
return server, nil
}
func (s *Server) Start() error {
func (s *Server) Name() string {
return "v2ray server"
}
func (s *Server) Start(stage adapter.StartStage) error {
if stage != adapter.StartStatePostStart {
return nil
}
listener, err := net.Listen("tcp", s.listen)
if err != nil {
return err
@@ -70,6 +77,6 @@ func (s *Server) Close() error {
)
}
func (s *Server) StatsService() adapter.V2RayStatsService {
func (s *Server) StatsService() adapter.ConnectionTracker {
return s.statsService
}

View File

@@ -22,7 +22,7 @@ func init() {
}
var (
_ adapter.V2RayStatsService = (*StatsService)(nil)
_ adapter.ConnectionTracker = (*StatsService)(nil)
_ StatsServiceServer = (*StatsService)(nil)
)
@@ -60,7 +60,10 @@ func NewStatsService(options option.V2RayStatsServiceOptions) *StatsService {
}
}
func (s *StatsService) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn {
func (s *StatsService) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) net.Conn {
inbound := metadata.Inbound
user := metadata.User
outbound := matchOutbound.Tag()
var readCounter []*atomic.Int64
var writeCounter []*atomic.Int64
countInbound := inbound != "" && s.inbounds[inbound]
@@ -86,7 +89,10 @@ func (s *StatsService) RoutedConnection(inbound string, outbound string, user st
return bufio.NewInt64CounterConn(conn, readCounter, writeCounter)
}
func (s *StatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn {
func (s *StatsService) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule, matchOutbound adapter.Outbound) N.PacketConn {
inbound := metadata.Inbound
user := metadata.User
outbound := matchOutbound.Tag()
var readCounter []*atomic.Int64
var writeCounter []*atomic.Int64
countInbound := inbound != "" && s.inbounds[inbound]

24
go.mod
View File

@@ -23,16 +23,16 @@ require (
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.4
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3
github.com/sagernet/quic-go v0.48.0-beta.1
github.com/sagernet/quic-go v0.48.1-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.5.0-rc.4.0.20241101160402-8452992a6369
github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a
github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec
github.com/sagernet/sing-quic v0.3.0-rc.1
github.com/sagernet/sing v0.6.0-alpha.3
github.com/sagernet/sing-dns v0.4.0-alpha.1
github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.3.0-rc.2
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241023054150-3b5b396d06f7
github.com/sagernet/sing-tun v0.6.0-alpha.3
github.com/sagernet/sing-vmess v0.1.12
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7
@@ -42,17 +42,19 @@ require (
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.28.0
golang.org/x/crypto v0.29.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/mod v0.20.0
golang.org/x/net v0.30.0
golang.org/x/sys v0.26.0
golang.org/x/net v0.31.0
golang.org/x/sys v0.27.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
howett.net/plist v1.0.1
)
//replace github.com/sagernet/sing => ../sing
require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
@@ -86,8 +88,8 @@ require (
github.com/vishvananda/netns v0.0.4 // indirect
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect

46
go.sum
View File

@@ -105,27 +105,27 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg=
github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
github.com/sagernet/quic-go v0.48.1-beta.1 h1:ElPaV5yzlXIKZpqFMAcUGax6vddi3zt4AEpT94Z0vwo=
github.com/sagernet/quic-go v0.48.1-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.0-rc.4.0.20241101160402-8452992a6369 h1:gfiUYWslwKM7OtvG37PV0iIDCWcacJSEUS2h29rpYac=
github.com/sagernet/sing v0.5.0-rc.4.0.20241101160402-8452992a6369/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a h1:jpAlbmZxc1LymZrmJacsvHI57Wito5xy8qASZJMWoOQ=
github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M=
github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48=
github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec/go.mod h1:RSwqqHwbtTOX3vs6ms8vMtBGH/0ZNyLm/uwt6TlmR84=
github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts=
github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ=
github.com/sagernet/sing v0.6.0-alpha.3 h1:GLp9d6Gbt+Ioeplauuzojz1nY2J6moceVGYIOv/h5gA=
github.com/sagernet/sing v0.6.0-alpha.3/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-alpha.1 h1:2KlP8DeqtGkULFiZtvG2r7SuoJP6orANFzJwC5vDKvg=
github.com/sagernet/sing-dns v0.4.0-alpha.1/go.mod h1:vgHATsm4wdymwpvBZPei8RY+546iGXS6hlWv2x6YKcM=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.3.0-rc.2 h1:7vcC4bdS1GBJzHZhfmJiH0CfzQ4mYLUW51Z2RNHcGwc=
github.com/sagernet/sing-quic v0.3.0-rc.2/go.mod h1:3UOq51WVqzra7eCgod7t4hqnTaOiZzFUci9avMrtOqs=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241023054150-3b5b396d06f7 h1:wWfRBSP8v0Gc9yUeMgoKCiG+LIs/+bYLWWwVVYSbGFI=
github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241023054150-3b5b396d06f7/go.mod h1:2v1L3BQKzoOpGuKMwC6pcs/5/Xb5PBqzqL6Lq88IoS8=
github.com/sagernet/sing-tun v0.6.0-alpha.3 h1:KddmFF9ZYC1n+HZzpAZ1StMC5v38ir6hH0PJ7uGpAGE=
github.com/sagernet/sing-tun v0.6.0-alpha.3/go.mod h1:R/UaKB1oFIvAeMIH2btQCaDVsfCNomEjfYBSCSA94sw=
github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=
github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@@ -163,18 +163,18 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -182,14 +182,14 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -13,7 +13,7 @@ import (
)
func init() {
experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
experimental.RegisterClashServerConstructor(func(ctx context.Context, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) {
return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`)
})
}

View File

@@ -82,6 +82,7 @@ nav:
- configuration/dns/index.md
- DNS Server: configuration/dns/server.md
- DNS Rule: configuration/dns/rule.md
- DNS Rule Action: configuration/dns/rule_action.md
- FakeIP: configuration/dns/fakeip.md
- NTP:
- configuration/ntp/index.md
@@ -90,6 +91,7 @@ nav:
- GeoIP: configuration/route/geoip.md
- Geosite: configuration/route/geosite.md
- Route Rule: configuration/route/rule.md
- Rule Action: configuration/route/rule_action.md
- Protocol Sniff: configuration/route/sniff.md
- Rule Set:
- configuration/rule-set/index.md
@@ -218,9 +220,11 @@ plugins:
Log: 日志
DNS Server: DNS 服务器
DNS Rule: DNS 规则
DNS Rule Action: DNS 规则动作
Route: 路由
Route Rule: 路由规则
Rule Action: 规则动作
Protocol Sniff: 协议探测
Rule Set: 规则集

View File

@@ -1,6 +1,10 @@
package option
import "net/netip"
import (
"net/netip"
"github.com/sagernet/sing/common/json/badoption"
)
type DNSOptions struct {
Servers []DNSServerOptions `json:"servers,omitempty"`
@@ -12,22 +16,22 @@ type DNSOptions struct {
}
type DNSServerOptions struct {
Tag string `json:"tag,omitempty"`
Address string `json:"address"`
AddressResolver string `json:"address_resolver,omitempty"`
AddressStrategy DomainStrategy `json:"address_strategy,omitempty"`
AddressFallbackDelay Duration `json:"address_fallback_delay,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"`
Detour string `json:"detour,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
Tag string `json:"tag,omitempty"`
Address string `json:"address"`
AddressResolver string `json:"address_resolver,omitempty"`
AddressStrategy DomainStrategy `json:"address_strategy,omitempty"`
AddressFallbackDelay badoption.Duration `json:"address_fallback_delay,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"`
Detour string `json:"detour,omitempty"`
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
}
type DNSClientOptions struct {
Strategy DomainStrategy `json:"strategy,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
DisableExpire bool `json:"disable_expire,omitempty"`
IndependentCache bool `json:"independent_cache,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
DisableExpire bool `json:"disable_expire,omitempty"`
IndependentCache bool `json:"independent_cache,omitempty"`
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
}
type DNSFakeIPOptions struct {

Some files were not shown because too many files have changed in this diff Show More