mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79d3649a8b | ||
|
|
e483c909b4 | ||
|
|
d9579c26ee | ||
|
|
48d3021b2c | ||
|
|
ce0fcd5c8b | ||
|
|
d9d0a2373a | ||
|
|
0c754505f7 | ||
|
|
606ff668da | ||
|
|
f43703801b | ||
|
|
1ed8f3a8d3 | ||
|
|
60fc913dc3 | ||
|
|
be8ee370ac | ||
|
|
0a9bf97438 | ||
|
|
74de437bfb | ||
|
|
c385e7c137 | ||
|
|
01291d16e0 | ||
|
|
11a448b52d | ||
|
|
22bda86bbf | ||
|
|
04cc343b2e | ||
|
|
093e539d3d | ||
|
|
5821b974bd | ||
|
|
8ce40f77b4 | ||
|
|
b45cb0763e | ||
|
|
48d102a0ab | ||
|
|
6a5943f4ce | ||
|
|
c9f9d9ee1c | ||
|
|
f3bf440c91 | ||
|
|
292fcde876 | ||
|
|
53e227a318 | ||
|
|
65cb225a2c | ||
|
|
7819f13489 | ||
|
|
f2780d0713 | ||
|
|
72239dcbd3 | ||
|
|
daf38a84e1 | ||
|
|
f2ddc5883b | ||
|
|
0437ac512b | ||
|
|
d297ad4c56 | ||
|
|
6823670f3d | ||
|
|
4a611eddf4 | ||
|
|
f12a294fb7 | ||
|
|
040a188c66 | ||
|
|
7ed10b35d0 | ||
|
|
afd341adfd | ||
|
|
7d26bac5ac | ||
|
|
63d8f6dc1c | ||
|
|
15cc3b85eb | ||
|
|
5a1c59ca88 | ||
|
|
7686503df8 | ||
|
|
96a8de9548 |
44
.github/workflows/build.yml
vendored
44
.github/workflows/build.yml
vendored
@@ -144,7 +144,7 @@ jobs:
|
|||||||
~/go/go1.20.14
|
~/go/go1.20.14
|
||||||
key: go120
|
key: go120
|
||||||
- name: Setup legacy Go
|
- name: Setup legacy Go
|
||||||
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.require_legacy_go == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
run: |-
|
run: |-
|
||||||
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||||
tar -xzf go1.20.14.linux-amd64.tar.gz
|
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||||
@@ -159,7 +159,7 @@ jobs:
|
|||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
distribution: goreleaser-pro
|
||||||
version: 2.5.1
|
version: latest
|
||||||
install-only: true
|
install-only: true
|
||||||
- name: 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-beta3
|
ndk-version: r28-beta2
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -256,7 +256,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: ~/.gradle
|
path: ~/.gradle
|
||||||
key: gradle-${{ hashFiles('**/*.gradle') }}
|
key: gradle-${{ hashFiles('**/*.gradle') }}
|
||||||
- name: Build
|
- name: Build release
|
||||||
|
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
|
||||||
@@ -267,18 +268,47 @@ 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: Prepare upload
|
- name: Build debug
|
||||||
|
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'
|
||||||
@@ -299,7 +329,7 @@ jobs:
|
|||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28-beta3
|
ndk-version: r28-beta2
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -548,7 +578,7 @@ jobs:
|
|||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
distribution: goreleaser-pro
|
||||||
version: 2.5.1
|
version: latest
|
||||||
install-only: true
|
install-only: true
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ builds:
|
|||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
||||||
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
|
|||||||
@@ -39,17 +39,17 @@ type CacheFile interface {
|
|||||||
StoreSelected(group string, selected string) error
|
StoreSelected(group string, selected string) error
|
||||||
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
LoadGroupExpand(group string) (isExpand bool, loaded bool)
|
||||||
StoreGroupExpand(group string, expand bool) error
|
StoreGroupExpand(group string, expand bool) error
|
||||||
LoadRuleSet(tag string) *SavedBinary
|
LoadRuleSet(tag string) *SavedRuleSet
|
||||||
SaveRuleSet(tag string, set *SavedBinary) error
|
SaveRuleSet(tag string, set *SavedRuleSet) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedBinary struct {
|
type SavedRuleSet struct {
|
||||||
Content []byte
|
Content []byte
|
||||||
LastUpdated time.Time
|
LastUpdated time.Time
|
||||||
LastEtag string
|
LastEtag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,7 +70,7 @@ func (s *SavedBinary) MarshalBinary() ([]byte, error) {
|
|||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
var version uint8
|
var version uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &version)
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
|||||||
9
box.go
9
box.go
@@ -12,6 +12,7 @@ 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"
|
||||||
@@ -84,7 +85,6 @@ func New(options Options) (*Box, error) {
|
|||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
|
|
||||||
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
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,7 +101,10 @@ func New(options Options) (*Box, error) {
|
|||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
debugOptions := 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
|
||||||
@@ -399,7 +402,7 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.network,
|
s.inbound, s.outbound, 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 {
|
||||||
|
|||||||
Submodule clients/android updated: 6df2f60c3a...e1049099a0
Submodule clients/apple updated: 17fed6b9fc...3d889ae017
@@ -48,7 +48,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "28.0.12916984"
|
const fixedVersion = "28.0.12674087"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateTLSKeyPair(serverName string) error {
|
func generateTLSKeyPair(serverName string) error {
|
||||||
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,15 +61,14 @@ func upgradeRuleSet(sourcePath string) error {
|
|||||||
log.Info("already up-to-date")
|
log.Info("already up-to-date")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
|
plainRuleSet, err := plainRuleSetCompat.Upgrade()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
encoder := json.NewEncoder(buffer)
|
encoder := json.NewEncoder(buffer)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
err = encoder.Encode(plainRuleSetCompat)
|
err = encoder.Encode(plainRuleSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "encode config")
|
return E.Cause(err, "encode config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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"
|
||||||
@@ -57,7 +58,7 @@ func syncTime() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if commandSyncTimeWrite {
|
if commandSyncTimeWrite {
|
||||||
err = ntp.SetSystemTime(response.Time)
|
err = settings.SetSystemTime(response.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "write time to system")
|
return E.Cause(err, "write time to system")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
14
common/conntrack/context.go
Normal file
14
common/conntrack/context.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
245
common/conntrack/default.go
Normal file
245
common/conntrack/default.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build !with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = false
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
//go:build with_conntrack
|
|
||||||
|
|
||||||
package conntrack
|
|
||||||
|
|
||||||
const Enabled = true
|
|
||||||
32
common/conntrack/tracker.go
Normal file
32
common/conntrack/tracker.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
|
tracker conntrack.Tracker
|
||||||
dialer4 tcpDialer
|
dialer4 tcpDialer
|
||||||
dialer6 tcpDialer
|
dialer6 tcpDialer
|
||||||
udpDialer4 net.Dialer
|
udpDialer4 net.Dialer
|
||||||
@@ -46,6 +47,7 @@ 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)
|
||||||
|
|
||||||
@@ -197,6 +199,7 @@ 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,
|
||||||
@@ -219,18 +222,26 @@ 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 trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
return d.trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||||
} else {
|
} else {
|
||||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
return d.trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !address.IsIPv6() {
|
if !address.IsIPv6() {
|
||||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
return d.trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||||
} else {
|
} else {
|
||||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
return d.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)
|
||||||
@@ -282,17 +293,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 trackConn(conn, nil)
|
return d.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 trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
return d.trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||||
} else {
|
} else {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
return d.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)
|
||||||
@@ -329,23 +340,23 @@ func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destina
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return trackPacketConn(packetConn, nil)
|
return d.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 trackConn(conn net.Conn, err error) (net.Conn, error) {
|
func (d *DefaultDialer) trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||||
if !conntrack.Enabled || err != nil {
|
if d.tracker == nil || err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return conntrack.NewConn(conn)
|
return d.tracker.NewConn(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
func (d *DefaultDialer) trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||||
if !conntrack.Enabled || err != nil {
|
if err != nil {
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return conntrack.NewPacketConn(conn)
|
return d.tracker.NewPacketConn(conn)
|
||||||
}
|
}
|
||||||
|
|||||||
12
common/settings/time_stub.go
Normal file
12
common/settings/time_stub.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !(windows || linux || darwin)
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
14
common/settings/time_unix.go
Normal file
14
common/settings/time_unix.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
||||||
|
return unix.Settimeofday(&timeVal)
|
||||||
|
}
|
||||||
32
common/settings/time_windows.go
Normal file
32
common/settings/time_windows.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
var systemTime windows.Systemtime
|
||||||
|
systemTime.Year = uint16(nowTime.Year())
|
||||||
|
systemTime.Month = uint16(nowTime.Month())
|
||||||
|
systemTime.Day = uint16(nowTime.Day())
|
||||||
|
systemTime.Hour = uint16(nowTime.Hour())
|
||||||
|
systemTime.Minute = uint16(nowTime.Minute())
|
||||||
|
systemTime.Second = uint16(nowTime.Second())
|
||||||
|
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
||||||
|
|
||||||
|
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
proc := dllKernel32.NewProc("SetSystemTime")
|
||||||
|
|
||||||
|
_, _, err := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(&systemTime)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil && err.Error() != "The operation completed successfully." {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||||
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))
|
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() ti
|
|||||||
return &certificate, err
|
return &certificate, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
||||||
if timeFunc == nil {
|
if timeFunc == nil {
|
||||||
timeFunc = time.Now
|
timeFunc = time.Now
|
||||||
}
|
}
|
||||||
@@ -47,11 +47,7 @@ func GenerateCertificate(parent *x509.Certificate, parentKey any, timeFunc func(
|
|||||||
},
|
},
|
||||||
DNSNames: []string{serverName},
|
DNSNames: []string{serverName},
|
||||||
}
|
}
|
||||||
if parent == nil {
|
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
||||||
parent = template
|
|
||||||
parentKey = key
|
|
||||||
}
|
|
||||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &realityClientConnWrapper{uConn}, nil
|
return &utlsConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
@@ -249,36 +249,3 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type realityClientConnWrapper struct {
|
|
||||||
*utls.UConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {
|
|
||||||
state := c.Conn.ConnectionState()
|
|
||||||
//nolint:staticcheck
|
|
||||||
return tls.ConnectionState{
|
|
||||||
Version: state.Version,
|
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
|
||||||
DidResume: state.DidResume,
|
|
||||||
CipherSuite: state.CipherSuite,
|
|
||||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
|
||||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
|
||||||
ServerName: state.ServerName,
|
|
||||||
PeerCertificates: state.PeerCertificates,
|
|
||||||
VerifiedChains: state.VerifiedChains,
|
|
||||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
|
||||||
OCSPResponse: state.OCSPResponse,
|
|
||||||
TLSUnique: state.TLSUnique,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityClientConnWrapper) Upstream() any {
|
|
||||||
return c.UConn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
|
||||||
// We fixed it by calling Close() directly.
|
|
||||||
func (c *realityClientConnWrapper) CloseWrite() error {
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -194,9 +194,3 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
|||||||
func (c *realityConnWrapper) Upstream() any {
|
func (c *realityConnWrapper) Upstream() any {
|
||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
|
||||||
// We fixed it by calling Close() directly.
|
|
||||||
func (c *realityConnWrapper) CloseWrite() error {
|
|
||||||
return c.Close()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
return GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const (
|
|||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
|
TypeNDIS = "ndis"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -80,6 +81,8 @@ 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"
|
||||||
}
|
}
|
||||||
|
|||||||
5
debug.go
5
debug.go
@@ -3,7 +3,6 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,9 +25,5 @@ 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,134 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
### 1.11.1
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
### 1.11.0
|
|
||||||
|
|
||||||
Important changes since 1.10:
|
|
||||||
|
|
||||||
* Introducing rule actions **1**
|
|
||||||
* Improve tun compatibility **3**
|
|
||||||
* Merge route options to route actions **4**
|
|
||||||
* Add `network_type`, `network_is_expensive` and `network_is_constrainted` rule items **5**
|
|
||||||
* Add multi network dialing **6**
|
|
||||||
* Add `cache_capacity` DNS option **7**
|
|
||||||
* Add `override_address` and `override_port` route options **8**
|
|
||||||
* Upgrade WireGuard outbound to endpoint **9**
|
|
||||||
* Add UDP GSO support for WireGuard
|
|
||||||
* Make GSO adaptive **10**
|
|
||||||
* Add UDP timeout route option **11**
|
|
||||||
* Add more masquerade options for hysteria2 **12**
|
|
||||||
* Add `rule-set merge` command
|
|
||||||
* Add port hopping support for Hysteria2 **13**
|
|
||||||
* Hysteria2 `ignore_client_bandwidth` behavior update **14**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
New rule actions replace legacy inbound fields and special outbound fields,
|
|
||||||
and can be used for pre-matching **2**.
|
|
||||||
|
|
||||||
See [Rule](/configuration/route/rule/),
|
|
||||||
[Rule Action](/configuration/route/rule_action/),
|
|
||||||
[DNS Rule](/configuration/dns/rule/) and
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/).
|
|
||||||
|
|
||||||
For migration, see
|
|
||||||
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
|
|
||||||
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
|
|
||||||
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Similar to Surge's pre-matching.
|
|
||||||
|
|
||||||
Specifically, new rule actions allow you to reject connections with
|
|
||||||
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
|
|
||||||
before connection established to improve tun's compatibility.
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/rule_action/).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
When `gvisor` tun stack is enabled, even if the request passes routing,
|
|
||||||
if the outbound connection establishment fails,
|
|
||||||
the connection still does not need to be established and a TCP RST is replied.
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
Route options in DNS route actions will no longer be considered deprecated,
|
|
||||||
see [DNS Route Action](/configuration/dns/rule_action/).
|
|
||||||
|
|
||||||
Also, now `udp_disable_domain_unmapping` and `udp_connect` can also be configured in route action,
|
|
||||||
see [Route Action](/configuration/route/rule_action/).
|
|
||||||
|
|
||||||
**5**:
|
|
||||||
|
|
||||||
When using in graphical clients, new routing rule items allow you to match on
|
|
||||||
network type (WIFI, cellular, etc.), whether the network is expensive, and whether Low Data Mode is enabled.
|
|
||||||
|
|
||||||
See [Route Rule](/configuration/route/rule/), [DNS Route Rule](/configuration/dns/rule/)
|
|
||||||
and [Headless Rule](/configuration/rule-set/headless-rule/).
|
|
||||||
|
|
||||||
**6**:
|
|
||||||
|
|
||||||
Similar to Surge's strategy.
|
|
||||||
|
|
||||||
New options allow you to connect using multiple network interfaces,
|
|
||||||
prefer or only use one type of interface,
|
|
||||||
and configure a timeout to fallback to other interfaces.
|
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/#network_strategy),
|
|
||||||
[Rule Action](/configuration/route/rule_action/#network_strategy)
|
|
||||||
and [Route](/configuration/route/#default_network_strategy).
|
|
||||||
|
|
||||||
**7**:
|
|
||||||
|
|
||||||
See [DNS](/configuration/dns/#cache_capacity).
|
|
||||||
|
|
||||||
**8**:
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/#override_address) and
|
|
||||||
[Migrate destination override fields to route options](/migration/#migrate-destination-override-fields-to-route-options).
|
|
||||||
|
|
||||||
**9**:
|
|
||||||
|
|
||||||
The new WireGuard endpoint combines inbound and outbound capabilities,
|
|
||||||
and the old outbound will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
See [Endpoint](/configuration/endpoint/), [WireGuard Endpoint](/configuration/endpoint/wireguard/)
|
|
||||||
and [Migrate WireGuard outbound fields to route options](/migration/#migrate-wireguard-outbound-to-endpoint).
|
|
||||||
|
|
||||||
**10**:
|
|
||||||
|
|
||||||
For WireGuard outbound and endpoint, GSO will be automatically enabled when available,
|
|
||||||
see [WireGuard Outbound](/configuration/outbound/wireguard/#gso).
|
|
||||||
|
|
||||||
For TUN, GSO has been removed,
|
|
||||||
see [Deprecated](/deprecated/#gso-option-in-tun).
|
|
||||||
|
|
||||||
**11**:
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/rule_action/#udp_timeout).
|
|
||||||
|
|
||||||
**12**:
|
|
||||||
|
|
||||||
See [Hysteria2](/configuration/inbound/hysteria2/#masquerade).
|
|
||||||
|
|
||||||
**13**:
|
|
||||||
|
|
||||||
See [Hysteria2](/configuration/outbound/hysteria2/).
|
|
||||||
|
|
||||||
**14**:
|
|
||||||
|
|
||||||
When `up_mbps` and `down_mbps` are set, `ignore_client_bandwidth` instead denies clients from using BBR CC.
|
|
||||||
|
|
||||||
### 1.10.7
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.11.0-beta.20
|
#### 1.11.0-beta.20
|
||||||
|
|
||||||
* Hysteria2 `ignore_client_bandwidth` behavior update **1**
|
* Hysteria2 `ignore_client_bandwidth` behavior update **1**
|
||||||
|
|||||||
@@ -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 中的更改"
|
||||||
|
|
||||||
|
|||||||
@@ -31,45 +31,6 @@ 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
|
||||||
@@ -148,6 +109,45 @@ 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
|
||||||
|
|||||||
@@ -27,45 +27,6 @@ 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
|
||||||
@@ -146,6 +107,45 @@ 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
|
||||||
|
|||||||
@@ -284,8 +284,8 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
|
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedRuleSet {
|
||||||
var savedSet adapter.SavedBinary
|
var savedSet adapter.SavedRuleSet
|
||||||
err := c.DB.View(func(t *bbolt.Tx) error {
|
err := c.DB.View(func(t *bbolt.Tx) error {
|
||||||
bucket := c.bucket(t, bucketRuleSet)
|
bucket := c.bucket(t, bucketRuleSet)
|
||||||
if bucket == nil {
|
if bucket == nil {
|
||||||
@@ -303,7 +303,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
|
|||||||
return &savedSet
|
return &savedSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
|
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedRuleSet) error {
|
||||||
return c.DB.Batch(func(t *bbolt.Tx) error {
|
return c.DB.Batch(func(t *bbolt.Tx) error {
|
||||||
bucket, err := c.createBucket(t, bucketRuleSet)
|
bucket, err := c.createBucket(t, bucketRuleSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ 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 {
|
||||||
@@ -19,7 +17,7 @@ func (c *CommandClient) CloseConnections() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CommandServer) handleCloseConnections(conn net.Conn) error {
|
func (s *CommandServer) handleCloseConnections(conn net.Conn) error {
|
||||||
conntrack.Close()
|
tracker.Close()
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
runtimeDebug.FreeOSMemory()
|
runtimeDebug.FreeOSMemory()
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ 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"
|
||||||
@@ -28,7 +27,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(conntrack.Count())
|
message.ConnectionsOut = int32(tracker.Count())
|
||||||
|
|
||||||
if s.service != nil {
|
if s.service != nil {
|
||||||
message.TrafficAvailable = true
|
message.TrafficAvailable = true
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,21 @@ 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)
|
||||||
conntrack.KillerEnabled = true
|
tracker = conntrack.NewDefaultTracker(true, memoryLimit)
|
||||||
conntrack.MemoryLimit = memoryLimit
|
|
||||||
} else {
|
} else {
|
||||||
runtimeDebug.SetGCPercent(100)
|
runtimeDebug.SetGCPercent(100)
|
||||||
runtimeDebug.SetMemoryLimit(math.MaxInt64)
|
runtimeDebug.SetMemoryLimit(math.MaxInt64)
|
||||||
conntrack.KillerEnabled = false
|
tracker = conntrack.NewDefaultTracker(false, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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"
|
||||||
@@ -60,6 +61,7 @@ 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,
|
||||||
@@ -174,6 +176,20 @@ 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,
|
||||||
|
|||||||
@@ -7,13 +7,11 @@ var (
|
|||||||
|
|
||||||
type Locale struct {
|
type Locale struct {
|
||||||
// deprecated messages for graphical clients
|
// deprecated messages for graphical clients
|
||||||
Locale string
|
|
||||||
DeprecatedMessage string
|
DeprecatedMessage string
|
||||||
DeprecatedMessageNoLink string
|
DeprecatedMessageNoLink string
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultLocal = &Locale{
|
var defaultLocal = &Locale{
|
||||||
Locale: "en_US",
|
|
||||||
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
|
DeprecatedMessage: "%s is deprecated in sing-box %s and will be removed in sing-box %s please checkout documentation for migration.",
|
||||||
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
|
DeprecatedMessageNoLink: "%s is deprecated in sing-box %s and will be removed in sing-box %s.",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ var warningMessageForEndUsers = "\n\n如果您不明白此消息意味着什么
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
localeRegistry["zh_CN"] = &Locale{
|
localeRegistry["zh_CN"] = &Locale{
|
||||||
Locale: "zh_CN",
|
|
||||||
DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers,
|
DeprecatedMessage: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除,请参阅迁移指南。" + warningMessageForEndUsers,
|
||||||
DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers,
|
DeprecatedMessageNoLink: "%s 已在 sing-box %s 中被弃用,且将在 sing-box %s 中被移除。" + warningMessageForEndUsers,
|
||||||
}
|
}
|
||||||
|
|||||||
9
go.mod
9
go.mod
@@ -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.12
|
github.com/sagernet/sing v0.6.0-beta.9
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2
|
github.com/sagernet/sing-dns v0.4.0-beta.1
|
||||||
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.4
|
github.com/sagernet/sing-quic v0.4.0-beta.3
|
||||||
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.8
|
github.com/sagernet/sing-tun v0.6.0-beta.7
|
||||||
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,6 +41,7 @@ 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
19
go.sum
@@ -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.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
|
github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs=
|
||||||
github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY=
|
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
|
github.com/sagernet/sing-dns v0.4.0-beta.1/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.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
|
github.com/sagernet/sing-quic v0.4.0-beta.3 h1:cOBjlhVdRZmBm6hIw1GleERpnTSFdBB2htgx5kQ5uqg=
|
||||||
github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
|
github.com/sagernet/sing-quic v0.4.0-beta.3/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.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
|
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/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
github.com/sagernet/sing-tun v0.6.0-beta.7/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,6 +158,8 @@ 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=
|
||||||
@@ -191,6 +193,7 @@ 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=
|
||||||
|
|||||||
12
include/ndis.go
Normal file
12
include/ndis.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//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)
|
||||||
|
}
|
||||||
20
include/ndis_nongvisor_stub.go
Normal file
20
include/ndis_nongvisor_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//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
|
||||||
|
})
|
||||||
|
}
|
||||||
20
include/ndis_nonwindows_stub.go
Normal file
20
include/ndis_nonwindows_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//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")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -51,6 +51,7 @@ func InboundRegistry() *inbound.Registry {
|
|||||||
|
|
||||||
registerQUICInbounds(registry)
|
registerQUICInbounds(registry)
|
||||||
registerStubForRemovedInbounds(registry)
|
registerStubForRemovedInbounds(registry)
|
||||||
|
registerNDISInbound(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ 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 {
|
||||||
|
|||||||
17
option/ndis.go
Normal file
17
option/ndis.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
@@ -80,22 +80,34 @@ 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) {
|
||||||
i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, M.Socksaddr{}, nil)
|
var destination M.Socksaddr
|
||||||
|
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()
|
||||||
destination := metadata.OriginDestination
|
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr())
|
||||||
switch i.overrideOption {
|
switch i.overrideOption {
|
||||||
case 1:
|
case 1:
|
||||||
destination = i.overrideDestination
|
metadata.Destination = i.overrideDestination
|
||||||
case 2:
|
case 2:
|
||||||
destination.Addr = i.overrideDestination.Addr
|
destination := i.overrideDestination
|
||||||
case 3:
|
|
||||||
destination.Port = metadata.Destination.Port
|
destination.Port = metadata.Destination.Port
|
||||||
|
metadata.Destination = destination
|
||||||
|
case 3:
|
||||||
|
metadata.Destination.Port = i.overrideDestination.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)
|
||||||
}
|
}
|
||||||
@@ -113,16 +125,6 @@ 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)
|
||||||
|
|||||||
110
protocol/ndis/endpoint.go
Normal file
110
protocol/ndis/endpoint.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
203
protocol/ndis/inbound.go
Normal file
203
protocol/ndis/inbound.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
//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)
|
||||||
|
}
|
||||||
267
protocol/ndis/stack.go
Normal file
267
protocol/ndis/stack.go
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
//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
|
||||||
|
}
|
||||||
@@ -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 !C.IsAndroid && (len(inbound.routeRuleSet) > 0 || len(inbound.routeExcludeRuleSet) > 0) {
|
if runtime.GOOS != "android" && len(inbound.routeAddressSet) > 0 || len(inbound.routeExcludeAddressSet) > 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,8 +305,7 @@ 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 {
|
if t.platformInterface == nil || runtime.GOOS != "android" {
|
||||||
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 {
|
||||||
@@ -316,11 +315,10 @@ 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_address_set: no destination IP CIDR rules found in rule-set: ", routeExcludeRuleSet.Name())
|
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))
|
t.routeExcludeRuleSetCallback = append(t.routeExcludeRuleSetCallback, routeExcludeRuleSet.RegisterCallback(t.updateRouteAddressSet))
|
||||||
routeExcludeRuleSet.DecRef()
|
routeExcludeRuleSet.DecRef()
|
||||||
@@ -421,7 +419,41 @@ 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)
|
||||||
t.autoRedirect.UpdateRouteAddressSet()
|
if t.autoRedirect != nil {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,17 +225,6 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source io.Reader
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](destination); isEarlyConn && earlyConn.NeedHandshake() {
|
|
||||||
_, err := destination.Write(nil)
|
|
||||||
if err != nil {
|
|
||||||
if !direction {
|
|
||||||
m.logger.ErrorContext(ctx, "connection upload handshake: ", err)
|
|
||||||
} else {
|
|
||||||
m.logger.ErrorContext(ctx, "connection download handshake: ", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
|
_, err := bufio.CopyWithCounters(destination, source, originSource, readCounters, writeCounters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Close(originDestination)
|
common.Close(originDestination)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ 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]
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ 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{
|
||||||
@@ -355,7 +357,7 @@ func (r *NetworkManager) WIFIState() adapter.WIFIState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *NetworkManager) ResetNetwork() {
|
func (r *NetworkManager) ResetNetwork() {
|
||||||
conntrack.Close()
|
r.tracker.Close()
|
||||||
|
|
||||||
for _, endpoint := range r.endpoint.Endpoints() {
|
for _, endpoint := range r.endpoint.Endpoints() {
|
||||||
listener, isListener := endpoint.(adapter.InterfaceUpdateListener)
|
listener, isListener := endpoint.(adapter.InterfaceUpdateListener)
|
||||||
|
|||||||
@@ -11,7 +11,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/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"
|
||||||
@@ -33,18 +32,7 @@ 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 {
|
||||||
done := make(chan interface{})
|
return r.routeConnection(ctx, conn, metadata, nil)
|
||||||
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) {
|
||||||
@@ -83,7 +71,10 @@ 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
|
||||||
}
|
}
|
||||||
conntrack.KillerCheck()
|
err := r.connTracker.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:
|
||||||
@@ -152,10 +143,7 @@ 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 {
|
||||||
done := make(chan interface{})
|
err := r.routePacketConnection(ctx, conn, metadata, nil)
|
||||||
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) {
|
||||||
@@ -164,10 +152,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +192,10 @@ 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
|
||||||
}
|
}
|
||||||
conntrack.KillerCheck()
|
err := r.connTracker.KillerCheck()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: move to UoT
|
// TODO: move to UoT
|
||||||
metadata.Network = N.NetworkUDP
|
metadata.Network = N.NetworkUDP
|
||||||
@@ -491,7 +478,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 {
|
if metadata.InboundType == C.TypeSOCKS || metadata.InboundType == C.TypeMixed {
|
||||||
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)
|
||||||
@@ -590,7 +577,7 @@ func (r *Router) actionSniff(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !metadata.Destination.Addr.IsGlobalUnicast() {
|
if !metadata.Destination.IsFqdn() && !metadata.Destination.Addr.IsGlobalUnicast() {
|
||||||
metadata.Destination = destination
|
metadata.Destination = destination
|
||||||
}
|
}
|
||||||
if len(packetBuffers) > 0 {
|
if len(packetBuffers) > 0 {
|
||||||
|
|||||||
@@ -45,22 +45,28 @@ 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 currentRuleIndex int
|
var (
|
||||||
|
currentRuleIndex int
|
||||||
|
currentRule adapter.DNSRule
|
||||||
|
)
|
||||||
if ruleIndex != -1 {
|
if ruleIndex != -1 {
|
||||||
currentRuleIndex = ruleIndex + 1
|
currentRuleIndex = ruleIndex + 1
|
||||||
}
|
}
|
||||||
for ; currentRuleIndex < len(r.dnsRules); currentRuleIndex++ {
|
for currentRuleIndex, currentRule = range 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[", currentRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] ", currentRule, " => ", currentRule.Action())
|
||||||
} else {
|
} else {
|
||||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
}
|
}
|
||||||
switch action := currentRule.Action().(type) {
|
switch action := currentRule.Action().(type) {
|
||||||
case *R.RuleActionDNSRoute:
|
case *R.RuleActionDNSRoute:
|
||||||
@@ -87,7 +93,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[", currentRuleIndex, "] => ", currentRule.Action())
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
return transport, options, currentRule, currentRuleIndex
|
return transport, options, currentRule, currentRuleIndex
|
||||||
case *R.RuleActionDNSRouteOptions:
|
case *R.RuleActionDNSRouteOptions:
|
||||||
if action.DisableCache {
|
if action.DisableCache {
|
||||||
@@ -99,9 +105,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[", currentRuleIndex, "] => ", currentRule.Action())
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
r.logger.DebugContext(ctx, "match[", currentRuleIndex, "] => ", currentRule.Action())
|
r.logger.DebugContext(ctx, "match[", displayRuleIndex, "] => ", currentRule.Action())
|
||||||
return nil, options, currentRule, currentRuleIndex
|
return nil, options, currentRule, currentRuleIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,6 +133,7 @@ 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
|
||||||
@@ -167,11 +174,14 @@ 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(responseAddrs []netip.Addr) bool {
|
response, err = r.dnsClient.ExchangeWithResponseCheck(dnsCtx, transport, message, options, func(response *mDNS.Msg) bool {
|
||||||
metadata.DestinationAddresses = responseAddrs
|
addresses, addrErr := dns.MessageToAddresses(response)
|
||||||
|
if addrErr != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
metadata.DestinationAddresses = addresses
|
||||||
return rule.MatchAddressLimit(metadata)
|
return rule.MatchAddressLimit(metadata)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,6 +10,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/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"
|
||||||
@@ -38,6 +39,7 @@ 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
|
||||||
@@ -75,6 +77,7 @@ 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),
|
||||||
@@ -484,13 +487,6 @@ func (r *Router) Close() error {
|
|||||||
})
|
})
|
||||||
monitor.Finish()
|
monitor.Finish()
|
||||||
}
|
}
|
||||||
for i, ruleSet := range r.ruleSets {
|
|
||||||
monitor.Start("close rule-set[", i, "]")
|
|
||||||
err = E.Append(err, ruleSet.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close rule-set[", i, "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.ProcessPathRegex) > 0 || len(rule.PackageName) > 0
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
func isWIFIHeadlessRule(rule option.DefaultHeadlessRule) bool {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/fswatch"
|
"github.com/sagernet/fswatch"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -27,16 +26,14 @@ import (
|
|||||||
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
var _ adapter.RuleSet = (*LocalRuleSet)(nil)
|
||||||
|
|
||||||
type LocalRuleSet struct {
|
type LocalRuleSet struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
tag string
|
tag string
|
||||||
rules []adapter.HeadlessRule
|
rules []adapter.HeadlessRule
|
||||||
metadata adapter.RuleSetMetadata
|
metadata adapter.RuleSetMetadata
|
||||||
fileFormat string
|
fileFormat string
|
||||||
watcher *fswatch.Watcher
|
watcher *fswatch.Watcher
|
||||||
callbackAccess sync.Mutex
|
refs atomic.Int32
|
||||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
|
||||||
refs atomic.Int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
|
func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.RuleSet) (*LocalRuleSet, error) {
|
||||||
@@ -55,12 +52,13 @@ func NewLocalRuleSet(ctx context.Context, logger logger.Logger, options option.R
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
filePath := filemanager.BasePath(ctx, options.LocalOptions.Path)
|
err := ruleSet.reloadFile(filemanager.BasePath(ctx, options.LocalOptions.Path))
|
||||||
filePath, _ = filepath.Abs(filePath)
|
|
||||||
err := ruleSet.reloadFile(filePath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if options.Type == C.RuleSetTypeLocal {
|
||||||
|
filePath, _ := filepath.Abs(options.LocalOptions.Path)
|
||||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||||
Path: []string{filePath},
|
Path: []string{filePath},
|
||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
@@ -143,12 +141,6 @@ func (s *LocalRuleSet) reloadRules(headlessRules []option.HeadlessRule) error {
|
|||||||
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
metadata.ContainsIPCIDRRule = hasHeadlessRule(headlessRules, isIPCIDRHeadlessRule)
|
||||||
s.rules = rules
|
s.rules = rules
|
||||||
s.metadata = metadata
|
s.metadata = metadata
|
||||||
s.callbackAccess.Lock()
|
|
||||||
callbacks := s.callbacks.Array()
|
|
||||||
s.callbackAccess.Unlock()
|
|
||||||
for _, callback := range callbacks {
|
|
||||||
callback(s)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,15 +173,10 @@ func (s *LocalRuleSet) Cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
func (s *LocalRuleSet) RegisterCallback(callback adapter.RuleSetUpdateCallback) *list.Element[adapter.RuleSetUpdateCallback] {
|
||||||
s.callbackAccess.Lock()
|
return nil
|
||||||
defer s.callbackAccess.Unlock()
|
|
||||||
return s.callbacks.PushBack(callback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
func (s *LocalRuleSet) UnregisterCallback(element *list.Element[adapter.RuleSetUpdateCallback]) {
|
||||||
s.callbackAccess.Lock()
|
|
||||||
defer s.callbackAccess.Unlock()
|
|
||||||
s.callbacks.Remove(element)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LocalRuleSet) Close() error {
|
func (s *LocalRuleSet) Close() error {
|
||||||
|
|||||||
@@ -33,23 +33,23 @@ import (
|
|||||||
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
|
var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
|
||||||
|
|
||||||
type RemoteRuleSet struct {
|
type RemoteRuleSet struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
logger logger.ContextLogger
|
outboundManager adapter.OutboundManager
|
||||||
outbound adapter.OutboundManager
|
logger logger.ContextLogger
|
||||||
options option.RuleSet
|
options option.RuleSet
|
||||||
metadata adapter.RuleSetMetadata
|
metadata adapter.RuleSetMetadata
|
||||||
updateInterval time.Duration
|
updateInterval time.Duration
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
rules []adapter.HeadlessRule
|
rules []adapter.HeadlessRule
|
||||||
lastUpdated time.Time
|
lastUpdated time.Time
|
||||||
lastEtag string
|
lastEtag string
|
||||||
updateTicker *time.Ticker
|
updateTicker *time.Ticker
|
||||||
cacheFile adapter.CacheFile
|
cacheFile adapter.CacheFile
|
||||||
pauseManager pause.Manager
|
pauseManager pause.Manager
|
||||||
callbackAccess sync.Mutex
|
callbackAccess sync.Mutex
|
||||||
callbacks list.List[adapter.RuleSetUpdateCallback]
|
callbacks list.List[adapter.RuleSetUpdateCallback]
|
||||||
refs atomic.Int32
|
refs atomic.Int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
|
func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
|
||||||
@@ -61,13 +61,13 @@ func NewRemoteRuleSet(ctx context.Context, logger logger.ContextLogger, options
|
|||||||
updateInterval = 24 * time.Hour
|
updateInterval = 24 * time.Hour
|
||||||
}
|
}
|
||||||
return &RemoteRuleSet{
|
return &RemoteRuleSet{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
outboundManager: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
options: options,
|
options: options,
|
||||||
updateInterval: updateInterval,
|
updateInterval: updateInterval,
|
||||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +83,13 @@ func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext *adapter.
|
|||||||
s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx)
|
s.cacheFile = service.FromContext[adapter.CacheFile](s.ctx)
|
||||||
var dialer N.Dialer
|
var dialer N.Dialer
|
||||||
if s.options.RemoteOptions.DownloadDetour != "" {
|
if s.options.RemoteOptions.DownloadDetour != "" {
|
||||||
outbound, loaded := s.outbound.Outbound(s.options.RemoteOptions.DownloadDetour)
|
outbound, loaded := s.outboundManager.Outbound(s.options.RemoteOptions.DownloadDetour)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return E.New("download detour not found: ", s.options.RemoteOptions.DownloadDetour)
|
return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour)
|
||||||
}
|
}
|
||||||
dialer = outbound
|
dialer = outbound
|
||||||
} else {
|
} else {
|
||||||
dialer = s.outbound.Default()
|
dialer = s.outboundManager.Default()
|
||||||
}
|
}
|
||||||
s.dialer = dialer
|
s.dialer = dialer
|
||||||
if s.cacheFile != nil {
|
if s.cacheFile != nil {
|
||||||
@@ -286,7 +286,7 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT
|
|||||||
}
|
}
|
||||||
s.lastUpdated = time.Now()
|
s.lastUpdated = time.Now()
|
||||||
if s.cacheFile != nil {
|
if s.cacheFile != nil {
|
||||||
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedBinary{
|
err = s.cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{
|
||||||
LastUpdated: s.lastUpdated,
|
LastUpdated: s.lastUpdated,
|
||||||
Content: content,
|
Content: content,
|
||||||
LastEtag: s.lastEtag,
|
LastEtag: s.lastEtag,
|
||||||
@@ -301,10 +301,8 @@ func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext *adapter.HTT
|
|||||||
|
|
||||||
func (s *RemoteRuleSet) Close() error {
|
func (s *RemoteRuleSet) Close() error {
|
||||||
s.rules = nil
|
s.rules = nil
|
||||||
|
s.updateTicker.Stop()
|
||||||
s.cancel()
|
s.cancel()
|
||||||
if s.updateTicker != nil {
|
|
||||||
s.updateTicker.Stop()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ 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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.12
|
github.com/sagernet/sing v0.6.0-beta.9
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2
|
github.com/sagernet/sing-dns v0.4.0-beta.1
|
||||||
github.com/sagernet/sing-quic v0.4.0-beta.4
|
github.com/sagernet/sing-quic v0.4.0-beta.3
|
||||||
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.8 // indirect
|
github.com/sagernet/sing-tun v0.6.0-beta.7 // 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
|
||||||
|
|||||||
16
test/go.sum
16
test/go.sum
@@ -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.12 h1:2DnTJcvypK3/PM/8JjmgG8wVK48gdcpRwU98c4J/a7s=
|
github.com/sagernet/sing v0.6.0-beta.9 h1:P8lKa5hN53fRNAVCIKy5cWd6/kLO5c4slhdsfehSmHs=
|
||||||
github.com/sagernet/sing v0.6.0-beta.12/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.0-beta.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2 h1:HW94bUEp7K/vf5DlYz646LTZevQtJ0250jZa/UZRlbY=
|
github.com/sagernet/sing-dns v0.4.0-beta.1 h1:W1XkdhigwxDOMgMDVB+9kdomCpb7ExsZfB4acPcTZFY=
|
||||||
github.com/sagernet/sing-dns v0.4.0-beta.2/go.mod h1:8wuFcoFkWM4vJuQyg8e97LyvDwe0/Vl7G839WLcKDs8=
|
github.com/sagernet/sing-dns v0.4.0-beta.1/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.4 h1:kKiMLGaxvVLDCSvCMYo4PtWd1xU6FTL7xvUAQfXO09g=
|
github.com/sagernet/sing-quic v0.4.0-beta.3 h1:cOBjlhVdRZmBm6hIw1GleERpnTSFdBB2htgx5kQ5uqg=
|
||||||
github.com/sagernet/sing-quic v0.4.0-beta.4/go.mod h1:1UNObFodd8CnS3aCT53x9cigjPSCl3P//8dfBMCwBDM=
|
github.com/sagernet/sing-quic v0.4.0-beta.3/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.8 h1:GFNt/w8r1v30zC/hfCytk8C9+N/f1DfvosFXJkyJlrw=
|
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/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
github.com/sagernet/sing-tun v0.6.0-beta.7/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=
|
||||||
|
|||||||
Reference in New Issue
Block a user