Compare commits

..

2 Commits

Author SHA1 Message Date
世界
2d90a6d6b0 release: Fix android version 2024-12-20 22:30:54 +08:00
世界
04a36348bd release: Fix check tag 2024-12-20 21:19:11 +08:00
410 changed files with 8760 additions and 16750 deletions

View File

@@ -144,7 +144,7 @@ jobs:
~/go/go1.20.14 ~/go/go1.20.14
key: go120 key: go120
- name: Setup legacy Go - name: Setup legacy Go
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true' if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |- run: |-
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
tar -xzf go1.20.14.linux-amd64.tar.gz tar -xzf go1.20.14.linux-amd64.tar.gz
@@ -153,13 +153,13 @@ jobs:
if: matrix.goos == 'android' if: matrix.goos == 'android'
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
with: with:
ndk-version: r28 ndk-version: r28-beta2
local-cache: true local-cache: true
- name: Setup Goreleaser - name: Setup Goreleaser
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: 2.5.1 version: latest
install-only: true install-only: true
- name: Extract signing key - name: Extract signing key
run: |- run: |-
@@ -170,7 +170,7 @@ jobs:
echo "HOME=$HOME" >> "$GITHUB_ENV" echo "HOME=$HOME" >> "$GITHUB_ENV"
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=true" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build - name: Build
if: matrix.goos != 'android' if: matrix.goos != 'android'
@@ -224,14 +224,14 @@ jobs:
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
with: with:
ndk-version: r28 ndk-version: r28-beta2
- name: Setup OpenJDK - name: Setup OpenJDK
run: |- run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=true" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library - name: Build library
run: |- run: |-
@@ -299,14 +299,14 @@ jobs:
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
with: with:
ndk-version: r28 ndk-version: r28-beta2
- name: Setup OpenJDK - name: Setup OpenJDK
run: |- run: |-
sudo apt update && sudo apt install -y openjdk-17-jdk-headless sudo apt update && sudo apt install -y openjdk-17-jdk-headless
/usr/lib/jvm/java-17-openjdk-amd64/bin/java --version /usr/lib/jvm/java-17-openjdk-amd64/bin/java --version
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=true" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f git tag v${{ needs.calculate_version.outputs.version }} -f
- name: Build library - name: Build library
run: |- run: |-
@@ -404,7 +404,7 @@ jobs:
- name: Set tag - name: Set tag
if: matrix.if if: matrix.if
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=true" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Checkout main branch - name: Checkout main branch
@@ -548,7 +548,7 @@ jobs:
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: 2.5.1 version: latest
install-only: true install-only: true
- name: Cache ghr - name: Cache ghr
uses: actions/cache@v4 uses: actions/cache@v4
@@ -566,7 +566,7 @@ jobs:
go install -v . go install -v .
- name: Set tag - name: Set tag
run: |- run: |-
git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=false" >> "$GITHUB_ENV" git ls-remote --exit-code --tags origin v${{ needs.calculate_version.outputs.version }} || echo "PUBLISHED=true" >> "$GITHUB_ENV"
git tag v${{ needs.calculate_version.outputs.version }} -f git tag v${{ needs.calculate_version.outputs.version }} -f
echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV" echo "VERSION=${{ needs.calculate_version.outputs.version }}" >> "$GITHUB_ENV"
- name: Download builds - name: Download builds
@@ -584,14 +584,14 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- name: Upload builds - name: Upload builds
if: ${{ env.PUBLISHED == 'false' }} if: ${{ env.PUBLISHED != 'true' }}
run: |- run: |-
export PATH="$PATH:$HOME/go/bin" export PATH="$PATH:$HOME/go/bin"
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Replace builds - name: Replace builds
if: ${{ env.PUBLISHED != 'false' }} if: ${{ env.PUBLISHED == 'true' }}
run: |- run: |-
export PATH="$PATH:$HOME/go/bin" export PATH="$PATH:$HOME/go/bin"
ghr --replace -p 5 "v${VERSION}" dist/release ghr --replace -p 5 "v${VERSION}" dist/release

View File

@@ -22,16 +22,6 @@ linters-settings:
run: run:
go: "1.23" go: "1.23"
build-tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_ech
- with_utls
- with_reality_server
- with_acme
- with_clash_api
issues: issues:
exclude-dirs: exclude-dirs:

View File

@@ -52,7 +52,7 @@ builds:
env: env:
- CGO_ENABLED=0 - CGO_ENABLED=0
- GOROOT={{ .Env.GOPATH }}/go1.20.14 - GOROOT={{ .Env.GOPATH }}/go1.20.14
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go" gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
targets: targets:
- windows_amd64_v1 - windows_amd64_v1
- windows_386 - windows_386

View File

@@ -28,7 +28,7 @@ ci_build:
go build $(MAIN_PARAMS) $(MAIN) go build $(MAIN_PARAMS) $(MAIN)
generate_completions: generate_completions:
go run -v --tags $(TAGS),generate,generate_completions $(MAIN) go run -v --tags generate,generate_completions $(MAIN)
install: install:
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN) go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)

104
adapter/conn_router.go Normal file
View File

@@ -0,0 +1,104 @@
package adapter
import (
"context"
"net"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type ConnectionRouter interface {
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

View File

@@ -1,14 +0,0 @@
package adapter
import (
"context"
"net"
N "github.com/sagernet/sing/common/network"
)
type ConnectionManager interface {
Lifecycle
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}

View File

@@ -1,28 +0,0 @@
package adapter
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type Endpoint interface {
Lifecycle
Type() string
Tag() string
Outbound
}
type EndpointRegistry interface {
option.EndpointOptionsRegistry
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
}
type EndpointManager interface {
Lifecycle
Endpoints() []Endpoint
Get(tag string) (Endpoint, bool)
Remove(tag string) error
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
}

View File

@@ -1,43 +0,0 @@
package endpoint
import "github.com/sagernet/sing-box/option"
type Adapter struct {
endpointType string
endpointTag string
network []string
dependencies []string
}
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
return Adapter{
endpointType: endpointType,
endpointTag: endpointTag,
network: network,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(endpointType, endpointTag, network, dependencies)
}
func (a *Adapter) Type() string {
return a.endpointType
}
func (a *Adapter) Tag() string {
return a.endpointTag
}
func (a *Adapter) Network() []string {
return a.network
}
func (a *Adapter) Dependencies() []string {
return a.dependencies
}

View File

@@ -1,147 +0,0 @@
package endpoint
import (
"context"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ adapter.EndpointManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.EndpointRegistry
access sync.Mutex
started bool
stage adapter.StartStage
endpoints []adapter.Endpoint
endpointByTag map[string]adapter.Endpoint
}
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpointByTag: make(map[string]adapter.Endpoint),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
defer m.access.Unlock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
if stage == adapter.StartStateStart {
// started with outbound manager
return nil
}
for _, endpoint := range m.endpoints {
err := adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
endpoints := m.endpoints
m.endpoints = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, endpoint := range endpoints {
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
err = E.Append(err, endpoint.Close(), func(err error) error {
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
})
monitor.Finish()
}
return nil
}
func (m *Manager) Endpoints() []adapter.Endpoint {
m.access.Lock()
defer m.access.Unlock()
return m.endpoints
}
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
m.access.Lock()
defer m.access.Unlock()
endpoint, found := m.endpointByTag[tag]
return endpoint, found
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
endpoint, found := m.endpointByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.endpointByTag, tag)
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == endpoint
})
if index == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return endpoint.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(endpoint, stage)
if err != nil {
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
}
}
}
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
if m.started {
err = existsEndpoint.Close()
if err != nil {
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
}
}
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
return it == existsEndpoint
})
if existsIndex == -1 {
panic("invalid endpoint index")
}
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
}
m.endpoints = append(m.endpoints, endpoint)
m.endpointByTag[tag] = endpoint
return nil
}

View File

@@ -1,72 +0,0 @@
package endpoint
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.EndpointRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
m.access.Lock()
defer m.access.Unlock()
constructor, loaded := m.constructor[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

View File

@@ -4,28 +4,28 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/binary" "encoding/binary"
"net"
"time" "time"
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/varbin" "github.com/sagernet/sing/common/varbin"
) )
type ClashServer interface { type ClashServer interface {
LifecycleService Service
ConnectionTracker PreStarter
Mode() string Mode() string
ModeList() []string ModeList() []string
HistoryStorage() *urltest.HistoryStorage HistoryStorage() *urltest.HistoryStorage
} RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
type V2RayServer interface {
LifecycleService
StatsService() ConnectionTracker
} }
type CacheFile interface { type CacheFile interface {
LifecycleService Service
PreStarter
StoreFakeIP() bool StoreFakeIP() bool
FakeIPStorage FakeIPStorage
@@ -39,17 +39,17 @@ type CacheFile interface {
StoreSelected(group string, selected string) error StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool) LoadGroupExpand(group string) (isExpand bool, loaded bool)
StoreGroupExpand(group string, expand bool) error StoreGroupExpand(group string, expand bool) error
LoadRuleSet(tag string) *SavedBinary LoadRuleSet(tag string) *SavedRuleSet
SaveRuleSet(tag string, set *SavedBinary) error SaveRuleSet(tag string, set *SavedRuleSet) error
} }
type SavedBinary struct { type SavedRuleSet struct {
Content []byte Content []byte
LastUpdated time.Time LastUpdated time.Time
LastEtag string LastEtag string
} }
func (s *SavedBinary) MarshalBinary() ([]byte, error) { func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1)) err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil { if err != nil {
@@ -70,7 +70,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
return buffer.Bytes(), nil return buffer.Bytes(), nil
} }
func (s *SavedBinary) UnmarshalBinary(data []byte) error { func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data) reader := bytes.NewReader(data)
var version uint8 var version uint8
err := binary.Read(reader, binary.BigEndian, &version) err := binary.Read(reader, binary.BigEndian, &version)
@@ -94,6 +94,10 @@ func (s *SavedBinary) UnmarshalBinary(data []byte) error {
return nil return nil
} }
type Tracker interface {
Leave()
}
type OutboundGroup interface { type OutboundGroup interface {
Outbound Outbound
Now() string Now() string
@@ -111,3 +115,13 @@ func OutboundTag(detour Outbound) string {
} }
return detour.Tag() return detour.Tag()
} }
type V2RayServer interface {
Service
StatsService() V2RayStatsService
}
type V2RayStatsService interface {
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
}

View File

@@ -6,56 +6,27 @@ import (
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
// Deprecated
type ConnectionHandler interface { type ConnectionHandler interface {
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
} }
type ConnectionHandlerEx interface {
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use PacketHandlerEx instead
type PacketHandler interface { type PacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
} }
type PacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
}
// Deprecated: use OOBPacketHandlerEx instead
type OOBPacketHandler interface { type OOBPacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
} }
type OOBPacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
}
// Deprecated
type PacketConnectionHandler interface { type PacketConnectionHandler interface {
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
} }
type PacketConnectionHandlerEx interface {
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use TCPConnectionHandlerEx instead
//
//nolint:staticcheck
type UpstreamHandlerAdapter interface { type UpstreamHandlerAdapter interface {
N.TCPConnectionHandler N.TCPConnectionHandler
N.UDPConnectionHandler N.UDPConnectionHandler
E.Handler E.Handler
} }
type UpstreamHandlerAdapterEx interface {
N.TCPConnectionHandlerEx
N.UDPConnectionHandlerEx
}

View File

@@ -2,43 +2,26 @@ package adapter
import ( import (
"context" "context"
"net"
"net/netip" "net/netip"
"time"
"github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/process"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
) )
type Inbound interface { type Inbound interface {
Lifecycle Service
Type() string Type() string
Tag() string Tag() string
} }
type TCPInjectableInbound interface { type InjectableInbound interface {
Inbound Inbound
ConnectionHandlerEx Network() []string
} NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
type UDPInjectableInbound interface {
Inbound
PacketConnectionHandlerEx
}
type InboundRegistry interface {
option.InboundOptionsRegistry
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 { type InboundContext struct {
@@ -60,25 +43,10 @@ type InboundContext struct {
// cache // cache
// Deprecated: implement in rule action InboundDetour string
InboundDetour string LastInbound string
LastInbound string OriginDestination M.Socksaddr
OriginDestination M.Socksaddr InboundOptions option.InboundOptions
RouteOriginalDestination M.Socksaddr
// Deprecated: to be removed
//nolint:staticcheck
InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool
UDPConnect bool
UDPTimeout time.Duration
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
DNSServer string
DestinationAddresses []netip.Addr DestinationAddresses []netip.Addr
SourceGeoIPCode string SourceGeoIPCode string
GeoIPCode string GeoIPCode string

View File

@@ -1,21 +0,0 @@
package inbound
type Adapter struct {
inboundType string
inboundTag string
}
func NewAdapter(inboundType string, inboundTag string) Adapter {
return Adapter{
inboundType: inboundType,
inboundTag: inboundTag,
}
}
func (a *Adapter) Type() string {
return a.inboundType
}
func (a *Adapter) Tag() string {
return a.inboundTag
}

View File

@@ -1,148 +0,0 @@
package inbound
import (
"context"
"os"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ adapter.InboundManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.InboundRegistry
endpoint adapter.EndpointManager
access sync.Mutex
started bool
stage adapter.StartStage
inbounds []adapter.Inbound
inboundByTag map[string]adapter.Inbound
}
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
inboundByTag: make(map[string]adapter.Inbound),
}
}
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
defer m.access.Unlock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
for _, inbound := range m.inbounds {
err := adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
}
return nil
}
func (m *Manager) Close() error {
m.access.Lock()
defer m.access.Unlock()
if !m.started {
return nil
}
m.started = false
inbounds := m.inbounds
m.inbounds = nil
monitor := taskmonitor.New(m.logger, C.StopTimeout)
var err error
for _, inbound := range inbounds {
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
err = E.Append(err, inbound.Close(), func(err error) error {
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
})
monitor.Finish()
}
return nil
}
func (m *Manager) Inbounds() []adapter.Inbound {
m.access.Lock()
defer m.access.Unlock()
return m.inbounds
}
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
m.access.Lock()
inbound, found := m.inboundByTag[tag]
m.access.Unlock()
if found {
return inbound, true
}
return m.endpoint.Get(tag)
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
inbound, found := m.inboundByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.inboundByTag, tag)
index := common.Index(m.inbounds, func(it adapter.Inbound) bool {
return it == inbound
})
if index == -1 {
panic("invalid inbound index")
}
m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)
started := m.started
m.access.Unlock()
if started {
return inbound.Close()
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
}
}
}
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
if m.started {
err = existsInbound.Close()
if err != nil {
return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]")
}
}
existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {
return it == existsInbound
})
if existsIndex == -1 {
panic("invalid inbound index")
}
m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)
}
m.inbounds = append(m.inbounds, inbound)
m.inboundByTag[tag] = inbound
return nil
}

View File

