Compare commits

..

56 Commits

Author SHA1 Message Date
世界
d09d2fb665 Bump version 2025-01-30 15:09:54 +08:00
世界
e64cf3b7df Do not set address sets to routes on Apple platforms
Network Extension was observed to stop for unknown reasons
2025-01-27 13:40:39 +08:00
世界
9b73222314 documentation: Bump version 2025-01-27 10:53:14 +08:00
世界
3923b57abf release: Update NDK to r28-beta3 2025-01-27 10:53:14 +08:00
世界
4807e64609 documentation: Fix typo 2025-01-27 10:04:07 +08:00
世界
eeb37d89f1 Fix rule-set upgrade command 2025-01-27 09:50:27 +08:00
世界
08c1ec4b7e Fix legacy routes 2025-01-27 09:50:27 +08:00
HystericalDragon
6b4cf67add Fix endpoints not close 2025-01-27 09:50:27 +08:00
世界
e65926fd08 Fix tests 2025-01-13 15:14:30 +08:00
世界
f2ec319fe1 Fix system time 2025-01-13 15:14:30 +08:00
世界
32377a61b7 Add port hopping for hysteria2 2025-01-13 15:14:30 +08:00
世界
7aac801ccd tun: Set address sets to routes 2025-01-13 15:14:30 +08:00
世界
96fdf59ee4 Fix default dialer on legacy xiaomi systems 2025-01-13 15:14:30 +08:00
世界
50b8f3ab94 Add rule-set merge command 2025-01-13 15:14:30 +08:00
世界
ff7aaf977b Fix DNS match 2025-01-13 15:14:30 +08:00
世界
9a1efbe54d Fix domain strategy 2025-01-13 15:14:30 +08:00
世界
906c21f458 Fix time service 2025-01-13 15:14:30 +08:00
世界
d5e7af7a7e Fix socks5 UDP implementation 2025-01-13 15:14:30 +08:00
世界
4d41f03bd5 clash-api: Fix missing endpoints 2025-01-13 15:14:30 +08:00
世界
30704a15a7 hysteria2: Add more masquerade options 2025-01-13 15:14:30 +08:00
世界
83889178ed Improve timeouts 2025-01-13 15:14:30 +08:00
世界
1d2720bf5e Add UDP timeout route option 2025-01-13 15:14:30 +08:00
世界
c4b6d0eadb Make GSO adaptive 2025-01-13 15:14:30 +08:00
世界
0c66888691 Fix lint 2025-01-13 15:14:30 +08:00
世界
68781387fe refactor: WireGuard endpoint 2025-01-13 15:14:30 +08:00
世界
fd299a0961 refactor: connection manager 2025-01-13 15:14:30 +08:00
世界
285a82050c documentation: Fix typo 2025-01-13 15:14:30 +08:00
世界
2dbb8c55c9 Add override destination to route options 2025-01-13 15:14:30 +08:00
世界
effcf39469 Add dns.cache_capacity 2025-01-13 15:14:30 +08:00
世界
9db9484863 Refactor multi networks strategy 2025-01-13 15:14:30 +08:00
世界
ca813f461b documentation: Remove unused titles 2025-01-13 15:14:30 +08:00
世界
bb46cdb2b3 Add multi network dialing 2025-01-13 15:14:30 +08:00
世界
dcb10c21a1 documentation: Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
05ea0ca00e Add network_[type/is_expensive/is_constrained] rule items 2025-01-13 15:14:29 +08:00
世界
c098f282b1 Merge route options to route actions 2025-01-13 15:14:29 +08:00
世界
ecf82d197c refactor: Platform Interfaces 2025-01-13 15:14:29 +08:00
世界
9afe75586a refactor: Extract services form router 2025-01-13 15:14:29 +08:00
世界
a1be455202 refactor: Modular network manager 2025-01-13 15:14:29 +08:00
世界
19fb214226 refactor: Modular inbound/outbound manager 2025-01-13 15:14:29 +08:00
世界
28ec898a8c documentation: Add rule action 2025-01-13 15:14:29 +08:00
世界
467b1bbeeb documentation: Update the scheduled removal time of deprecated features 2025-01-13 15:14:29 +08:00
世界
02ab8ce806 documentation: Remove outdated icons 2025-01-13 15:14:29 +08:00
世界
ce69e620e9 Migrate bad options to library 2025-01-13 15:14:29 +08:00
世界
1133cf3ef5 Implement udp connect 2025-01-13 15:14:29 +08:00
世界
59a607e303 Implement new deprecated warnings 2025-01-13 15:14:29 +08:00
世界
313be3d7a4 Improve rule actions 2025-01-13 15:14:29 +08:00
世界
4fe40fcee0 Remove unused reject methods 2025-01-13 15:14:29 +08:00
世界
e233fd4fe5 refactor: Modular inbounds/outbounds 2025-01-13 15:14:29 +08:00
世界
9f7683818f Implement dns-hijack 2025-01-13 15:14:29 +08:00
世界
179e3cb2f5 Implement resolve(server) 2025-01-13 15:14:29 +08:00
世界
41b960552d Implement TCP and ICMP rejects 2025-01-13 15:14:29 +08:00
世界
8304295c48 Crazy sekai overturns the small pond 2025-01-13 15:14:29 +08:00
世界
253b41936e Bump version 2025-01-11 20:58:00 +08:00
世界
ce5b4b06b5 auto-redirect: Fix fetch interfaces 2025-01-07 21:24:52 +08:00
世界
50f5006c43 Fix leak in reality server 2025-01-07 17:27:10 +08:00
世界
e42ff22c2e quic: Fix source not unwrapped 2025-01-07 17:27:10 +08:00
58 changed files with 897 additions and 1640 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 == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true' if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
run: |- run: |-
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
tar -xzf go1.20.14.linux-amd64.tar.gz tar -xzf go1.20.14.linux-amd64.tar.gz
@@ -159,7 +159,7 @@ jobs:
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: 2.5.1
install-only: true install-only: true
- name: Extract signing key - name: Extract signing key
run: |- run: |-
@@ -224,7 +224,7 @@ jobs:
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
with: with:
ndk-version: r28-beta2 ndk-version: r28-beta3
- 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
@@ -256,8 +256,7 @@ jobs:
with: with:
path: ~/.gradle path: ~/.gradle
key: gradle-${{ hashFiles('**/*.gradle') }} key: gradle-${{ hashFiles('**/*.gradle') }}
- name: Build release - name: Build
if: github.event_name == 'workflow_dispatch'
run: |- run: |-
go run -v ./cmd/internal/update_android_version --ci go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs mkdir clients/android/app/libs
@@ -268,47 +267,18 @@ jobs:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64 JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }} LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Build debug - name: Prepare upload
if: github.event_name != 'workflow_dispatch'
run: |-
go run -v ./cmd/internal/update_android_version --ci
mkdir clients/android/app/libs
cp libbox.aar clients/android/app/libs
cd clients/android
./gradlew :app:assemblePlayRelease
env:
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
LOCAL_PROPERTIES: ${{ secrets.LOCAL_PROPERTIES }}
- name: Prepare release upload
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
mkdir -p dist/release mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release cp clients/android/app/build/outputs/apk/other/release/*-universal.apk dist/release
- name: Prepare debug upload
if: github.event_name != 'workflow_dispatch'
run: |-
mkdir -p dist/release
cp clients/android/app/build/outputs/apk/play/release/*.apk dist/release
- name: Upload artifact - name: Upload artifact
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: binary-android-apks name: binary-android-apks
path: 'dist' path: 'dist'
- name: Upload debug apk (arm64-v8a)
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: "SFA-${{ needs.calculate_version.outputs.version }}-arm64-v8a.apk"
path: 'dist/release/*-arm64-v8a.apk'
- name: Upload debug apk (universal)
if: github.event_name != 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: "SFA-${{ needs.calculate_version.outputs.version }}-universal.apk"
path: 'dist/release/*-universal.apk'
publish_android: publish_android:
name: Publish Android name: Publish Android
if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android' if: github.event_name == 'workflow_dispatch' && inputs.build == 'publish-android'
@@ -329,7 +299,7 @@ jobs:
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
with: with:
ndk-version: r28-beta2 ndk-version: r28-beta3
- 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
@@ -578,7 +548,7 @@ jobs:
uses: goreleaser/goreleaser-action@v6 uses: goreleaser/goreleaser-action@v6
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: 2.5.1
install-only: true install-only: true
- name: Cache ghr - name: Cache ghr
uses: actions/cache@v4 uses: actions/cache@v4

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
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go" tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
targets: targets:
- windows_amd64_v1 - windows_amd64_v1
- windows_386 - windows_386

9
box.go
View File

@@ -12,7 +12,6 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/taskmonitor"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
@@ -85,6 +84,7 @@ 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) endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
@@ -101,10 +101,7 @@ func New(options Options) (*Box, error) {
ctx = pause.WithDefaultManager(ctx) ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental) experimentalOptions := common.PtrValueOrDefault(options.Experimental)
debugOptions := common.PtrValueOrDefault(experimentalOptions.Debug) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
applyDebugOptions(debugOptions)
ctx = conntrack.ContextWithDefaultTracker(ctx, debugOptions.OOMKiller, uint64(debugOptions.MemoryLimit))
var needCacheFile bool var needCacheFile bool
var needClashAPI bool var needClashAPI bool
var needV2RayAPI bool var needV2RayAPI bool
@@ -402,7 +399,7 @@ func (s *Box) Close() error {
close(s.done) close(s.done)
} }
err := common.Close( err := common.Close(
s.inbound, s.outbound, s.router, s.connection, s.network, s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.network,
) )
for _, lifecycleService := range s.services { for _, lifecycleService := range s.services {
err = E.Append(err, lifecycleService.Close(), func(err error) error { err = E.Append(err, lifecycleService.Close(), func(err error) error {

View File

@@ -48,7 +48,7 @@ func FindSDK() {
} }
func findNDK() bool { func findNDK() bool {
const fixedVersion = "28.0.12674087" const fixedVersion = "28.0.12916984"
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

@@ -61,14 +61,15 @@ func upgradeRuleSet(sourcePath string) error {
log.Info("already up-to-date") log.Info("already up-to-date")
return nil return nil
} }
plainRuleSet, err := plainRuleSetCompat.Upgrade() plainRuleSetCompat.Options, 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(plainRuleSet) err = encoder.Encode(plainRuleSetCompat)
if err != nil { if err != nil {
return E.Cause(err, "encode config") return E.Cause(err, "encode config")
} }

View File

@@ -4,7 +4,6 @@ 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"
@@ -58,7 +57,7 @@ func syncTime() error {
return err return err
} }
if commandSyncTimeWrite { if commandSyncTimeWrite {
err = settings.SetSystemTime(response.Time) err = ntp.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")
} }

54
common/conntrack/conn.go Normal file
View File

@@ -0,0 +1,54 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/x/list"
)
type Conn struct {
net.Conn
element *list.Element[io.Closer]
}
func NewConn(conn net.Conn) (net.Conn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
}
return &Conn{
Conn: conn,
element: element,
}, nil
}
func (c *Conn) Close() error {
if c.element.Value != nil {
connAccess.Lock()
if c.element.Value != nil {
openConnection.Remove(c.element)
c.element.Value = nil
}
connAccess.Unlock()
}
return c.Conn.Close()
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) ReaderReplaceable() bool {
return true
}
func (c *Conn) WriterReplaceable() bool {
return true
}

View File

@@ -1,14 +0,0 @@
package conntrack
import (
"context"
"github.com/sagernet/sing/service"
)
func ContextWithDefaultTracker(ctx context.Context, killerEnabled bool, memoryLimit uint64) context.Context {
if service.FromContext[Tracker](ctx) != nil {
return ctx
}
return service.ContextWith[Tracker](ctx, NewDefaultTracker(killerEnabled, memoryLimit))
}

View File

@@ -1,245 +0,0 @@
package conntrack
import (
"net"
"net/netip"
runtimeDebug "runtime/debug"
"sync"
"time"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
)
var _ Tracker = (*DefaultTracker)(nil)
type DefaultTracker struct {
connAccess sync.RWMutex
connList list.List[net.Conn]
connAddress map[netip.AddrPort]netip.AddrPort
packetConnAccess sync.RWMutex
packetConnList list.List[AbstractPacketConn]
packetConnAddress map[netip.AddrPort]bool
pendingAccess sync.RWMutex
pendingList list.List[netip.AddrPort]
killerEnabled bool
memoryLimit uint64
killerLastCheck time.Time
}
func NewDefaultTracker(killerEnabled bool, memoryLimit uint64) *DefaultTracker {
return &DefaultTracker{
connAddress: make(map[netip.AddrPort]netip.AddrPort),
packetConnAddress: make(map[netip.AddrPort]bool),
killerEnabled: killerEnabled,
memoryLimit: memoryLimit,
}
}
func (t *DefaultTracker) NewConn(conn net.Conn) (net.Conn, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.connAccess.Lock()
element := t.connList.PushBack(conn)
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
t.connAccess.Unlock()
return &Conn{
Conn: conn,
closeFunc: common.OnceFunc(func() {
t.removeConn(element)
}),
}, nil
}
func (t *DefaultTracker) NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.connAccess.Lock()
element := t.connList.PushBack(conn)
t.connAddress[M.AddrPortFromNet(conn.LocalAddr())] = M.AddrPortFromNet(conn.RemoteAddr())
t.connAccess.Unlock()
return N.OnceClose(func(it error) {
t.removeConn(element)
}), nil
}
func (t *DefaultTracker) NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.packetConnAccess.Lock()
element := t.packetConnList.PushBack(conn)
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
t.packetConnAccess.Unlock()
return &PacketConn{
PacketConn: conn,
closeFunc: common.OnceFunc(func() {
t.removePacketConn(element)
}),
}, nil
}
func (t *DefaultTracker) NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error) {
err := t.KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
t.packetConnAccess.Lock()
element := t.packetConnList.PushBack(conn)
t.packetConnAddress[M.AddrPortFromNet(conn.LocalAddr())] = true
t.packetConnAccess.Unlock()
return N.OnceClose(func(it error) {
t.removePacketConn(element)
}), nil
}
func (t *DefaultTracker) CheckConn(source netip.AddrPort, destination netip.AddrPort) bool {
t.connAccess.RLock()
defer t.connAccess.RUnlock()
return t.connAddress[source] == destination
}
func (t *DefaultTracker) CheckPacketConn(source netip.AddrPort) bool {
t.packetConnAccess.RLock()
defer t.packetConnAccess.RUnlock()
return t.packetConnAddress[source]
}
func (t *DefaultTracker) AddPendingDestination(destination netip.AddrPort) func() {
t.pendingAccess.Lock()
defer t.pendingAccess.Unlock()
element := t.pendingList.PushBack(destination)
return func() {
t.pendingAccess.Lock()
defer t.pendingAccess.Unlock()
t.pendingList.Remove(element)
}
}
func (t *DefaultTracker) CheckDestination(destination netip.AddrPort) bool {
t.pendingAccess.RLock()
defer t.pendingAccess.RUnlock()
for element := t.pendingList.Front(); element != nil; element = element.Next() {
if element.Value == destination {
return true
}
}
return false
}
func (t *DefaultTracker) KillerCheck() error {
if !t.killerEnabled {
return nil
}
nowTime := time.Now()
if nowTime.Sub(t.killerLastCheck) < 3*time.Second {
return nil
}
t.killerLastCheck = nowTime
if memory.Total() > t.memoryLimit {
t.Close()
go func() {
time.Sleep(time.Second)
runtimeDebug.FreeOSMemory()
}()
return E.New("out of memory")
}
return nil
}
func (t *DefaultTracker) Count() int {
t.connAccess.RLock()
defer t.connAccess.RUnlock()
t.packetConnAccess.RLock()
defer t.packetConnAccess.RUnlock()
return t.connList.Len() + t.packetConnList.Len()
}
func (t *DefaultTracker) Close() {
t.connAccess.Lock()
for element := t.connList.Front(); element != nil; element = element.Next() {
element.Value.Close()
}
t.connList.Init()
t.connAccess.Unlock()
t.packetConnAccess.Lock()
for element := t.packetConnList.Front(); element != nil; element = element.Next() {
element.Value.Close()
}
t.packetConnList.Init()
t.packetConnAccess.Unlock()
}
func (t *DefaultTracker) removeConn(element *list.Element[net.Conn]) {
t.connAccess.Lock()
defer t.connAccess.Unlock()
delete(t.connAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
t.connList.Remove(element)
}
func (t *DefaultTracker) removePacketConn(element *list.Element[AbstractPacketConn]) {
t.packetConnAccess.Lock()
defer t.packetConnAccess.Unlock()
delete(t.packetConnAddress, M.AddrPortFromNet(element.Value.LocalAddr()))
t.packetConnList.Remove(element)
}
type Conn struct {
net.Conn
closeFunc func()
}
func (c *Conn) Close() error {
c.closeFunc()
return c.Conn.Close()
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) ReaderReplaceable() bool {
return true
}
func (c *Conn) WriterReplaceable() bool {
return true
}
type PacketConn struct {
net.PacketConn
closeFunc func()
}
func (c *PacketConn) Close() error {
c.closeFunc()
return c.PacketConn.Close()
}
func (c *PacketConn) Upstream() any {
return c.PacketConn
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}

View File

@@ -0,0 +1,35 @@
package conntrack
import (
runtimeDebug "runtime/debug"
"time"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory"
)
var (
KillerEnabled bool
MemoryLimit uint64
killerLastCheck time.Time
)
func KillerCheck() error {
if !KillerEnabled {
return nil
}
nowTime := time.Now()
if nowTime.Sub(killerLastCheck) < 3*time.Second {
return nil
}
killerLastCheck = nowTime
if memory.Total() > MemoryLimit {
Close()
go func() {
time.Sleep(time.Second)
runtimeDebug.FreeOSMemory()
}()
return E.New("out of memory")
}
return nil
}

View File

@@ -0,0 +1,55 @@
package conntrack
import (
"io"
"net"
"github.com/sagernet/sing/common/bufio"
"github.com/sagernet/sing/common/x/list"
)
type PacketConn struct {
net.PacketConn
element *list.Element[io.Closer]
}
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
connAccess.Lock()
element := openConnection.PushBack(conn)
connAccess.Unlock()
if KillerEnabled {
err := KillerCheck()
if err != nil {
conn.Close()
return nil, err
}
}
return &PacketConn{
PacketConn: conn,
element: element,
}, nil
}
func (c *PacketConn) Close() error {
if c.element.Value != nil {
connAccess.Lock()
if c.element.Value != nil {
openConnection.Remove(c.element)
c.element.Value = nil
}
connAccess.Unlock()
}
return c.PacketConn.Close()
}
func (c *PacketConn) Upstream() any {
return bufio.NewPacketConn(c.PacketConn)
}
func (c *PacketConn) ReaderReplaceable() bool {
return true
}
func (c *PacketConn) WriterReplaceable() bool {
return true
}

47
common/conntrack/track.go Normal file
View File

@@ -0,0 +1,47 @@
package conntrack
import (
"io"
"sync"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/x/list"
)
var (
connAccess sync.RWMutex
openConnection list.List[io.Closer]
)
func Count() int {
if !Enabled {
return 0
}
return openConnection.Len()
}
func List() []io.Closer {
if !Enabled {
return nil
}
connAccess.RLock()
defer connAccess.RUnlock()
connList := make([]io.Closer, 0, openConnection.Len())
for element := openConnection.Front(); element != nil; element = element.Next() {
connList = append(connList, element.Value)
}
return connList
}
func Close() {
if !Enabled {
return
}
connAccess.Lock()
defer connAccess.Unlock()
for element := openConnection.Front(); element != nil; element = element.Next() {
common.Close(element.Value)
element.Value = nil
}
openConnection.Init()
}

View File

@@ -0,0 +1,5 @@
//go:build !with_conntrack
package conntrack
const Enabled = false

View File

@@ -0,0 +1,5 @@
//go:build with_conntrack
package conntrack
const Enabled = true

View File

@@ -1,32 +0,0 @@
package conntrack
import (
"net"
"net/netip"
"time"
N "github.com/sagernet/sing/common/network"
)
// TODO: add to N
type AbstractPacketConn interface {
Close() error
LocalAddr() net.Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
type Tracker interface {
NewConn(conn net.Conn) (net.Conn, error)
NewPacketConn(conn net.PacketConn) (net.PacketConn, error)
NewConnEx(conn net.Conn) (N.CloseHandlerFunc, error)
NewPacketConnEx(conn AbstractPacketConn) (N.CloseHandlerFunc, error)
CheckConn(source netip.AddrPort, destination netip.AddrPort) bool
CheckPacketConn(source netip.AddrPort) bool
AddPendingDestination(destination netip.AddrPort) func()
CheckDestination(destination netip.AddrPort) bool
KillerCheck() error
Count() int
Close()
}

View File

@@ -28,7 +28,6 @@ var (
) )
type DefaultDialer struct { type DefaultDialer struct {
tracker conntrack.Tracker
dialer4 tcpDialer dialer4 tcpDialer
dialer6 tcpDialer dialer6 tcpDialer
udpDialer4 net.Dialer udpDialer4 net.Dialer
@@ -47,7 +46,6 @@ type DefaultDialer struct {
} }
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) { func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
tracker := service.FromContext[conntrack.Tracker](ctx)
networkManager := service.FromContext[adapter.NetworkManager](ctx) networkManager := service.FromContext[adapter.NetworkManager](ctx)
platformInterface := service.FromContext[platform.Interface](ctx) platformInterface := service.FromContext[platform.Interface](ctx)
@@ -199,7 +197,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
return nil, err return nil, err
} }
return &DefaultDialer{ return &DefaultDialer{
tracker: tracker,
dialer4: tcpDialer4, dialer4: tcpDialer4,
dialer6: tcpDialer6, dialer6: tcpDialer6,
udpDialer4: udpDialer4, udpDialer4: udpDialer4,
@@ -222,26 +219,18 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
return nil, E.New("invalid address") return nil, E.New("invalid address")
} }
if d.networkStrategy == nil { if d.networkStrategy == nil {
if address.IsFqdn() {
return nil, E.New("unexpected domain destination")
}
// Since pending check is only used by ndis, it is not performed for non-windows connections which are only supported on platform clients
if d.tracker != nil {
done := d.tracker.AddPendingDestination(address.AddrPort())
defer done()
}
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkUDP: case N.NetworkUDP:
if !address.IsIPv6() { if !address.IsIPv6() {
return d.trackConn(d.udpDialer4.DialContext(ctx, network, address.String())) return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
} else { } else {
return d.trackConn(d.udpDialer6.DialContext(ctx, network, address.String())) return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
} }
} }
if !address.IsIPv6() { if !address.IsIPv6() {
return d.trackConn(DialSlowContext(&d.dialer4, ctx, network, address)) return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
} else { } else {
return d.trackConn(DialSlowContext(&d.dialer6, ctx, network, address)) return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
} }
} else { } else {
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay) return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
@@ -293,17 +282,17 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
if !fastFallback && !isPrimary { if !fastFallback && !isPrimary {
d.networkLastFallback.Store(time.Now()) d.networkLastFallback.Store(time.Now())
} }
return d.trackConn(conn, nil) 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 d.networkStrategy == nil {
if destination.IsIPv6() { if destination.IsIPv6() {
return d.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 d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)) return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
} else { } else {
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)) 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 d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
@@ -340,23 +329,23 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
return nil, err return nil, err
} }
} }
return d.trackPacketConn(packetConn, nil) 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)
} }
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) { func trackConn(conn net.Conn, err error) (net.Conn, error) {
if d.tracker == nil || err != nil { if !conntrack.Enabled || err != nil {
return conn, err return conn, err
} }
return d.tracker.NewConn(conn) return conntrack.NewConn(conn)
} }
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) { func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
if err != nil { if !conntrack.Enabled || err != nil {
return conn, err return conn, err
} }
return d.tracker.NewPacketConn(conn) return conntrack.NewPacketConn(conn)
} }

View File

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

View File

@@ -1,14 +0,0 @@
//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

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

@@ -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 &utlsConnWrapper{uConn}, nil return &realityClientConnWrapper{uConn}, nil
} }
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) { func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
@@ -249,3 +249,36 @@ 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

@@ -194,3 +194,9 @@ 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

@@ -23,7 +23,6 @@ const (
TypeVLESS = "vless" TypeVLESS = "vless"
TypeTUIC = "tuic" TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2" TypeHysteria2 = "hysteria2"
TypeNDIS = "ndis"
) )
const ( const (
@@ -81,8 +80,6 @@ func ProxyDisplayName(proxyType string) string {
return "Selector" return "Selector"
case TypeURLTest: case TypeURLTest:
return "URLTest" return "URLTest"
case TypeNDIS:
return "NDIS"
default: default:
return "Unknown" return "Unknown"
} }

View File

@@ -3,6 +3,7 @@ package box
import ( import (
"runtime/debug" "runtime/debug"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
) )
@@ -25,5 +26,9 @@ func applyDebugOptions(options option.DebugOptions) {
} }
if options.MemoryLimit != 0 { if options.MemoryLimit != 0 {
debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5)) debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5))
conntrack.MemoryLimit = uint64(options.MemoryLimit)
}
if options.OOMKiller != nil {
conntrack.KillerEnabled = *options.OOMKiller
} }
} }

View File

@@ -2,6 +2,130 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
### 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 #### 1.11.0-beta.20
* Hysteria2 `ignore_client_bandwidth` behavior update **1** * Hysteria2 `ignore_client_bandwidth` behavior update **1**

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! failure "已在 sing-box 1.11.0 废弃" !!! failure "已在 sing-box 1.11.0 废弃"
WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。 WireGuard 出站已被用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-wireguard-outbound-to-endpoint)。
!!! quote "sing-box 1.11.0 中的更改" !!! quote "sing-box 1.11.0 中的更改"

View File

@@ -31,6 +31,45 @@ Tag of target outbound.
See `route-options` fields below. See `route-options` fields below.
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject connections
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
For non-tun connections and already established connections, will just be closed.
#### method
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### route-options ### route-options
```json ```json
@@ -109,45 +148,6 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
| 443 | `quic` | | 443 | `quic` |
| 3478 | `stun` | | 3478 | `stun` |
### reject
```json
{
"action": "reject",
"method": "default", // default
"no_drop": false
}
```
`reject` reject connections
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
For non-tun connections and already established connections, will just be closed.
#### method
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
- `drop`: Drop packets.
#### no_drop
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
Not available when `method` is set to drop.
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` hijack DNS requests to the sing-box DNS module.
## Non-final actions
### sniff ### sniff
```json ```json

View File

@@ -27,6 +27,45 @@ icon: material/new-box
参阅下方的 `route-options` 字段。 参阅下方的 `route-options` 字段。
### reject
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
`reject` 拒绝连接。
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
对于非 tun 连接和已建立的连接,将直接关闭。
#### method
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
### route-options ### route-options
```json ```json
@@ -107,45 +146,6 @@ UDP 连接超时时间。
| 443 | `quic` | | 443 | `quic` |
| 3478 | `stun` | | 3478 | `stun` |
### reject
```json
{
"action": "reject",
"method": "default", // 默认
"no_drop": false
}
```
`reject` 拒绝连接。
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
对于非 tun 连接和已建立的连接,将直接关闭。
#### method
- `default`: 对于 TCP 连接回复 RST对于 UDP 包回复 ICMP 端口不可达。
- `drop`: 丢弃数据包。
#### no_drop
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`
`method` 设为 `drop` 时不可用。
### hijack-dns
```json
{
"action": "hijack-dns"
}
```
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
## 非最终动作
### sniff ### sniff
```json ```json

View File

@@ -5,6 +5,8 @@ import (
"net" "net"
runtimeDebug "runtime/debug" runtimeDebug "runtime/debug"
"time" "time"
"github.com/sagernet/sing-box/common/conntrack"
) )
func (c *CommandClient) CloseConnections() error { func (c *CommandClient) CloseConnections() error {
@@ -17,7 +19,7 @@ func (c *CommandClient) CloseConnections() error {
} }
func (s *CommandServer) handleCloseConnections(conn net.Conn) error { func (s *CommandServer) handleCloseConnections(conn net.Conn) error {
tracker.Close() conntrack.Close()
go func() { go func() {
time.Sleep(time.Second) time.Sleep(time.Second)
runtimeDebug.FreeOSMemory() runtimeDebug.FreeOSMemory()

View File

@@ -6,6 +6,7 @@ import (
"runtime" "runtime"
"time" "time"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/experimental/clashapi" "github.com/sagernet/sing-box/experimental/clashapi"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/memory" "github.com/sagernet/sing/common/memory"
@@ -27,7 +28,7 @@ func (s *CommandServer) readStatus() StatusMessage {
var message StatusMessage var message StatusMessage
message.Memory = int64(memory.Inuse()) message.Memory = int64(memory.Inuse())
message.Goroutines = int32(runtime.NumGoroutine()) message.Goroutines = int32(runtime.NumGoroutine())
message.ConnectionsOut = int32(tracker.Count()) message.ConnectionsOut = int32(conntrack.Count())
if s.service != nil { if s.service != nil {
message.TrafficAvailable = true message.TrafficAvailable = true

View File

@@ -66,10 +66,6 @@ func (s *platformInterfaceStub) OpenTun(options *tun.Options, platformOptions op
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }
func (s *platformInterfaceStub) UpdateRouteOptions(options *tun.Options, platformInterface option.TunPlatformOptions) error {
return os.ErrInvalid
}
func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool { func (s *platformInterfaceStub) UsePlatformDefaultInterfaceMonitor() bool {
return true return true
} }

View File

@@ -7,21 +7,17 @@ import (
"github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/conntrack"
) )
var tracker *conntrack.DefaultTracker
func SetMemoryLimit(enabled bool) { func SetMemoryLimit(enabled bool) {
if tracker != nil {
tracker.Close()
}
const memoryLimit = 45 * 1024 * 1024 const memoryLimit = 45 * 1024 * 1024
const memoryLimitGo = memoryLimit / 1.5 const memoryLimitGo = memoryLimit / 1.5
if enabled { if enabled {
runtimeDebug.SetGCPercent(10) runtimeDebug.SetGCPercent(10)
runtimeDebug.SetMemoryLimit(memoryLimitGo) runtimeDebug.SetMemoryLimit(memoryLimitGo)
tracker = conntrack.NewDefaultTracker(true, memoryLimit) conntrack.KillerEnabled = true
conntrack.MemoryLimit = memoryLimit
} else { } else {
runtimeDebug.SetGCPercent(100) runtimeDebug.SetGCPercent(100)
runtimeDebug.SetMemoryLimit(math.MaxInt64) runtimeDebug.SetMemoryLimit(math.MaxInt64)
tracker = conntrack.NewDefaultTracker(false, 0) conntrack.KillerEnabled = false
} }
} }

View File

@@ -9,7 +9,6 @@ type PlatformInterface interface {
UsePlatformAutoDetectInterfaceControl() bool UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int32) error AutoDetectInterfaceControl(fd int32) error
OpenTun(options TunOptions) (int32, error) OpenTun(options TunOptions) (int32, error)
UpdateRouteOptions(options TunOptions) error
WriteLog(message string) WriteLog(message string)
UseProcFS() bool UseProcFS() bool
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error) FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)

View File

@@ -13,7 +13,6 @@ type Interface interface {
UsePlatformAutoDetectInterfaceControl() bool UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error AutoDetectInterfaceControl(fd int) error
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
Interfaces() ([]adapter.NetworkInterface, error) Interfaces() ([]adapter.NetworkInterface, error)
UnderNetworkExtension() bool UnderNetworkExtension() bool

View File

@@ -12,7 +12,6 @@ import (
"github.com/sagernet/sing-box" "github.com/sagernet/sing-box"
"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/process" "github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@@ -61,7 +60,6 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
useProcFS: platformInterface.UseProcFS(), useProcFS: platformInterface.UseProcFS(),
} }
service.MustRegister[platform.Interface](ctx, platformWrapper) service.MustRegister[platform.Interface](ctx, platformWrapper)
service.MustRegister[conntrack.Tracker](ctx, tracker)
instance, err := box.New(box.Options{ instance, err := box.New(box.Options{
Context: ctx, Context: ctx,
Options: options, Options: options,
@@ -176,20 +174,6 @@ func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions
return tun.New(*options) return tun.New(*options)
} }
func (w *platformInterfaceWrapper) UpdateRouteOptions(options *tun.Options, platformOptions option.TunPlatformOptions) error {
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
return E.New("android: unsupported uid options")
}
if len(options.IncludeAndroidUser) > 0 {
return E.New("android: unsupported android_user option")
}
routeRanges, err := options.BuildAutoRouteRanges(true)
if err != nil {
return err
}
return w.iif.UpdateRouteOptions(&tunOptions{options, routeRanges, platformOptions})
}
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor { func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor {
return &platformDefaultInterfaceMonitor{ return &platformDefaultInterfaceMonitor{
platformInterfaceWrapper: w, platformInterfaceWrapper: w,

9
go.mod
View File

@@ -26,14 +26,14 @@ require (
github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff
github.com/sagernet/quic-go v0.48.2-beta.1 github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.6.0-beta.9 github.com/sagernet/sing v0.6.0-beta.12
github.com/sagernet/sing-dns v0.4.0-beta.1 github.com/sagernet/sing-dns v0.4.0-beta.2
github.com/sagernet/sing-mux v0.3.0-alpha.1 github.com/sagernet/sing-mux v0.3.0-alpha.1
github.com/sagernet/sing-quic v0.4.0-beta.3 github.com/sagernet/sing-quic v0.4.0-beta.4
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 github.com/sagernet/sing-shadowtls v0.2.0-alpha.2
github.com/sagernet/sing-tun v0.6.0-beta.7 github.com/sagernet/sing-tun v0.6.0-beta.8
github.com/sagernet/sing-vmess v0.2.0-beta.2 github.com/sagernet/sing-vmess v0.2.0-beta.2
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7 github.com/sagernet/utls v1.6.7
@@ -41,7 +41,6 @@ require (
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/wiresock/ndisapi-go v0.0.0-20241230094942-3299a7566e08
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.31.0

19
go.sum
View File

@@ -119,22 +119,22 @@ github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs= github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY= github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY=
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.4.0-beta.3 h1:cOBjlhVdRZmBm6hIw1GleERpnTSFdBB2htgx5kQ5uqg= github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
github.com/sagernet/sing-quic v0.4.0-beta.3/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM= github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
github.com/sagernet/sing-tun v0.6.0-beta.7 h1:FCSX8oGBqb0H57AAvfGeeH/jMGYWCOg6XWkN/oeES+0= github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
github.com/sagernet/sing-tun v0.6.0-beta.7/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs=
github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@@ -158,8 +158,6 @@ github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gV
github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wiresock/ndisapi-go v0.0.0-20241230094942-3299a7566e08 h1:is+7xN6CAKtgxt3mDSl9OQNvjfi6LggugSP07QhDtws=
github.com/wiresock/ndisapi-go v0.0.0-20241230094942-3299a7566e08/go.mod h1:lFE7JYt3LC2UYJ31mRDwl/K35pbtxDnkSDlXrYzgyqg=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
@@ -193,7 +191,6 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View File

@@ -1,12 +0,0 @@
//go:build windows && with_gvisor
package include
import (
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/protocol/ndis"
)
func registerNDISInbound(registry *inbound.Registry) {
ndis.RegisterInbound(registry)
}

View File

@@ -1,20 +0,0 @@
//go:build windows && !with_gvisor
package include
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
)
func registerNDISInbound(registry *inbound.Registry) {
inbound.Register[option.NDISInboundOptions](registry, C.TypeNDIS, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NDISInboundOptions) (adapter.Inbound, error) {
return nil, tun.ErrGVisorNotIncluded
})
}

View File

@@ -1,20 +0,0 @@
//go:build !windows
package include
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func registerNDISInbound(registry *inbound.Registry) {
inbound.Register[option.NDISInboundOptions](registry, C.TypeNDIS, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NDISInboundOptions) (adapter.Inbound, error) {
return nil, E.New("NDIS is only supported in windows")
})
}

View File

@@ -51,7 +51,6 @@ func InboundRegistry() *inbound.Registry {
registerQUICInbounds(registry) registerQUICInbounds(registry)
registerStubForRemovedInbounds(registry) registerStubForRemovedInbounds(registry)
registerNDISInbound(registry)
return registry return registry
} }

View File

@@ -13,7 +13,7 @@ type DebugOptions struct {
PanicOnFault *bool `json:"panic_on_fault,omitempty"` PanicOnFault *bool `json:"panic_on_fault,omitempty"`
TraceBack string `json:"trace_back,omitempty"` TraceBack string `json:"trace_back,omitempty"`
MemoryLimit MemoryBytes `json:"memory_limit,omitempty"` MemoryLimit MemoryBytes `json:"memory_limit,omitempty"`
OOMKiller bool `json:"oom_killer,omitempty"` OOMKiller *bool `json:"oom_killer,omitempty"`
} }
type MemoryBytes uint64 type MemoryBytes uint64

View File

@@ -74,6 +74,7 @@ func (m *Hysteria2Masquerade) UnmarshalJSON(bytes []byte) error {
default: default:
return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) return E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme)
} }
return nil
} }
err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m)) err = json.Unmarshal(bytes, (*_Hysteria2Masquerade)(m))
if err != nil { if err != nil {

View File

@@ -1,17 +0,0 @@
package option
import (
"net/netip"
"github.com/sagernet/sing/common/json/badoption"
)
type NDISInboundOptions struct {
Network NetworkList `json:"network,omitempty"`
RouteAddress badoption.Listable[netip.Prefix] `json:"route_address,omitempty"`
RouteAddressSet badoption.Listable[string] `json:"route_address_set,omitempty"`
RouteExcludeAddress badoption.Listable[netip.Prefix] `json:"route_exclude_address,omitempty"`
RouteExcludeAddressSet badoption.Listable[string] `json:"route_exclude_address_set,omitempty"`
InterfaceName string `json:"interface_name,omitempty"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
}

View File

@@ -80,34 +80,22 @@ func (i *Inbound) Close() error {
} }
func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) {
var destination M.Socksaddr i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil)
switch i.overrideOption {
case 1:
destination = i.overrideDestination
case 2:
destination = i.overrideDestination
destination.Port = i.listener.UDPAddr().Port
case 3:
destination = source
destination.Port = i.overrideDestination.Port
}
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil)
} }
func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
metadata.Inbound = i.Tag() metadata.Inbound = i.Tag()
metadata.InboundType = i.Type() metadata.InboundType = i.Type()
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()) destination := metadata.OriginDestination
switch i.overrideOption { switch i.overrideOption {
case 1: case 1:
metadata.Destination = i.overrideDestination destination = i.overrideDestination
case 2: case 2:
destination := i.overrideDestination destination.Addr = i.overrideDestination.Addr
destination.Port = metadata.Destination.Port
metadata.Destination = destination
case 3: case 3:
metadata.Destination.Port = i.overrideDestination.Port destination.Port = metadata.Destination.Port
} }
metadata.Destination = destination
if i.overrideOption != 0 { if i.overrideOption != 0 {
i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
} }
@@ -125,6 +113,16 @@ func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
//nolint:staticcheck //nolint:staticcheck
metadata.InboundOptions = i.listener.ListenOptions().InboundOptions metadata.InboundOptions = i.listener.ListenOptions().InboundOptions
metadata.Source = source metadata.Source = source
destination = i.listener.UDPAddr()
switch i.overrideOption {
case 1:
destination = i.overrideDestination
case 2:
destination.Addr = i.overrideDestination.Addr
case 3:
destination.Port = i.overrideDestination.Port
default:
}
metadata.Destination = destination metadata.Destination = destination
metadata.OriginDestination = i.listener.UDPAddr() metadata.OriginDestination = i.listener.UDPAddr()
i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)

View File

@@ -1,110 +0,0 @@
//go:build windows
package ndis
import (
"sync"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/wiresock/ndisapi-go"
"github.com/wiresock/ndisapi-go/driver"
)
var _ stack.LinkEndpoint = (*ndisEndpoint)(nil)
type ndisEndpoint struct {
filter *driver.QueuedPacketFilter
mtu uint32
address tcpip.LinkAddress
dispatcher stack.NetworkDispatcher
}
func (e *ndisEndpoint) MTU() uint32 {
return e.mtu
}
func (e *ndisEndpoint) SetMTU(mtu uint32) {
}
func (e *ndisEndpoint) MaxHeaderLength() uint16 {
return header.EthernetMinimumSize
}
func (e *ndisEndpoint) LinkAddress() tcpip.LinkAddress {
return e.address
}
func (e *ndisEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
}
func (e *ndisEndpoint) Capabilities() stack.LinkEndpointCapabilities {
return 0
}
func (e *ndisEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
e.dispatcher = dispatcher
}
func (e *ndisEndpoint) IsAttached() bool {
return e.dispatcher != nil
}
func (e *ndisEndpoint) Wait() {
}
func (e *ndisEndpoint) ARPHardwareType() header.ARPHardwareType {
return header.ARPHardwareEther
}
func (e *ndisEndpoint) AddHeader(pkt *stack.PacketBuffer) {
eth := header.Ethernet(pkt.LinkHeader().Push(header.EthernetMinimumSize))
fields := header.EthernetFields{
SrcAddr: pkt.EgressRoute.LocalLinkAddress,
DstAddr: pkt.EgressRoute.RemoteLinkAddress,
Type: pkt.NetworkProtocolNumber,
}
eth.Encode(&fields)
}
func (e *ndisEndpoint) ParseHeader(pkt *stack.PacketBuffer) bool {
_, ok := pkt.LinkHeader().Consume(header.EthernetMinimumSize)
return ok
}
func (e *ndisEndpoint) Close() {
}
func (e *ndisEndpoint) SetOnCloseAction(f func()) {
}
var bufferPool = sync.Pool{
New: func() any {
return new(ndisapi.IntermediateBuffer)
},
}
func (e *ndisEndpoint) WritePackets(list stack.PacketBufferList) (int, tcpip.Error) {
for _, packetBuffer := range list.AsSlice() {
ndisBuf := bufferPool.Get().(*ndisapi.IntermediateBuffer)
viewList, offset := packetBuffer.AsViewList()
var view *buffer.View
for view = viewList.Front(); view != nil && offset >= view.Size(); view = view.Next() {
offset -= view.Size()
}
index := copy(ndisBuf.Buffer[:], view.AsSlice()[offset:])
for view = view.Next(); view != nil; view = view.Next() {
index += copy(ndisBuf.Buffer[index:], view.AsSlice())
}
ndisBuf.Length = uint32(index)
err := e.filter.InsertPacketToMstcp(ndisBuf)
bufferPool.Put(ndisBuf)
if err != nil {
return 0, &tcpip.ErrAborted{}
}
}
return list.Len(), nil
}

View File

@@ -1,203 +0,0 @@
//go:build windows
package ndis
import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
"github.com/wiresock/ndisapi-go"
"go4.org/netipx"
)
func RegisterInbound(registry *inbound.Registry) {
inbound.Register[option.NDISInboundOptions](registry, C.TypeNDIS, NewInbound)
}
type Inbound struct {
inbound.Adapter
ctx context.Context
router adapter.Router
logger log.ContextLogger
api *ndisapi.NdisApi
tracker conntrack.Tracker
routeAddress []netip.Prefix
routeExcludeAddress []netip.Prefix
routeRuleSet []adapter.RuleSet
routeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
routeExcludeRuleSet []adapter.RuleSet
routeExcludeRuleSetCallback []*list.Element[adapter.RuleSetUpdateCallback]
stack *Stack
}
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NDISInboundOptions) (adapter.Inbound, error) {
api, err := ndisapi.NewNdisApi()
if err != nil {
return nil, E.Cause(err, "create NDIS API")
}
//if !api.IsDriverLoaded() {
// return nil, E.New("missing NDIS driver")
//}
networkManager := service.FromContext[adapter.NetworkManager](ctx)
trackerOut := service.FromContext[conntrack.Tracker](ctx)
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
var (
routeRuleSet []adapter.RuleSet
routeExcludeRuleSet []adapter.RuleSet
)
for _, routeAddressSet := range options.RouteAddressSet {
ruleSet, loaded := router.RuleSet(routeAddressSet)
if !loaded {
return nil, E.New("parse route_address_set: rule-set not found: ", routeAddressSet)
}
ruleSet.IncRef()
routeRuleSet = append(routeRuleSet, ruleSet)
}
for _, routeExcludeAddressSet := range options.RouteExcludeAddressSet {
ruleSet, loaded := router.RuleSet(routeExcludeAddressSet)
if !loaded {
return nil, E.New("parse route_exclude_address_set: rule-set not found: ", routeExcludeAddressSet)
}
ruleSet.IncRef()
routeExcludeRuleSet = append(routeExcludeRuleSet, ruleSet)
}
trackerIn := conntrack.NewDefaultTracker(false, 0)
return &Inbound{
Adapter: inbound.NewAdapter(C.TypeNDIS, tag),
ctx: ctx,
router: router,
logger: logger,
api: api,
tracker: trackerIn,
routeRuleSet: routeRuleSet,
routeExcludeRuleSet: routeExcludeRuleSet,
stack: &Stack{
ctx: ctx,
logger: logger,
network: networkManager,
trackerIn: trackerIn,
trackerOut: trackerOut,
api: api,
udpTimeout: udpTimeout,
routeAddress: options.RouteAddress,
routeExcludeAddress: options.RouteExcludeAddress,
},
}, nil
}
func (t *Inbound) Start(stage adapter.StartStage) error {
switch stage {
case adapter.StartStateStart:
monitor := taskmonitor.New(t.logger, C.StartTimeout)
var (
routeAddressSet []*netipx.IPSet
routeExcludeAddressSet []*netipx.IPSet
)
for _, routeRuleSet := range t.routeRuleSet {
ipSets := routeRuleSet.ExtractIPSet()
if len(ipSets) == 0 {
t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeRuleSet.Name())
}
t.routeRuleSetCallback = append(t.routeRuleSetCallback, routeRuleSet.RegisterCallback(t.updateRouteAddressSet))
routeRuleSet.DecRef()
routeAddressSet = append(routeAddressSet, ipSets...)
}
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
ipSets := routeExcludeRuleSet.ExtractIPSet()
if len(ipSets) == 0 {
t.logger.Warn("route_exclude_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
}
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
routeExcludeRuleSet.DecRef()
routeExcludeAddressSet = append(routeExcludeAddressSet, ipSets...)
}
t.stack.routeAddressSet = routeAddressSet
t.stack.routeExcludeAddressSet = routeExcludeAddressSet
monitor.Start("starting NDIS stack")
t.stack.handler = t
err := t.stack.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "starting NDIS stack")
}
}
return nil
}
func (t *Inbound) Close() error {
if t.api != nil {
t.stack.Close()
t.api.Close()
}
return nil
}
func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error {
return t.router.PreMatch(adapter.InboundContext{
Inbound: t.Tag(),
InboundType: C.TypeNDIS,
Network: network,
Source: source,
Destination: destination,
})
}
func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
ctx = log.ContextWithNewID(ctx)
var metadata adapter.InboundContext
metadata.Inbound = t.Tag()
metadata.InboundType = C.TypeNDIS
metadata.Source = source
metadata.Destination = destination
t.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
done, err := t.tracker.NewConnEx(conn)
if err != nil {
t.logger.ErrorContext(ctx, E.Cause(err, "track inbound connection"))
return
}
t.router.RouteConnectionEx(ctx, conn, metadata, N.AppendClose(onClose, done))
}
func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
ctx = log.ContextWithNewID(ctx)
var metadata adapter.InboundContext
metadata.Inbound = t.Tag()
metadata.InboundType = C.TypeNDIS
metadata.Source = source
metadata.Destination = destination
t.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
t.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
done, err := t.tracker.NewPacketConnEx(conn)
if err != nil {
t.logger.ErrorContext(ctx, E.Cause(err, "track inbound connection"))
return
}
t.router.RoutePacketConnectionEx(ctx, conn, metadata, N.AppendClose(onClose, done))
}
func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) {
t.stack.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
t.stack.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
}

View File

@@ -1,267 +0,0 @@
//go:build windows
package ndis
import (
"context"
"net/netip"
"time"
"github.com/sagernet/gvisor/pkg/buffer"
"github.com/sagernet/gvisor/pkg/tcpip"
"github.com/sagernet/gvisor/pkg/tcpip/header"
"github.com/sagernet/gvisor/pkg/tcpip/stack"
"github.com/sagernet/gvisor/pkg/tcpip/transport/tcp"
"github.com/sagernet/gvisor/pkg/tcpip/transport/udp"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
"github.com/sagernet/sing/common/debug"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/wiresock/ndisapi-go"
"github.com/wiresock/ndisapi-go/driver"
"go4.org/netipx"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
type Stack struct {
ctx context.Context
logger logger.ContextLogger
network adapter.NetworkManager
trackerIn conntrack.Tracker
trackerOut conntrack.Tracker
api *ndisapi.NdisApi
handler tun.Handler
udpTimeout time.Duration
filter *driver.QueuedPacketFilter
stack *stack.Stack
endpoint *ndisEndpoint
routeAddress []netip.Prefix
routeExcludeAddress []netip.Prefix
routeAddressSet []*netipx.IPSet
routeExcludeAddressSet []*netipx.IPSet
currentInterface *control.Interface
}
func (s *Stack) Start() error {
err := s.start(s.network.InterfaceMonitor().DefaultInterface())
if err != nil {
return err
}
s.network.InterfaceMonitor().RegisterCallback(s.updateDefaultInterface)
return nil
}
func (s *Stack) updateDefaultInterface(defaultInterface *control.Interface, flags int) {
if s.currentInterface.Equals(*defaultInterface) {
return
}
err := s.start(defaultInterface)
if err != nil {
s.logger.Error(E.Cause(err, "reconfigure NDIS at: ", defaultInterface.Name))
}
}
func (s *Stack) start(defaultInterface *control.Interface) error {
_ = s.Close()
adapters, err := s.api.GetTcpipBoundAdaptersInfo()
if err != nil {
return err
}
if defaultInterface != nil {
for index := 0; index < int(adapters.AdapterCount); index++ {
name := s.api.ConvertWindows2000AdapterName(string(adapters.AdapterNameList[index][:]))
if name != defaultInterface.Name {
continue
}
s.filter, err = driver.NewQueuedPacketFilter(s.api, adapters, nil, s.processOut)
if err != nil {
return err
}
address := tcpip.LinkAddress(adapters.CurrentAddress[index][:])
mtu := uint32(adapters.MTU[index])
endpoint := &ndisEndpoint{
filter: s.filter,
mtu: mtu,
address: address,
}
s.stack, err = tun.NewGVisorStack(endpoint)
if err != nil {
s.filter = nil
return err
}
s.stack.SetTransportProtocolHandler(tcp.ProtocolNumber, tun.NewTCPForwarder(s.ctx, s.stack, s.handler).HandlePacket)
s.stack.SetTransportProtocolHandler(udp.ProtocolNumber, tun.NewUDPForwarder(s.ctx, s.stack, s.handler, s.udpTimeout).HandlePacket)
err = s.filter.StartFilter(index)
if err != nil {
s.filter = nil
s.stack.Close()
s.stack = nil
return err
}
s.endpoint = endpoint
s.logger.Info("started at ", defaultInterface.Name)
break
}
}
s.currentInterface = defaultInterface
return nil
}
func (s *Stack) Close() error {
if s.filter != nil {
s.filter.StopFilter()
s.filter.Close()
s.filter = nil
}
if s.stack != nil {
s.stack.Close()
for _, endpoint := range s.stack.CleanupEndpoints() {
endpoint.Abort()
}
s.stack = nil
}
return nil
}
func (s *Stack) processOut(handle ndisapi.Handle, packet *ndisapi.IntermediateBuffer) ndisapi.FilterAction {
if packet.Length < header.EthernetMinimumSize {
return ndisapi.FilterActionPass
}
if s.endpoint.dispatcher == nil || s.filterPacket(packet.Buffer[:packet.Length]) {
return ndisapi.FilterActionPass
}
packetBuffer := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(packet.Buffer[:packet.Length]),
})
_, ok := packetBuffer.LinkHeader().Consume(header.EthernetMinimumSize)
if !ok {
packetBuffer.DecRef()
return ndisapi.FilterActionPass
}
ethHdr := header.Ethernet(packetBuffer.LinkHeader().Slice())
destinationAddress := ethHdr.DestinationAddress()
if destinationAddress == header.EthernetBroadcastAddress {
packetBuffer.PktType = tcpip.PacketBroadcast
} else if header.IsMulticastEthernetAddress(destinationAddress) {
packetBuffer.PktType = tcpip.PacketMulticast
} else if destinationAddress == s.endpoint.address {
packetBuffer.PktType = tcpip.PacketHost
} else {
packetBuffer.PktType = tcpip.PacketOtherHost
}
s.endpoint.dispatcher.DeliverNetworkPacket(ethHdr.Type(), packetBuffer)
packetBuffer.DecRef()
return ndisapi.FilterActionDrop
}
func (s *Stack) filterPacket(packet []byte) bool {
var ipHdr header.Network
switch header.IPVersion(packet[header.EthernetMinimumSize:]) {
case ipv4.Version:
ipHdr = header.IPv4(packet[header.EthernetMinimumSize:])
case ipv6.Version:
ipHdr = header.IPv6(packet[header.EthernetMinimumSize:])
default:
return true
}
sourceAddr := tun.AddrFromAddress(ipHdr.SourceAddress())
destinationAddr := tun.AddrFromAddress(ipHdr.DestinationAddress())
if !destinationAddr.IsGlobalUnicast() {
return true
}
var (
transportProtocol tcpip.TransportProtocolNumber
transportHdr header.Transport
)
switch ipHdr.TransportProtocol() {
case tcp.ProtocolNumber:
transportProtocol = header.TCPProtocolNumber
transportHdr = header.TCP(ipHdr.Payload())
case udp.ProtocolNumber:
transportProtocol = header.UDPProtocolNumber
transportHdr = header.UDP(ipHdr.Payload())
default:
return false
}
source := netip.AddrPortFrom(sourceAddr, transportHdr.SourcePort())
destination := netip.AddrPortFrom(destinationAddr, transportHdr.DestinationPort())
if transportProtocol == header.TCPProtocolNumber {
if s.trackerIn.CheckConn(source, destination) {
if debug.Enabled {
s.logger.Trace("fall exists TCP ", source, " ", destination)
}
return false
}
} else {
if s.trackerIn.CheckPacketConn(source) {
if debug.Enabled {
s.logger.Trace("fall exists UDP ", source, " ", destination)
}
}
}
if len(s.routeAddress) > 0 {
var match bool
for _, route := range s.routeAddress {
if route.Contains(destinationAddr) {
match = true
}
}
if !match {
return true
}
}
if len(s.routeAddressSet) > 0 {
var match bool
for _, ipSet := range s.routeAddressSet {
if ipSet.Contains(destinationAddr) {
match = true
}
}
if !match {
return true
}
}
if len(s.routeExcludeAddress) > 0 {
for _, address := range s.routeExcludeAddress {
if address.Contains(destinationAddr) {
return true
}
}
}
if len(s.routeExcludeAddressSet) > 0 {
for _, ipSet := range s.routeAddressSet {
if ipSet.Contains(destinationAddr) {
return true
}
}
}
if s.trackerOut.CheckDestination(destination) {
if debug.Enabled {
s.logger.Trace("passing pending ", source, " ", destination)
}
return true
}
if transportProtocol == header.TCPProtocolNumber {
if s.trackerOut.CheckConn(source, destination) {
if debug.Enabled {
s.logger.Trace("passing TCP ", source, " ", destination)
}
return true
}
} else {
if s.trackerOut.CheckPacketConn(source) {
if debug.Enabled {
s.logger.Trace("passing UDP ", source, " ", destination)
}
}
}
if debug.Enabled {
s.logger.Trace("fall ", source, " ", destination)
}
return false
}

View File

@@ -245,7 +245,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
if err != nil { if err != nil {
return nil, E.Cause(err, "initialize auto-redirect") return nil, E.Cause(err, "initialize auto-redirect")
} }
if runtime.GOOS != "android" && len(inbound.routeAddressSet) > 0 || len(inbound.routeExcludeAddressSet) > 0 { if !C.IsAndroid && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) {
inbound.tunOptions.AutoRedirectMarkMode = true inbound.tunOptions.AutoRedirectMarkMode = true
err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark) err = networkManager.RegisterAutoRedirectOutputMark(inbound.tunOptions.AutoRedirectOutputMark)
if err != nil { if err != nil {
@@ -305,7 +305,8 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
if t.tunOptions.Name == "" { if t.tunOptions.Name == "" {
t.tunOptions.Name = tun.CalculateInterfaceName("") t.tunOptions.Name = tun.CalculateInterfaceName("")
} }
if t.platformInterface == nil || runtime.GOOS != "android" { if t.platformInterface == nil {
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeRuleSet := range t.routeRuleSet { for _, routeRuleSet := range t.routeRuleSet {
ipSets := routeRuleSet.ExtractIPSet() ipSets := routeRuleSet.ExtractIPSet()
if len(ipSets) == 0 { if len(ipSets) == 0 {
@@ -315,10 +316,11 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
routeRuleSet.DecRef() routeRuleSet.DecRef()
t.routeAddressSet = append(t.routeAddressSet, ipSets...) t.routeAddressSet = append(t.routeAddressSet, ipSets...)
} }
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
for _, routeExcludeRuleSet := range t.routeExcludeRuleSet { for _, routeExcludeRuleSet := range t.routeExcludeRuleSet {
ipSets := routeExcludeRuleSet.ExtractIPSet() ipSets := routeExcludeRuleSet.ExtractIPSet()
if len(ipSets) == 0 { if len(ipSets) == 0 {
t.logger.Warn("route_exclude_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name()) t.logger.Warn("route_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
} }
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet)) t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
routeExcludeRuleSet.DecRef() routeExcludeRuleSet.DecRef()
@@ -419,41 +421,7 @@ func (t *Inbound) Start(stage adapter.StartStage) error {
func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) {
t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet)
t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet)
if t.autoRedirect != nil {
t.autoRedirect.UpdateRouteAddressSet() t.autoRedirect.UpdateRouteAddressSet()
} else {
tunOptions := t.tunOptions
for _, ipSet := range t.routeAddressSet {
for _, prefix := range ipSet.Prefixes() {
if prefix.Addr().Is4() {
tunOptions.Inet4RouteAddress = append(tunOptions.Inet4RouteAddress, prefix)
} else {
tunOptions.Inet6RouteAddress = append(tunOptions.Inet6RouteAddress, prefix)
}
}
}
for _, ipSet := range t.routeExcludeAddressSet {
for _, prefix := range ipSet.Prefixes() {
if prefix.Addr().Is4() {
tunOptions.Inet4RouteExcludeAddress = append(tunOptions.Inet4RouteExcludeAddress, prefix)
} else {
tunOptions.Inet6RouteExcludeAddress = append(tunOptions.Inet6RouteExcludeAddress, prefix)
}
}
}
if t.platformInterface != nil {
err := t.platformInterface.UpdateRouteOptions(&tunOptions, t.platformOptions)
if err != nil {
t.logger.Error("update route addresses: ", err)
}
} else {
err := t.tunIf.UpdateRouteOptions(tunOptions)
if err != nil {
t.logger.Error("update route addresses: ", err)
}
}
t.logger.Info("updated route addresses")
}
t.routeAddressSet = nil t.routeAddressSet = nil
t.routeExcludeAddressSet = nil t.routeExcludeAddressSet = nil
} }

View File

@@ -35,7 +35,6 @@ var _ adapter.NetworkManager = (*NetworkManager)(nil)
type NetworkManager struct { type NetworkManager struct {
logger logger.ContextLogger logger logger.ContextLogger
tracker conntrack.Tracker
interfaceFinder *control.DefaultInterfaceFinder interfaceFinder *control.DefaultInterfaceFinder
networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface] networkInterfaces atomic.TypedValue[[]adapter.NetworkInterface]
@@ -58,7 +57,6 @@ type NetworkManager struct {
func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) { func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOptions option.RouteOptions) (*NetworkManager, error) {
nm := &NetworkManager{ nm := &NetworkManager{
logger: logger, logger: logger,
tracker: service.FromContext[conntrack.Tracker](ctx),
interfaceFinder: control.NewDefaultInterfaceFinder(), interfaceFinder: control.NewDefaultInterfaceFinder(),
autoDetectInterface: routeOptions.AutoDetectInterface, autoDetectInterface: routeOptions.AutoDetectInterface,
defaultOptions: adapter.NetworkOptions{ defaultOptions: adapter.NetworkOptions{
@@ -357,7 +355,7 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState {
} }
func (r *NetworkManager) ResetNetwork() { func (r *NetworkManager) ResetNetwork() {
r.tracker.Close() conntrack.Close()
for _, endpoint := range r.endpoint.Endpoints() { for _, endpoint := range r.endpoint.Endpoints() {
listener, isListener := endpoint.(adapter.InterfaceUpdateListener) listener, isListener := endpoint.(adapter.InterfaceUpdateListener)

View File

@@ -11,6 +11,7 @@ import (
"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/process" "github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/common/sniff" "github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@@ -32,7 +33,18 @@ import (
// Deprecated: use RouteConnectionEx instead. // 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 {
return r.routeConnection(ctx, conn, metadata, nil) done := make(chan interface{})
err := r.routeConnection(ctx, conn, metadata, N.OnceClose(func(it error) {
close(done)
}))
if err != nil {
return err
}
select {
case <-done:
case <-r.ctx.Done():
}
return nil
} }
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
@@ -71,10 +83,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
injectable.NewConnectionEx(ctx, conn, metadata, onClose) injectable.NewConnectionEx(ctx, conn, metadata, onClose)
return nil return nil
} }
err := r.connTracker.KillerCheck() conntrack.KillerCheck()
if err != nil {
return err
}
metadata.Network = N.NetworkTCP metadata.Network = N.NetworkTCP
switch metadata.Destination.Fqdn { switch metadata.Destination.Fqdn {
case mux.Destination.Fqdn: case mux.Destination.Fqdn:
@@ -143,7 +152,10 @@ 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 {
err := r.routePacketConnection(ctx, conn, metadata, nil) done := make(chan interface{})
err := r.routePacketConnection(ctx, conn, metadata, N.OnceClose(func(it error) {
close(done)
}))
if err != nil { if err != nil {
conn.Close() conn.Close()
if E.IsClosedOrCanceled(err) { if E.IsClosedOrCanceled(err) {
@@ -152,6 +164,10 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
r.logger.ErrorContext(ctx, err) r.logger.ErrorContext(ctx, err)
} }
} }
select {
case <-done:
case <-r.ctx.Done():
}
return nil return nil
} }
@@ -192,10 +208,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
injectable.NewPacketConnectionEx(ctx, conn, metadata, onClose) injectable.NewPacketConnectionEx(ctx, conn, metadata, onClose)
return nil return nil
} }
err := r.connTracker.KillerCheck() conntrack.KillerCheck()
if err != nil {
return err
}
// TODO: move to UoT // TODO: move to UoT
metadata.Network = N.NetworkUDP metadata.Network = N.NetworkUDP
@@ -478,7 +491,7 @@ match:
} }
if !preMatch && inputPacketConn != nil && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { if !preMatch && inputPacketConn != nil && !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() {
var timeout time.Duration var timeout time.Duration
if metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed { if metadata.InboundType == C.TypeSOCKS {
timeout = C.TCPTimeout timeout = C.TCPTimeout
} }
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: timeout}, inputConn, inputPacketConn) newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{Timeout: timeout}, inputConn, inputPacketConn)
@@ -577,7 +590,7 @@ func (r *Router) actionSniff(
return return
} }
} else { } else {
if !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() { if !metadata.Destination.Addr.IsGlobalUnicast() {
metadata.Destination = destination metadata.Destination = destination
} }
if len(packetBuffers) > 0 { if len(packetBuffers) > 0 {

View File

@@ -45,28 +45,22 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
panic("no context") panic("no context")
} }
var options dns.QueryOptions var options dns.QueryOptions
var ( var currentRuleIndex int
currentRuleIndex int
currentRule adapter.DNSRule
)
if ruleIndex != -1 { if ruleIndex != -1 {
currentRuleIndex = ruleIndex + 1 currentRuleIndex = ruleIndex + 1
} }
for currentRuleIndex, currentRule = range r.dnsRules[currentRuleIndex:] { for ; currentRuleIndex < len(r.dnsRules); currentRuleIndex++ {
currentRule := r.dnsRules[currentRuleIndex]
if currentRule.WithAddressLimit() && !isAddressQuery { if currentRule.WithAddressLimit() && !isAddressQuery {
continue continue
} }
metadata.ResetRuleCache() metadata.ResetRuleCache()
if currentRule.Match(metadata) { if currentRule.Match(metadata) {
displayRuleIndex := currentRuleIndex
if ruleIndex != -1 {
displayRuleIndex += ruleIndex + 1
}
ruleDescription := currentRule.String() ruleDescription := currentRule.String()
if ruleDescription != "" { if ruleDescription != "" {
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action()) r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
} else { } else {
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
} }
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *R.RuleActionDNSRoute: case *R.RuleActionDNSRoute:
@@ -93,7 +87,7 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
} else { } else {
options.Strategy = r.defaultDomainStrategy options.Strategy = r.defaultDomainStrategy
} }
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
return transport, options, currentRule, currentRuleIndex return transport, options, currentRule, currentRuleIndex
case *R.RuleActionDNSRouteOptions: case *R.RuleActionDNSRouteOptions:
if action.DisableCache { if action.DisableCache {
@@ -105,9 +99,9 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
if action.ClientSubnet.IsValid() { if action.ClientSubnet.IsValid() {
options.ClientSubnet = action.ClientSubnet options.ClientSubnet = action.ClientSubnet
} }
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
case *R.RuleActionReject: case *R.RuleActionReject:
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action()) r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
return nil, options, currentRule, currentRuleIndex return nil, options, currentRule, currentRuleIndex
} }
} }
@@ -133,7 +127,6 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
} }
return &responseMessage, nil return &responseMessage, nil
} }
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()))
var ( var (
response *mDNS.Msg response *mDNS.Msg
cached bool cached bool
@@ -174,14 +167,11 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, er
} }
} }
} }
r.dnsLogger.DebugContext(ctx, "exchange ", formatQuestion(message.Question[0].String()), " via ", transport.Name())
if rule != nil && rule.WithAddressLimit() { if rule != nil && rule.WithAddressLimit() {
addressLimit = true addressLimit = true
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(response *mDNS.Msg) bool { response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(responseAddrs []netip.Addr) bool {
addresses, addrErr := dns.MessageToAddresses(response) metadata.DestinationAddresses = responseAddrs
if addrErr != nil {
return false
}
metadata.DestinationAddresses = addresses
return rule.MatchAddressLimit(metadata) return rule.MatchAddressLimit(metadata)
}) })
} else { } else {

View File

@@ -10,7 +10,6 @@ import (
"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/dialer" "github.com/sagernet/sing-box/common/dialer"
"github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geoip"
"github.com/sagernet/sing-box/common/geosite" "github.com/sagernet/sing-box/common/geosite"
@@ -39,7 +38,6 @@ type Router struct {
ctx context.Context ctx context.Context
logger log.ContextLogger logger log.ContextLogger
dnsLogger log.ContextLogger dnsLogger log.ContextLogger
connTracker conntrack.Tracker
inbound adapter.InboundManager inbound adapter.InboundManager
outbound adapter.OutboundManager outbound adapter.OutboundManager
connection adapter.ConnectionManager connection adapter.ConnectionManager
@@ -77,7 +75,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
ctx: ctx, ctx: ctx,
logger: logFactory.NewLogger("router"), logger: logFactory.NewLogger("router"),
dnsLogger: logFactory.NewLogger("dns"), dnsLogger: logFactory.NewLogger("dns"),
connTracker: service.FromContext[conntrack.Tracker](ctx),
inbound: service.FromContext[adapter.InboundManager](ctx), inbound: service.FromContext[adapter.InboundManager](ctx),
outbound: service.FromContext[adapter.OutboundManager](ctx), outbound: service.FromContext[adapter.OutboundManager](ctx),
connection: service.FromContext[adapter.ConnectionManager](ctx), connection: service.FromContext[adapter.ConnectionManager](ctx),

View File

@@ -59,7 +59,7 @@ func hasHeadlessRule(rules []option.HeadlessRule, cond func(rule option.DefaultH
} }
func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool { func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool {
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0
} }
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool { func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {

View File

@@ -62,10 +62,6 @@ func isProcessDNSRule(rule option.DefaultDNSRule) bool {
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0 return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
} }
func isProcessHeadlessRule(rule option.DefaultHeadlessRule) bool {
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.ProcessPathRegex) > 0 || len(rule.PackageName) > 0
}
func notPrivateNode(code string) bool { func notPrivateNode(code string) bool {
return code != "private" return code != "private"
} }

View File

@@ -13,9 +13,9 @@ require (
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.5.0
github.com/gofrs/uuid/v5 v5.3.0 github.com/gofrs/uuid/v5 v5.3.0
github.com/sagernet/quic-go v0.48.2-beta.1 github.com/sagernet/quic-go v0.48.2-beta.1
github.com/sagernet/sing v0.6.0-beta.9 github.com/sagernet/sing v0.6.0-beta.12
github.com/sagernet/sing-dns v0.4.0-beta.1 github.com/sagernet/sing-dns v0.4.0-beta.2
github.com/sagernet/sing-quic v0.4.0-beta.3 github.com/sagernet/sing-quic v0.4.0-beta.4
github.com/sagernet/sing-shadowsocks v0.2.7 github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0 github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/spyzhov/ajson v0.9.4 github.com/spyzhov/ajson v0.9.4
@@ -85,7 +85,7 @@ require (
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect
github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect github.com/sagernet/sing-mux v0.3.0-alpha.1 // indirect
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 // indirect
github.com/sagernet/sing-tun v0.6.0-beta.7 // indirect github.com/sagernet/sing-tun v0.6.0-beta.8 // indirect
github.com/sagernet/sing-vmess v0.2.0-beta.2 // indirect github.com/sagernet/sing-vmess v0.2.0-beta.2 // indirect
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
github.com/sagernet/utls v1.6.7 // indirect github.com/sagernet/utls v1.6.7 // indirect

View File

@@ -146,22 +146,22 @@ github.com/sagernet/quic-go v0.48.2-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs= github.com/sagernet/sing v0.6.0-beta.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY= github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY=
github.com/sagernet/sing-dns v0.4.0-beta.1/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8= github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg= github.com/sagernet/sing-mux v0.3.0-alpha.1 h1:IgNX5bJBpL41gGbp05pdDOvh/b5eUQ6cv9240+Ngipg=
github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE= github.com/sagernet/sing-mux v0.3.0-alpha.1/go.mod h1:FTcImmdfW38Lz7b+HQ+mxxOth1lz4ao8uEnz+MwIJQE=
github.com/sagernet/sing-quic v0.4.0-beta.3 h1:cOBjlhVdRZmBm6hIw1GleERpnTSFdBB2htgx5kQ5uqg= github.com/sagernet/sing-quic v0.4.0-beta.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
github.com/sagernet/sing-quic v0.4.0-beta.3/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM= github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2 h1:RPrpgAdkP5td0vLfS5ldvYosFjSsZtRPxiyLV6jyKg0=
github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA= github.com/sagernet/sing-shadowtls v0.2.0-alpha.2/go.mod h1:0j5XlzKxaWRIEjc1uiSKmVoWb0k+L9QgZVb876+thZA=
github.com/sagernet/sing-tun v0.6.0-beta.7 h1:FCSX8oGBqb0H57AAvfGeeH/jMGYWCOg6XWkN/oeES+0= github.com/sagernet/sing-tun v0.6.0-beta.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
github.com/sagernet/sing-tun v0.6.0-beta.7/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.0-beta.8/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs= github.com/sagernet/sing-vmess v0.2.0-beta.2 h1:obAkAL35X7ql4RnGzDg4dBYIRpGXRKqcN4LyLZpZGSs=
github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk= github.com/sagernet/sing-vmess v0.2.0-beta.2/go.mod h1:HGhf9XUdeE2iOWrX0hQNFgXPbKyGlzpeYFyX0c/pykk=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=