@@ -1,72 +0,0 @@
package inbound
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.InboundRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructor map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructor: make(map[string]constructorFunc),
}
}
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
m.access.Lock()
defer m.access.Unlock()
optionsConstructor, loaded := m.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.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 (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
m.access.Lock()
defer m.access.Unlock()
m.optionsType[outboundType] = optionsConstructor
m.constructor[outboundType] = constructor
}

View File

@@ -1,64 +0,0 @@
package adapter
import E "github.com/sagernet/sing/common/exceptions"
type StartStage uint8
const (
StartStateInitialize StartStage = iota
StartStateStart
StartStatePostStart
StartStateStarted
)
var ListStartStages = []StartStage{
StartStateInitialize,
StartStateStart,
StartStatePostStart,
StartStateStarted,
}
func (s StartStage) String() string {
switch s {
case StartStateInitialize:
return "initialize"
case StartStateStart:
return "start"
case StartStatePostStart:
return "post-start"
case StartStateStarted:
return "finish-start"
default:
panic("unknown stage")
}
}
type Lifecycle interface {
Start(stage StartStage) error
Close() error
}
type LifecycleService interface {
Name() string
Lifecycle
}
func Start(stage StartStage, services ...Lifecycle) error {
for _, service := range services {
err := service.Start(stage)
if err != nil {
return err
}
}
return nil
}
func StartNamed(stage StartStage, services []LifecycleService) error {
for _, service := range services {
err := service.Start(stage)
if err != nil {
return E.Cause(err, stage.String(), " ", service.Name())
}
}
return nil
}

View File

@@ -1,52 +0,0 @@
package adapter
func LegacyStart(starter any, stage StartStage) error {
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
return lifecycle.Start(stage)
}
switch stage {
case StartStateInitialize:
if preStarter, isPreStarter := starter.(interface {
PreStart() error
}); isPreStarter {
return preStarter.PreStart()
}
case StartStateStart:
if starter, isStarter := starter.(interface {
Start() error
}); isStarter {
return starter.Start()
}
case StartStateStarted:
if postStarter, isPostStarter := starter.(interface {
PostStart() error
}); isPostStarter {
return postStarter.PostStart()
}
}
return nil
}
type lifecycleServiceWrapper struct {
Service
name string
}
func NewLifecycleService(service Service, name string) LifecycleService {
return &lifecycleServiceWrapper{
Service: service,
name: name,
}
}
func (l *lifecycleServiceWrapper) Name() string {
return l.name
}
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
return LegacyStart(l.Service, stage)
}
func (l *lifecycleServiceWrapper) Close() error {
return l.Service.Close()
}

View File

@@ -1,54 +0,0 @@
package adapter
import (
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
)
type NetworkManager interface {
Lifecycle
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultNetworkInterface() *NetworkInterface
NetworkInterfaces() []NetworkInterface
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
ProtectFunc() control.Func
DefaultOptions() NetworkOptions
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
WIFIState() WIFIState
ResetNetwork()
}
type NetworkOptions struct {
NetworkStrategy *C.NetworkStrategy
NetworkType []C.InterfaceType
FallbackNetworkType []C.InterfaceType
FallbackDelay time.Duration
BindInterface string
RoutingMark uint32
}
type InterfaceUpdateListener interface {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}
type NetworkInterface struct {
control.Interface
Type C.InterfaceType
DNSServers []string
Expensive bool
Constrained bool
}

View File

@@ -2,9 +2,8 @@ package adapter
import ( import (
"context" "context"
"net"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@@ -16,18 +15,6 @@ type Outbound interface {
Network() []string Network() []string
Dependencies() []string Dependencies() []string
N.Dialer N.Dialer
} NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
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

@@ -1,45 +0,0 @@
package outbound
import (
"github.com/sagernet/sing-box/option"
)
type Adapter struct {
outboundType string
outboundTag string
network []string
dependencies []string
}
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
return Adapter{
outboundType: outboundType,
outboundTag: outboundTag,
network: network,
dependencies: dependencies,
}
}
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
var dependencies []string
if dialOptions.Detour != "" {
dependencies = []string{dialOptions.Detour}
}
return NewAdapter(outboundType, outboundTag, network, dependencies)
}
func (a *Adapter) Type() string {
return a.outboundType
}
func (a *Adapter) Tag() string {
return a.outboundTag
}
func (a *Adapter) Network() []string {
return a.network
}
func (a *Adapter) Dependencies() []string {
return a.dependencies
}

View File

@@ -1,288 +0,0 @@
package outbound
import (
"context"
"io"
"os"
"strings"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
)
var _ adapter.OutboundManager = (*Manager)(nil)
type Manager struct {
logger log.ContextLogger
registry adapter.OutboundRegistry
endpoint adapter.EndpointManager
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, endpoint adapter.EndpointManager, defaultTag string) *Manager {
return &Manager{
logger: logger,
registry: registry,
endpoint: endpoint,
defaultTag: defaultTag,
outboundByTag: make(map[string]adapter.Outbound),
dependByTag: make(map[string][]string),
}
}
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 {
if m.defaultTag != "" && m.defaultOutbound == nil {
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
if !loaded {
return E.New("default outbound not found: ", m.defaultTag)
}
m.defaultOutbound = defaultEndpoint
}
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
} else {
for _, outbound := range outbounds {
err := adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " 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.(adapter.Lifecycle); isStarter {
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start(adapter.StartStateStart)
monitor.Finish()
if err != nil {
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
} else if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
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()
outbound, found := m.outboundByTag[tag]
m.access.Unlock()
if found {
return outbound, true
}
return m.endpoint.Get(tag)
}
func (m *Manager) Default() adapter.Outbound {
m.access.Lock()
defer m.access.Unlock()
if m.defaultOutbound != nil {
return m.defaultOutbound
} else {
return m.defaultOutboundFallback
}
}
func (m *Manager) Remove(tag string) error {
m.access.Lock()
outbound, found := m.outboundByTag[tag]
if !found {
m.access.Unlock()
return os.ErrInvalid
}
delete(m.outboundByTag, tag)
index := common.Index(m.outbounds, func(it adapter.Outbound) bool {
return it == outbound
})
if index == -1 {
panic("invalid inbound index")
}
m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)
started := m.started
if m.defaultOutbound == outbound {
if len(m.outbounds) > 0 {
m.defaultOutbound = m.outbounds[0]
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
} else {
m.defaultOutbound = nil
}
}
dependBy := m.dependByTag[tag]
if len(dependBy) > 0 {
return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", "))
}
dependencies := outbound.Dependencies()
for _, dependency := range dependencies {
if len(m.dependByTag[dependency]) == 1 {
delete(m.dependByTag, dependency)
} else {
m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {
return it != tag
})
}
}
m.access.Unlock()
if started {
return common.Close(outbound)
}
return nil
}
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {
if tag == "" {
return os.ErrInvalid
}
outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)
if err != nil {
return err
}
m.access.Lock()
defer m.access.Unlock()
if m.started {
for _, stage := range adapter.ListStartStages {
err = adapter.LegacyStart(outbound, stage)
if err != nil {
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
}
}
}
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
if m.started {
err = common.Close(existsOutbound)
if err != nil {
return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]")
}
}
existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {
return it == existsOutbound
})
if existsIndex == -1 {
panic("invalid inbound index")
}
m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)
}
m.outbounds = append(m.outbounds, outbound)
m.outboundByTag[tag] = outbound
dependencies := outbound.Dependencies()
for _, dependency := range dependencies {
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
}
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
m.defaultOutbound = outbound
if m.started {
m.logger.Info("updated default outbound to ", outbound.Tag())
}
}
return nil
}

View File

@@ -1,72 +0,0 @@
package outbound
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
var options *Options
if rawOptions != nil {
options = rawOptions.(*Options)
}
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
})
}
var _ adapter.OutboundRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructors: make(map[string]constructorFunc),
}
}
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[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
}

View File

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

View File

@@ -10,54 +10,94 @@ import (
"github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geoip"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
mdns "github.com/miekg/dns" mdns "github.com/miekg/dns"
"go4.org/netipx" "go4.org/netipx"
) )
type Router interface { type Router interface {
Lifecycle Service
PreStarter
PostStarter
Cleanup() error
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) (Outbound, error)
FakeIPStore() FakeIPStore FakeIPStore() FakeIPStore
ConnectionRouter ConnectionRouter
PreMatch(metadata InboundContext) error
ConnectionRouterEx
GeoIPReader() *geoip.Reader GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error) LoadGeosite(code string) (Rule, error)
RuleSet(tag string) (RuleSet, bool) RuleSet(tag string) (RuleSet, bool)
NeedWIFIState() bool NeedWIFIState() bool
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
ClearDNSCache() ClearDNSCache()
InterfaceFinder() control.InterfaceFinder
UpdateInterfaces() error
DefaultInterface() string
AutoDetectInterface() bool
AutoDetectInterfaceFunc() control.Func
DefaultMark() uint32
RegisterAutoRedirectOutputMark(mark uint32) error
AutoRedirectOutputMark() uint32
NetworkMonitor() tun.NetworkUpdateMonitor
InterfaceMonitor() tun.DefaultInterfaceMonitor
PackageManager() tun.PackageManager
WIFIState() WIFIState
Rules() []Rule Rules() []Rule
SetTracker(tracker ConnectionTracker) ClashServer() ClashServer
SetClashServer(server ClashServer)
ResetNetwork() V2RayServer() V2RayServer
SetV2RayServer(server V2RayServer)
ResetNetwork() error
} }
type ConnectionTracker interface { func ContextWithRouter(ctx context.Context, router Router) context.Context {
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn return service.ContextWith(ctx, router)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
} }
// Deprecated: Use ConnectionRouterEx instead. func RouterFromContext(ctx context.Context) Router {
type ConnectionRouter interface { return service.FromContext[Router](ctx)
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
} }
type ConnectionRouterEx interface { type HeadlessRule interface {
ConnectionRouter Match(metadata *InboundContext) bool
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) String() string
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) }
type Rule interface {
HeadlessRule
Service
Type() string
UpdateGeosite() error
Outbound() string
}
type DNSRule interface {
Rule
DisableCache() bool
RewriteTTL() *uint32
ClientSubnet() *netip.Prefix
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
} }
type RuleSet interface { type RuleSet interface {
@@ -119,3 +159,12 @@ func (c *HTTPStartContext) Close() {
client.CloseIdleConnections() client.CloseIdleConnections()
} }
} }
type InterfaceUpdateListener interface {
InterfaceUpdated()
}
type WIFIState struct {
SSID string
BSSID string
}

View File

@@ -1,38 +0,0 @@
package adapter
import (
C "github.com/sagernet/sing-box/constant"
)
type HeadlessRule interface {
Match(metadata *InboundContext) bool
String() string
}
type Rule interface {
HeadlessRule
Service
Type() string
UpdateGeosite() error
Action() RuleAction
}
type DNSRule interface {
Rule
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
}
type RuleAction interface {
Type() string
String() string
}
func IsFinalAction(action RuleAction) bool {
switch action.Type() {
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
return false
default:
return true
}
}

View File

@@ -4,165 +4,112 @@ import (
"context" "context"
"net" "net"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
type ( type (
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc) ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc) PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
) )
func NewUpstreamHandlerEx( func NewUpstreamHandler(
metadata InboundContext, metadata InboundContext,
connectionHandler ConnectionHandlerFuncEx, connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFuncEx, packetHandler PacketConnectionHandlerFunc,
) UpstreamHandlerAdapterEx { errorHandler E.Handler,
return &myUpstreamHandlerWrapperEx{ ) UpstreamHandlerAdapter {
return &myUpstreamHandlerWrapper{
metadata: metadata, metadata: metadata,
connectionHandler: connectionHandler, connectionHandler: connectionHandler,
packetHandler: packetHandler, packetHandler: packetHandler,
errorHandler: errorHandler,
} }
} }
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil) var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
type myUpstreamHandlerWrapperEx struct { type myUpstreamHandlerWrapper struct {
metadata InboundContext metadata InboundContext
connectionHandler ConnectionHandlerFuncEx connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFuncEx packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
} }
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata myMetadata := w.metadata
if source.IsValid() { if metadata.Source.IsValid() {
myMetadata.Source = source myMetadata.Source = metadata.Source
} }
if destination.IsValid() { if metadata.Destination.IsValid() {
myMetadata.Destination = destination myMetadata.Destination = metadata.Destination
} }
w.connectionHandler(ctx, conn, myMetadata, onClose) return w.connectionHandler(ctx, conn, myMetadata)
} }
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata myMetadata := w.metadata
if source.IsValid() { if metadata.Source.IsValid() {
myMetadata.Source = source myMetadata.Source = metadata.Source
} }
if destination.IsValid() { if metadata.Destination.IsValid() {
myMetadata.Destination = destination myMetadata.Destination = metadata.Destination
} }
w.packetHandler(ctx, conn, myMetadata, onClose) return w.packetHandler(ctx, conn, myMetadata)
} }
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil) func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
type myUpstreamContextHandlerWrapperEx struct {
connectionHandler ConnectionHandlerFuncEx
packetHandler PacketConnectionHandlerFuncEx
} }
func NewUpstreamContextHandlerEx( func UpstreamMetadata(metadata InboundContext) M.Metadata {
connectionHandler ConnectionHandlerFuncEx, return M.Metadata{
packetHandler PacketConnectionHandlerFuncEx, Source: metadata.Source,
) UpstreamHandlerAdapterEx { Destination: metadata.Destination,
return &myUpstreamContextHandlerWrapperEx{ }
}
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
connectionHandler: connectionHandler, connectionHandler: connectionHandler,
packetHandler: packetHandler, packetHandler: packetHandler,
errorHandler: errorHandler,
} }
} }
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx) myMetadata := ContextFrom(ctx)
if source.IsValid() { if metadata.Source.IsValid() {
myMetadata.Source = source myMetadata.Source = metadata.Source
} }
if destination.IsValid() { if metadata.Destination.IsValid() {
myMetadata.Destination = destination myMetadata.Destination = metadata.Destination
} }
w.connectionHandler(ctx, conn, *myMetadata, onClose) return w.connectionHandler(ctx, conn, *myMetadata)
} }
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx) myMetadata := ContextFrom(ctx)
if source.IsValid() { if metadata.Source.IsValid() {
myMetadata.Source = source myMetadata.Source = metadata.Source
} }
if destination.IsValid() { if metadata.Destination.IsValid() {
myMetadata.Destination = destination myMetadata.Destination = metadata.Destination
} }
w.packetHandler(ctx, conn, *myMetadata, onClose) return w.packetHandler(ctx, conn, *myMetadata)
} }
func NewRouteHandlerEx( func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
metadata InboundContext, w.errorHandler.NewError(ctx, err)
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeHandlerWrapperEx{
metadata: metadata,
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
type routeHandlerWrapperEx struct {
metadata InboundContext
router ConnectionRouterEx
}
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
}
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
}
func NewRouteContextHandlerEx(
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeContextHandlerWrapperEx{
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
type routeContextHandlerWrapperEx struct {
router ConnectionRouterEx
}
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
}
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
} }

View File

@@ -1,234 +0,0 @@
package adapter
import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type (
// Deprecated
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
// Deprecated
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
)
// Deprecated
//
//nolint:staticcheck
func NewUpstreamHandler(
metadata InboundContext,
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamHandlerWrapper{
metadata: metadata,
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
// Deprecated: use myUpstreamHandlerWrapperEx instead.
//
//nolint:staticcheck
type myUpstreamHandlerWrapper struct {
metadata InboundContext
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, myMetadata)
}
// Deprecated: use myUpstreamHandlerWrapperEx instead.
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: removed
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source,
Destination: metadata.Destination,
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, *myMetadata)
}
// Deprecated: Use NewUpstreamContextHandlerEx instead.
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
//
//nolint:staticcheck
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
// Deprecated: Use ConnectionRouterEx instead.
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"net" "net"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@@ -15,7 +16,8 @@ type V2RayServerTransport interface {
} }
type V2RayServerTransportHandler interface { type V2RayServerTransportHandler interface {
N.TCPConnectionHandlerEx N.TCPConnectionHandler
E.Handler
} }
type V2RayClientTransport interface { type V2RayClientTransport interface {

428
box.go
View File

@@ -9,24 +9,19 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/route" "github.com/sagernet/sing-box/route"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/pause" "github.com/sagernet/sing/service/pause"
) )
@@ -34,49 +29,25 @@ import (
var _ adapter.Service = (*Box)(nil) var _ adapter.Service = (*Box)(nil)
type Box struct { type Box struct {
createdAt time.Time createdAt time.Time
logFactory log.Factory router adapter.Router
logger log.ContextLogger inbounds []adapter.Inbound
network *route.NetworkManager outbounds []adapter.Outbound
endpoint *endpoint.Manager logFactory log.Factory
inbound *inbound.Manager logger log.ContextLogger
outbound *outbound.Manager preServices1 map[string]adapter.Service
connection *route.ConnectionManager preServices2 map[string]adapter.Service
router *route.Router postServices map[string]adapter.Service
services []adapter.LifecycleService done chan struct{}
done chan struct{}
} }
type Options struct { type Options struct {
option.Options option.Options
Context context.Context Context context.Context
PlatformInterface platform.Interface
PlatformLogWriter log.PlatformWriter PlatformLogWriter log.PlatformWriter
} }
func Context(
ctx context.Context,
inboundRegistry adapter.InboundRegistry,
outboundRegistry adapter.OutboundRegistry,
endpointRegistry adapter.EndpointRegistry,
) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
}
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
}
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
}
return ctx
}
func New(options Options) (*Box, error) { func New(options Options) (*Box, error) {
createdAt := time.Now() createdAt := time.Now()
ctx := options.Context ctx := options.Context
@@ -84,21 +55,6 @@ func New(options Options) (*Box, error) {
ctx = context.Background() ctx = context.Background()
} }
ctx = service.ContextWithDefaultRegistry(ctx) ctx = service.ContextWithDefaultRegistry(ctx)
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
if endpointRegistry == nil {
return nil, E.New("missing endpoint registry in context")
}
if inboundRegistry == nil {
return nil, E.New("missing inbound registry in context")
}
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
ctx = pause.WithDefaultManager(ctx) ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental) experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
@@ -114,9 +70,8 @@ func New(options Options) (*Box, error) {
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true needV2RayAPI = true
} }
platformInterface := service.FromContext[platform.Interface](ctx)
var defaultLogWriter io.Writer var defaultLogWriter io.Writer
if platformInterface != nil { if options.PlatformInterface != nil {
defaultLogWriter = io.Discard defaultLogWriter = io.Discard
} }
logFactory, err := log.New(log.Options{ logFactory, err := log.New(log.Options{
@@ -130,167 +85,115 @@ func New(options Options) (*Box, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "create log factory") return nil, E.Cause(err, "create log factory")
} }
router, err := route.NewRouter(
routeOptions := common.PtrValueOrDefault(options.Route) ctx,
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) logFactory,
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) common.PtrValueOrDefault(options.Route),
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) common.PtrValueOrDefault(options.DNS),
service.MustRegister[adapter.EndpointManager](ctx, endpointManager) common.PtrValueOrDefault(options.NTP),
service.MustRegister[adapter.InboundManager](ctx, inboundManager) options.Inbounds,
service.MustRegister[adapter.OutboundManager](ctx, outboundManager) options.PlatformInterface,
)
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize network manager") return nil, E.Cause(err, "parse route options")
}
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection"))
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
router, err := route.NewRouter(ctx, logFactory, routeOptions, common.PtrValueOrDefault(options.DNS))
if err != nil {
return nil, E.Cause(err, "initialize router")
}
ntpOptions := common.PtrValueOrDefault(options.NTP)
var timeService *tls.TimeServiceWrapper
if ntpOptions.Enabled {
timeService = new(tls.TimeServiceWrapper)
service.MustRegister[ntp.TimeService](ctx, timeService)
}
for i, endpointOptions := range options.Endpoints {
var tag string
if endpointOptions.Tag != "" {
tag = endpointOptions.Tag
} else {
tag = F.ToString(i)
}
err = endpointManager.Create(ctx,
router,
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
tag,
endpointOptions.Type,
endpointOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "initialize inbound[", i, "]")
}
} }
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
for i, inboundOptions := range options.Inbounds { for i, inboundOptions := range options.Inbounds {
var in adapter.Inbound
var tag string var tag string
if inboundOptions.Tag != "" { if inboundOptions.Tag != "" {
tag = inboundOptions.Tag tag = inboundOptions.Tag
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
err = inboundManager.Create(ctx, in, err = inbound.New(
ctx,
router, router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
tag, tag,
inboundOptions.Type, inboundOptions,
inboundOptions.Options, options.PlatformInterface,
) )
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize inbound[", i, "]") return nil, E.Cause(err, "parse inbound[", i, "]")
} }
inbounds = append(inbounds, in)
} }
for i, outboundOptions := range options.Outbounds { for i, outboundOptions := range options.Outbounds {
var out adapter.Outbound
var tag string var tag string
if outboundOptions.Tag != "" { if outboundOptions.Tag != "" {
tag = outboundOptions.Tag tag = outboundOptions.Tag
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
outboundCtx := ctx out, err = outbound.New(
if tag != "" { ctx,
// TODO: remove this
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
Outbound: tag,
})
}
err = outboundManager.Create(
outboundCtx,
router, router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag, tag,
outboundOptions.Type, outboundOptions)
outboundOptions.Options,
)
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize outbound[", i, "]") return nil, E.Cause(err, "parse outbound[", i, "]")
} }
outbounds = append(outbounds, out)
} }
outboundManager.Initialize(common.Must1( err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
direct.NewOutbound( out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
ctx, common.Must(oErr)
router, outbounds = append(outbounds, out)
logFactory.NewLogger("outbound/direct"), return out
"direct", })
option.DirectOutboundOptions{}, if err != nil {
), return nil, err
)) }
if platformInterface != nil { if options.PlatformInterface != nil {
err = platformInterface.Initialize(networkManager) err = options.PlatformInterface.Initialize(ctx, router)
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize platform interface") return nil, E.Cause(err, "initialize platform interface")
} }
} }
var services []adapter.LifecycleService preServices1 := make(map[string]adapter.Service)
preServices2 := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needCacheFile { if needCacheFile {
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile)) cacheFile := service.FromContext[adapter.CacheFile](ctx)
service.MustRegister[adapter.CacheFile](ctx, cacheFile) if cacheFile == nil {
services = append(services, cacheFile) cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
}
preServices1["cache file"] = cacheFile
} }
if needClashAPI { if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
if err != nil { if err != nil {
return nil, E.Cause(err, "create clash-server") return nil, E.Cause(err, "create clash api server")
} }
router.SetTracker(clashServer) router.SetClashServer(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer) preServices2["clash api"] = clashServer
services = append(services, clashServer)
} }
if needV2RayAPI { if needV2RayAPI {
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
if err != nil { if err != nil {
return nil, E.Cause(err, "create v2ray-server") return nil, E.Cause(err, "create v2ray api server")
} }
if v2rayServer.StatsService() != nil { router.SetV2RayServer(v2rayServer)
router.SetTracker(v2rayServer.StatsService()) preServices2["v2ray api"] = v2rayServer
services = append(services, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
}
if ntpOptions.Enabled {
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions)
if err != nil {
return nil, E.Cause(err, "create NTP service")
}
ntpService := ntp.NewService(ntp.Options{
Context: ctx,
Dialer: ntpDialer,
Logger: logFactory.NewLogger("ntp"),
Server: ntpOptions.ServerOptions.Build(),
Interval: time.Duration(ntpOptions.Interval),
WriteToSystem: ntpOptions.WriteToSystem,
})
timeService.TimeService = ntpService
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
} }
return &Box{ return &Box{
network: networkManager, router: router,
endpoint: endpointManager, inbounds: inbounds,
inbound: inboundManager, outbounds: outbounds,
outbound: outboundManager, createdAt: createdAt,
connection: connectionManager, logFactory: logFactory,
router: router, logger: logFactory.Logger(),
createdAt: createdAt, preServices1: preServices1,
logFactory: logFactory, preServices2: preServices2,
logger: logFactory.Logger(), postServices: postServices,
services: services, done: make(chan struct{}),
done: make(chan struct{}),
}, nil }, nil
} }
@@ -340,19 +243,35 @@ func (s *Box) preStart() error {
if err != nil { if err != nil {
return E.Cause(err, "start logger") return E.Cause(err, "start logger")
} }
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api for serviceName, service := range s.preServices1 {
if preService, isPreService := service.(adapter.PreStarter); isPreService {
monitor.Start("pre-start ", serviceName)
err := preService.PreStart()
monitor.Finish()
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
}
}
}
for serviceName, service := range s.preServices2 {
if preService, isPreService := service.(adapter.PreStarter); isPreService {
monitor.Start("pre-start ", serviceName)
err := preService.PreStart()
monitor.Finish()
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
}
}
}
err = s.router.PreStart()
if err != nil {
return E.Cause(err, "pre-start router")
}
err = s.startOutbounds()
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateInitialize, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint) return s.router.Start()
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.outbound, s.network, s.connection, s.router)
if err != nil {
return err
}
return nil
} }
func (s *Box) start() error { func (s *Box) start() error {
@@ -360,33 +279,64 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
err = adapter.StartNamed(adapter.StartStateStart, s.services) for serviceName, service := range s.preServices1 {
err = service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for serviceName, service := range s.preServices2 {
err = service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for i, in := range s.inbounds {
var tag string
if in.Tag() == "" {
tag = F.ToString(i)
} else {
tag = in.Tag()
}
err = in.Start()
if err != nil {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
err = s.postStart()
if err != nil { if err != nil {
return err return err
} }
err = s.inbound.Start(adapter.StartStateStart) return s.router.Cleanup()
}
func (s *Box) postStart() error {
for serviceName, service := range s.postServices {
err := service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
// TODO: reorganize ALL start order
for _, out := range s.outbounds {
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
err := lateOutbound.PostStart()
if err != nil {
return E.Cause(err, "post-start outbound/", out.Tag())
}
}
}
err := s.router.PostStart()
if err != nil { if err != nil {
return err return err
} }
err = adapter.Start(adapter.StartStateStart, s.endpoint) for _, in := range s.inbounds {
if err != nil { if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
return err err = lateInbound.PostStart()
} if err != nil {
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.connection, s.router, s.inbound, s.endpoint) return E.Cause(err, "post-start inbound/", in.Tag())
if err != nil { }
return err }
}
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStarted, s.network, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
if err != nil {
return err
} }
return nil return nil
} }
@@ -398,32 +348,58 @@ func (s *Box) Close() error {
default: default:
close(s.done) close(s.done)
} }
err := common.Close( monitor := taskmonitor.New(s.logger, C.StopTimeout)
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.network, var errors error
) for serviceName, service := range s.postServices {
for _, lifecycleService := range s.services { monitor.Start("close ", serviceName)
err = E.Append(err, lifecycleService.Close(), func(err error) error { errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", lifecycleService.Name()) return E.Cause(err, "close ", serviceName)
})
monitor.Finish()
}
for i, in := range s.inbounds {
monitor.Start("close inbound/", in.Type(), "[", i, "]")
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
})
monitor.Finish()
}
for i, out := range s.outbounds {
monitor.Start("close outbound/", out.Type(), "[", i, "]")
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
})
monitor.Finish()
}
monitor.Start("close router")
if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router")
}) })
} }
err = E.Append(err, s.logFactory.Close(), func(err error) error { monitor.Finish()
return E.Cause(err, "close logger") for serviceName, service := range s.preServices1 {
}) monitor.Start("close ", serviceName)
return err errors = E.Append(errors, service.Close(), func(err error) error {
} return E.Cause(err, "close ", serviceName)
})
func (s *Box) Network() adapter.NetworkManager { monitor.Finish()
return s.network }
for serviceName, service := range s.preServices2 {
monitor.Start("close ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
monitor.Finish()
}
if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close logger")
})
}
return errors
} }
func (s *Box) Router() adapter.Router { func (s *Box) Router() adapter.Router {
return s.router return s.router
} }
func (s *Box) Inbound() adapter.InboundManager {
return s.inbound
}
func (s *Box) Outbound() adapter.OutboundManager {
return s.outbound
}

85
box_outbound.go Normal file
View File

@@ -0,0 +1,85 @@
package box
import (
"strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
)
func (s *Box) startOutbounds() error {
monitor := taskmonitor.New(s.logger, C.StartTimeout)
outboundTags := make(map[adapter.Outbound]string)
outbounds := make(map[string]adapter.Outbound)
for i, outboundToStart := range s.outbounds {
var outboundTag string
if outboundToStart.Tag() == "" {
outboundTag = F.ToString(i)
} else {
outboundTag = outboundToStart.Tag()
}
if _, exists := outbounds[outboundTag]; exists {
return E.New("outbound tag ", outboundTag, " duplicated")
}
outboundTags[outboundToStart] = outboundTag
outbounds[outboundTag] = outboundToStart
}
started := make(map[string]bool)
for {
canContinue := false
startOne:
for _, outboundToStart := range s.outbounds {
outboundTag := outboundTags[outboundToStart]
if started[outboundTag] {
continue
}
dependencies := outboundToStart.Dependencies()
for _, dependency := range dependencies {
if !started[dependency] {
continue startOne
}
}
started[outboundTag] = true
canContinue = true
if starter, isStarter := outboundToStart.(interface {
Start() error
}); isStarter {
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
}
}
}
if len(started) == len(s.outbounds) {
break
}
if canContinue {
continue
}
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
return !started[outboundTags[it]]
})
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
return !started[it]
})
if common.Contains(oTree, problemOutboundTag) {
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
}
problemOutbound := outbounds[problemOutboundTag]
if problemOutbound == nil {
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
}
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
}
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
}
return nil
}

View File

@@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/sagernet/asc-go/asc" "github.com/sagernet/asc-go/asc"
@@ -195,10 +194,6 @@ func publishTestflight(ctx context.Context) error {
log.Info(string(platform), " ", tag, " create submission") log.Info(string(platform), " ", tag, " create submission")
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID) _, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
log.Error(err)
break
}
return err return err
} }
} }
@@ -364,7 +359,7 @@ func prepareAppStore(ctx context.Context) error {
if localization.ID == "" { if localization.ID == "" {
log.Info(string(platform), " ", tag, " no en-US localization found") log.Info(string(platform), " ", tag, " no en-US localization found")
} }
if localization.Attributes == nil || localization.Attributes.WhatsNew == nil || *localization.Attributes.WhatsNew == "" { if localization.Attributes.WhatsNew == nil && *localization.Attributes.WhatsNew == "" {
log.Info(string(platform), " ", tag, " update localization") log.Info(string(platform), " ", tag, " update localization")
_, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{ _, _, err = client.Apps.UpdateAppStoreVersionLocalization(ctx, localization.ID, &asc.AppStoreVersionLocalizationUpdateRequestAttributes{
PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."), PromotionalText: common.Ptr("Yet another distribution for sing-box, the universal proxy platform."),
@@ -383,14 +378,16 @@ func prepareAppStore(ctx context.Context) error {
case http.StatusInternalServerError: case http.StatusInternalServerError:
continue continue
default: default:
return err response.Write(os.Stderr)
log.Info(string(platform), " ", tag, " unexpected response: ", response.Status)
} }
} }
switch response.StatusCode { switch response.StatusCode {
case http.StatusCreated: case http.StatusCreated:
break fixSubmit break fixSubmit
default: default:
return err response.Write(os.Stderr)
log.Info(string(platform), " ", tag, " unexpected response: ", response.Status)
} }
} }
} }

View File

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

View File

@@ -7,9 +7,8 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include" _ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager" "github.com/sagernet/sing/service/filemanager"
@@ -69,5 +68,4 @@ func preRun(cmd *cobra.Command, args []string) {
configPaths = append(configPaths, "config.json") configPaths = append(configPaths, "config.json")
} }
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry())
} }

View File

@@ -38,7 +38,7 @@ func format() error {
return err return err
} }
for _, optionsEntry := range optionsList { for _, optionsEntry := range optionsList {
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options) optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -30,7 +30,7 @@ func init() {
} }
func generateTLSKeyPair(serverName string) error { func generateTLSKeyPair(serverName string) error {
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0)) privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
if err != nil { if err != nil {
return err return err
} }

View File

@@ -18,7 +18,7 @@ import (
) )
var commandMerge = &cobra.Command{ var commandMerge = &cobra.Command{
Use: "merge <output-path>", Use: "merge <output>",
Short: "Merge configurations", Short: "Merge configurations",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := merge(args[0]) err := merge(args[0])
@@ -68,19 +68,29 @@ func merge(outputPath string) error {
} }
func mergePathResources(options *option.Options) error { func mergePathResources(options *option.Options) error {
for _, inbound := range options.Inbounds { for index, inbound := range options.Inbounds {
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions { rawOptions, err := inbound.RawOptions()
if err != nil {
return err
}
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions())) tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
} }
options.Inbounds[index] = inbound
} }
for _, outbound := range options.Outbounds { for index, outbound := range options.Outbounds {
rawOptions, err := outbound.RawOptions()
if err != nil {
return err
}
switch outbound.Type { switch outbound.Type {
case C.TypeSSH: case C.TypeSSH:
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions)) outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
} }
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions { if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions())) tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
} }
options.Outbounds[index] = outbound
} }
return nil return nil
} }
@@ -128,12 +138,13 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
return options return options
} }
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) { func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
if options.PrivateKeyPath != "" { if options.PrivateKeyPath != "" {
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil { if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n")) options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
} }
} }
return options
} }
func trimStringArray(array []string) []string { func trimStringArray(array []string) []string {

View File

@@ -2,7 +2,6 @@ package main
import ( import (
"bytes" "bytes"
"context"
"io" "io"
"os" "os"
@@ -11,7 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/route"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
@@ -84,7 +83,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
} }
for i, ruleOptions := range plainRuleSet.Rules { for i, ruleOptions := range plainRuleSet.Rules {
var currentRule adapter.HeadlessRule var currentRule adapter.HeadlessRule
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions) currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
if err != nil { if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]") return E.Cause(err, "parse rule_set.rules.[", i, "]")
} }

View File

@@ -1,162 +0,0 @@
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra"
)
var (
ruleSetPaths []string
ruleSetDirectories []string
)
var commandRuleSetMerge = &cobra.Command{
Use: "merge <output-path>",
Short: "Merge rule-set source files",
Run: func(cmd *cobra.Command, args []string) {
err := mergeRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
Args: cobra.ExactArgs(1),
}
func init() {
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
commandRuleSet.AddCommand(commandRuleSetMerge)
}
type RuleSetEntry struct {
content []byte
path string
options option.PlainRuleSetCompat
}
func readRuleSetAt(path string) (*RuleSetEntry, error) {
var (
configContent []byte
err error
)
if path == "stdin" {
configContent, err = io.ReadAll(os.Stdin)
} else {
configContent, err = os.ReadFile(path)
}
if err != nil {
return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
return &RuleSetEntry{
content: configContent,
path: path,
options: options,
}, nil
}
func readRuleSet() ([]*RuleSetEntry, error) {
var optionsList []*RuleSetEntry
for _, path := range ruleSetPaths {
optionsEntry, err := readRuleSetAt(path)
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
for _, directory := range ruleSetDirectories {
entries, err := os.ReadDir(directory)
if err != nil {
return nil, E.Cause(err, "read rule-set directory at ", directory)
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
continue
}
optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))
if err != nil {
return nil, err
}
optionsList = append(optionsList, optionsEntry)
}
}
sort.Slice(optionsList, func(i, j int) bool {
return optionsList[i].path < optionsList[j].path
})
return optionsList, nil
}
func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
optionsList, err := readRuleSet()
if err != nil {
return option.PlainRuleSetCompat{}, err
}
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var optionVersion uint8
for _, options := range optionsList {
if optionVersion < options.options.Version {
optionVersion = options.options.Version
}
}
var mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
}
}
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
if err != nil {
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
}
mergedOptions.Version = optionVersion
return mergedOptions, nil
}
func mergeRuleSet(outputPath string) error {
mergedOptions, err := readRuleSetAndMerge()
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(mergedOptions)
if err != nil {
return E.Cause(err, "encode config")
}
if existsContent, err := os.ReadFile(outputPath); err != nil {
if string(existsContent) == buffer.String() {
return nil
}
}
err = rw.MkdirParent(outputPath)
if err != nil {
return err
}
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
if err != nil {
return err
}
outputPath, _ = filepath.Abs(outputPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}

View File

@@ -61,15 +61,14 @@ func upgradeRuleSet(sourcePath string) error {
log.Info("already up-to-date") log.Info("already up-to-date")
return nil return nil
} }
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade() plainRuleSet, err := plainRuleSetCompat.Upgrade()
if err != nil { if err != nil {
return err return err
} }
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer) encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ") encoder.SetIndent("", " ")
err = encoder.Encode(plainRuleSetCompat) err = encoder.Encode(plainRuleSet)
if err != nil { if err != nil {
return E.Cause(err, "encode config") return E.Cause(err, "encode config")
} }

View File

@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "read config at ", path) return nil, E.Cause(err, "read config at ", path)
} }
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent) options, err := json.UnmarshalExtended[option.Options](configContent)
if err != nil { if err != nil {
return nil, E.Cause(err, "decode config at ", path) return nil, E.Cause(err, "decode config at ", path)
} }
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
} }
var mergedMessage json.RawMessage var mergedMessage json.RawMessage
for _, options := range optionsList { for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false) mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
if err != nil { if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path) return option.Options{}, E.Cause(err, "merge config at ", options.path)
} }
} }
var mergedOptions option.Options var mergedOptions option.Options
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage) err = mergedOptions.UnmarshalJSON(mergedMessage)
if err != nil { if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged config") return option.Options{}, E.Cause(err, "unmarshal merged config")
} }

View File

@@ -1,9 +1,6 @@
package main package main
import ( import (
"errors"
"os"
"github.com/sagernet/sing-box" "github.com/sagernet/sing-box"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@@ -26,11 +23,9 @@ func init() {
func createPreStartedClient() (*box.Box, error) { func createPreStartedClient() (*box.Box, error) {
options, err := readConfigAndMerge() options, err := readConfigAndMerge()
if err != nil { if err != nil {
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" { return nil, err
return nil, err
}
} }
instance, err := box.New(box.Options{Context: globalCtx, Options: options}) instance, err := box.New(box.Options{Options: options})
if err != nil { if err != nil {
return nil, E.Cause(err, "create service") return nil, E.Cause(err, "create service")
} }
@@ -41,11 +36,11 @@ func createPreStartedClient() (*box.Box, error) {
return instance, nil return instance, nil
} }
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) { func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
if outboundTag == "" { if outboundTag == "" {
return instance.Outbound().Default(), nil return instance.Router().DefaultOutbound(N.NetworkName(network))
} else { } else {
outbound, loaded := instance.Outbound().Outbound(outboundTag) outbound, loaded := instance.Router().Outbound(outboundTag)
if !loaded { if !loaded {
return nil, E.New("outbound not found: ", outboundTag) return nil, E.New("outbound not found: ", outboundTag)
} }

View File

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

View File

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

View File

@@ -16,12 +16,12 @@ import (
) )
func initializeHTTP3Client(instance *box.Box) error { func initializeHTTP3Client(instance *box.Box) error {
dialer, err := createDialer(instance, commandToolsFlagOutbound) dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
if err != nil { if err != nil {
return err return err
} }
http3Client = &http.Client{ http3Client = &http.Client{
Transport: &http3.Transport{ Transport: &http3.RoundTripper{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
destination := M.ParseSocksaddr(addr) destination := M.ParseSocksaddr(addr)
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination) udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)

View File

@@ -4,10 +4,12 @@ import (
"context" "context"
"os" "os"
"github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -43,7 +45,7 @@ func syncTime() error {
if err != nil { if err != nil {
return err return err
} }
dialer, err := createDialer(instance, commandToolsFlagOutbound) dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
if err != nil { if err != nil {
return err return err
} }
@@ -57,7 +59,7 @@ func syncTime() error {
return err return err
} }
if commandSyncTimeWrite { if commandSyncTimeWrite {
err = ntp.SetSystemTime(response.Time) err = settings.SetSystemTime(response.Time)
if err != nil { if err != nil {
return E.Cause(err, "write time to system") return E.Cause(err, "write time to system")
} }

View File

@@ -1,12 +1,11 @@
package adguard package adguard
import ( import (
"context"
"strings" "strings"
"testing" "testing"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/route"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -27,7 +26,7 @@ example.arpa
`)) `))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, rules, 1) require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) rule, err := route.NewHeadlessRule(nil, rules[0])
require.NoError(t, err) require.NoError(t, err)
matchDomain := []string{ matchDomain := []string{
"example.org", "example.org",
@@ -86,7 +85,7 @@ func TestHosts(t *testing.T) {
`)) `))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, rules, 1) require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) rule, err := route.NewHeadlessRule(nil, rules[0])
require.NoError(t, err) require.NoError(t, err)
matchDomain := []string{ matchDomain := []string{
"google.com", "google.com",
@@ -116,7 +115,7 @@ www.example.org
`)) `))
require.NoError(t, err) require.NoError(t, err)
require.Len(t, rules, 1) require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0]) rule, err := route.NewHeadlessRule(nil, rules[0])
require.NoError(t, err) require.NoError(t, err)
matchDomain := []string{ matchDomain := []string{
"example.com", "example.com",

View File

@@ -2,132 +2,73 @@ package dialer
import ( import (
"context" "context"
"errors"
"net" "net"
"net/netip"
"syscall"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/conntrack"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/control" "github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
) )
var ( var _ WireGuardListener = (*DefaultDialer)(nil)
_ ParallelInterfaceDialer = (*DefaultDialer)(nil)
_ WireGuardListener = (*DefaultDialer)(nil)
)
type DefaultDialer struct { type DefaultDialer struct {
dialer4 tcpDialer dialer4 tcpDialer
dialer6 tcpDialer dialer6 tcpDialer
udpDialer4 net.Dialer udpDialer4 net.Dialer
udpDialer6 net.Dialer udpDialer6 net.Dialer
udpListener net.ListenConfig udpListener net.ListenConfig
udpAddr4 string udpAddr4 string
udpAddr6 string udpAddr6 string
isWireGuardListener bool isWireGuardListener bool
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
networkLastFallback atomic.TypedValue[time.Time]
} }
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
networkManager := service.FromContext[adapter.NetworkManager](ctx) var dialer net.Dialer
platformInterface := service.FromContext[platform.Interface](ctx) var listener net.ListenConfig
var (
dialer net.Dialer
listener net.ListenConfig
interfaceFinder control.InterfaceFinder
networkStrategy *C.NetworkStrategy
defaultNetworkStrategy bool
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
)
if networkManager != nil {
interfaceFinder = networkManager.InterfaceFinder()
} else {
interfaceFinder = control.NewDefaultInterfaceFinder()
}
if options.BindInterface != "" { if options.BindInterface != "" {
var interfaceFinder control.InterfaceFinder
if router != nil {
interfaceFinder = router.InterfaceFinder()
} else {
interfaceFinder = control.NewDefaultInterfaceFinder()
}
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1) bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc) dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc) listener.Control = control.Append(listener.Control, bindFunc)
} else if router != nil && router.AutoDetectInterface() {
bindFunc := router.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if router != nil && router.DefaultInterface() != "" {
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
var autoRedirectOutputMark uint32
if router != nil {
autoRedirectOutputMark = router.AutoRedirectOutputMark()
}
if autoRedirectOutputMark > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
} }
if options.RoutingMark > 0 { if options.RoutingMark > 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark))) dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark))) listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
}
if networkManager != nil {
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
if autoRedirectOutputMark > 0 { if autoRedirectOutputMark > 0 {
if options.RoutingMark > 0 { return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
}
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
} }
} } else if router != nil && router.DefaultMark() > 0 {
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
if disableDefaultBind || options.TCPFastOpen { listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 { if autoRedirectOutputMark > 0 {
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`") return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
}
}
if networkManager != nil {
defaultOptions := networkManager.DefaultOptions()
if !disableDefaultBind {
if defaultOptions.BindInterface != "" {
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else if networkManager.AutoDetectInterface() {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
networkStrategy = defaultOptions.NetworkStrategy
networkType = defaultOptions.NetworkType
fallbackNetworkType = defaultOptions.FallbackNetworkType
}
networkFallbackDelay = time.Duration(options.FallbackDelay)
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
} }
} }
if options.ReuseAddr { if options.ReuseAddr {
@@ -161,7 +102,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr4 string udpAddr4 string
) )
if options.Inet4BindAddress != nil { if options.Inet4BindAddress != nil {
bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified()) bindAddr := options.Inet4BindAddress.Build()
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String() udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
@@ -172,7 +113,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr6 string udpAddr6 string
) )
if options.Inet6BindAddress != nil { if options.Inet6BindAddress != nil {
bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified()) bindAddr := options.Inet6BindAddress.Build()
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()} dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()} udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String() udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
@@ -184,7 +125,7 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
setMultiPathTCP(&dialer4) setMultiPathTCP(&dialer4)
} }
if options.IsWireGuardListener { if options.IsWireGuardListener {
for _, controlFn := range WgControlFns { for _, controlFn := range wgControlFns {
listener.Control = control.Append(listener.Control, controlFn) listener.Control = control.Append(listener.Control, controlFn)
} }
} }
@@ -197,20 +138,14 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
return nil, err return nil, err
} }
return &DefaultDialer{ return &DefaultDialer{
dialer4: tcpDialer4, tcpDialer4,
dialer6: tcpDialer6, tcpDialer6,
udpDialer4: udpDialer4, udpDialer4,
udpDialer6: udpDialer6, udpDialer6,
udpListener: listener, listener,
udpAddr4: udpAddr4, udpAddr4,
udpAddr6: udpAddr6, udpAddr6,
isWireGuardListener: options.IsWireGuardListener, options.IsWireGuardListener,
networkManager: networkManager,
networkStrategy: networkStrategy,
defaultNetworkStrategy: defaultNetworkStrategy,
networkType: networkType,
fallbackNetworkType: fallbackNetworkType,
networkFallbackDelay: networkFallbackDelay,
}, nil }, nil
} }
@@ -218,120 +153,31 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
if !address.IsValid() { if !address.IsValid() {
return nil, E.New("invalid address") return nil, E.New("invalid address")
} }
if d.networkStrategy == nil { switch N.NetworkName(network) {
switch N.NetworkName(network) { case N.NetworkUDP:
case N.NetworkUDP:
if !address.IsIPv6() {
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else {
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
}
}
if !address.IsIPv6() { if !address.IsIPv6() {
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else { } else {
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
}
} else {
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
}
}
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if strategy == nil {
strategy = d.networkStrategy
}
if strategy == nil {
return d.DialContext(ctx, network, address)
}
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
}
var dialer net.Dialer
if N.NetworkName(network) == N.NetworkTCP {
dialer = dialerFromTCPDialer(d.dialer4)
} else {
dialer = d.udpDialer4
}
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
var (
conn net.Conn
isPrimary bool
err error
)
if !fastFallback {
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} else {
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
}
if err != nil {
// bind interface failed on legacy xiaomi systems
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
d.networkStrategy = nil
return d.DialContext(ctx, network, address)
} else {
return nil, err
} }
} }
if !fastFallback && !isPrimary { if !address.IsIPv6() {
d.networkLastFallback.Store(time.Now()) return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else {
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
} }
return trackConn(conn, nil)
} }
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == nil { if destination.IsIPv6() {
if destination.IsIPv6() { return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)) } else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() { return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
} else {
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
}
} else { } else {
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
} }
} }
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if strategy == nil {
strategy = d.networkStrategy
}
if strategy == nil {
return d.ListenPacket(ctx, destination)
}
if len(interfaceType) == 0 {
interfaceType = d.networkType
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaceType = d.fallbackNetworkType
}
if fallbackDelay == 0 {
fallbackDelay = d.networkFallbackDelay
}
network := N.NetworkUDP
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
network += "4"
}
packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err != nil {
// bind interface failed on legacy xiaomi systems
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
d.networkStrategy = nil
return d.ListenPacket(ctx, destination)
} else {
return nil, err
}
}
return trackPacketConn(packetConn, nil)
}
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) { func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return d.udpListener.ListenPacket(context.Background(), network, address) return d.udpListener.ListenPacket(context.Background(), network, address)
} }

View File

@@ -13,7 +13,3 @@ type tcpDialer = tfo.Dialer
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
} }
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
return dialer.Dialer
}

View File

@@ -16,7 +16,3 @@ func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
} }
return dialer, nil return dialer, nil
} }
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
return dialer
}

View File

@@ -1,233 +0,0 @@
package dialer
import (
"context"
"net"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
)
func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
net.Conn
error
primary bool
}
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil {
select {
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
case <-returned:
}
} else {
select {
case results <- dialResult{Conn: conn, primary: primary}:
case <-returned:
conn.Close()
}
}
}
primaryCtx, primaryCancel := context.WithCancel(ctx)
defer primaryCancel()
for _, iif := range primaryInterfaces {
go startRacer(primaryCtx, true, iif)
}
var (
fallbackTimer *time.Timer
fallbackChan <-chan time.Time
)
if len(fallbackInterfaces) > 0 {
fallbackTimer = time.NewTimer(fallbackDelay)
defer fallbackTimer.Stop()
fallbackChan = fallbackTimer.C
}
var errors []error
for {
select {
case <-fallbackChan:
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
defer fallbackCancel()
for _, iif := range fallbackInterfaces {
go startRacer(fallbackCtx, false, iif)
}
case res := <-results:
if res.error == nil {
return res.Conn, res.primary, nil
}
errors = append(errors, res.error)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
}
if res.primary && fallbackTimer != nil && fallbackTimer.Stop() {
fallbackTimer.Reset(0)
}
}
}
}
func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, false, E.New("no available network interface")
}
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
net.Conn
error
primary bool
}
startAt := time.Now()
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
perNetDialer := dialer
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
conn, err := perNetDialer.DialContext(ctx, network, addr)
if err != nil {
select {
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
case <-returned:
}
} else {
select {
case results <- dialResult{Conn: conn, primary: primary}:
case <-returned:
if primary && time.Since(startAt) <= fallbackDelay {
resetFastFallback(time.Time{})
}
conn.Close()
}
}
}
for _, iif := range primaryInterfaces {
go startRacer(ctx, true, iif)
}
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
defer fallbackCancel()
for _, iif := range fallbackInterfaces {
go startRacer(fallbackCtx, false, iif)
}
var errors []error
for {
select {
case res := <-results:
if res.error == nil {
return res.Conn, res.primary, nil
}
errors = append(errors, res.error)
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
return nil, false, E.Errors(errors...)
}
}
}
}
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
return nil, E.New("no available network interface")
}
var errors []error
for _, primaryInterface := range primaryInterfaces {
perNetListener := listener
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil {
return conn, nil
}
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")"))
}
for _, fallbackInterface := range fallbackInterfaces {
perNetListener := listener
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
conn, err := perNetListener.ListenPacket(ctx, network, addr)
if err == nil {
return conn, nil
}
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")"))
}
return nil, E.Errors(errors...)
}
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
interfaces := networkManager.NetworkInterfaces()
switch strategy {
case C.NetworkStrategyDefault:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
if defaultIf != nil {
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
}
}
} else {
primaryInterfaces = interfaces
}
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
case C.NetworkStrategyHybrid:
if len(interfaceType) == 0 {
primaryInterfaces = interfaces
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
case C.NetworkStrategyFallback:
if len(interfaceType) == 0 {
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
if defaultIf != nil {
for _, iif := range interfaces {
if iif.Index == defaultIf.Index {
primaryInterfaces = append(primaryInterfaces, iif)
break
}
}
} else {
primaryInterfaces = interfaces
}
} else {
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return common.Contains(interfaceType, it.Type)
})
}
if len(fallbackInterfaceType) == 0 {
fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {
return it.Index == iif.Index
})
})
} else {
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
return common.Contains(fallbackInterfaceType, iif.Type)
})
}
}
return primaryInterfaces, fallbackInterfaces
}

View File

@@ -1,161 +0,0 @@
package dialer
import (
"context"
"net"
"net/netip"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
var errors []error
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
for _, address := range destinationAddresses {
conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err == nil {
return conn, nil
}
errors = append(errors, err)
}
} else {
for _, address := range destinationAddresses {
conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port))
if err == nil {
return conn, nil
}
errors = append(errors, err)
}
}
return nil, E.Errors(errors...)
}
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
if fallbackDelay == 0 {
fallbackDelay = N.DefaultFallbackDelay
}
returned := make(chan struct{})
defer close(returned)
addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
return address.Is4() || address.Is4In6()
})
addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
return address.Is6() && !address.Is4In6()
})
if len(addresses4) == 0 || len(addresses6) == 0 {
return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
var primaries, fallbacks []netip.Addr
if preferIPv6 {
primaries = addresses6
fallbacks = addresses4
} else {
primaries = addresses4
fallbacks = addresses6
}
type dialResult struct {
net.Conn
error
primary bool
done bool
}
results := make(chan dialResult) // unbuffered
startRacer := func(ctx context.Context, primary bool) {
ras := primaries
if !primary {
ras = fallbacks
}
c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
select {
case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
case <-returned:
if c != nil {
c.Close()
}
}
}
var primary, fallback dialResult
primaryCtx, primaryCancel := context.WithCancel(ctx)
defer primaryCancel()
go startRacer(primaryCtx, true)
fallbackTimer := time.NewTimer(fallbackDelay)
defer fallbackTimer.Stop()
for {
select {
case <-fallbackTimer.C:
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
defer fallbackCancel()
go startRacer(fallbackCtx, false)
case res := <-results:
if res.error == nil {
return res.Conn, nil
}
if res.primary {
primary = res
} else {
fallback = res
}
if primary.done && fallback.done {
return nil, primary.error
}
if res.primary && fallbackTimer.Stop() {
fallbackTimer.Reset(0)
}
}
}
}
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
if len(destinationAddresses) == 0 {
if !destination.IsIP() {
panic("invalid usage")
}
destinationAddresses = []netip.Addr{destination.Addr}
}
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
var errors []error
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
for _, address := range destinationAddresses {
conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err == nil {
return conn, address, nil
}
errors = append(errors, err)
}
} else {
for _, address := range destinationAddresses {
conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port))
if err == nil {
return conn, address, nil
}
errors = append(errors, err)
}
}
return nil, netip.Addr{}, E.Errors(errors...)
}

View File

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

View File

@@ -1,82 +1,40 @@
package dialer package dialer
import ( import (
"context"
"net"
"net/netip"
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service"
) )
func New(ctx context.Context, options option.DialerOptions) (N.Dialer, error) { func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
if options.IsWireGuardListener { if options.IsWireGuardListener {
return NewDefault(ctx, options) return NewDefault(router, options)
}
if router == nil {
return NewDefault(nil, options)
} }
var ( var (
dialer N.Dialer dialer N.Dialer
err error err error
) )
if options.Detour == "" { if options.Detour == "" {
dialer, err = NewDefault(ctx, options) dialer, err = NewDefault(router, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
outboundManager := service.FromContext[adapter.OutboundManager](ctx) dialer = NewDetour(router, options.Detour)
if outboundManager == nil {
return nil, E.New("missing outbound manager")
}
dialer = NewDetour(outboundManager, options.Detour)
} }
if options.Detour == "" { if options.Detour == "" {
router := service.FromContext[adapter.Router](ctx) dialer = NewResolveDialer(
if router != nil { router,
dialer = NewResolveDialer( dialer,
router, options.Detour == "" && !options.TCPFastOpen,
dialer, dns.DomainStrategy(options.DomainStrategy),
options.Detour == "" && !options.TCPFastOpen, time.Duration(options.FallbackDelay))
dns.DomainStrategy(options.DomainStrategy),
time.Duration(options.FallbackDelay))
}
} }
return dialer, nil return dialer, nil
} }
func NewDirect(ctx context.Context, options option.DialerOptions) (ParallelInterfaceDialer, error) {
if options.Detour != "" {
return nil, E.New("`detour` is not supported in direct context")
}
if options.IsWireGuardListener {
return NewDefault(ctx, options)
}
dialer, err := NewDefault(ctx, options)
if err != nil {
return nil, err
}
return NewResolveParallelInterfaceDialer(
service.FromContext[adapter.Router](ctx),
dialer,
true,
dns.DomainStrategy(options.DomainStrategy),
time.Duration(options.FallbackDelay),
), nil
}
type ParallelInterfaceDialer interface {
N.Dialer
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
}
type ParallelNetworkDialer interface {
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
}

View File

@@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@@ -15,12 +14,7 @@ import (
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
var ( type ResolveDialer struct {
_ N.Dialer = (*resolveDialer)(nil)
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
)
type resolveDialer struct {
dialer N.Dialer dialer N.Dialer
parallel bool parallel bool
router adapter.Router router adapter.Router
@@ -28,8 +22,8 @@ type resolveDialer struct {
fallbackDelay time.Duration fallbackDelay time.Duration
} }
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) N.Dialer { func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
return &resolveDialer{ return &ResolveDialer{
dialer, dialer,
parallel, parallel,
router, router,
@@ -38,25 +32,7 @@ func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, str
} }
} }
type resolveParallelNetworkDialer struct { func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
resolveDialer
dialer ParallelInterfaceDialer
}
func NewResolveParallelInterfaceDialer(router adapter.Router, dialer ParallelInterfaceDialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) ParallelInterfaceDialer {
return &resolveParallelNetworkDialer{
resolveDialer{
dialer,
parallel,
router,
strategy,
fallbackDelay,
},
dialer,
}
}
func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
if !destination.IsFqdn() { if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination) return d.dialer.DialContext(ctx, network, destination)
} }
@@ -81,7 +57,7 @@ func (d *resolveDialer) DialContext(ctx context.Context, network string, destina
} }
} }
func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if !destination.IsFqdn() { if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination) return d.dialer.ListenPacket(ctx, destination)
} }
@@ -106,59 +82,6 @@ func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
} }
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) { func (d *ResolveDialer) Upstream() any {
if !destination.IsFqdn() {
return d.dialer.DialContext(ctx, network, destination)
}
ctx, metadata := adapter.ExtendContext(ctx)
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
metadata.Destination = destination
metadata.Domain = ""
var addresses []netip.Addr
var err error
if d.strategy == dns.DomainStrategyAsIS {
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
} else {
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
}
if err != nil {
return nil, err
}
if fallbackDelay == 0 {
fallbackDelay = d.fallbackDelay
}
if d.parallel {
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
} else {
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
}
}
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
if !destination.IsFqdn() {
return d.dialer.ListenPacket(ctx, destination)
}
ctx, metadata := adapter.ExtendContext(ctx)
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
metadata.Destination = destination
metadata.Domain = ""
var addresses []netip.Addr
var err error
if d.strategy == dns.DomainStrategyAsIS {
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
} else {
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
}
if err != nil {
return nil, err
}
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
if err != nil {
return nil, err
}
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
}
func (d *resolveDialer) Upstream() any {
return d.dialer return d.dialer
} }

View File

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

View File

@@ -2,12 +2,8 @@ package dialer
import ( import (
"net" "net"
"github.com/sagernet/sing/common/control"
) )
type WireGuardListener interface { type WireGuardListener interface {
ListenPacketCompat(network, address string) (net.PacketConn, error) ListenPacketCompat(network, address string) (net.PacketConn, error)
} }
var WgControlFns []control.Func

View File

@@ -0,0 +1,11 @@
//go:build with_wireguard
package dialer
import (
"github.com/sagernet/wireguard-go/conn"
)
var _ WireGuardListener = (conn.Listener)(nil)
var wgControlFns = conn.ControlFns

View File

@@ -0,0 +1,9 @@
//go:build !with_wireguard
package dialer
import (
"github.com/sagernet/sing/common/control"
)
var wgControlFns []control.Func

View File

@@ -1,137 +0,0 @@
package listener
import (
"context"
"net"
"net/netip"
"sync/atomic"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/settings"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Listener struct {
ctx context.Context
logger logger.ContextLogger
network []string
listenOptions option.ListenOptions
connHandler adapter.ConnectionHandlerEx
packetHandler adapter.PacketHandlerEx
oobPacketHandler adapter.OOBPacketHandlerEx
threadUnsafePacketWriter bool
disablePacketOutput bool
setSystemProxy bool
systemProxySOCKS bool
tcpListener net.Listener
systemProxy settings.SystemProxy
udpConn *net.UDPConn
udpAddr M.Socksaddr
packetOutbound chan *N.PacketBuffer
packetOutboundClosed chan struct{}
shutdown atomic.Bool
}
type Options struct {
Context context.Context
Logger logger.ContextLogger
Network []string
Listen option.ListenOptions
ConnectionHandler adapter.ConnectionHandlerEx
PacketHandler adapter.PacketHandlerEx
OOBPacketHandler adapter.OOBPacketHandlerEx
ThreadUnsafePacketWriter bool
DisablePacketOutput bool
SetSystemProxy bool
SystemProxySOCKS bool
}
func New(
options Options,
) *Listener {
return &Listener{
ctx: options.Context,
logger: options.Logger,
network: options.Network,
listenOptions: options.Listen,
connHandler: options.ConnectionHandler,
packetHandler: options.PacketHandler,
oobPacketHandler: options.OOBPacketHandler,
threadUnsafePacketWriter: options.ThreadUnsafePacketWriter,
disablePacketOutput: options.DisablePacketOutput,
setSystemProxy: options.SetSystemProxy,
systemProxySOCKS: options.SystemProxySOCKS,
}
}
func (l *Listener) Start() error {
if common.Contains(l.network, N.NetworkTCP) {
_, err := l.ListenTCP()
if err != nil {
return err
}
go l.loopTCPIn()
}
if common.Contains(l.network, N.NetworkUDP) {
_, err := l.ListenUDP()
if err != nil {
return err
}
l.packetOutboundClosed = make(chan struct{})
l.packetOutbound = make(chan *N.PacketBuffer, 64)
go l.loopUDPIn()
if !l.disablePacketOutput {
go l.loopUDPOut()
}
}
if l.setSystemProxy {
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
var listenAddrString string
listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())
if listenAddr.IsUnspecified() {
listenAddrString = "127.0.0.1"
} else {
listenAddrString = listenAddr.String()
}
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
if err != nil {
return E.Cause(err, "initialize system proxy")
}
err = systemProxy.Enable()
if err != nil {
return E.Cause(err, "set system proxy")
}
l.systemProxy = systemProxy
}
return nil
}
func (l *Listener) Close() error {
l.shutdown.Store(true)
var err error
if l.systemProxy != nil && l.systemProxy.IsEnabled() {
err = l.systemProxy.Disable()
}
return E.Errors(err, common.Close(
l.tcpListener,
common.PtrOrNil(l.udpConn),
))
}
func (l *Listener) TCPListener() net.Listener {
return l.tcpListener
}
func (l *Listener) UDPConn() *net.UDPConn {
return l.udpConn
}
func (l *Listener) ListenOptions() option.ListenOptions {
return l.listenOptions
}

View File

@@ -1,16 +0,0 @@
//go:build go1.23
package listener
import (
"net"
"time"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: idle,
Interval: interval,
}
}

View File

@@ -1,15 +0,0 @@
//go:build !go1.23
package listener
import (
"net"
"time"
"github.com/sagernet/sing/common/control"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAlive = idle
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
}

View File

@@ -1,86 +0,0 @@
package listener
import (
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"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/metacubex/tfo-go"
)
func (l *Listener) ListenTCP() (net.Listener, error) {
var err error
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 {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial
}
keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval)
if keepInterval == 0 {
keepInterval = C.TCPKeepAliveInterval
}
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
}
if l.listenOptions.TCPMultiPath {
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&listenConfig)
}
if l.listenOptions.TCPFastOpen {
var tfoConfig tfo.ListenConfig
tfoConfig.ListenConfig = listenConfig
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
} else {
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
}
if err == nil {
l.logger.Info("tcp server started at ", tcpListener.Addr())
}
//nolint:staticcheck
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
}
l.tcpListener = tcpListener
return tcpListener, err
}
func (l *Listener) loopTCPIn() {
tcpListener := l.tcpListener
var metadata adapter.InboundContext
for {
conn, err := tcpListener.Accept()
if err != nil {
//nolint:staticcheck
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
l.logger.Error(err)
continue
}
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.tcpListener.Close()
l.logger.Error("tcp listener closed: ", err)
continue
}
//nolint:staticcheck
metadata.InboundDetour = l.listenOptions.Detour
//nolint:staticcheck
metadata.InboundOptions = l.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
ctx := log.ContextWithNewID(l.ctx)
l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil)
}
}

View File

@@ -1,154 +0,0 @@
package listener
import (
"net"
"net/netip"
"os"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func (l *Listener) ListenUDP() (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
var lc net.ListenConfig
var udpFragment bool
if l.listenOptions.UDPFragment != nil {
udpFragment = *l.listenOptions.UDPFragment
} else {
udpFragment = l.listenOptions.UDPFragmentDefault
}
if !udpFragment {
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
}
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
if err != nil {
return nil, err
}
l.udpConn = udpConn.(*net.UDPConn)
l.udpAddr = bindAddr
l.logger.Info("udp server started at ", udpConn.LocalAddr())
return udpConn, err
}
func (l *Listener) UDPAddr() M.Socksaddr {
return l.udpAddr
}
func (l *Listener) PacketWriter() N.PacketWriter {
return (*packetWriter)(l)
}
func (l *Listener) loopUDPIn() {
defer close(l.packetOutboundClosed)
var buffer *buf.Buffer
if !l.threadUnsafePacketWriter {
buffer = buf.NewPacket()
defer buffer.Release()
buffer.IncRef()
defer buffer.DecRef()
}
if l.oobPacketHandler != nil {
oob := make([]byte, 1024)
for {
if l.threadUnsafePacketWriter {
buffer = buf.NewPacket()
} else {
buffer.Reset()
}
n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
if err != nil {
if l.threadUnsafePacketWriter {
buffer.Release()
}
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.udpConn.Close()
l.logger.Error("udp listener closed: ", err)
return
}
buffer.Truncate(n)
l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap())
}
} else {
for {
if l.threadUnsafePacketWriter {
buffer = buf.NewPacket()
} else {
buffer.Reset()
}
n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
if l.threadUnsafePacketWriter {
buffer.Release()
}
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.udpConn.Close()
l.logger.Error("udp listener closed: ", err)
return
}
buffer.Truncate(n)
l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap())
}
}
}
func (l *Listener) loopUDPOut() {
for {
select {
case packet := <-l.packetOutbound:
destination := packet.Destination.AddrPort()
_, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination)
packet.Buffer.Release()
N.PutPacketBuffer(packet)
if err != nil {
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.udpConn.Close()
l.logger.Error("udp listener write back: ", destination, ": ", err)
return
}
continue
case <-l.packetOutboundClosed:
}
for {
select {
case packet := <-l.packetOutbound:
packet.Buffer.Release()
N.PutPacketBuffer(packet)
default:
return
}
}
}
}
type packetWriter Listener
func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
packet := N.NewPacketBuffer()
packet.Buffer = buffer
packet.Destination = destination
select {
case w.packetOutbound <- packet:
return nil
default:
buffer.Release()
N.PutPacketBuffer(packet)
if w.shutdown.Load() {
return os.ErrClosed
}
w.logger.Trace("dropped packet to ", destination)
return nil
}
}
func (w *packetWriter) WriteIsThreadUnsafe() {
}

View File

@@ -15,11 +15,11 @@ import (
) )
type Router struct { type Router struct {
router adapter.ConnectionRouterEx router adapter.ConnectionRouter
service *mux.Service service *mux.Service
} }
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) { func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
if !options.Enabled { if !options.Enabled {
return router, nil return router, nil
} }
@@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context { NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
return log.ContextWithNewID(ctx) return log.ContextWithNewID(ctx)
}, },
Logger: logger, Logger: logger,
HandlerEx: adapter.NewRouteContextHandlerEx(router), Handler: adapter.NewRouteContextHandler(router, logger),
Padding: options.Padding, Padding: options.Padding,
Brutal: brutalOptions, Brutal: brutalOptions,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@@ -52,29 +52,14 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
return &Router{router, service}, nil return &Router{router, service}, nil
} }
// Deprecated: Use RouteConnectionEx instead.
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination == mux.Destination { if metadata.Destination == mux.Destination {
// TODO: check if WithContext is necessary
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
} else { } else {
return r.router.RouteConnection(ctx, conn, metadata) return r.router.RouteConnection(ctx, conn, metadata)
} }
} }
// Deprecated: Use RoutePacketConnectionEx instead.
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata) return r.router.RoutePacketConnection(ctx, conn, metadata)
} }
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
if metadata.Destination == mux.Destination {
r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose)
return
}
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -0,0 +1,32 @@
package mux
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
type V2RayLegacyRouter struct {
router adapter.ConnectionRouter
logger logger.ContextLogger
}
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
return &V2RayLegacyRouter{router, logger}
}
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
}
return r.router.RouteConnection(ctx, conn, metadata)
}
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@@ -2,17 +2,16 @@ package settings
import ( import (
"context" "context"
"net/netip"
"strconv" "strconv"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/shell"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
) )
type DarwinSystemProxy struct { type DarwinSystemProxy struct {
@@ -25,7 +24,7 @@ type DarwinSystemProxy struct {
} }
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) { func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor() interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
if interfaceMonitor == nil { if interfaceMonitor == nil {
return nil, E.New("missing interface monitor") return nil, E.New("missing interface monitor")
} }
@@ -34,7 +33,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo
serverAddr: serverAddr, serverAddr: serverAddr,
supportSOCKS: supportSOCKS, supportSOCKS: supportSOCKS,
} }
proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate) proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
return proxy, nil return proxy, nil
} }
@@ -66,22 +65,25 @@ func (p *DarwinSystemProxy) Disable() error {
return err return err
} }
func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) { func (p *DarwinSystemProxy) update(event int) {
if !p.isEnabled || defaultInterface == nil { if event&tun.EventInterfaceUpdate == 0 {
return
}
if !p.isEnabled {
return return
} }
_ = p.update0() _ = p.update0()
} }
func (p *DarwinSystemProxy) update0() error { func (p *DarwinSystemProxy) update0() error {
newInterface := p.monitor.DefaultInterface() newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterface.Name { if p.interfaceName == newInterfaceName {
return nil return nil
} }
if p.interfaceName != "" { if p.interfaceName != "" {
_ = p.Disable() _ = p.Disable()
} }
p.interfaceName = newInterface.Name p.interfaceName = newInterfaceName
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil { if err != nil {
return err return err

View File

@@ -0,0 +1,12 @@
//go:build !(windows || linux || darwin)
package settings
import (
"os"
"time"
)
func SetSystemTime(nowTime time.Time) error {
return os.ErrInvalid
}

View File

@@ -0,0 +1,14 @@
//go:build linux || darwin
package settings
import (
"time"
"golang.org/x/sys/unix"
)
func SetSystemTime(nowTime time.Time) error {
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
return unix.Settimeofday(&timeVal)
}

View File

@@ -0,0 +1,32 @@
package settings
import (
"time"
"unsafe"
"golang.org/x/sys/windows"
)
func SetSystemTime(nowTime time.Time) error {
var systemTime windows.Systemtime
systemTime.Year = uint16(nowTime.Year())
systemTime.Month = uint16(nowTime.Month())
systemTime.Day = uint16(nowTime.Day())
systemTime.Hour = uint16(nowTime.Hour())
systemTime.Minute = uint16(nowTime.Minute())
systemTime.Second = uint16(nowTime.Second())
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
proc := dllKernel32.NewProc("SetSystemTime")
_, _, err := proc.Call(
uintptr(unsafe.Pointer(&systemTime)),
)
if err != nil && err.Error() != "The operation completed successfully." {
return err
}
return nil
}

View File

@@ -18,7 +18,7 @@ type (
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
) )
func Skip(metadata *adapter.InboundContext) bool { func Skip(metadata adapter.InboundContext) bool {
// skip server first protocols // skip server first protocols
switch metadata.Destination.Port { switch metadata.Destination.Port {
case 25, 465, 587: case 25, 465, 587:

View File

@@ -38,9 +38,6 @@ const (
ruleItemWIFIBSSID ruleItemWIFIBSSID
ruleItemAdGuardDomain ruleItemAdGuardDomain
ruleItemProcessPathRegex ruleItemProcessPathRegex
ruleItemNetworkType
ruleItemNetworkIsExpensive
ruleItemNetworkIsConstrained
ruleItemFinal uint8 = 0xFF ruleItemFinal uint8 = 0xFF
) )
@@ -225,12 +222,6 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
return return
} }
rule.AdGuardDomainMatcher = matcher rule.AdGuardDomainMatcher = matcher
case ruleItemNetworkType:
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
case ruleItemNetworkIsExpensive:
rule.NetworkIsExpensive = true
case ruleItemNetworkIsConstrained:
rule.NetworkIsConstrained = true
case ruleItemFinal: case ruleItemFinal:
err = binary.Read(reader, binary.BigEndian, &rule.Invert) err = binary.Read(reader, binary.BigEndian, &rule.Invert)
return return
@@ -345,27 +336,6 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
return err return err
} }
} }
if len(rule.NetworkType) > 0 {
if generateVersion < C.RuleSetVersion3 {
return E.New("network_type rule item is only supported in version 3 or later")
}
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
if err != nil {
return err
}
}
if rule.NetworkIsExpensive {
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
if err != nil {
return err
}
}
if rule.NetworkIsConstrained {
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
if err != nil {
return err
}
}
if len(rule.WIFISSID) > 0 { if len(rule.WIFISSID) > 0 {
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID) err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
if err != nil { if err != nil {
@@ -414,18 +384,6 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
return varbin.Write(writer, binary.BigEndian, value) return varbin.Write(writer, binary.BigEndian, value)
} }
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
return varbin.ReadValue[[]E](reader, binary.BigEndian)
}
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
err := writer.WriteByte(itemType)
if err != nil {
return err
}
return varbin.Write(writer, binary.BigEndian, value)
}
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) { func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
return varbin.ReadValue[[]uint16](reader, binary.BigEndian) return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
} }

View File

@@ -19,7 +19,6 @@ import (
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/common/ntp"
"github.com/sagernet/sing/service"
mDNS "github.com/miekg/dns" mDNS "github.com/miekg/dns"
) )
@@ -64,7 +63,6 @@ type echConnWrapper struct {
func (c *echConnWrapper) ConnectionState() tls.ConnectionState { func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState() state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{ return tls.ConnectionState{
Version: state.Version, Version: state.Version,
HandshakeComplete: state.HandshakeComplete, HandshakeComplete: state.HandshakeComplete,
@@ -215,7 +213,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam
}, },
}, },
} }
response, err := service.FromContext[adapter.Router](ctx).Exchange(ctx, message) response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -147,9 +147,6 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
pair.rawConf = b pair.rawConf = b
secBuf, err := sec.MarshalBinary() secBuf, err := sec.MarshalBinary()
if err != nil {
return nil, E.Cause(err, "serialize ECH private key")
}
sk := []byte{} sk := []byte{}
sk = be.AppendUint16(sk, uint16(len(secBuf))) sk = be.AppendUint16(sk, uint16(len(secBuf)))
sk = append(sk, secBuf...) sk = append(sk, secBuf...)

View File

@@ -28,7 +28,7 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
} }
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper { func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
return &http3.Transport{ return &http3.RoundTripper{
TLSClientConfig: c.config, TLSClientConfig: c.config,
QUICConfig: quicConfig, QUICConfig: quicConfig,
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {

View File

@@ -11,8 +11,8 @@ import (
"time" "time"
) )
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) { func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour)) privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -23,7 +23,7 @@ func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() ti
return &certificate, err return &certificate, err
} }
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) { func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
if timeFunc == nil { if timeFunc == nil {
timeFunc = time.Now timeFunc = time.Now
} }
@@ -47,11 +47,7 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
}, },
DNSNames: []string{serverName}, DNSNames: []string{serverName},
} }
if parent == nil { publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
parent = template
parentKey = key
}
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
if err != nil { if err != nil {
return return
} }

View File

@@ -184,7 +184,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
return nil, E.New("reality verification failed") return nil, E.New("reality verification failed")
} }
return &realityClientConnWrapper{uConn}, nil return &utlsConnWrapper{uConn}, nil
} }
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
@@ -249,36 +249,3 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
} }
return nil return nil
} }
type realityClientConnWrapper struct {
*utls.UConn
}
func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{
Version: state.Version,
HandshakeComplete: state.HandshakeComplete,
DidResume: state.DidResume,
CipherSuite: state.CipherSuite,
NegotiatedProtocol: state.NegotiatedProtocol,
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
ServerName: state.ServerName,
PeerCertificates: state.PeerCertificates,
VerifiedChains: state.VerifiedChains,
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
OCSPResponse: state.OCSPResponse,
TLSUnique: state.TLSUnique,
}
}
func (c *realityClientConnWrapper) Upstream() any {
return c.UConn
}
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
// We fixed it by calling Close() directly.
func (c *realityClientConnWrapper) CloseWrite() error {
return c.Close()
}

View File

@@ -11,6 +11,7 @@ import (
"time" "time"
"github.com/sagernet/reality" "github.com/sagernet/reality"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@@ -101,7 +102,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
tlsConfig.ShortIds[shortID] = true tlsConfig.ShortIds[shortID] = true
} }
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions) handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -174,7 +175,6 @@ type realityConnWrapper struct {
func (c *realityConnWrapper) ConnectionState() ConnectionState { func (c *realityConnWrapper) ConnectionState() ConnectionState {
state := c.Conn.ConnectionState() state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{ return tls.ConnectionState{
Version: state.Version, Version: state.Version,
HandshakeComplete: state.HandshakeComplete, HandshakeComplete: state.HandshakeComplete,
@@ -194,9 +194,3 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState {
func (c *realityConnWrapper) Upstream() any { func (c *realityConnWrapper) Upstream() any {
return c.Conn return c.Conn
} }
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
// We fixed it by calling Close() directly.
func (c *realityConnWrapper) CloseWrite() error {
return c.Close()
}

View File

@@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
} }
if certificate == nil && key == nil && options.Insecure { if certificate == nil && key == nil && options.Insecure {
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName) return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
} }
} else { } else {
if certificate == nil { if certificate == nil {

View File

@@ -1,22 +0,0 @@
package tls
import (
"time"
"github.com/sagernet/sing/common/ntp"
)
type TimeServiceWrapper struct {
ntp.TimeService
}
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
if w.TimeService == nil {
return nil
}
return w.TimeService.TimeFunc()
}
func (w *TimeServiceWrapper) Upstream() any {
return w.TimeService
}

View File

@@ -69,7 +69,6 @@ type utlsConnWrapper struct {
func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState { func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
state := c.Conn.ConnectionState() state := c.Conn.ConnectionState()
//nolint:staticcheck
return tls.ConnectionState{ return tls.ConnectionState{
Version: state.Version, Version: state.Version,
HandshakeComplete: state.HandshakeComplete, HandshakeComplete: state.HandshakeComplete,

View File

@@ -13,14 +13,14 @@ import (
"github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/common/uot"
) )
var _ adapter.ConnectionRouterEx = (*Router)(nil) var _ adapter.ConnectionRouter = (*Router)(nil)
type Router struct { type Router struct {
router adapter.ConnectionRouterEx router adapter.ConnectionRouter
logger logger.ContextLogger logger logger.ContextLogger
} }
func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router { func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
return &Router{router, logger} return &Router{router, logger}
} }
@@ -51,36 +51,3 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata) return r.router.RoutePacketConnection(ctx, conn, metadata)
} }
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
switch metadata.Destination.Fqdn {
case uot.MagicAddress:
request, err := uot.ReadRequest(conn)
if err != nil {
err = E.Cause(err, "UoT read request")
r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err)
N.CloseOnHandshakeFailure(conn, onClose, err)
return
}
if request.IsConnect {
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
} else {
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
}
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = request.Destination
r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose)
return
case uot.LegacyMagicAddress:
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose)
return
}
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -0,0 +1,8 @@
//go:build android && debug
package constant
// TODO: remove after fixed
// https://github.com/golang/go/issues/68760
const FixAndroidStack = true

View File

@@ -0,0 +1,5 @@
//go:build !(android && debug)
package constant
const FixAndroidStack = false

View File

@@ -1,7 +0,0 @@
package constant
const (
Hysterai2MasqueradeTypeFile = "file"
Hysterai2MasqueradeTypeProxy = "proxy"
Hysterai2MasqueradeTypeString = "string"
)

View File

@@ -1,58 +0,0 @@
package constant
import (
"github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format"
)
type InterfaceType uint8
const (
InterfaceTypeWIFI InterfaceType = iota
InterfaceTypeCellular
InterfaceTypeEthernet
InterfaceTypeOther
)
var (
interfaceTypeToString = map[InterfaceType]string{
InterfaceTypeWIFI: "wifi",
InterfaceTypeCellular: "cellular",
InterfaceTypeEthernet: "ethernet",
InterfaceTypeOther: "other",
}
StringToInterfaceType = common.ReverseMap(interfaceTypeToString)
)
func (t InterfaceType) String() string {
name, loaded := interfaceTypeToString[t]
if !loaded {
return F.ToString(int(t))
}
return name
}
type NetworkStrategy uint8
const (
NetworkStrategyDefault NetworkStrategy = iota
NetworkStrategyFallback
NetworkStrategyHybrid
)
var (
networkStrategyToString = map[NetworkStrategy]string{
NetworkStrategyDefault: "default",
NetworkStrategyFallback: "fallback",
NetworkStrategyHybrid: "hybrid",
}
StringToNetworkStrategy = common.ReverseMap(networkStrategyToString)
)
func (s NetworkStrategy) String() string {
name, loaded := networkStrategyToString[s]
if !loaded {
return F.ToString(int(s))
}
return name
}

View File

@@ -6,7 +6,7 @@ import (
const IsAndroid = goos.IsAndroid == 1 const IsAndroid = goos.IsAndroid == 1
const IsDarwin = goos.IsDarwin == 1 || goos.IsIos == 1 const IsDarwin = goos.IsDarwin == 1
const IsDragonfly = goos.IsDragonfly == 1 const IsDragonfly = goos.IsDragonfly == 1

View File

@@ -10,7 +10,6 @@ const (
ProtocolDTLS = "dtls" ProtocolDTLS = "dtls"
ProtocolSSH = "ssh" ProtocolSSH = "ssh"
ProtocolRDP = "rdp" ProtocolRDP = "rdp"
ProtocolNTP = "ntp"
) )
const ( const (

View File

@@ -21,21 +21,5 @@ const (
const ( const (
RuleSetVersion1 = 1 + iota RuleSetVersion1 = 1 + iota
RuleSetVersion2 RuleSetVersion2
RuleSetVersion3 RuleSetVersionCurrent = RuleSetVersion2
RuleSetVersionCurrent = RuleSetVersion3
)
const (
RuleActionTypeRoute = "route"
RuleActionTypeRouteOptions = "route-options"
RuleActionTypeDirect = "direct"
RuleActionTypeReject = "reject"
RuleActionTypeHijackDNS = "hijack-dns"
RuleActionTypeSniff = "sniff"
RuleActionTypeResolve = "resolve"
)
const (
RuleActionRejectMethodDefault = "default"
RuleActionRejectMethodDrop = "drop"
) )

View File

@@ -9,6 +9,8 @@ const (
TCPTimeout = 15 * time.Second TCPTimeout = 15 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute DefaultURLTestIdleTimeout = 30 * time.Minute
@@ -17,18 +19,3 @@ const (
FatalStopTimeout = 10 * time.Second FatalStopTimeout = 10 * time.Second
FakeIPMetadataSaveInterval = 10 * time.Second FakeIPMetadataSaveInterval = 10 * time.Second
) )
var PortProtocols = map[uint16]string{
53: ProtocolDNS,
123: ProtocolNTP,
3478: ProtocolSTUN,
443: ProtocolQUIC,
}
var ProtocolTimeouts = map[string]time.Duration{
ProtocolDNS: 10 * time.Second,
ProtocolNTP: 10 * time.Second,
ProtocolSTUN: 10 * time.Second,
ProtocolQUIC: 30 * time.Second,
ProtocolDTLS: 30 * time.Second,
}

View File

@@ -2,366 +2,16 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
### 1.11.2 ### 1.10.4
* Fixes and improvements * Fixes and improvements
### 1.11.1
* Fixes and improvements
### 1.11.0
Important changes since 1.10:
* Introducing rule actions **1**
* Improve tun compatibility **3**
* Merge route options to route actions **4**
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**
* Add multi network dialing **6**
* Add `cache_capacity` DNS option **7**
* Add `override_address` and `override_port` route options **8**
* Upgrade WireGuard outbound to endpoint **9**
* Add UDP GSO support for WireGuard
* Make GSO adaptive **10**
* Add UDP timeout route option **11**
* Add more masquerade options for hysteria2 **12**
* Add `rule-set merge` command
* Add port hopping support for Hysteria2 **13**
* Hysteria2 `ignore_client_bandwidth` behavior update **14**
**1**:
New rule actions replace legacy inbound fields and special outbound fields,
and can be used for pre-matching **2**.
See [Rule](/configuration/route/rule/),
[Rule Action](/configuration/route/rule_action/),
[DNS Rule](/configuration/dns/rule/) and
[DNS Rule Action](/configuration/dns/rule_action/).
For migration, see
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
**2**:
Similar to Surge's pre-matching.
Specifically, new rule actions allow you to reject connections with
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
before connection established to improve tun's compatibility.
See [Rule Action](/configuration/route/rule_action/).
**3**:
When `gvisor` tun stack is enabled, even if the request passes routing,
if the outbound connection establishment fails,
the connection still does not need to be established and a TCP RST is replied.
**4**:
Route options in DNS route actions will no longer be considered deprecated,
see [DNS Route Action](/configuration/dns/rule_action/).
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
see [Route Action](/configuration/route/rule_action/).
**5**:
When using in graphical clients, new routing rule items allow you to match on
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
and [Headless Rule](/configuration/rule-set/headless-rule/).
**6**:
Similar to Surge's strategy.
New options allow you to connect using multiple network interfaces,
prefer or only use one type of interface,
and configure a timeout to fallback to other interfaces.
See [Dial Fields](/configuration/shared/dial/#network_strategy),
[Rule Action](/configuration/route/rule_action/#network_strategy)
and [Route](/configuration/route/#default_network_strategy).
**7**:
See [DNS](/configuration/dns/#cache_capacity).
**8**:
See [Rule Action](/configuration/route/#override_address) and
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
**9**:
The new WireGuard endpoint combines inbound and outbound capabilities,
and the old outbound will be removed in sing-box 1.13.0.
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
**10**:
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
For TUN, GSO has been removed,
see [Deprecated](/deprecated/#gso-option-in-tun).
**11**:
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
**12**:
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
**13**:
See [Hysteria2](/configuration/outbound/hysteria2/).
**14**:
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
### 1.10.7
* Fixes and improvements
#### 1.11.0-beta.20
* Hysteria2 `ignore_client_bandwidth` behavior update **1**
* Fixes and improvements
**1**:
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
See [Hysteria2](/configuration/inbound/hysteria2/#ignore_client_bandwidth).
#### 1.11.0-beta.17
* Add port hopping support for Hysteria2 **1**
* Fixes and improvements
**1**:
See [Hysteria2](/configuration/outbound/hysteria2/).
#### 1.11.0-beta.14
* Allow adding route (exclude) address sets to routes **1**
* Fixes and improvements
**1**:
When `auto_redirect` is not enabled, directly add `route[_exclude]_address_set`
to tun routes (equivalent to `route[_exclude]_address`).
Note that it **doesn't work on the Android graphical client** due to
the Android VpnService not being able to handle a large number of routes (DeadSystemException),
but otherwise it works fine on all command line clients and Apple platforms.
See [route_address_set](/configuration/inbound/tun/#route_address_set) and
[route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).
#### 1.11.0-beta.12
* Add `rule-set merge` command
* Fixes and improvements
#### 1.11.0-beta.3
* Add more masquerade options for hysteria2 **1**
* Fixes and improvements
**1**:
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
#### 1.11.0-alpha.25
* Update quic-go to v0.48.2
* Fixes and improvements
#### 1.11.0-alpha.22
* Add UDP timeout route option **1**
* Fixes and improvements
**1**:
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
#### 1.11.0-alpha.20
* Add UDP GSO support for WireGuard
* Make GSO adaptive **1**
**1**:
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
For TUN, GSO has been removed,
see [Deprecated](/deprecated/#gso-option-in-tun).
#### 1.11.0-alpha.19
* Upgrade WireGuard outbound to endpoint **1**
* Fixes and improvements
**1**:
The new WireGuard endpoint combines inbound and outbound capabilities,
and the old outbound will be removed in sing-box 1.13.0.
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
### 1.10.2 ### 1.10.2
* Add deprecated warnings * Add deprecated warnings
* Fix proxying websocket connections in HTTP/mixed inbounds * Fix proxying websocket connections in HTTP/mixed inbounds
* Fixes and improvements * Fixes and improvements
#### 1.11.0-alpha.18
* Fixes and improvements
#### 1.11.0-alpha.16
* Add `cache_capacity` DNS option **1**
* Add `override_address` and `override_port` route options **2**
* Fixes and improvements
**1**:
See [DNS](/configuration/dns/#cache_capacity).
**2**:
See [Rule Action](/configuration/route/#override_address) and
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
#### 1.11.0-alpha.15
* Improve multi network dialing **1**
* Fixes and improvements
**1**:
New options allow you to configure the network strategy flexibly.
See [Dial Fields](/configuration/shared/dial/#network_strategy),
[Rule Action](/configuration/route/rule_action/#network_strategy)
and [Route](/configuration/route/#default_network_strategy).
#### 1.11.0-alpha.14
* Add multi network dialing **1**
* Fixes and improvements
**1**:
Similar to Surge's strategy.
New options allow you to connect using multiple network interfaces,
prefer or only use one type of interface,
and configure a timeout to fallback to other interfaces.
See [Dial Fields](/configuration/shared/dial/#network_strategy),
[Rule Action](/configuration/route/rule_action/#network_strategy)
and [Route](/configuration/route/#default_network_strategy).
#### 1.11.0-alpha.13
* Fixes and improvements
#### 1.11.0-alpha.12
* Merge route options to route actions **1**
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **2**
* Fixes and improvements
**1**:
Route options in DNS route actions will no longer be considered deprecated,
see [DNS Route Action](/configuration/dns/rule_action/).
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
see [Route Action](/configuration/route/rule_action/).
**2**:
When using in graphical clients, new routing rule items allow you to match on
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
and [Headless Rule](/configuration/rule-set/headless-rule/).
#### 1.11.0-alpha.9
* Improve tun compatibility **1**
* Fixes and improvements
**1**:
When `gvisor` tun stack is enabled, even if the request passes routing,
if the outbound connection establishment fails,
the connection still does not need to be established and a TCP RST is replied.
#### 1.11.0-alpha.7
* Introducing rule actions **1**
**1**:
New rule actions replace legacy inbound fields and special outbound fields,
and can be used for pre-matching **2**.
See [Rule](/configuration/route/rule/),
[Rule Action](/configuration/route/rule_action/),
[DNS Rule](/configuration/dns/rule/) and
[DNS Rule Action](/configuration/dns/rule_action/).
For migration, see
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
**2**:
Similar to Surge's pre-matching.
Specifically, new rule actions allow you to reject connections with
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
before connection established to improve tun's compatibility.
See [Rule Action](/configuration/route/rule_action/).
#### 1.11.0-alpha.6
* Update quic-go to v0.48.1
* Set gateway for tun correctly
* Fixes and improvements
#### 1.11.0-alpha.2
* Add warnings for usage of deprecated features
* Fixes and improvements
#### 1.11.0-alpha.1
* Update quic-go to v0.48.0
* Fixes and improvements
### 1.10.1 ### 1.10.1
* Fixes and improvements * Fixes and improvements
@@ -437,7 +87,7 @@ allows you to write headless rules directly without creating a rule-set file.
**8**: **8**:
With new access control options, not only can you allow Clash dashboards With the new access control options, not only can you allow Clash dashboards
to access the Clash API on your local network, to access the Clash API on your local network,
you can also manually limit the websites that can access the API instead of allowing everyone. you can also manually limit the websites that can access the API instead of allowing everyone.

View File

@@ -1,3 +1,5 @@
# FakeIP
### Structure ### Structure
```json ```json

View File

@@ -1,3 +1,5 @@
# FakeIP
### 结构 ### 结构
```json ```json

View File

@@ -2,9 +2,9 @@
icon: material/new-box icon: material/new-box
--- ---
!!! quote "Changes in sing-box 1.11.0" !!! quote "Changes in sing-box 1.9.0"
:material-plus: [cache_capacity](#cache_capacity) :material-plus: [client_subnet](#client_subnet)
# DNS # DNS
@@ -20,7 +20,6 @@ icon: material/new-box
"disable_cache": false, "disable_cache": false,
"disable_expire": false, "disable_expire": false,
"independent_cache": false, "independent_cache": false,
"cache_capacity": 0,
"reverse_mapping": false, "reverse_mapping": false,
"client_subnet": "", "client_subnet": "",
"fakeip": {} "fakeip": {}
@@ -63,14 +62,6 @@ Disable dns cache expire.
Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance. Make each DNS server's cache independent for special purposes. If enabled, will slightly degrade performance.
#### cache_capacity
!!! question "Since sing-box 1.11.0"
LRU cache capacity.
Value less than 1024 will be ignored.
#### reverse_mapping #### reverse_mapping
Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing. Stores a reverse mapping of IP addresses after responding to a DNS query in order to provide domain names when routing.

View File

@@ -2,9 +2,9 @@
icon: material/new-box icon: material/new-box
--- ---
!!! quote "sing-box 1.11.0 中的更改" !!! quote "sing-box 1.9.0 中的更改"
:material-plus: [cache_capacity](#cache_capacity) :material-plus: [client_subnet](#client_subnet)
# DNS # DNS
@@ -20,7 +20,6 @@ icon: material/new-box
"disable_cache": false, "disable_cache": false,
"disable_expire": false, "disable_expire": false,
"independent_cache": false, "independent_cache": false,
"cache_capacity": 0,
"reverse_mapping": false, "reverse_mapping": false,
"client_subnet": "", "client_subnet": "",
"fakeip": {} "fakeip": {}
@@ -62,14 +61,6 @@ icon: material/new-box
使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。 使每个 DNS 服务器的缓存独立,以满足特殊目的。如果启用,将轻微降低性能。
#### cache_capacity
!!! question "自 sing-box 1.11.0 起"
LRU 缓存容量。
小于 1024 的值将被忽略。
#### reverse_mapping #### reverse_mapping
在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。 在响应 DNS 查询后存储 IP 地址的反向映射以为路由目的提供域名。

View File

@@ -2,17 +2,6 @@
icon: material/new-box icon: material/new-box
--- ---
!!! quote "Changes in sing-box 1.11.0"
:material-plus: [action](#action)
:material-alert: [server](#server)
:material-alert: [disable_cache](#disable_cache)
:material-alert: [rewrite_ttl](#rewrite_ttl)
:material-alert: [client_subnet](#client_subnet)
:material-plus: [network_type](#network_type)
:material-plus: [network_is_expensive](#network_is_expensive)
:material-plus: [network_is_constrained](#network_is_constrained)
!!! quote "Changes in sing-box 1.10.0" !!! quote "Changes in sing-box 1.10.0"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
@@ -25,7 +14,7 @@ icon: material/new-box
:material-plus: [geoip](#geoip) :material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_cidr](#ip_cidr)
:material-plus: [ip_is_private](#ip_is_private) :material-plus: [ip_is_private](#ip_is_private)
:material-plus: [client_subnet](#client_subnet) :material-plus: [client_subnet](#client_subnet)
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
!!! quote "Changes in sing-box 1.8.0" !!! quote "Changes in sing-box 1.8.0"
@@ -128,11 +117,6 @@ icon: material/new-box
1000 1000
], ],
"clash_mode": "direct", "clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [ "wifi_ssid": [
"My WIFI" "My WIFI"
], ],
@@ -151,15 +135,19 @@ icon: material/new-box
"outbound": [ "outbound": [
"direct" "direct"
], ],
"action": "route", "server": "local",
"server": "local" "disable_cache": false,
"rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
}, },
{ {
"type": "logical", "type": "logical",
"mode": "and", "mode": "and",
"rules": [], "rules": [],
"action": "route", "server": "local",
"server": "local" "disable_cache": false,
"rewrite_ttl": 100,
"client_subnet": "127.0.0.1/24"
} }
] ]
} }
@@ -230,7 +218,7 @@ Match domain using regular expression.
!!! failure "Deprecated in sing-box 1.8.0" !!! failure "Deprecated in sing-box 1.8.0"
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets). Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Match geosite. Match geosite.
@@ -238,7 +226,7 @@ Match geosite.
!!! failure "Deprecated in sing-box 1.8.0" !!! failure "Deprecated in sing-box 1.8.0"
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets). GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
Match source geoip. Match source geoip.
@@ -318,39 +306,6 @@ Match user id.
Match Clash mode. Match Clash mode.
#### network_type
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match network type.
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Android and Apple platforms.
Match if network is considered Metered (on Android) or considered expensive,
such as Cellular or a Personal Hotspot (on Apple platforms).
#### network_is_constrained
!!! question "Since sing-box 1.11.0"
!!! quote ""
Only supported in graphical clients on Apple platforms.
Match if network is in Low Data Mode.
#### wifi_ssid #### wifi_ssid
!!! quote "" !!! quote ""
@@ -399,35 +354,29 @@ Match outbound.
`any` can be used as a value to match any outbound. `any` can be used as a value to match any outbound.
#### action #### server
==Required== ==Required==
See [DNS Rule Actions](../rule_action/) for details. Tag of the target dns server.
#### server
!!! failure "Deprecated in sing-box 1.11.0"
Moved to [DNS Rule Action](../rule_action#route).
#### disable_cache #### disable_cache
!!! failure "Deprecated in sing-box 1.11.0" Disable cache and save cache in this query.
Moved to [DNS Rule Action](../rule_action#route).
#### rewrite_ttl #### rewrite_ttl
!!! failure "Deprecated in sing-box 1.11.0" Rewrite TTL in DNS responses.
Moved to [DNS Rule Action](../rule_action#route).
#### client_subnet #### client_subnet
!!! failure "Deprecated in sing-box 1.11.0" !!! question "Since sing-box 1.9.0"
Moved to [DNS Rule Action](../rule_action#route). Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
### Address Filter Fields ### Address Filter Fields

View File

@@ -2,17 +2,6 @@
icon: material/new-box icon: material/new-box
--- ---
!!! quote "sing-box 1.11.0 中的更改"
:material-plus: [action](#action)
:material-alert: [server](#server)
:material-alert: [disable_cache](#disable_cache)
:material-alert: [rewrite_ttl](#rewrite_ttl)
:material-alert: [client_subnet](#client_subnet)
:material-plus: [network_type](#network_type)
:material-plus: [network_is_expensive](#network_is_expensive)
:material-plus: [network_is_constrained](#network_is_constrained)
!!! quote "sing-box 1.10.0 中的更改" !!! quote "sing-box 1.10.0 中的更改"
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
@@ -25,7 +14,7 @@ icon: material/new-box
:material-plus: [geoip](#geoip) :material-plus: [geoip](#geoip)
:material-plus: [ip_cidr](#ip_cidr) :material-plus: [ip_cidr](#ip_cidr)
:material-plus: [ip_is_private](#ip_is_private) :material-plus: [ip_is_private](#ip_is_private)
:material-plus: [client_subnet](#client_subnet) :material-plus: [client_subnet](#client_subnet)
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source) :material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
!!! quote "sing-box 1.8.0 中的更改" !!! quote "sing-box 1.8.0 中的更改"
@@ -128,11 +117,6 @@ icon: material/new-box
1000 1000
], ],
"clash_mode": "direct", "clash_mode": "direct",
"network_type": [
"wifi"
],
"network_is_expensive": false,
"network_is_constrained": false,
"wifi_ssid": [ "wifi_ssid": [
"My WIFI" "My WIFI"
], ],
@@ -151,15 +135,17 @@ icon: material/new-box
"outbound": [ "outbound": [
"direct" "direct"
], ],
"action": "route", "server": "local",
"server": "local" "disable_cache": false,
"client_subnet": "127.0.0.1/24"
}, },
{ {
"type": "logical", "type": "logical",
"mode": "and", "mode": "and",
"rules": [], "rules": [],
"action": "route", "server": "local",
"server": "local" "disable_cache": false,
"client_subnet": "127.0.0.1/24"
} }
] ]
} }
@@ -318,39 +304,6 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配 Clash 模式。 匹配 Clash 模式。
#### network_type
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配网络类型。
Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### network_is_expensive
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Android 与 Apple 平台图形客户端中支持。
匹配如果网络被视为计费 (在 Android) 或被视为昂贵,
像蜂窝网络或个人热点 (在 Apple 平台)。
#### network_is_constrained
!!! question "自 sing-box 1.11.0 起"
!!! quote ""
仅在 Apple 平台图形客户端中支持。
匹配如果网络在低数据模式下。
#### wifi_ssid #### wifi_ssid
!!! quote "" !!! quote ""
@@ -379,7 +332,7 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
!!! failure "已在 sing-box 1.10.0 废弃" !!! failure "已在 sing-box 1.10.0 废弃"
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。 `rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
使规则集中的 `ip_cidr` 规则匹配源 IP。 使规则集中的 `ip_cidr` 规则匹配源 IP。
@@ -399,35 +352,29 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
`any` 可作为值用于匹配任意出站。 `any` 可作为值用于匹配任意出站。
#### action #### server
==必填== ==必填==
参阅 [规则动作](../rule_action/) 目标 DNS 服务器的标签
#### server
!!! failure "已在 sing-box 1.11.0 废弃"
已移动到 [DNS 规则动作](../rule_action#route).
#### disable_cache #### disable_cache
!!! failure "已在 sing-box 1.11.0 废弃" 在此查询中禁用缓存。
已移动到 [DNS 规则动作](../rule_action#route).
#### rewrite_ttl #### rewrite_ttl
!!! failure "已在 sing-box 1.11.0 废弃" 重写 DNS 回应中的 TTL。
已移动到 [DNS 规则动作](../rule_action#route).
#### client_subnet #### client_subnet
!!! failure "已在 sing-box 1.11.0 废弃" !!! question "自 sing-box 1.9.0 "
已移动到 [DNS 规则动作](../rule_action#route). 默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
如果值是 IP 地址而不是前缀,则会自动附加 `/32``/128`
将覆盖 `dns.client_subnet``servers.[].client_subnet`
### 地址筛选字段 ### 地址筛选字段
@@ -473,12 +420,8 @@ Available values: `wifi`, `cellular`, `ethernet` and `other`.
#### mode #### mode
==必填==
`and``or` `and``or`
#### rules #### rules
==必填== 包括的规则。
包括的规则。

View File

@@ -1,77 +0,0 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.11.0"
### route
```json
{
"action": "route", // default
"server": "",
"disable_cache": false,
"rewrite_ttl": 0,
"client_subnet": null
}
```
`route` inherits the classic rule behavior of routing DNS requests to the specified server.
#### server
==Required==
Tag of target server.
#### disable_cache
Disable cache and save cache in this query.
#### rewrite_ttl
Rewrite TTL in DNS responses.
#### client_subnet
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
### route-options
```json
{
"action": "route-options",
"disable_cache": false,
"rewrite_ttl": null,
"client_subnet": null
}
```
`route-options` set options for routing.
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject DNS requests.
#### method
- `default`: Reply with NXDOMAIN.
- `drop`: Drop the request.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.

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