mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 10:07:20 +10:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
003423745e | ||
|
|
fb3007fa80 | ||
|
|
5361d2acec | ||
|
|
1fe983a81b | ||
|
|
b01fe5d364 | ||
|
|
74920b44ac | ||
|
|
5e28a80e63 | ||
|
|
b55bfca7de | ||
|
|
a0dc394c8f | ||
|
|
87f3423d7e | ||
|
|
244243f206 | ||
|
|
89855ff548 | ||
|
|
8a388e9c90 | ||
|
|
6bf39156ec | ||
|
|
ab021bee74 | ||
|
|
6fd95e157a | ||
|
|
6c0e2bf526 | ||
|
|
4f61fc20e0 | ||
|
|
438405bbf1 | ||
|
|
b0a2ed9f7e | ||
|
|
f4d5823bb2 | ||
|
|
e8b43a97f7 | ||
|
|
00460f15f6 | ||
|
|
ca33246c9e | ||
|
|
e04a4181f4 | ||
|
|
ff4f455f25 | ||
|
|
9a7c0d9136 | ||
|
|
57de88b9c9 | ||
|
|
7f79458b4f | ||
|
|
9b4c11ba95 | ||
|
|
27c31eac5d | ||
|
|
bab8dc0b82 | ||
|
|
d09d2fb665 | ||
|
|
e64cf3b7df | ||
|
|
9b73222314 | ||
|
|
3923b57abf | ||
|
|
4807e64609 | ||
|
|
eeb37d89f1 | ||
|
|
08c1ec4b7e | ||
|
|
6b4cf67add | ||
|
|
e65926fd08 | ||
|
|
f2ec319fe1 | ||
|
|
32377a61b7 | ||
|
|
7aac801ccd | ||
|
|
96fdf59ee4 | ||
|
|
50b8f3ab94 | ||
|
|
ff7aaf977b | ||
|
|
9a1efbe54d | ||
|
|
906c21f458 | ||
|
|
d5e7af7a7e | ||
|
|
4d41f03bd5 | ||
|
|
30704a15a7 | ||
|
|
83889178ed | ||
|
|
1d2720bf5e | ||
|
|
c4b6d0eadb | ||
|
|
0c66888691 | ||
|
|
68781387fe | ||
|
|
fd299a0961 | ||
|
|
285a82050c | ||
|
|
2dbb8c55c9 | ||
|
|
effcf39469 | ||
|
|
9db9484863 | ||
|
|
ca813f461b | ||
|
|
bb46cdb2b3 | ||
|
|
dcb10c21a1 | ||
|
|
05ea0ca00e | ||
|
|
c098f282b1 | ||
|
|
ecf82d197c | ||
|
|
9afe75586a | ||
|
|
a1be455202 | ||
|
|
19fb214226 | ||
|
|
28ec898a8c | ||
|
|
467b1bbeeb | ||
|
|
02ab8ce806 | ||
|
|
ce69e620e9 | ||
|
|
1133cf3ef5 | ||
|
|
59a607e303 | ||
|
|
313be3d7a4 | ||
|
|
4fe40fcee0 | ||
|
|
e233fd4fe5 | ||
|
|
9f7683818f | ||
|
|
179e3cb2f5 | ||
|
|
41b960552d | ||
|
|
8304295c48 | ||
|
|
253b41936e | ||
|
|
ce5b4b06b5 | ||
|
|
50f5006c43 | ||
|
|
e42ff22c2e |
10
.github/workflows/build.yml
vendored
10
.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 == 'true' && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
if: matrix.require_legacy_go && steps.cache-legacy-go.outputs.cache-hit != 'true'
|
||||||
run: |-
|
run: |-
|
||||||
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
wget https://dl.google.com/go/go1.20.14.linux-amd64.tar.gz
|
||||||
tar -xzf go1.20.14.linux-amd64.tar.gz
|
tar -xzf go1.20.14.linux-amd64.tar.gz
|
||||||
@@ -159,7 +159,7 @@ jobs:
|
|||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
distribution: goreleaser-pro
|
||||||
version: latest
|
version: 2.5.1
|
||||||
install-only: true
|
install-only: true
|
||||||
- name: Extract signing key
|
- name: Extract signing key
|
||||||
run: |-
|
run: |-
|
||||||
@@ -224,7 +224,7 @@ jobs:
|
|||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28-beta2
|
ndk-version: r28-beta3
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -299,7 +299,7 @@ jobs:
|
|||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
with:
|
with:
|
||||||
ndk-version: r28-beta2
|
ndk-version: r28-beta3
|
||||||
- name: Setup OpenJDK
|
- name: Setup OpenJDK
|
||||||
run: |-
|
run: |-
|
||||||
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
sudo apt update && sudo apt install -y openjdk-17-jdk-headless
|
||||||
@@ -548,7 +548,7 @@ jobs:
|
|||||||
uses: goreleaser/goreleaser-action@v6
|
uses: goreleaser/goreleaser-action@v6
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser-pro
|
distribution: goreleaser-pro
|
||||||
version: latest
|
version: 2.5.1
|
||||||
install-only: true
|
install-only: true
|
||||||
- name: Cache ghr
|
- name: Cache ghr
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ builds:
|
|||||||
- -v
|
- -v
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ builds:
|
|||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
- GOROOT={{ .Env.GOPATH }}/go1.20.14
|
||||||
gobinary: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
tool: "{{ .Env.GOPATH }}/go1.20.14/bin/go"
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -28,7 +28,7 @@ ci_build:
|
|||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
generate_completions:
|
generate_completions:
|
||||||
go run -v --tags generate,generate_completions $(MAIN)
|
go run -v --tags $(TAGS),generate,generate_completions $(MAIN)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
go build -o $(PREFIX)/bin/$(NAME) $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -61,6 +61,12 @@ proto_install:
|
|||||||
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
go install -v google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||||
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
go install -v google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||||
|
|
||||||
|
update_public_suffix:
|
||||||
|
go generate common/tlsfragment/public_suffix.go
|
||||||
|
|
||||||
|
update_certificates:
|
||||||
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
|
|||||||
21
adapter/certificate.go
Normal file
21
adapter/certificate.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CertificateStore interface {
|
||||||
|
LifecycleService
|
||||||
|
Pool() *x509.CertPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootPoolFromContext(ctx context.Context) *x509.CertPool {
|
||||||
|
store := service.FromContext[CertificateStore](ctx)
|
||||||
|
if store == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return store.Pool()
|
||||||
|
}
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConnectionRouter interface {
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteHandler(
|
|
||||||
metadata InboundContext,
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeHandlerWrapper{
|
|
||||||
metadata: metadata,
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRouteContextHandler(
|
|
||||||
router ConnectionRouter,
|
|
||||||
logger logger.ContextLogger,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &routeContextHandlerWrapper{
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
type routeHandlerWrapper struct {
|
|
||||||
metadata InboundContext
|
|
||||||
router ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
myMetadata := w.metadata
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RouteConnection(ctx, conn, myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
|
||||||
myMetadata := w.metadata
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
|
||||||
w.logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
|
||||||
|
|
||||||
type routeContextHandlerWrapper struct {
|
|
||||||
router ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
myMetadata := ContextFrom(ctx)
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
|
||||||
myMetadata := ContextFrom(ctx)
|
|
||||||
if metadata.Source.IsValid() {
|
|
||||||
myMetadata.Source = metadata.Source
|
|
||||||
}
|
|
||||||
if metadata.Destination.IsValid() {
|
|
||||||
myMetadata.Destination = metadata.Destination
|
|
||||||
}
|
|
||||||
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
|
||||||
w.logger.ErrorContext(ctx, err)
|
|
||||||
}
|
|
||||||
14
adapter/connections.go
Normal file
14
adapter/connections.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectionManager interface {
|
||||||
|
Lifecycle
|
||||||
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
73
adapter/dns.go
Normal file
73
adapter/dns.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DNSRouter interface {
|
||||||
|
Lifecycle
|
||||||
|
Exchange(ctx context.Context, message *dns.Msg, options DNSQueryOptions) (*dns.Msg, error)
|
||||||
|
Lookup(ctx context.Context, domain string, options DNSQueryOptions) ([]netip.Addr, error)
|
||||||
|
ClearCache()
|
||||||
|
LookupReverseMapping(ip netip.Addr) (string, bool)
|
||||||
|
ResetNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSClient interface {
|
||||||
|
Start()
|
||||||
|
Exchange(ctx context.Context, transport DNSTransport, message *dns.Msg, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error)
|
||||||
|
Lookup(ctx context.Context, transport DNSTransport, domain string, options DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error)
|
||||||
|
LookupCache(domain string, strategy C.DomainStrategy) ([]netip.Addr, bool)
|
||||||
|
ExchangeCache(ctx context.Context, message *dns.Msg) (*dns.Msg, bool)
|
||||||
|
ClearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSQueryOptions struct {
|
||||||
|
Transport DNSTransport
|
||||||
|
Strategy C.DomainStrategy
|
||||||
|
DisableCache bool
|
||||||
|
RewriteTTL *uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
type RDRCStore interface {
|
||||||
|
LoadRDRC(transportName string, qName string, qType uint16) (rejected bool)
|
||||||
|
SaveRDRC(transportName string, qName string, qType uint16) error
|
||||||
|
SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransport interface {
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
Dependencies() []string
|
||||||
|
Reset()
|
||||||
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyDNSTransport interface {
|
||||||
|
LegacyStrategy() C.DomainStrategy
|
||||||
|
LegacyClientSubnet() netip.Prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransportRegistry interface {
|
||||||
|
option.DNSTransportOptionsRegistry
|
||||||
|
CreateDNSTransport(ctx context.Context, logger log.ContextLogger, tag string, transportType string, options any) (DNSTransport, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSTransportManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Transports() []DNSTransport
|
||||||
|
Transport(tag string) (DNSTransport, bool)
|
||||||
|
Default() DNSTransport
|
||||||
|
FakeIP() FakeIPTransport
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) error
|
||||||
|
}
|
||||||
28
adapter/endpoint.go
Normal file
28
adapter/endpoint.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Endpoint interface {
|
||||||
|
Lifecycle
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
Outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndpointRegistry interface {
|
||||||
|
option.EndpointOptionsRegistry
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) (Endpoint, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type EndpointManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Endpoints() []Endpoint
|
||||||
|
Get(tag string) (Endpoint, bool)
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, endpointType string, options any) error
|
||||||
|
}
|
||||||
43
adapter/endpoint/adapter.go
Normal file
43
adapter/endpoint/adapter.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing-box/option"
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
endpointType string
|
||||||
|
endpointTag string
|
||||||
|
network []string
|
||||||
|
dependencies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(endpointType string, endpointTag string, network []string, dependencies []string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
endpointType: endpointType,
|
||||||
|
endpointTag: endpointTag,
|
||||||
|
network: network,
|
||||||
|
dependencies: dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapterWithDialerOptions(endpointType string, endpointTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||||
|
var dependencies []string
|
||||||
|
if dialOptions.Detour != "" {
|
||||||
|
dependencies = []string{dialOptions.Detour}
|
||||||
|
}
|
||||||
|
return NewAdapter(endpointType, endpointTag, network, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.endpointType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.endpointTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Network() []string {
|
||||||
|
return a.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Dependencies() []string {
|
||||||
|
return a.dependencies
|
||||||
|
}
|
||||||
147
adapter/endpoint/manager.go
Normal file
147
adapter/endpoint/manager.go
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.EndpointManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.EndpointRegistry
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
endpoints []adapter.Endpoint
|
||||||
|
endpointByTag map[string]adapter.Endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.EndpointRegistry) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
endpointByTag: make(map[string]adapter.Endpoint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
if stage == adapter.StartStateStart {
|
||||||
|
// started with outbound manager
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, endpoint := range m.endpoints {
|
||||||
|
err := adapter.LegacyStart(endpoint, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
endpoints := m.endpoints
|
||||||
|
m.endpoints = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
monitor.Start("close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
err = E.Append(err, endpoint.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Endpoints() []adapter.Endpoint {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.endpoints
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Endpoint, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
endpoint, found := m.endpointByTag[tag]
|
||||||
|
return endpoint, found
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
endpoint, found := m.endpointByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.endpointByTag, tag)
|
||||||
|
index := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||||
|
return it == endpoint
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid endpoint index")
|
||||||
|
}
|
||||||
|
m.endpoints = append(m.endpoints[:index], m.endpoints[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return endpoint.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||||
|
endpoint, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(endpoint, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " endpoint/", endpoint.Type(), "[", endpoint.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsEndpoint, loaded := m.endpointByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsEndpoint.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close endpoint/", existsEndpoint.Type(), "[", existsEndpoint.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.endpoints, func(it adapter.Endpoint) bool {
|
||||||
|
return it == existsEndpoint
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid endpoint index")
|
||||||
|
}
|
||||||
|
m.endpoints = append(m.endpoints[:existsIndex], m.endpoints[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.endpoints = append(m.endpoints, endpoint)
|
||||||
|
m.endpointByTag[tag] = endpoint
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/endpoint/registry.go
Normal file
72
adapter/endpoint/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package endpoint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Endpoint, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Endpoint, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.EndpointRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Endpoint, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructor map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructor: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Endpoint, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
constructor, loaded := m.constructor[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.optionsType[outboundType] = optionsConstructor
|
||||||
|
m.constructor[outboundType] = constructor
|
||||||
|
}
|
||||||
@@ -4,34 +4,45 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
Service
|
LifecycleService
|
||||||
PreStarter
|
ConnectionTracker
|
||||||
Mode() string
|
Mode() string
|
||||||
ModeList() []string
|
ModeList() []string
|
||||||
HistoryStorage() *urltest.HistoryStorage
|
HistoryStorage() URLTestHistoryStorage
|
||||||
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
|
}
|
||||||
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
|
|
||||||
|
type URLTestHistory struct {
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
Delay uint16 `json:"delay"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type URLTestHistoryStorage interface {
|
||||||
|
SetHook(hook chan<- struct{})
|
||||||
|
LoadURLTestHistory(tag string) *URLTestHistory
|
||||||
|
DeleteURLTestHistory(tag string)
|
||||||
|
StoreURLTestHistory(tag string, history *URLTestHistory)
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type V2RayServer interface {
|
||||||
|
LifecycleService
|
||||||
|
StatsService() ConnectionTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheFile interface {
|
type CacheFile interface {
|
||||||
Service
|
LifecycleService
|
||||||
PreStarter
|
|
||||||
|
|
||||||
StoreFakeIP() bool
|
StoreFakeIP() bool
|
||||||
FakeIPStorage
|
FakeIPStorage
|
||||||
|
|
||||||
StoreRDRC() bool
|
StoreRDRC() bool
|
||||||
dns.RDRCStore
|
RDRCStore
|
||||||
|
|
||||||
LoadMode() string
|
LoadMode() string
|
||||||
StoreMode(mode string) error
|
StoreMode(mode string) error
|
||||||
@@ -39,17 +50,21 @@ 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) *SavedRuleSet
|
LoadRuleSet(tag string) *SavedBinary
|
||||||
SaveRuleSet(tag string, set *SavedRuleSet) error
|
SaveRuleSet(tag string, set *SavedBinary) error
|
||||||
|
LoadScript(tag string) *SavedBinary
|
||||||
|
SaveScript(tag string, script *SavedBinary) error
|
||||||
|
SurgePersistentStoreRead(key string) string
|
||||||
|
SurgePersistentStoreWrite(key string, value string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedRuleSet struct {
|
type SavedBinary struct {
|
||||||
Content []byte
|
Content []byte
|
||||||
LastUpdated time.Time
|
LastUpdated time.Time
|
||||||
LastEtag string
|
LastEtag string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
func (s *SavedBinary) 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 +85,7 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
|||||||
return buffer.Bytes(), nil
|
return buffer.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
func (s *SavedBinary) UnmarshalBinary(data []byte) error {
|
||||||
reader := bytes.NewReader(data)
|
reader := bytes.NewReader(data)
|
||||||
var version uint8
|
var version uint8
|
||||||
err := binary.Read(reader, binary.BigEndian, &version)
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
@@ -94,10 +109,6 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tracker interface {
|
|
||||||
Leave()
|
|
||||||
}
|
|
||||||
|
|
||||||
type OutboundGroup interface {
|
type OutboundGroup interface {
|
||||||
Outbound
|
Outbound
|
||||||
Now() string
|
Now() string
|
||||||
@@ -115,13 +126,3 @@ func OutboundTag(detour Outbound) string {
|
|||||||
}
|
}
|
||||||
return detour.Tag()
|
return detour.Tag()
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServer interface {
|
|
||||||
Service
|
|
||||||
StatsService() V2RayStatsService
|
|
||||||
}
|
|
||||||
|
|
||||||
type V2RayStatsService interface {
|
|
||||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
|
||||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,6 +26,6 @@ type FakeIPStorage interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FakeIPTransport interface {
|
type FakeIPTransport interface {
|
||||||
dns.Transport
|
DNSTransport
|
||||||
Store() FakeIPStore
|
Store() FakeIPStore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,27 +6,56 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
type ConnectionHandler interface {
|
type ConnectionHandler interface {
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionHandlerEx interface {
|
||||||
|
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use PacketHandlerEx instead
|
||||||
type PacketHandler interface {
|
type PacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketHandlerEx interface {
|
||||||
|
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use OOBPacketHandlerEx instead
|
||||||
type OOBPacketHandler interface {
|
type OOBPacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OOBPacketHandlerEx interface {
|
||||||
|
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
type PacketConnectionHandler interface {
|
type PacketConnectionHandler interface {
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PacketConnectionHandlerEx interface {
|
||||||
|
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use TCPConnectionHandlerEx instead
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
type UpstreamHandlerAdapter interface {
|
type UpstreamHandlerAdapter interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandler
|
||||||
N.UDPConnectionHandler
|
N.UDPConnectionHandler
|
||||||
E.Handler
|
E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpstreamHandlerAdapterEx interface {
|
||||||
|
N.TCPConnectionHandlerEx
|
||||||
|
N.UDPConnectionHandlerEx
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,26 +2,45 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"crypto/tls"
|
||||||
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
Service
|
Lifecycle
|
||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type InjectableInbound interface {
|
type TCPInjectableInbound interface {
|
||||||
Inbound
|
Inbound
|
||||||
Network() []string
|
ConnectionHandlerEx
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
}
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
type UDPInjectableInbound interface {
|
||||||
|
Inbound
|
||||||
|
PacketConnectionHandlerEx
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundRegistry interface {
|
||||||
|
option.InboundOptionsRegistry
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type InboundManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Inbounds() []Inbound
|
||||||
|
Get(tag string) (Inbound, bool)
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
@@ -40,13 +59,31 @@ type InboundContext struct {
|
|||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
|
HTTPRequest *http.Request
|
||||||
|
ClientHello *tls.ClientHelloInfo
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
InboundDetour string
|
// Deprecated: implement in rule action
|
||||||
LastInbound string
|
InboundDetour string
|
||||||
OriginDestination M.Socksaddr
|
LastInbound string
|
||||||
InboundOptions option.InboundOptions
|
OriginDestination M.Socksaddr
|
||||||
|
RouteOriginalDestination M.Socksaddr
|
||||||
|
// Deprecated: to be removed
|
||||||
|
//nolint:staticcheck
|
||||||
|
InboundOptions option.InboundOptions
|
||||||
|
UDPDisableDomainUnmapping bool
|
||||||
|
UDPConnect bool
|
||||||
|
UDPTimeout time.Duration
|
||||||
|
TLSFragment bool
|
||||||
|
TLSFragmentFallbackDelay time.Duration
|
||||||
|
MITM *option.MITMRouteOptions
|
||||||
|
|
||||||
|
NetworkStrategy *C.NetworkStrategy
|
||||||
|
NetworkType []C.InterfaceType
|
||||||
|
FallbackNetworkType []C.InterfaceType
|
||||||
|
FallbackDelay time.Duration
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
|
|||||||
21
adapter/inbound/adapter.go
Normal file
21
adapter/inbound/adapter.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
inboundType string
|
||||||
|
inboundTag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(inboundType string, inboundTag string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
inboundType: inboundType,
|
||||||
|
inboundTag: inboundTag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.inboundType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.inboundTag
|
||||||
|
}
|
||||||
148
adapter/inbound/manager.go
Normal file
148
adapter/inbound/manager.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.InboundManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.InboundRegistry
|
||||||
|
endpoint adapter.EndpointManager
|
||||||
|
access sync.Mutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
inbounds []adapter.Inbound
|
||||||
|
inboundByTag map[string]adapter.Inbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endpoint adapter.EndpointManager) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
endpoint: endpoint,
|
||||||
|
inboundByTag: make(map[string]adapter.Inbound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
for _, inbound := range m.inbounds {
|
||||||
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if !m.started {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
inbounds := m.inbounds
|
||||||
|
m.inbounds = nil
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
var err error
|
||||||
|
for _, inbound := range inbounds {
|
||||||
|
monitor.Start("close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
err = E.Append(err, inbound.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Inbounds() []adapter.Inbound {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
return m.inbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Get(tag string) (adapter.Inbound, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
inbound, found := m.inboundByTag[tag]
|
||||||
|
m.access.Unlock()
|
||||||
|
if found {
|
||||||
|
return inbound, true
|
||||||
|
}
|
||||||
|
return m.endpoint.Get(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
inbound, found := m.inboundByTag[tag]
|
||||||
|
if !found {
|
||||||
|
m.access.Unlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.inboundByTag, tag)
|
||||||
|
index := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
||||||
|
return it == inbound
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.inbounds = append(m.inbounds[:index], m.inbounds[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
m.access.Unlock()
|
||||||
|
if started {
|
||||||
|
return inbound.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) error {
|
||||||
|
inbound, err := m.registry.Create(ctx, router, logger, tag, outboundType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(inbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsInbound, loaded := m.inboundByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = existsInbound.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close inbound/", existsInbound.Type(), "[", existsInbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.inbounds, func(it adapter.Inbound) bool {
|
||||||
|
return it == existsInbound
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.inbounds = append(m.inbounds[:existsIndex], m.inbounds[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.inbounds = append(m.inbounds, inbound)
|
||||||
|
m.inboundByTag[tag] = inbound
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/inbound/registry.go
Normal file
72
adapter/inbound/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Inbound, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.InboundRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructor map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructor: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
optionsConstructor, loaded := m.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
constructor, loaded := m.constructor[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.optionsType[outboundType] = optionsConstructor
|
||||||
|
m.constructor[outboundType] = constructor
|
||||||
|
}
|
||||||
69
adapter/lifecycle.go
Normal file
69
adapter/lifecycle.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StartStage uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
StartStateInitialize StartStage = iota
|
||||||
|
StartStateStart
|
||||||
|
StartStatePostStart
|
||||||
|
StartStateStarted
|
||||||
|
)
|
||||||
|
|
||||||
|
var ListStartStages = []StartStage{
|
||||||
|
StartStateInitialize,
|
||||||
|
StartStateStart,
|
||||||
|
StartStatePostStart,
|
||||||
|
StartStateStarted,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s StartStage) String() string {
|
||||||
|
switch s {
|
||||||
|
case StartStateInitialize:
|
||||||
|
return "initialize"
|
||||||
|
case StartStateStart:
|
||||||
|
return "start"
|
||||||
|
case StartStatePostStart:
|
||||||
|
return "post-start"
|
||||||
|
case StartStateStarted:
|
||||||
|
return "finish-start"
|
||||||
|
default:
|
||||||
|
panic("unknown stage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Lifecycle interface {
|
||||||
|
Start(stage StartStage) error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type LifecycleService interface {
|
||||||
|
Name() string
|
||||||
|
Lifecycle
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(stage StartStage, services ...Lifecycle) error {
|
||||||
|
for _, service := range services {
|
||||||
|
if service == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := service.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StartNamed(stage StartStage, services []LifecycleService) error {
|
||||||
|
for _, service := range services {
|
||||||
|
err := service.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage.String(), " ", service.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
52
adapter/lifecycle_legacy.go
Normal file
52
adapter/lifecycle_legacy.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
func LegacyStart(starter any, stage StartStage) error {
|
||||||
|
if lifecycle, isLifecycle := starter.(Lifecycle); isLifecycle {
|
||||||
|
return lifecycle.Start(stage)
|
||||||
|
}
|
||||||
|
switch stage {
|
||||||
|
case StartStateInitialize:
|
||||||
|
if preStarter, isPreStarter := starter.(interface {
|
||||||
|
PreStart() error
|
||||||
|
}); isPreStarter {
|
||||||
|
return preStarter.PreStart()
|
||||||
|
}
|
||||||
|
case StartStateStart:
|
||||||
|
if starter, isStarter := starter.(interface {
|
||||||
|
Start() error
|
||||||
|
}); isStarter {
|
||||||
|
return starter.Start()
|
||||||
|
}
|
||||||
|
case StartStateStarted:
|
||||||
|
if postStarter, isPostStarter := starter.(interface {
|
||||||
|
PostStart() error
|
||||||
|
}); isPostStarter {
|
||||||
|
return postStarter.PostStart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type lifecycleServiceWrapper struct {
|
||||||
|
Service
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLifecycleService(service Service, name string) LifecycleService {
|
||||||
|
return &lifecycleServiceWrapper{
|
||||||
|
Service: service,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lifecycleServiceWrapper) Name() string {
|
||||||
|
return l.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
|
return LegacyStart(l.Service, stage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
|
return l.Service.Close()
|
||||||
|
}
|
||||||
15
adapter/mitm.go
Normal file
15
adapter/mitm.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MITMEngine interface {
|
||||||
|
Lifecycle
|
||||||
|
ExportCertificate() *x509.Certificate
|
||||||
|
NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
}
|
||||||
56
adapter/network.go
Normal file
56
adapter/network.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkManager interface {
|
||||||
|
Lifecycle
|
||||||
|
InterfaceFinder() control.InterfaceFinder
|
||||||
|
UpdateInterfaces() error
|
||||||
|
DefaultNetworkInterface() *NetworkInterface
|
||||||
|
NetworkInterfaces() []NetworkInterface
|
||||||
|
AutoDetectInterface() bool
|
||||||
|
AutoDetectInterfaceFunc() control.Func
|
||||||
|
ProtectFunc() control.Func
|
||||||
|
DefaultOptions() NetworkOptions
|
||||||
|
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||||
|
AutoRedirectOutputMark() uint32
|
||||||
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
|
PackageManager() tun.PackageManager
|
||||||
|
WIFIState() WIFIState
|
||||||
|
ResetNetwork()
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkOptions struct {
|
||||||
|
BindInterface string
|
||||||
|
RoutingMark uint32
|
||||||
|
DomainResolver string
|
||||||
|
DomainResolveOptions DNSQueryOptions
|
||||||
|
NetworkStrategy *C.NetworkStrategy
|
||||||
|
NetworkType []C.InterfaceType
|
||||||
|
FallbackNetworkType []C.InterfaceType
|
||||||
|
FallbackDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type InterfaceUpdateListener interface {
|
||||||
|
InterfaceUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
type WIFIState struct {
|
||||||
|
SSID string
|
||||||
|
BSSID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetworkInterface struct {
|
||||||
|
control.Interface
|
||||||
|
Type C.InterfaceType
|
||||||
|
DNSServers []string
|
||||||
|
Expensive bool
|
||||||
|
Constrained bool
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +16,18 @@ type Outbound interface {
|
|||||||
Network() []string
|
Network() []string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
N.Dialer
|
N.Dialer
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
}
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
|
||||||
|
type OutboundRegistry interface {
|
||||||
|
option.OutboundOptionsRegistry
|
||||||
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Outbounds() []Outbound
|
||||||
|
Outbound(tag string) (Outbound, bool)
|
||||||
|
Default() Outbound
|
||||||
|
Remove(tag string) error
|
||||||
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) error
|
||||||
}
|
}
|
||||||
|
|||||||
45
adapter/outbound/adapter.go
Normal file
45
adapter/outbound/adapter.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Adapter struct {
|
||||||
|
outboundType string
|
||||||
|
outboundTag string
|
||||||
|
network []string
|
||||||
|
dependencies []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapter(outboundType string, outboundTag string, network []string, dependencies []string) Adapter {
|
||||||
|
return Adapter{
|
||||||
|
outboundType: outboundType,
|
||||||
|
outboundTag: outboundTag,
|
||||||
|
network: network,
|
||||||
|
dependencies: dependencies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdapterWithDialerOptions(outboundType string, outboundTag string, network []string, dialOptions option.DialerOptions) Adapter {
|
||||||
|
var dependencies []string
|
||||||
|
if dialOptions.Detour != "" {
|
||||||
|
dependencies = []string{dialOptions.Detour}
|
||||||
|
}
|
||||||
|
return NewAdapter(outboundType, outboundTag, network, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Type() string {
|
||||||
|
return a.outboundType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Tag() string {
|
||||||
|
return a.outboundTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Network() []string {
|
||||||
|
return a.network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) Dependencies() []string {
|
||||||
|
return a.dependencies
|
||||||
|
}
|
||||||
287
adapter/outbound/manager.go
Normal file
287
adapter/outbound/manager.go
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.OutboundManager = (*Manager)(nil)
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
logger log.ContextLogger
|
||||||
|
registry adapter.OutboundRegistry
|
||||||
|
endpoint adapter.EndpointManager
|
||||||
|
defaultTag string
|
||||||
|
access sync.RWMutex
|
||||||
|
started bool
|
||||||
|
stage adapter.StartStage
|
||||||
|
outbounds []adapter.Outbound
|
||||||
|
outboundByTag map[string]adapter.Outbound
|
||||||
|
dependByTag map[string][]string
|
||||||
|
defaultOutbound adapter.Outbound
|
||||||
|
defaultOutboundFallback adapter.Outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManager(logger logger.ContextLogger, registry adapter.OutboundRegistry, endpoint adapter.EndpointManager, defaultTag string) *Manager {
|
||||||
|
return &Manager{
|
||||||
|
logger: logger,
|
||||||
|
registry: registry,
|
||||||
|
endpoint: endpoint,
|
||||||
|
defaultTag: defaultTag,
|
||||||
|
outboundByTag: make(map[string]adapter.Outbound),
|
||||||
|
dependByTag: make(map[string][]string),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Initialize(defaultOutboundFallback adapter.Outbound) {
|
||||||
|
m.defaultOutboundFallback = defaultOutboundFallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
|
m.access.Lock()
|
||||||
|
if m.started && m.stage >= stage {
|
||||||
|
panic("already started")
|
||||||
|
}
|
||||||
|
m.started = true
|
||||||
|
m.stage = stage
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.access.Unlock()
|
||||||
|
if stage == adapter.StartStateStart {
|
||||||
|
if m.defaultTag != "" && m.defaultOutbound == nil {
|
||||||
|
defaultEndpoint, loaded := m.endpoint.Get(m.defaultTag)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("default outbound not found: ", m.defaultTag)
|
||||||
|
}
|
||||||
|
m.defaultOutbound = defaultEndpoint
|
||||||
|
}
|
||||||
|
return m.startOutbounds(append(outbounds, common.Map(m.endpoint.Endpoints(), func(it adapter.Endpoint) adapter.Outbound { return it })...))
|
||||||
|
} else {
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
err := adapter.LegacyStart(outbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) startOutbounds(outbounds []adapter.Outbound) error {
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StartTimeout)
|
||||||
|
started := make(map[string]bool)
|
||||||
|
for {
|
||||||
|
canContinue := false
|
||||||
|
startOne:
|
||||||
|
for _, outboundToStart := range outbounds {
|
||||||
|
outboundTag := outboundToStart.Tag()
|
||||||
|
if started[outboundTag] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dependencies := outboundToStart.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if !started[dependency] {
|
||||||
|
continue startOne
|
||||||
|
}
|
||||||
|
}
|
||||||
|
started[outboundTag] = true
|
||||||
|
canContinue = true
|
||||||
|
if starter, isStarter := outboundToStart.(adapter.Lifecycle); isStarter {
|
||||||
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
err := starter.Start(adapter.StartStateStart)
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
}
|
||||||
|
} else if starter, isStarter := outboundToStart.(interface {
|
||||||
|
Start() error
|
||||||
|
}); isStarter {
|
||||||
|
monitor.Start("start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
err := starter.Start()
|
||||||
|
monitor.Finish()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(started) == len(outbounds) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if canContinue {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
currentOutbound := common.Find(outbounds, func(it adapter.Outbound) bool {
|
||||||
|
return !started[it.Tag()]
|
||||||
|
})
|
||||||
|
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
||||||
|
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
||||||
|
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
||||||
|
return !started[it]
|
||||||
|
})
|
||||||
|
if common.Contains(oTree, problemOutboundTag) {
|
||||||
|
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
problemOutbound := m.outboundByTag[problemOutboundTag]
|
||||||
|
m.access.Unlock()
|
||||||
|
if problemOutbound == nil {
|
||||||
|
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", oCurrent.Tag(), "]")
|
||||||
|
}
|
||||||
|
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
||||||
|
}
|
||||||
|
return lintOutbound([]string{currentOutbound.Tag()}, currentOutbound)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Close() error {
|
||||||
|
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
||||||
|
m.access.Lock()
|
||||||
|
if !m.started {
|
||||||
|
m.access.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
m.started = false
|
||||||
|
outbounds := m.outbounds
|
||||||
|
m.outbounds = nil
|
||||||
|
m.access.Unlock()
|
||||||
|
var err error
|
||||||
|
for _, outbound := range outbounds {
|
||||||
|
if closer, isCloser := outbound.(io.Closer); isCloser {
|
||||||
|
monitor.Start("close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
err = E.Append(err, closer.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
})
|
||||||
|
monitor.Finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Outbounds() []adapter.Outbound {
|
||||||
|
m.access.RLock()
|
||||||
|
defer m.access.RUnlock()
|
||||||
|
return m.outbounds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
|
m.access.RLock()
|
||||||
|
outbound, found := m.outboundByTag[tag]
|
||||||
|
m.access.RUnlock()
|
||||||
|
if found {
|
||||||
|
return outbound, true
|
||||||
|
}
|
||||||
|
return m.endpoint.Get(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Default() adapter.Outbound {
|
||||||
|
m.access.RLock()
|
||||||
|
defer m.access.RUnlock()
|
||||||
|
if m.defaultOutbound != nil {
|
||||||
|
return m.defaultOutbound
|
||||||
|
} else {
|
||||||
|
return m.defaultOutboundFallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Remove(tag string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
outbound, found := m.outboundByTag[tag]
|
||||||
|
if !found {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
delete(m.outboundByTag, tag)
|
||||||
|
index := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
||||||
|
return it == outbound
|
||||||
|
})
|
||||||
|
if index == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.outbounds = append(m.outbounds[:index], m.outbounds[index+1:]...)
|
||||||
|
started := m.started
|
||||||
|
if m.defaultOutbound == outbound {
|
||||||
|
if len(m.outbounds) > 0 {
|
||||||
|
m.defaultOutbound = m.outbounds[0]
|
||||||
|
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
|
||||||
|
} else {
|
||||||
|
m.defaultOutbound = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dependBy := m.dependByTag[tag]
|
||||||
|
if len(dependBy) > 0 {
|
||||||
|
return E.New("outbound[", tag, "] is depended by ", strings.Join(dependBy, ", "))
|
||||||
|
}
|
||||||
|
dependencies := outbound.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
if len(m.dependByTag[dependency]) == 1 {
|
||||||
|
delete(m.dependByTag, dependency)
|
||||||
|
} else {
|
||||||
|
m.dependByTag[dependency] = common.Filter(m.dependByTag[dependency], func(it string) bool {
|
||||||
|
return it != tag
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if started {
|
||||||
|
return common.Close(outbound)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, inboundType string, options any) error {
|
||||||
|
if tag == "" {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
outbound, err := m.registry.CreateOutbound(ctx, router, logger, tag, inboundType, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if m.started {
|
||||||
|
for _, stage := range adapter.ListStartStages {
|
||||||
|
err = adapter.LegacyStart(outbound, stage)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, stage, " outbound/", outbound.Type(), "[", outbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existsOutbound, loaded := m.outboundByTag[tag]; loaded {
|
||||||
|
if m.started {
|
||||||
|
err = common.Close(existsOutbound)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "close outbound/", existsOutbound.Type(), "[", existsOutbound.Tag(), "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
existsIndex := common.Index(m.outbounds, func(it adapter.Outbound) bool {
|
||||||
|
return it == existsOutbound
|
||||||
|
})
|
||||||
|
if existsIndex == -1 {
|
||||||
|
panic("invalid inbound index")
|
||||||
|
}
|
||||||
|
m.outbounds = append(m.outbounds[:existsIndex], m.outbounds[existsIndex+1:]...)
|
||||||
|
}
|
||||||
|
m.outbounds = append(m.outbounds, outbound)
|
||||||
|
m.outboundByTag[tag] = outbound
|
||||||
|
dependencies := outbound.Dependencies()
|
||||||
|
for _, dependency := range dependencies {
|
||||||
|
m.dependByTag[dependency] = append(m.dependByTag[dependency], tag)
|
||||||
|
}
|
||||||
|
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
||||||
|
m.defaultOutbound = outbound
|
||||||
|
if m.started {
|
||||||
|
m.logger.Info("updated default outbound to ", outbound.Tag())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
72
adapter/outbound/registry.go
Normal file
72
adapter/outbound/registry.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error)
|
||||||
|
|
||||||
|
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
||||||
|
registry.register(outboundType, func() any {
|
||||||
|
return new(Options)
|
||||||
|
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, rawOptions any) (adapter.Outbound, error) {
|
||||||
|
var options *Options
|
||||||
|
if rawOptions != nil {
|
||||||
|
options = rawOptions.(*Options)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.OutboundRegistry = (*Registry)(nil)
|
||||||
|
|
||||||
|
type (
|
||||||
|
optionsConstructorFunc func() any
|
||||||
|
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
access sync.Mutex
|
||||||
|
optionsType map[string]optionsConstructorFunc
|
||||||
|
constructors map[string]constructorFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegistry() *Registry {
|
||||||
|
return &Registry{
|
||||||
|
optionsType: make(map[string]optionsConstructorFunc),
|
||||||
|
constructors: make(map[string]constructorFunc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
optionsConstructor, loaded := r.optionsType[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return optionsConstructor(), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
constructor, loaded := r.constructors[outboundType]
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
}
|
||||||
|
return constructor(ctx, router, logger, tag, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
||||||
|
r.access.Lock()
|
||||||
|
defer r.access.Unlock()
|
||||||
|
r.optionsType[outboundType] = optionsConstructor
|
||||||
|
r.constructors[outboundType] = constructor
|
||||||
|
}
|
||||||
@@ -1,9 +1 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
type PreStarter interface {
|
|
||||||
PreStart() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostStarter interface {
|
|
||||||
PostStart() error
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,102 +2,47 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
mdns "github.com/miekg/dns"
|
|
||||||
"go4.org/netipx"
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Lifecycle
|
||||||
PreStarter
|
|
||||||
PostStarter
|
|
||||||
Cleanup() error
|
|
||||||
|
|
||||||
Outbounds() []Outbound
|
|
||||||
Outbound(tag string) (Outbound, bool)
|
|
||||||
DefaultOutbound(network string) (Outbound, error)
|
|
||||||
|
|
||||||
FakeIPStore() FakeIPStore
|
|
||||||
|
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
|
PreMatch(metadata InboundContext) error
|
||||||
GeoIPReader() *geoip.Reader
|
ConnectionRouterEx
|
||||||
LoadGeosite(code string) (Rule, error)
|
|
||||||
|
|
||||||
RuleSet(tag string) (RuleSet, bool)
|
RuleSet(tag string) (RuleSet, bool)
|
||||||
|
|
||||||
NeedWIFIState() bool
|
NeedWIFIState() bool
|
||||||
|
|
||||||
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
|
|
||||||
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
|
|
||||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
|
||||||
ClearDNSCache()
|
|
||||||
|
|
||||||
InterfaceFinder() control.InterfaceFinder
|
|
||||||
UpdateInterfaces() error
|
|
||||||
DefaultInterface() string
|
|
||||||
AutoDetectInterface() bool
|
|
||||||
AutoDetectInterfaceFunc() control.Func
|
|
||||||
DefaultMark() uint32
|
|
||||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
|
||||||
AutoRedirectOutputMark() uint32
|
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
|
||||||
PackageManager() tun.PackageManager
|
|
||||||
WIFIState() WIFIState
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
SetTracker(tracker ConnectionTracker)
|
||||||
ClashServer() ClashServer
|
ResetNetwork()
|
||||||
SetClashServer(server ClashServer)
|
|
||||||
|
|
||||||
V2RayServer() V2RayServer
|
|
||||||
SetV2RayServer(server V2RayServer)
|
|
||||||
|
|
||||||
ResetNetwork() error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
type ConnectionTracker interface {
|
||||||
return service.ContextWith(ctx, router)
|
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) net.Conn
|
||||||
|
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule, matchOutbound Outbound) N.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func RouterFromContext(ctx context.Context) Router {
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
return service.FromContext[Router](ctx)
|
type ConnectionRouter interface {
|
||||||
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadlessRule interface {
|
type ConnectionRouterEx interface {
|
||||||
Match(metadata *InboundContext) bool
|
ConnectionRouter
|
||||||
String() string
|
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
}
|
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
|
|
||||||
type Rule interface {
|
|
||||||
HeadlessRule
|
|
||||||
Service
|
|
||||||
Type() string
|
|
||||||
UpdateGeosite() error
|
|
||||||
Outbound() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule interface {
|
|
||||||
Rule
|
|
||||||
DisableCache() bool
|
|
||||||
RewriteTTL() *uint32
|
|
||||||
ClientSubnet() *netip.Prefix
|
|
||||||
WithAddressLimit() bool
|
|
||||||
MatchAddressLimit(metadata *InboundContext) bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleSet interface {
|
type RuleSet interface {
|
||||||
@@ -123,12 +68,14 @@ type RuleSetMetadata struct {
|
|||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
type HTTPStartContext struct {
|
||||||
|
ctx context.Context
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
httpClientCache map[string]*http.Client
|
httpClientCache map[string]*http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPStartContext() *HTTPStartContext {
|
func NewHTTPStartContext(ctx context.Context) *HTTPStartContext {
|
||||||
return &HTTPStartContext{
|
return &HTTPStartContext{
|
||||||
|
ctx: ctx,
|
||||||
httpClientCache: make(map[string]*http.Client),
|
httpClientCache: make(map[string]*http.Client),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,6 +93,10 @@ func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Clie
|
|||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
},
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(c.ctx),
|
||||||
|
RootCAs: RootPoolFromContext(c.ctx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
c.httpClientCache[detour] = httpClient
|
c.httpClientCache[detour] = httpClient
|
||||||
@@ -159,12 +110,3 @@ func (c *HTTPStartContext) Close() {
|
|||||||
client.CloseIdleConnections()
|
client.CloseIdleConnections()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
|
||||||
InterfaceUpdated()
|
|
||||||
}
|
|
||||||
|
|
||||||
type WIFIState struct {
|
|
||||||
SSID string
|
|
||||||
BSSID string
|
|
||||||
}
|
|
||||||
|
|||||||
37
adapter/rule.go
Normal file
37
adapter/rule.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HeadlessRule interface {
|
||||||
|
Match(metadata *InboundContext) bool
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule interface {
|
||||||
|
HeadlessRule
|
||||||
|
Service
|
||||||
|
Type() string
|
||||||
|
Action() RuleAction
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule interface {
|
||||||
|
Rule
|
||||||
|
WithAddressLimit() bool
|
||||||
|
MatchAddressLimit(metadata *InboundContext) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleAction interface {
|
||||||
|
Type() string
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsFinalAction(action RuleAction) bool {
|
||||||
|
switch action.Type() {
|
||||||
|
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
54
adapter/script.go
Normal file
54
adapter/script.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ScriptManager interface {
|
||||||
|
Lifecycle
|
||||||
|
Scripts() []Script
|
||||||
|
Script(name string) (Script, bool)
|
||||||
|
SurgeCache() *SurgeInMemoryCache
|
||||||
|
}
|
||||||
|
|
||||||
|
type SurgeInMemoryCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
Data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Script interface {
|
||||||
|
Type() string
|
||||||
|
Tag() string
|
||||||
|
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
||||||
|
PostStart() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SurgeScript interface {
|
||||||
|
Script
|
||||||
|
ExecuteGeneric(ctx context.Context, scriptType string, timeout time.Duration, arguments []string) error
|
||||||
|
ExecuteHTTPRequest(ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool, arguments []string) (*HTTPRequestScriptResult, error)
|
||||||
|
ExecuteHTTPResponse(ctx context.Context, timeout time.Duration, request *http.Request, response *http.Response, body []byte, binaryBody bool, arguments []string) (*HTTPResponseScriptResult, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPRequestScriptResult struct {
|
||||||
|
URL string
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
Response *HTTPRequestScriptResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPRequestScriptResponse struct {
|
||||||
|
Status int
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTPResponseScriptResult struct {
|
||||||
|
Status int
|
||||||
|
Headers http.Header
|
||||||
|
Body []byte
|
||||||
|
}
|
||||||
@@ -4,112 +4,165 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUpstreamHandler(
|
func NewUpstreamHandlerEx(
|
||||||
metadata InboundContext,
|
metadata InboundContext,
|
||||||
connectionHandler ConnectionHandlerFunc,
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
errorHandler E.Handler,
|
) UpstreamHandlerAdapterEx {
|
||||||
) UpstreamHandlerAdapter {
|
return &myUpstreamHandlerWrapperEx{
|
||||||
return &myUpstreamHandlerWrapper{
|
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
type myUpstreamHandlerWrapper struct {
|
type myUpstreamHandlerWrapperEx struct {
|
||||||
metadata InboundContext
|
metadata InboundContext
|
||||||
connectionHandler ConnectionHandlerFunc
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
packetHandler PacketConnectionHandlerFunc
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, myMetadata)
|
w.connectionHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, myMetadata)
|
w.packetHandler(ctx, conn, myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
|
||||||
w.errorHandler.NewError(ctx, err)
|
|
||||||
|
type myUpstreamContextHandlerWrapperEx struct {
|
||||||
|
connectionHandler ConnectionHandlerFuncEx
|
||||||
|
packetHandler PacketConnectionHandlerFuncEx
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
func NewUpstreamContextHandlerEx(
|
||||||
return M.Metadata{
|
connectionHandler ConnectionHandlerFuncEx,
|
||||||
Source: metadata.Source,
|
packetHandler PacketConnectionHandlerFuncEx,
|
||||||
Destination: metadata.Destination,
|
) UpstreamHandlerAdapterEx {
|
||||||
}
|
return &myUpstreamContextHandlerWrapperEx{
|
||||||
}
|
|
||||||
|
|
||||||
type myUpstreamContextHandlerWrapper struct {
|
|
||||||
connectionHandler ConnectionHandlerFunc
|
|
||||||
packetHandler PacketConnectionHandlerFunc
|
|
||||||
errorHandler E.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUpstreamContextHandler(
|
|
||||||
connectionHandler ConnectionHandlerFunc,
|
|
||||||
packetHandler PacketConnectionHandlerFunc,
|
|
||||||
errorHandler E.Handler,
|
|
||||||
) UpstreamHandlerAdapter {
|
|
||||||
return &myUpstreamContextHandlerWrapper{
|
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
errorHandler: errorHandler,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.connectionHandler(ctx, conn, *myMetadata)
|
w.connectionHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if metadata.Source.IsValid() {
|
if source.IsValid() {
|
||||||
myMetadata.Source = metadata.Source
|
myMetadata.Source = source
|
||||||
}
|
}
|
||||||
if metadata.Destination.IsValid() {
|
if destination.IsValid() {
|
||||||
myMetadata.Destination = metadata.Destination
|
myMetadata.Destination = destination
|
||||||
}
|
}
|
||||||
return w.packetHandler(ctx, conn, *myMetadata)
|
w.packetHandler(ctx, conn, *myMetadata, onClose)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
func NewRouteHandlerEx(
|
||||||
w.errorHandler.NewError(ctx, err)
|
metadata InboundContext,
|
||||||
|
router ConnectionRouterEx,
|
||||||
|
) UpstreamHandlerAdapterEx {
|
||||||
|
return &routeHandlerWrapperEx{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
|
type routeHandlerWrapperEx struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouterEx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
if source.IsValid() {
|
||||||
|
r.metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
r.metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
if source.IsValid() {
|
||||||
|
r.metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
r.metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouteContextHandlerEx(
|
||||||
|
router ConnectionRouterEx,
|
||||||
|
) UpstreamHandlerAdapterEx {
|
||||||
|
return &routeContextHandlerWrapperEx{
|
||||||
|
router: router,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
|
||||||
|
|
||||||
|
type routeContextHandlerWrapperEx struct {
|
||||||
|
router ConnectionRouterEx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if source.IsValid() {
|
||||||
|
metadata.Source = source
|
||||||
|
}
|
||||||
|
if destination.IsValid() {
|
||||||
|
metadata.Destination = destination
|
||||||
|
}
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
|
||||||
}
|
}
|
||||||
|
|||||||
234
adapter/upstream_legacy.go
Normal file
234
adapter/upstream_legacy.go
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Deprecated
|
||||||
|
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
// Deprecated
|
||||||
|
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Deprecated
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
|
func NewUpstreamHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
connectionHandler: connectionHandler,
|
||||||
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
|
type myUpstreamHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use myUpstreamHandlerWrapperEx instead.
|
||||||
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: removed
|
||||||
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
|
return M.Metadata{
|
||||||
|
Source: metadata.Source,
|
||||||
|
Destination: metadata.Destination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
type myUpstreamContextHandlerWrapper struct {
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func NewUpstreamContextHandler(
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamContextHandlerWrapper{
|
||||||
|
connectionHandler: connectionHandler,
|
||||||
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.packetHandler(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use NewUpstreamContextHandlerEx instead.
|
||||||
|
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func NewRouteHandler(
|
||||||
|
metadata InboundContext,
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeHandlerWrapper{
|
||||||
|
metadata: metadata,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func NewRouteContextHandler(
|
||||||
|
router ConnectionRouter,
|
||||||
|
logger logger.ContextLogger,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &routeContextHandlerWrapper{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
//
|
||||||
|
//nolint:staticcheck
|
||||||
|
type routeHandlerWrapper struct {
|
||||||
|
metadata InboundContext
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := w.metadata
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
type routeContextHandlerWrapper struct {
|
||||||
|
router ConnectionRouter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RouteConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
|
myMetadata := ContextFrom(ctx)
|
||||||
|
if metadata.Source.IsValid() {
|
||||||
|
myMetadata.Source = metadata.Source
|
||||||
|
}
|
||||||
|
if metadata.Destination.IsValid() {
|
||||||
|
myMetadata.Destination = metadata.Destination
|
||||||
|
}
|
||||||
|
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use ConnectionRouterEx instead.
|
||||||
|
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.logger.ErrorContext(ctx, err)
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,8 +15,7 @@ type V2RayServerTransport interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServerTransportHandler interface {
|
type V2RayServerTransportHandler interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandlerEx
|
||||||
E.Handler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
|
|||||||
488
box.go
488
box.go
@@ -9,19 +9,29 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
|
"github.com/sagernet/sing-box/dns/transport/local"
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/inbound"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/mitm"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/outbound"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
|
"github.com/sagernet/sing-box/script"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
@@ -30,24 +40,57 @@ var _ adapter.Service = (*Box)(nil)
|
|||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
router adapter.Router
|
|
||||||
inbounds []adapter.Inbound
|
|
||||||
outbounds []adapter.Outbound
|
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
preServices1 map[string]adapter.Service
|
network *route.NetworkManager
|
||||||
preServices2 map[string]adapter.Service
|
endpoint *endpoint.Manager
|
||||||
postServices map[string]adapter.Service
|
inbound *inbound.Manager
|
||||||
|
outbound *outbound.Manager
|
||||||
|
dnsTransport *dns.TransportManager
|
||||||
|
dnsRouter *dns.Router
|
||||||
|
connection *route.ConnectionManager
|
||||||
|
router *route.Router
|
||||||
|
script *script.Manager
|
||||||
|
mitm adapter.MITMEngine //*mitm.Engine
|
||||||
|
services []adapter.LifecycleService
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
option.Options
|
option.Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
PlatformInterface platform.Interface
|
|
||||||
PlatformLogWriter log.PlatformWriter
|
PlatformLogWriter log.PlatformWriter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Context(
|
||||||
|
ctx context.Context,
|
||||||
|
inboundRegistry adapter.InboundRegistry,
|
||||||
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
|
) context.Context {
|
||||||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil ||
|
||||||
|
service.FromContext[adapter.EndpointRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry)
|
||||||
|
}
|
||||||
|
if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil {
|
||||||
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
func New(options Options) (*Box, error) {
|
func New(options Options) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
ctx := options.Context
|
ctx := options.Context
|
||||||
@@ -55,6 +98,22 @@ func New(options Options) (*Box, error) {
|
|||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
ctx = service.ContextWithDefaultRegistry(ctx)
|
ctx = service.ContextWithDefaultRegistry(ctx)
|
||||||
|
|
||||||
|
endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx)
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
|
|
||||||
|
if endpointRegistry == nil {
|
||||||
|
return nil, E.New("missing endpoint registry in context")
|
||||||
|
}
|
||||||
|
if inboundRegistry == nil {
|
||||||
|
return nil, E.New("missing inbound registry in context")
|
||||||
|
}
|
||||||
|
if outboundRegistry == nil {
|
||||||
|
return nil, E.New("missing outbound registry in context")
|
||||||
|
}
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
@@ -70,8 +129,9 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if options.PlatformInterface != nil {
|
if platformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
@@ -85,114 +145,233 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
router, err := route.NewRouter(
|
|
||||||
ctx,
|
var services []adapter.LifecycleService
|
||||||
logFactory,
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
common.PtrValueOrDefault(options.Route),
|
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||||
common.PtrValueOrDefault(options.DNS),
|
len(certificateOptions.Certificate) > 0 ||
|
||||||
common.PtrValueOrDefault(options.NTP),
|
len(certificateOptions.CertificatePath) > 0 ||
|
||||||
options.Inbounds,
|
len(certificateOptions.CertificateDirectoryPath) > 0 {
|
||||||
options.PlatformInterface,
|
certificateStore, err := certificate.NewStore(ctx, logFactory.NewLogger("certificate"), certificateOptions)
|
||||||
)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, E.Cause(err, "parse route options")
|
}
|
||||||
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
|
services = append(services, certificateStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
|
dnsOptions := common.PtrValueOrDefault(options.DNS)
|
||||||
|
endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry)
|
||||||
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize network manager")
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.NetworkManager](ctx, networkManager)
|
||||||
|
connectionManager := route.NewConnectionManager(ctx, logFactory.NewLogger("connection"))
|
||||||
|
service.MustRegister[adapter.ConnectionManager](ctx, connectionManager)
|
||||||
|
router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions)
|
||||||
|
service.MustRegister[adapter.Router](ctx, router)
|
||||||
|
err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize router")
|
||||||
|
}
|
||||||
|
var timeService *tls.TimeServiceWrapper
|
||||||
|
ntpOptions := common.PtrValueOrDefault(options.NTP)
|
||||||
|
if ntpOptions.Enabled {
|
||||||
|
timeService = new(tls.TimeServiceWrapper)
|
||||||
|
service.MustRegister[ntp.TimeService](ctx, timeService)
|
||||||
|
}
|
||||||
|
for i, transportOptions := range dnsOptions.Servers {
|
||||||
|
var tag string
|
||||||
|
if transportOptions.Tag != "" {
|
||||||
|
tag = transportOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = dnsTransportManager.Create(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
transportOptions.Type,
|
||||||
|
transportOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize DNS server[", i, "]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dnsRouter.Initialize(dnsOptions.Rules)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize dns router")
|
||||||
|
}
|
||||||
|
for i, endpointOptions := range options.Endpoints {
|
||||||
|
var tag string
|
||||||
|
if endpointOptions.Tag != "" {
|
||||||
|
tag = endpointOptions.Tag
|
||||||
|
} else {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
}
|
||||||
|
err = endpointManager.Create(
|
||||||
|
ctx,
|
||||||
|
router,
|
||||||
|
logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")),
|
||||||
|
tag,
|
||||||
|
endpointOptions.Type,
|
||||||
|
endpointOptions.Options,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initialize endpoint[", i, "]")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
|
||||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
var in adapter.Inbound
|
|
||||||
var tag string
|
var tag string
|
||||||
if inboundOptions.Tag != "" {
|
if inboundOptions.Tag != "" {
|
||||||
tag = inboundOptions.Tag
|
tag = inboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
in, err = inbound.New(
|
err = inboundManager.Create(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
inboundOptions,
|
inboundOptions.Type,
|
||||||
options.PlatformInterface,
|
inboundOptions.Options,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "initialize inbound[", i, "]")
|
||||||
}
|
}
|
||||||
inbounds = append(inbounds, in)
|
|
||||||
}
|
}
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var out adapter.Outbound
|
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
tag = outboundOptions.Tag
|
tag = outboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
out, err = outbound.New(
|
outboundCtx := ctx
|
||||||
ctx,
|
if tag != "" {
|
||||||
|
// TODO: remove this
|
||||||
|
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
||||||
|
Outbound: tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = outboundManager.Create(
|
||||||
|
outboundCtx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
outboundOptions)
|
outboundOptions.Type,
|
||||||
|
outboundOptions.Options,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
outbounds = append(outbounds, out)
|
|
||||||
}
|
}
|
||||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
outboundManager.Initialize(common.Must1(
|
||||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
direct.NewOutbound(
|
||||||
common.Must(oErr)
|
ctx,
|
||||||
outbounds = append(outbounds, out)
|
router,
|
||||||
return out
|
logFactory.NewLogger("outbound/direct"),
|
||||||
})
|
"direct",
|
||||||
|
option.DirectOutboundOptions{},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
dnsTransportManager.Initialize(common.Must1(
|
||||||
|
local.NewTransport(
|
||||||
|
ctx,
|
||||||
|
logFactory.NewLogger("dns/local"),
|
||||||
|
"local",
|
||||||
|
option.LocalDNSServerOptions{},
|
||||||
|
)))
|
||||||
|
scriptManager, err := script.NewManager(ctx, logFactory, options.Scripts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, E.Cause(err, "initialize script manager")
|
||||||
}
|
}
|
||||||
if options.PlatformInterface != nil {
|
service.MustRegister[adapter.ScriptManager](ctx, scriptManager)
|
||||||
err = options.PlatformInterface.Initialize(ctx, router)
|
if platformInterface != nil {
|
||||||
|
err = platformInterface.Initialize(networkManager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
preServices1 := make(map[string]adapter.Service)
|
|
||||||
preServices2 := make(map[string]adapter.Service)
|
|
||||||
postServices := make(map[string]adapter.Service)
|
|
||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
if cacheFile == nil {
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
services = append(services, cacheFile)
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
|
||||||
}
|
|
||||||
preServices1["cache file"] = cacheFile
|
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
|
||||||
clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), clashAPIOptions)
|
clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash-server")
|
||||||
}
|
}
|
||||||
router.SetClashServer(clashServer)
|
router.SetTracker(clashServer)
|
||||||
preServices2["clash api"] = clashServer
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
|
services = append(services, clashServer)
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create v2ray api server")
|
return nil, E.Cause(err, "create v2ray-server")
|
||||||
}
|
}
|
||||||
router.SetV2RayServer(v2rayServer)
|
if v2rayServer.StatsService() != nil {
|
||||||
preServices2["v2ray api"] = v2rayServer
|
router.SetTracker(v2rayServer.StatsService())
|
||||||
|
services = append(services, v2rayServer)
|
||||||
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ntpOptions.Enabled {
|
||||||
|
ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create NTP service")
|
||||||
|
}
|
||||||
|
ntpService := ntp.NewService(ntp.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Dialer: ntpDialer,
|
||||||
|
Logger: logFactory.NewLogger("ntp"),
|
||||||
|
Server: ntpOptions.ServerOptions.Build(),
|
||||||
|
Interval: time.Duration(ntpOptions.Interval),
|
||||||
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
|
})
|
||||||
|
timeService.TimeService = ntpService
|
||||||
|
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
|
}
|
||||||
|
mitmOptions := common.PtrValueOrDefault(options.MITM)
|
||||||
|
var mitmEngine adapter.MITMEngine
|
||||||
|
if mitmOptions.Enabled {
|
||||||
|
engine, err := mitm.NewEngine(ctx, logFactory.NewLogger("mitm"), mitmOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create MITM engine")
|
||||||
|
}
|
||||||
|
service.MustRegister[adapter.MITMEngine](ctx, engine)
|
||||||
|
mitmEngine = engine
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
|
network: networkManager,
|
||||||
|
endpoint: endpointManager,
|
||||||
|
inbound: inboundManager,
|
||||||
|
outbound: outboundManager,
|
||||||
|
dnsTransport: dnsTransportManager,
|
||||||
|
dnsRouter: dnsRouter,
|
||||||
|
connection: connectionManager,
|
||||||
router: router,
|
router: router,
|
||||||
inbounds: inbounds,
|
script: scriptManager,
|
||||||
outbounds: outbounds,
|
mitm: mitmEngine,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
preServices1: preServices1,
|
services: services,
|
||||||
preServices2: preServices2,
|
|
||||||
postServices: postServices,
|
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -243,35 +422,19 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices1 {
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
||||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
|
||||||
monitor.Start("pre-start ", serviceName)
|
|
||||||
err := preService.PreStart()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for serviceName, service := range s.preServices2 {
|
|
||||||
if preService, isPreService := service.(adapter.PreStarter); isPreService {
|
|
||||||
monitor.Start("pre-start ", serviceName)
|
|
||||||
err := preService.PreStart()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = s.router.PreStart()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "pre-start router")
|
|
||||||
}
|
|
||||||
err = s.startOutbounds()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.router.Start()
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStart, s.outbound, s.dnsTransport, s.dnsRouter, s.network, s.connection, s.router, s.script, s.mitm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) start() error {
|
func (s *Box) start() error {
|
||||||
@@ -279,64 +442,33 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for serviceName, service := range s.preServices1 {
|
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||||
err = service.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for serviceName, service := range s.preServices2 {
|
|
||||||
err = service.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, in := range s.inbounds {
|
|
||||||
var tag string
|
|
||||||
if in.Tag() == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = in.Tag()
|
|
||||||
}
|
|
||||||
err = in.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = s.postStart()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.router.Cleanup()
|
err = s.inbound.Start(adapter.StartStateStart)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Box) postStart() error {
|
|
||||||
for serviceName, service := range s.postServices {
|
|
||||||
err := service.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start ", serviceName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: reorganize ALL start order
|
|
||||||
for _, out := range s.outbounds {
|
|
||||||
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
|
|
||||||
err := lateOutbound.PostStart()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "post-start outbound/", out.Tag())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err := s.router.PostStart()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, in := range s.inbounds {
|
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||||
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
|
if err != nil {
|
||||||
err = lateInbound.PostStart()
|
return err
|
||||||
if err != nil {
|
}
|
||||||
return E.Cause(err, "post-start inbound/", in.Tag())
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.inbound, s.endpoint)
|
||||||
}
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.script, s.mitm, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -348,58 +480,32 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
monitor := taskmonitor.New(s.logger, C.StopTimeout)
|
err := common.Close(
|
||||||
var errors error
|
s.inbound, s.outbound, s.endpoint, s.mitm, s.script, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
for serviceName, service := range s.postServices {
|
)
|
||||||
monitor.Start("close ", serviceName)
|
for _, lifecycleService := range s.services {
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", serviceName)
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
for i, in := range s.inbounds {
|
|
||||||
monitor.Start("close inbound/", in.Type(), "[", i, "]")
|
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
for i, out := range s.outbounds {
|
|
||||||
monitor.Start("close outbound/", out.Type(), "[", i, "]")
|
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
|
||||||
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
monitor.Start("close router")
|
|
||||||
if err := common.Close(s.router); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close router")
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
monitor.Finish()
|
err = E.Append(err, s.logFactory.Close(), func(err error) error {
|
||||||
for serviceName, service := range s.preServices1 {
|
return E.Cause(err, "close logger")
|
||||||
monitor.Start("close ", serviceName)
|
})
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
return err
|
||||||
return E.Cause(err, "close ", serviceName)
|
}
|
||||||
})
|
|
||||||
monitor.Finish()
|
func (s *Box) Network() adapter.NetworkManager {
|
||||||
}
|
return s.network
|
||||||
for serviceName, service := range s.preServices2 {
|
|
||||||
monitor.Start("close ", serviceName)
|
|
||||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close ", serviceName)
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close logger")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Router() adapter.Router {
|
func (s *Box) Router() adapter.Router {
|
||||||
return s.router
|
return s.router
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Box) Inbound() adapter.InboundManager {
|
||||||
|
return s.inbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) Outbound() adapter.OutboundManager {
|
||||||
|
return s.outbound
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
package box
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Box) startOutbounds() error {
|
|
||||||
monitor := taskmonitor.New(s.logger, C.StartTimeout)
|
|
||||||
outboundTags := make(map[adapter.Outbound]string)
|
|
||||||
outbounds := make(map[string]adapter.Outbound)
|
|
||||||
for i, outboundToStart := range s.outbounds {
|
|
||||||
var outboundTag string
|
|
||||||
if outboundToStart.Tag() == "" {
|
|
||||||
outboundTag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
outboundTag = outboundToStart.Tag()
|
|
||||||
}
|
|
||||||
if _, exists := outbounds[outboundTag]; exists {
|
|
||||||
return E.New("outbound tag ", outboundTag, " duplicated")
|
|
||||||
}
|
|
||||||
outboundTags[outboundToStart] = outboundTag
|
|
||||||
outbounds[outboundTag] = outboundToStart
|
|
||||||
}
|
|
||||||
started := make(map[string]bool)
|
|
||||||
for {
|
|
||||||
canContinue := false
|
|
||||||
startOne:
|
|
||||||
for _, outboundToStart := range s.outbounds {
|
|
||||||
outboundTag := outboundTags[outboundToStart]
|
|
||||||
if started[outboundTag] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dependencies := outboundToStart.Dependencies()
|
|
||||||
for _, dependency := range dependencies {
|
|
||||||
if !started[dependency] {
|
|
||||||
continue startOne
|
|
||||||
}
|
|
||||||
}
|
|
||||||
started[outboundTag] = true
|
|
||||||
canContinue = true
|
|
||||||
if starter, isStarter := outboundToStart.(interface {
|
|
||||||
Start() error
|
|
||||||
}); isStarter {
|
|
||||||
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
err := starter.Start()
|
|
||||||
monitor.Finish()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(started) == len(s.outbounds) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if canContinue {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool {
|
|
||||||
return !started[outboundTags[it]]
|
|
||||||
})
|
|
||||||
var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error
|
|
||||||
lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error {
|
|
||||||
problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool {
|
|
||||||
return !started[it]
|
|
||||||
})
|
|
||||||
if common.Contains(oTree, problemOutboundTag) {
|
|
||||||
return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag)
|
|
||||||
}
|
|
||||||
problemOutbound := outbounds[problemOutboundTag]
|
|
||||||
if problemOutbound == nil {
|
|
||||||
return E.New("dependency[", problemOutboundTag, "] not found for outbound[", outboundTags[oCurrent], "]")
|
|
||||||
}
|
|
||||||
return lintOutbound(append(oTree, problemOutboundTag), problemOutbound)
|
|
||||||
}
|
|
||||||
return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Submodule clients/android updated: e1049099a0...599d8cecac
Submodule clients/apple updated: 3d889ae017...e43e3a3f64
@@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/asc-go/asc"
|
"github.com/sagernet/asc-go/asc"
|
||||||
@@ -194,6 +195,10 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
log.Info(string(platform), " ", tag, " create submission")
|
log.Info(string(platform), " ", tag, " create submission")
|
||||||
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
_, _, err = client.TestFlight.CreateBetaAppReviewSubmission(ctx, build.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "ANOTHER_BUILD_IN_REVIEW") {
|
||||||
|
log.Error(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "28.0.12674087"
|
const fixedVersion = "28.0.12916984"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
|
|||||||
68
cmd/internal/update_certificates/main.go
Normal file
68
cmd/internal/update_certificates/main.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/csv"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := updateMozillaIncludedRootCAs()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateMozillaIncludedRootCAs() error {
|
||||||
|
response, err := http.Get("https://ccadb.my.salesforce-sites.com/mozilla/IncludedCACertificateReportPEMCSV")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
reader := csv.NewReader(response.Body)
|
||||||
|
header, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
geoIndex := slices.Index(header, "Geographic Focus")
|
||||||
|
nameIndex := slices.Index(header, "Common Name or Certificate Name")
|
||||||
|
certIndex := slices.Index(header, "PEM Info")
|
||||||
|
|
||||||
|
generated := strings.Builder{}
|
||||||
|
generated.WriteString(`// Code generated by 'make update_certificates'. DO NOT EDIT.
|
||||||
|
|
||||||
|
package certificate
|
||||||
|
|
||||||
|
import "crypto/x509"
|
||||||
|
|
||||||
|
var mozillaIncluded *x509.CertPool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mozillaIncluded = x509.NewCertPool()
|
||||||
|
`)
|
||||||
|
for {
|
||||||
|
record, err := reader.Read()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if record[geoIndex] == "China" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
generated.WriteString("\n // ")
|
||||||
|
generated.WriteString(record[nameIndex])
|
||||||
|
generated.WriteString("\n")
|
||||||
|
generated.WriteString(" mozillaIncluded.AppendCertsFromPEM([]byte(`")
|
||||||
|
generated.WriteString(record[certIndex])
|
||||||
|
generated.WriteString("`))\n")
|
||||||
|
}
|
||||||
|
generated.WriteString("}\n")
|
||||||
|
return os.WriteFile("common/certificate/mozilla.go", []byte(generated.String()), 0o644)
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
_ "github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
@@ -68,4 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func format() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, optionsEntry := range optionsList {
|
for _, optionsEntry := range optionsList {
|
||||||
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
|
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateTLSKeyPair(serverName string) error {
|
func generateTLSKeyPair(serverName string) error {
|
||||||
privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
privateKeyPem, publicKeyPem, err := tls.GenerateCertificate(nil, nil, time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var commandMerge = &cobra.Command{
|
var commandMerge = &cobra.Command{
|
||||||
Use: "merge <output>",
|
Use: "merge <output-path>",
|
||||||
Short: "Merge configurations",
|
Short: "Merge configurations",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
err := merge(args[0])
|
err := merge(args[0])
|
||||||
@@ -68,29 +68,19 @@ func merge(outputPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergePathResources(options *option.Options) error {
|
func mergePathResources(options *option.Options) error {
|
||||||
for index, inbound := range options.Inbounds {
|
for _, inbound := range options.Inbounds {
|
||||||
rawOptions, err := inbound.RawOptions()
|
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
|
||||||
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
||||||
}
|
}
|
||||||
options.Inbounds[index] = inbound
|
|
||||||
}
|
}
|
||||||
for index, outbound := range options.Outbounds {
|
for _, outbound := range options.Outbounds {
|
||||||
rawOptions, err := outbound.RawOptions()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch outbound.Type {
|
switch outbound.Type {
|
||||||
case C.TypeSSH:
|
case C.TypeSSH:
|
||||||
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
|
||||||
}
|
}
|
||||||
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
||||||
}
|
}
|
||||||
options.Outbounds[index] = outbound
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
|
||||||
if options.PrivateKeyPath != "" {
|
if options.PrivateKeyPath != "" {
|
||||||
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
||||||
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimStringArray(array []string) []string {
|
func trimStringArray(array []string) []string {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -83,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
}
|
}
|
||||||
for i, ruleOptions := range plainRuleSet.Rules {
|
for i, ruleOptions := range plainRuleSet.Rules {
|
||||||
var currentRule adapter.HeadlessRule
|
var currentRule adapter.HeadlessRule
|
||||||
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
currentRule, err = rule.NewHeadlessRule(context.Background(), ruleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
|
|||||||
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
162
cmd/sing-box/cmd_rule_set_merge.go
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/json"
|
||||||
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ruleSetPaths []string
|
||||||
|
ruleSetDirectories []string
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandRuleSetMerge = &cobra.Command{
|
||||||
|
Use: "merge <output-path>",
|
||||||
|
Short: "Merge rule-set source files",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := mergeRuleSet(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetPaths, "config", "c", nil, "set input rule-set file path")
|
||||||
|
commandRuleSetMerge.Flags().StringArrayVarP(&ruleSetDirectories, "config-directory", "C", nil, "set input rule-set directory path")
|
||||||
|
commandRuleSet.AddCommand(commandRuleSetMerge)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuleSetEntry struct {
|
||||||
|
content []byte
|
||||||
|
path string
|
||||||
|
options option.PlainRuleSetCompat
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSetAt(path string) (*RuleSetEntry, error) {
|
||||||
|
var (
|
||||||
|
configContent []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if path == "stdin" {
|
||||||
|
configContent, err = io.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
configContent, err = os.ReadFile(path)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
|
}
|
||||||
|
options, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, configContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
|
}
|
||||||
|
return &RuleSetEntry{
|
||||||
|
content: configContent,
|
||||||
|
path: path,
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSet() ([]*RuleSetEntry, error) {
|
||||||
|
var optionsList []*RuleSetEntry
|
||||||
|
for _, path := range ruleSetPaths {
|
||||||
|
optionsEntry, err := readRuleSetAt(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optionsList = append(optionsList, optionsEntry)
|
||||||
|
}
|
||||||
|
for _, directory := range ruleSetDirectories {
|
||||||
|
entries, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read rule-set directory at ", directory)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
optionsEntry, err := readRuleSetAt(filepath.Join(directory, entry.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optionsList = append(optionsList, optionsEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Slice(optionsList, func(i, j int) bool {
|
||||||
|
return optionsList[i].path < optionsList[j].path
|
||||||
|
})
|
||||||
|
return optionsList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readRuleSetAndMerge() (option.PlainRuleSetCompat, error) {
|
||||||
|
optionsList, err := readRuleSet()
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, err
|
||||||
|
}
|
||||||
|
if len(optionsList) == 1 {
|
||||||
|
return optionsList[0].options, nil
|
||||||
|
}
|
||||||
|
var optionVersion uint8
|
||||||
|
for _, options := range optionsList {
|
||||||
|
if optionVersion < options.options.Version {
|
||||||
|
optionVersion = options.options.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mergedMessage json.RawMessage
|
||||||
|
for _, options := range optionsList {
|
||||||
|
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, E.Cause(err, "merge config at ", options.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mergedOptions, err := json.UnmarshalExtendedContext[option.PlainRuleSetCompat](globalCtx, mergedMessage)
|
||||||
|
if err != nil {
|
||||||
|
return option.PlainRuleSetCompat{}, E.Cause(err, "unmarshal merged config")
|
||||||
|
}
|
||||||
|
mergedOptions.Version = optionVersion
|
||||||
|
return mergedOptions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeRuleSet(outputPath string) error {
|
||||||
|
mergedOptions, err := readRuleSetAndMerge()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(mergedOptions)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "encode config")
|
||||||
|
}
|
||||||
|
if existsContent, err := os.ReadFile(outputPath); err != nil {
|
||||||
|
if string(existsContent) == buffer.String() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = rw.MkdirParent(outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputPath, _ = filepath.Abs(outputPath)
|
||||||
|
os.Stderr.WriteString(outputPath + "\n")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -61,14 +61,15 @@ func upgradeRuleSet(sourcePath string) error {
|
|||||||
log.Info("already up-to-date")
|
log.Info("already up-to-date")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
plainRuleSet, err := plainRuleSetCompat.Upgrade()
|
plainRuleSetCompat.Options, err = plainRuleSetCompat.Upgrade()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
plainRuleSetCompat.Version = C.RuleSetVersionCurrent
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
encoder := json.NewEncoder(buffer)
|
encoder := json.NewEncoder(buffer)
|
||||||
encoder.SetIndent("", " ")
|
encoder.SetIndent("", " ")
|
||||||
err = encoder.Encode(plainRuleSet)
|
err = encoder.Encode(plainRuleSetCompat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "encode config")
|
return E.Cause(err, "encode config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read config at ", path)
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
}
|
}
|
||||||
options, err := json.UnmarshalExtended[option.Options](configContent)
|
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode config at ", path)
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
}
|
}
|
||||||
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
|
|||||||
}
|
}
|
||||||
var mergedMessage json.RawMessage
|
var mergedMessage json.RawMessage
|
||||||
for _, options := range optionsList {
|
for _, options := range optionsList {
|
||||||
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
|
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var mergedOptions option.Options
|
var mergedOptions option.Options
|
||||||
err = mergedOptions.UnmarshalJSON(mergedMessage)
|
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -23,9 +26,11 @@ func init() {
|
|||||||
func createPreStartedClient() (*box.Box, error) {
|
func createPreStartedClient() (*box.Box, error) {
|
||||||
options, err := readConfigAndMerge()
|
options, err := readConfigAndMerge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
instance, err := box.New(box.Options{Options: options})
|
instance, err := box.New(box.Options{Context: globalCtx, Options: options})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create service")
|
return nil, E.Cause(err, "create service")
|
||||||
}
|
}
|
||||||
@@ -36,11 +41,11 @@ func createPreStartedClient() (*box.Box, error) {
|
|||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
func createDialer(instance *box.Box, outboundTag string) (N.Dialer, error) {
|
||||||
if outboundTag == "" {
|
if outboundTag == "" {
|
||||||
return instance.Router().DefaultOutbound(N.NetworkName(network))
|
return instance.Outbound().Default(), nil
|
||||||
} else {
|
} else {
|
||||||
outbound, loaded := instance.Router().Outbound(outboundTag)
|
outbound, loaded := instance.Outbound().Outbound(outboundTag)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound not found: ", outboundTag)
|
return nil, E.New("outbound not found: ", outboundTag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func connect(address string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer instance.Close()
|
defer instance.Close()
|
||||||
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func fetch(args []string) error {
|
|||||||
httpClient = &http.Client{
|
httpClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func initializeHTTP3Client(instance *box.Box) error {
|
func initializeHTTP3Client(instance *box.Box) error {
|
||||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/settings"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -45,7 +43,7 @@ func syncTime() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
dialer, err := createDialer(instance, commandToolsFlagOutbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -59,7 +57,7 @@ func syncTime() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if commandSyncTimeWrite {
|
if commandSyncTimeWrite {
|
||||||
err = settings.SetSystemTime(response.Time)
|
err = ntp.SetSystemTime(response.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "write time to system")
|
return E.Cause(err, "write time to system")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package adguard
|
package adguard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -26,7 +27,7 @@ example.arpa
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.org",
|
"example.org",
|
||||||
@@ -85,7 +86,7 @@ func TestHosts(t *testing.T) {
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"google.com",
|
"google.com",
|
||||||
@@ -115,7 +116,7 @@ www.example.org
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := route.NewHeadlessRule(nil, rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|||||||
4359
common/certificate/mozilla.go
Normal file
4359
common/certificate/mozilla.go
Normal file
File diff suppressed because it is too large
Load Diff
184
common/certificate/store.go
Normal file
184
common/certificate/store.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package certificate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/x509"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/fswatch"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.CertificateStore = (*Store)(nil)
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
systemPool *x509.CertPool
|
||||||
|
currentPool *x509.CertPool
|
||||||
|
certificate string
|
||||||
|
certificatePaths []string
|
||||||
|
certificateDirectoryPaths []string
|
||||||
|
watcher *fswatch.Watcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStore(ctx context.Context, logger logger.Logger, options option.CertificateOptions) (*Store, error) {
|
||||||
|
var systemPool *x509.CertPool
|
||||||
|
switch options.Store {
|
||||||
|
case C.CertificateStoreSystem, "":
|
||||||
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
|
systemCertificates := platformInterface.SystemCertificates()
|
||||||
|
if len(systemCertificates) > 0 {
|
||||||
|
systemPool = x509.NewCertPool()
|
||||||
|
for _, cert := range systemCertificates {
|
||||||
|
if !systemPool.AppendCertsFromPEM([]byte(cert)) {
|
||||||
|
return nil, E.New("invalid system certificate PEM: ", cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
certPool, err := x509.SystemCertPool()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
systemPool = certPool
|
||||||
|
}
|
||||||
|
case C.CertificateStoreMozilla:
|
||||||
|
systemPool = mozillaIncluded
|
||||||
|
case C.CertificateStoreNone:
|
||||||
|
systemPool = nil
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown certificate store: ", options.Store)
|
||||||
|
}
|
||||||
|
store := &Store{
|
||||||
|
systemPool: systemPool,
|
||||||
|
certificate: strings.Join(options.Certificate, "\n"),
|
||||||
|
certificatePaths: options.CertificatePath,
|
||||||
|
certificateDirectoryPaths: options.CertificateDirectoryPath,
|
||||||
|
}
|
||||||
|
var watchPaths []string
|
||||||
|
for _, target := range options.CertificatePath {
|
||||||
|
watchPaths = append(watchPaths, target)
|
||||||
|
}
|
||||||
|
for _, target := range options.CertificateDirectoryPath {
|
||||||
|
watchPaths = append(watchPaths, target)
|
||||||
|
}
|
||||||
|
if len(watchPaths) > 0 {
|
||||||
|
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||||
|
Path: watchPaths,
|
||||||
|
Logger: logger,
|
||||||
|
Callback: func(_ string) {
|
||||||
|
err := store.update()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(E.Cause(err, "reload certificates"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "fswatch: create fsnotify watcher")
|
||||||
|
}
|
||||||
|
store.watcher = watcher
|
||||||
|
}
|
||||||
|
err := store.update()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "initializing certificate store")
|
||||||
|
}
|
||||||
|
return store, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Name() string {
|
||||||
|
return "certificate"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if s.watcher != nil {
|
||||||
|
return s.watcher.Start()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Close() error {
|
||||||
|
if s.watcher != nil {
|
||||||
|
return s.watcher.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Pool() *x509.CertPool {
|
||||||
|
return s.currentPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) update() error {
|
||||||
|
var currentPool *x509.CertPool
|
||||||
|
if s.systemPool == nil {
|
||||||
|
currentPool = x509.NewCertPool()
|
||||||
|
} else {
|
||||||
|
currentPool = s.systemPool.Clone()
|
||||||
|
}
|
||||||
|
if s.certificate != "" {
|
||||||
|
if !currentPool.AppendCertsFromPEM([]byte(s.certificate)) {
|
||||||
|
return E.New("invalid certificate PEM strings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, path := range s.certificatePaths {
|
||||||
|
pemContent, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !currentPool.AppendCertsFromPEM(pemContent) {
|
||||||
|
return E.New("invalid certificate PEM file: ", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var firstErr error
|
||||||
|
for _, directoryPath := range s.certificateDirectoryPaths {
|
||||||
|
directoryEntries, err := readUniqueDirectoryEntries(directoryPath)
|
||||||
|
if err != nil {
|
||||||
|
if firstErr == nil && !os.IsNotExist(err) {
|
||||||
|
firstErr = E.Cause(err, "invalid certificate directory: ", directoryPath)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, directoryEntry := range directoryEntries {
|
||||||
|
pemContent, err := os.ReadFile(filepath.Join(directoryPath, directoryEntry.Name()))
|
||||||
|
if err == nil {
|
||||||
|
currentPool.AppendCertsFromPEM(pemContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if firstErr != nil {
|
||||||
|
return firstErr
|
||||||
|
}
|
||||||
|
s.currentPool = currentPool
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readUniqueDirectoryEntries(dir string) ([]fs.DirEntry, error) {
|
||||||
|
files, err := os.ReadDir(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
uniq := files[:0]
|
||||||
|
for _, f := range files {
|
||||||
|
if !isSameDirSymlink(f, dir) {
|
||||||
|
uniq = append(uniq, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSameDirSymlink(f fs.DirEntry, dir string) bool {
|
||||||
|
if f.Type()&fs.ModeSymlink == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
target, err := os.Readlink(filepath.Join(dir, f.Name()))
|
||||||
|
return err == nil && !strings.Contains(target, "/")
|
||||||
|
}
|
||||||
@@ -2,73 +2,131 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/conntrack"
|
"github.com/sagernet/sing-box/common/conntrack"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ WireGuardListener = (*DefaultDialer)(nil)
|
var (
|
||||||
|
_ ParallelInterfaceDialer = (*DefaultDialer)(nil)
|
||||||
|
_ WireGuardListener = (*DefaultDialer)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type DefaultDialer struct {
|
type DefaultDialer struct {
|
||||||
dialer4 tcpDialer
|
dialer4 tcpDialer
|
||||||
dialer6 tcpDialer
|
dialer6 tcpDialer
|
||||||
udpDialer4 net.Dialer
|
udpDialer4 net.Dialer
|
||||||
udpDialer6 net.Dialer
|
udpDialer6 net.Dialer
|
||||||
udpListener net.ListenConfig
|
udpListener net.ListenConfig
|
||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
isWireGuardListener bool
|
networkManager adapter.NetworkManager
|
||||||
|
networkStrategy *C.NetworkStrategy
|
||||||
|
defaultNetworkStrategy bool
|
||||||
|
networkType []C.InterfaceType
|
||||||
|
fallbackNetworkType []C.InterfaceType
|
||||||
|
networkFallbackDelay time.Duration
|
||||||
|
networkLastFallback atomic.TypedValue[time.Time]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
|
func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDialer, error) {
|
||||||
var dialer net.Dialer
|
networkManager := service.FromContext[adapter.NetworkManager](ctx)
|
||||||
var listener net.ListenConfig
|
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dialer net.Dialer
|
||||||
|
listener net.ListenConfig
|
||||||
|
interfaceFinder control.InterfaceFinder
|
||||||
|
networkStrategy *C.NetworkStrategy
|
||||||
|
defaultNetworkStrategy bool
|
||||||
|
networkType []C.InterfaceType
|
||||||
|
fallbackNetworkType []C.InterfaceType
|
||||||
|
networkFallbackDelay time.Duration
|
||||||
|
)
|
||||||
|
if networkManager != nil {
|
||||||
|
interfaceFinder = networkManager.InterfaceFinder()
|
||||||
|
} else {
|
||||||
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
|
}
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
var interfaceFinder control.InterfaceFinder
|
|
||||||
if router != nil {
|
|
||||||
interfaceFinder = router.InterfaceFinder()
|
|
||||||
} else {
|
|
||||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
|
||||||
}
|
|
||||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router != nil && router.AutoDetectInterface() {
|
|
||||||
bindFunc := router.AutoDetectInterfaceFunc()
|
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
|
||||||
} else if router != nil && router.DefaultInterface() != "" {
|
|
||||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
|
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
|
||||||
}
|
|
||||||
var autoRedirectOutputMark uint32
|
|
||||||
if router != nil {
|
|
||||||
autoRedirectOutputMark = router.AutoRedirectOutputMark()
|
|
||||||
}
|
|
||||||
if autoRedirectOutputMark > 0 {
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
if options.RoutingMark > 0 {
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
listener.Control = control.Append(listener.Control, control.RoutingMark(uint32(options.RoutingMark)))
|
||||||
|
}
|
||||||
|
if networkManager != nil {
|
||||||
|
autoRedirectOutputMark := networkManager.AutoRedirectOutputMark()
|
||||||
if autoRedirectOutputMark > 0 {
|
if autoRedirectOutputMark > 0 {
|
||||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
if options.RoutingMark > 0 {
|
||||||
|
return nil, E.New("`routing_mark` is conflict with `tun.auto_redirect` with `tun.route_[_exclude]_address_set")
|
||||||
|
}
|
||||||
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||||
|
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||||
}
|
}
|
||||||
} else if router != nil && router.DefaultMark() > 0 {
|
}
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
disableDefaultBind := options.BindInterface != "" || options.Inet4BindAddress != nil || options.Inet6BindAddress != nil
|
||||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
if disableDefaultBind || options.TCPFastOpen {
|
||||||
if autoRedirectOutputMark > 0 {
|
if options.NetworkStrategy != nil || len(options.NetworkType) > 0 && options.FallbackNetworkType == nil && options.FallbackDelay == 0 {
|
||||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
return nil, E.New("`network_strategy` is conflict with `bind_interface`, `inet4_bind_address`, `inet6_bind_address` and `tcp_fast_open`")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if networkManager != nil {
|
||||||
|
defaultOptions := networkManager.DefaultOptions()
|
||||||
|
if !disableDefaultBind {
|
||||||
|
if defaultOptions.BindInterface != "" {
|
||||||
|
bindFunc := control.BindToInterface(networkManager.InterfaceFinder(), defaultOptions.BindInterface, -1)
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else if networkManager.AutoDetectInterface() {
|
||||||
|
if platformInterface != nil {
|
||||||
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
|
networkStrategy = defaultOptions.NetworkStrategy
|
||||||
|
networkType = defaultOptions.NetworkType
|
||||||
|
fallbackNetworkType = defaultOptions.FallbackNetworkType
|
||||||
|
}
|
||||||
|
networkFallbackDelay = time.Duration(options.FallbackDelay)
|
||||||
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
|
}
|
||||||
|
bindFunc := networkManager.ProtectFunc()
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
} else {
|
||||||
|
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||||
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
|
dialer.Control = control.Append(dialer.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||||
|
listener.Control = control.Append(listener.Control, control.RoutingMark(defaultOptions.RoutingMark))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.ReuseAddr {
|
if options.ReuseAddr {
|
||||||
@@ -102,7 +160,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
udpAddr4 string
|
udpAddr4 string
|
||||||
)
|
)
|
||||||
if options.Inet4BindAddress != nil {
|
if options.Inet4BindAddress != nil {
|
||||||
bindAddr := options.Inet4BindAddress.Build()
|
bindAddr := options.Inet4BindAddress.Build(netip.IPv4Unspecified())
|
||||||
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
dialer4.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
udpDialer4.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr4 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
@@ -113,7 +171,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
)
|
)
|
||||||
if options.Inet6BindAddress != nil {
|
if options.Inet6BindAddress != nil {
|
||||||
bindAddr := options.Inet6BindAddress.Build()
|
bindAddr := options.Inet6BindAddress.Build(netip.IPv6Unspecified())
|
||||||
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
dialer6.LocalAddr = &net.TCPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
udpDialer6.LocalAddr = &net.UDPAddr{IP: bindAddr.AsSlice()}
|
||||||
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
udpAddr6 = M.SocksaddrFrom(bindAddr, 0).String()
|
||||||
@@ -124,11 +182,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
}
|
}
|
||||||
setMultiPathTCP(&dialer4)
|
setMultiPathTCP(&dialer4)
|
||||||
}
|
}
|
||||||
if options.IsWireGuardListener {
|
|
||||||
for _, controlFn := range wgControlFns {
|
|
||||||
listener.Control = control.Append(listener.Control, controlFn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -138,14 +191,19 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &DefaultDialer{
|
return &DefaultDialer{
|
||||||
tcpDialer4,
|
dialer4: tcpDialer4,
|
||||||
tcpDialer6,
|
dialer6: tcpDialer6,
|
||||||
udpDialer4,
|
udpDialer4: udpDialer4,
|
||||||
udpDialer6,
|
udpDialer6: udpDialer6,
|
||||||
listener,
|
udpListener: listener,
|
||||||
udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
options.IsWireGuardListener,
|
networkManager: networkManager,
|
||||||
|
networkStrategy: networkStrategy,
|
||||||
|
defaultNetworkStrategy: defaultNetworkStrategy,
|
||||||
|
networkType: networkType,
|
||||||
|
fallbackNetworkType: fallbackNetworkType,
|
||||||
|
networkFallbackDelay: networkFallbackDelay,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,29 +211,118 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
|||||||
if !address.IsValid() {
|
if !address.IsValid() {
|
||||||
return nil, E.New("invalid address")
|
return nil, E.New("invalid address")
|
||||||
}
|
}
|
||||||
switch N.NetworkName(network) {
|
if d.networkStrategy == nil {
|
||||||
case N.NetworkUDP:
|
switch N.NetworkName(network) {
|
||||||
if !address.IsIPv6() {
|
case N.NetworkUDP:
|
||||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
if !address.IsIPv6() {
|
||||||
} else {
|
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
} else {
|
||||||
|
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !address.IsIPv6() {
|
||||||
|
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||||
|
} else {
|
||||||
|
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !address.IsIPv6() {
|
|
||||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
|
||||||
} else {
|
} else {
|
||||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
return d.DialParallelInterface(ctx, network, address, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network string, address M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
if destination.IsIPv6() {
|
if strategy == nil {
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
strategy = d.networkStrategy
|
||||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
|
||||||
} else {
|
|
||||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
|
||||||
}
|
}
|
||||||
|
if strategy == nil {
|
||||||
|
return d.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
if len(interfaceType) == 0 {
|
||||||
|
interfaceType = d.networkType
|
||||||
|
}
|
||||||
|
if len(fallbackInterfaceType) == 0 {
|
||||||
|
fallbackInterfaceType = d.fallbackNetworkType
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = d.networkFallbackDelay
|
||||||
|
}
|
||||||
|
var dialer net.Dialer
|
||||||
|
if N.NetworkName(network) == N.NetworkTCP {
|
||||||
|
dialer = dialerFromTCPDialer(d.dialer4)
|
||||||
|
} else {
|
||||||
|
dialer = d.udpDialer4
|
||||||
|
}
|
||||||
|
fastFallback := time.Now().Sub(d.networkLastFallback.Load()) < C.TCPTimeout
|
||||||
|
var (
|
||||||
|
conn net.Conn
|
||||||
|
isPrimary bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if !fastFallback {
|
||||||
|
conn, isPrimary, err = d.dialParallelInterface(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
} else {
|
||||||
|
conn, isPrimary, err = d.dialParallelInterfaceFastFallback(ctx, dialer, network, address.String(), *strategy, interfaceType, fallbackInterfaceType, fallbackDelay, d.networkLastFallback.Store)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// bind interface failed on legacy xiaomi systems
|
||||||
|
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
|
||||||
|
d.networkStrategy = nil
|
||||||
|
return d.DialContext(ctx, network, address)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fastFallback && !isPrimary {
|
||||||
|
d.networkLastFallback.Store(time.Now())
|
||||||
|
}
|
||||||
|
return trackConn(conn, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
if d.networkStrategy == nil {
|
||||||
|
if destination.IsIPv6() {
|
||||||
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||||
|
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4))
|
||||||
|
} else {
|
||||||
|
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return d.ListenSerialInterfacePacket(ctx, destination, d.networkStrategy, d.networkType, d.fallbackNetworkType, d.networkFallbackDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
|
if strategy == nil {
|
||||||
|
strategy = d.networkStrategy
|
||||||
|
}
|
||||||
|
if strategy == nil {
|
||||||
|
return d.ListenPacket(ctx, destination)
|
||||||
|
}
|
||||||
|
if len(interfaceType) == 0 {
|
||||||
|
interfaceType = d.networkType
|
||||||
|
}
|
||||||
|
if len(fallbackInterfaceType) == 0 {
|
||||||
|
fallbackInterfaceType = d.fallbackNetworkType
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = d.networkFallbackDelay
|
||||||
|
}
|
||||||
|
network := N.NetworkUDP
|
||||||
|
if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||||
|
network += "4"
|
||||||
|
}
|
||||||
|
packetConn, err := d.listenSerialInterfacePacket(ctx, d.udpListener, network, "", *strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
if err != nil {
|
||||||
|
// bind interface failed on legacy xiaomi systems
|
||||||
|
if d.defaultNetworkStrategy && errors.Is(err, syscall.EPERM) {
|
||||||
|
d.networkStrategy = nil
|
||||||
|
return d.ListenPacket(ctx, destination)
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trackPacketConn(packetConn, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
|
||||||
|
|||||||
@@ -13,3 +13,7 @@ type tcpDialer = tfo.Dialer
|
|||||||
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
||||||
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||||
|
return dialer.Dialer
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,3 +16,7 @@ func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) {
|
|||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dialerFromTCPDialer(dialer tcpDialer) net.Dialer {
|
||||||
|
return dialer
|
||||||
|
}
|
||||||
|
|||||||
233
common/dialer/default_parallel_interface.go
Normal file
233
common/dialer/default_parallel_interface.go
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *DefaultDialer) dialParallelInterface(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, bool, error) {
|
||||||
|
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||||
|
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||||
|
return nil, false, E.New("no available network interface")
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = N.DefaultFallbackDelay
|
||||||
|
}
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
type dialResult struct {
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
primary bool
|
||||||
|
}
|
||||||
|
results := make(chan dialResult) // unbuffered
|
||||||
|
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||||
|
perNetDialer := dialer
|
||||||
|
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||||
|
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||||
|
case <-returned:
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case results <- dialResult{Conn: conn, primary: primary}:
|
||||||
|
case <-returned:
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
primaryCtx, primaryCancel := context.WithCancel(ctx)
|
||||||
|
defer primaryCancel()
|
||||||
|
for _, iif := range primaryInterfaces {
|
||||||
|
go startRacer(primaryCtx, true, iif)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
fallbackTimer *time.Timer
|
||||||
|
fallbackChan <-chan time.Time
|
||||||
|
)
|
||||||
|
if len(fallbackInterfaces) > 0 {
|
||||||
|
fallbackTimer = time.NewTimer(fallbackDelay)
|
||||||
|
defer fallbackTimer.Stop()
|
||||||
|
fallbackChan = fallbackTimer.C
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-fallbackChan:
|
||||||
|
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||||
|
defer fallbackCancel()
|
||||||
|
for _, iif := range fallbackInterfaces {
|
||||||
|
go startRacer(fallbackCtx, false, iif)
|
||||||
|
}
|
||||||
|
case res := <-results:
|
||||||
|
if res.error == nil {
|
||||||
|
return res.Conn, res.primary, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, res.error)
|
||||||
|
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
||||||
|
return nil, false, E.Errors(errors...)
|
||||||
|
}
|
||||||
|
if res.primary && fallbackTimer != nil && fallbackTimer.Stop() {
|
||||||
|
fallbackTimer.Reset(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDialer) dialParallelInterfaceFastFallback(ctx context.Context, dialer net.Dialer, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration, resetFastFallback func(time.Time)) (net.Conn, bool, error) {
|
||||||
|
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||||
|
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||||
|
return nil, false, E.New("no available network interface")
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = N.DefaultFallbackDelay
|
||||||
|
}
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
type dialResult struct {
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
primary bool
|
||||||
|
}
|
||||||
|
startAt := time.Now()
|
||||||
|
results := make(chan dialResult) // unbuffered
|
||||||
|
startRacer := func(ctx context.Context, primary bool, iif adapter.NetworkInterface) {
|
||||||
|
perNetDialer := dialer
|
||||||
|
perNetDialer.Control = control.Append(perNetDialer.Control, control.BindToInterface(nil, iif.Name, iif.Index))
|
||||||
|
conn, err := perNetDialer.DialContext(ctx, network, addr)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case results <- dialResult{error: E.Cause(err, "dial ", iif.Name, " (", iif.Index, ")"), primary: primary}:
|
||||||
|
case <-returned:
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case results <- dialResult{Conn: conn, primary: primary}:
|
||||||
|
case <-returned:
|
||||||
|
if primary && time.Since(startAt) <= fallbackDelay {
|
||||||
|
resetFastFallback(time.Time{})
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, iif := range primaryInterfaces {
|
||||||
|
go startRacer(ctx, true, iif)
|
||||||
|
}
|
||||||
|
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||||
|
defer fallbackCancel()
|
||||||
|
for _, iif := range fallbackInterfaces {
|
||||||
|
go startRacer(fallbackCtx, false, iif)
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case res := <-results:
|
||||||
|
if res.error == nil {
|
||||||
|
return res.Conn, res.primary, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, res.error)
|
||||||
|
if len(errors) == len(primaryInterfaces)+len(fallbackInterfaces) {
|
||||||
|
return nil, false, E.Errors(errors...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listener net.ListenConfig, network string, addr string, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
|
primaryInterfaces, fallbackInterfaces := selectInterfaces(d.networkManager, strategy, interfaceType, fallbackInterfaceType)
|
||||||
|
if len(primaryInterfaces)+len(fallbackInterfaces) == 0 {
|
||||||
|
return nil, E.New("no available network interface")
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
for _, primaryInterface := range primaryInterfaces {
|
||||||
|
perNetListener := listener
|
||||||
|
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, primaryInterface.Name, primaryInterface.Index))
|
||||||
|
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, E.Cause(err, "listen ", primaryInterface.Name, " (", primaryInterface.Index, ")"))
|
||||||
|
}
|
||||||
|
for _, fallbackInterface := range fallbackInterfaces {
|
||||||
|
perNetListener := listener
|
||||||
|
perNetListener.Control = control.Append(perNetListener.Control, control.BindToInterface(nil, fallbackInterface.Name, fallbackInterface.Index))
|
||||||
|
conn, err := perNetListener.ListenPacket(ctx, network, addr)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, E.Cause(err, "listen ", fallbackInterface.Name, " (", fallbackInterface.Index, ")"))
|
||||||
|
}
|
||||||
|
return nil, E.Errors(errors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
|
||||||
|
interfaces := networkManager.NetworkInterfaces()
|
||||||
|
switch strategy {
|
||||||
|
case C.NetworkStrategyDefault:
|
||||||
|
if len(interfaceType) == 0 {
|
||||||
|
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||||
|
if defaultIf != nil {
|
||||||
|
for _, iif := range interfaces {
|
||||||
|
if iif.Index == defaultIf.Index {
|
||||||
|
primaryInterfaces = append(primaryInterfaces, iif)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
primaryInterfaces = interfaces
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
|
return common.Contains(interfaceType, it.Type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case C.NetworkStrategyHybrid:
|
||||||
|
if len(interfaceType) == 0 {
|
||||||
|
primaryInterfaces = interfaces
|
||||||
|
} else {
|
||||||
|
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
|
return common.Contains(interfaceType, it.Type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case C.NetworkStrategyFallback:
|
||||||
|
if len(interfaceType) == 0 {
|
||||||
|
defaultIf := networkManager.InterfaceMonitor().DefaultInterface()
|
||||||
|
if defaultIf != nil {
|
||||||
|
for _, iif := range interfaces {
|
||||||
|
if iif.Index == defaultIf.Index {
|
||||||
|
primaryInterfaces = append(primaryInterfaces, iif)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
primaryInterfaces = interfaces
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
primaryInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
|
return common.Contains(interfaceType, it.Type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(fallbackInterfaceType) == 0 {
|
||||||
|
fallbackInterfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
|
return !common.Any(primaryInterfaces, func(iif adapter.NetworkInterface) bool {
|
||||||
|
return it.Index == iif.Index
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fallbackInterfaces = common.Filter(interfaces, func(iif adapter.NetworkInterface) bool {
|
||||||
|
return common.Contains(fallbackInterfaceType, iif.Type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return primaryInterfaces, fallbackInterfaces
|
||||||
|
}
|
||||||
161
common/dialer/default_parallel_network.go
Normal file
161
common/dialer/default_parallel_network.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DialSerialNetwork(ctx context.Context, dialer N.Dialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
|
if len(destinationAddresses) == 0 {
|
||||||
|
if !destination.IsIP() {
|
||||||
|
panic("invalid usage")
|
||||||
|
}
|
||||||
|
destinationAddresses = []netip.Addr{destination.Addr}
|
||||||
|
}
|
||||||
|
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||||
|
return parallelDialer.DialParallelNetwork(ctx, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||||
|
for _, address := range destinationAddresses {
|
||||||
|
conn, err := parallelDialer.DialParallelInterface(ctx, network, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, address := range destinationAddresses {
|
||||||
|
conn, err := dialer.DialContext(ctx, network, M.SocksaddrFrom(address, destination.Port))
|
||||||
|
if err == nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.Errors(errors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialParallelNetwork(ctx context.Context, dialer ParallelInterfaceDialer, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, preferIPv6 bool, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
|
if len(destinationAddresses) == 0 {
|
||||||
|
if !destination.IsIP() {
|
||||||
|
panic("invalid usage")
|
||||||
|
}
|
||||||
|
destinationAddresses = []netip.Addr{destination.Addr}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = N.DefaultFallbackDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
returned := make(chan struct{})
|
||||||
|
defer close(returned)
|
||||||
|
|
||||||
|
addresses4 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
|
||||||
|
return address.Is4() || address.Is4In6()
|
||||||
|
})
|
||||||
|
addresses6 := common.Filter(destinationAddresses, func(address netip.Addr) bool {
|
||||||
|
return address.Is6() && !address.Is4In6()
|
||||||
|
})
|
||||||
|
if len(addresses4) == 0 || len(addresses6) == 0 {
|
||||||
|
return DialSerialNetwork(ctx, dialer, network, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
}
|
||||||
|
var primaries, fallbacks []netip.Addr
|
||||||
|
if preferIPv6 {
|
||||||
|
primaries = addresses6
|
||||||
|
fallbacks = addresses4
|
||||||
|
} else {
|
||||||
|
primaries = addresses4
|
||||||
|
fallbacks = addresses6
|
||||||
|
}
|
||||||
|
type dialResult struct {
|
||||||
|
net.Conn
|
||||||
|
error
|
||||||
|
primary bool
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
results := make(chan dialResult) // unbuffered
|
||||||
|
startRacer := func(ctx context.Context, primary bool) {
|
||||||
|
ras := primaries
|
||||||
|
if !primary {
|
||||||
|
ras = fallbacks
|
||||||
|
}
|
||||||
|
c, err := DialSerialNetwork(ctx, dialer, network, destination, ras, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
select {
|
||||||
|
case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:
|
||||||
|
case <-returned:
|
||||||
|
if c != nil {
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var primary, fallback dialResult
|
||||||
|
primaryCtx, primaryCancel := context.WithCancel(ctx)
|
||||||
|
defer primaryCancel()
|
||||||
|
go startRacer(primaryCtx, true)
|
||||||
|
fallbackTimer := time.NewTimer(fallbackDelay)
|
||||||
|
defer fallbackTimer.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-fallbackTimer.C:
|
||||||
|
fallbackCtx, fallbackCancel := context.WithCancel(ctx)
|
||||||
|
defer fallbackCancel()
|
||||||
|
go startRacer(fallbackCtx, false)
|
||||||
|
|
||||||
|
case res := <-results:
|
||||||
|
if res.error == nil {
|
||||||
|
return res.Conn, nil
|
||||||
|
}
|
||||||
|
if res.primary {
|
||||||
|
primary = res
|
||||||
|
} else {
|
||||||
|
fallback = res
|
||||||
|
}
|
||||||
|
if primary.done && fallback.done {
|
||||||
|
return nil, primary.error
|
||||||
|
}
|
||||||
|
if res.primary && fallbackTimer.Stop() {
|
||||||
|
fallbackTimer.Reset(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenSerialNetworkPacket(ctx context.Context, dialer N.Dialer, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error) {
|
||||||
|
if len(destinationAddresses) == 0 {
|
||||||
|
if !destination.IsIP() {
|
||||||
|
panic("invalid usage")
|
||||||
|
}
|
||||||
|
destinationAddresses = []netip.Addr{destination.Addr}
|
||||||
|
}
|
||||||
|
if parallelDialer, isParallel := dialer.(ParallelNetworkDialer); isParallel {
|
||||||
|
return parallelDialer.ListenSerialNetworkPacket(ctx, destination, destinationAddresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
}
|
||||||
|
var errors []error
|
||||||
|
if parallelDialer, isParallel := dialer.(ParallelInterfaceDialer); isParallel {
|
||||||
|
for _, address := range destinationAddresses {
|
||||||
|
conn, err := parallelDialer.ListenSerialInterfacePacket(ctx, M.SocksaddrFrom(address, destination.Port), strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
if err == nil {
|
||||||
|
return conn, address, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, address := range destinationAddresses {
|
||||||
|
conn, err := dialer.ListenPacket(ctx, M.SocksaddrFrom(address, destination.Port))
|
||||||
|
if err == nil {
|
||||||
|
return conn, address, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, netip.Addr{}, E.Errors(errors...)
|
||||||
|
}
|
||||||
@@ -12,15 +12,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type DetourDialer struct {
|
type DetourDialer struct {
|
||||||
router adapter.Router
|
outboundManager adapter.OutboundManager
|
||||||
detour string
|
detour string
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
initOnce sync.Once
|
initOnce sync.Once
|
||||||
initErr error
|
initErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDetour(router adapter.Router, detour string) N.Dialer {
|
func NewDetour(outboundManager adapter.OutboundManager, detour string) N.Dialer {
|
||||||
return &DetourDialer{router: router, detour: detour}
|
return &DetourDialer{outboundManager: outboundManager, detour: detour}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Start() error {
|
func (d *DetourDialer) Start() error {
|
||||||
@@ -29,16 +29,18 @@ func (d *DetourDialer) Start() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
func (d *DetourDialer) Dialer() (N.Dialer, error) {
|
||||||
d.initOnce.Do(func() {
|
d.initOnce.Do(d.init)
|
||||||
var loaded bool
|
|
||||||
d.dialer, loaded = d.router.Outbound(d.detour)
|
|
||||||
if !loaded {
|
|
||||||
d.initErr = E.New("outbound detour not found: ", d.detour)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return d.dialer, d.initErr
|
return d.dialer, d.initErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DetourDialer) init() {
|
||||||
|
var loaded bool
|
||||||
|
d.dialer, loaded = d.outboundManager.Outbound(d.detour)
|
||||||
|
if !loaded {
|
||||||
|
d.initErr = E.New("outbound detour not found: ", d.detour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (d *DetourDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
dialer, err := d.Dialer()
|
dialer, err := d.Dialer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,40 +1,127 @@
|
|||||||
package dialer
|
package dialer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
|
type Options struct {
|
||||||
if options.IsWireGuardListener {
|
Context context.Context
|
||||||
return NewDefault(router, options)
|
Options option.DialerOptions
|
||||||
}
|
RemoteIsDomain bool
|
||||||
if router == nil {
|
DirectResolver bool
|
||||||
return NewDefault(nil, options)
|
ResolverOnDetour bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: merge with NewWithOptions
|
||||||
|
func New(ctx context.Context, options option.DialerOptions, remoteIsDomain bool) (N.Dialer, error) {
|
||||||
|
return NewWithOptions(Options{
|
||||||
|
Context: ctx,
|
||||||
|
Options: options,
|
||||||
|
RemoteIsDomain: remoteIsDomain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWithOptions(options Options) (N.Dialer, error) {
|
||||||
|
dialOptions := options.Options
|
||||||
var (
|
var (
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if options.Detour == "" {
|
if dialOptions.Detour != "" {
|
||||||
dialer, err = NewDefault(router, options)
|
outboundManager := service.FromContext[adapter.OutboundManager](options.Context)
|
||||||
|
if outboundManager == nil {
|
||||||
|
return nil, E.New("missing outbound manager")
|
||||||
|
}
|
||||||
|
dialer = NewDetour(outboundManager, dialOptions.Detour)
|
||||||
|
} else {
|
||||||
|
dialer, err = NewDefault(options.Context, dialOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
dialer = NewDetour(router, options.Detour)
|
|
||||||
}
|
}
|
||||||
if options.Detour == "" {
|
if options.RemoteIsDomain && (dialOptions.Detour == "" || options.ResolverOnDetour) {
|
||||||
|
networkManager := service.FromContext[adapter.NetworkManager](options.Context)
|
||||||
|
dnsTransport := service.FromContext[adapter.DNSTransportManager](options.Context)
|
||||||
|
var defaultOptions adapter.NetworkOptions
|
||||||
|
if networkManager != nil {
|
||||||
|
defaultOptions = networkManager.DefaultOptions()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
server string
|
||||||
|
dnsQueryOptions adapter.DNSQueryOptions
|
||||||
|
resolveFallbackDelay time.Duration
|
||||||
|
)
|
||||||
|
if dialOptions.DomainResolver != nil && dialOptions.DomainResolver.Server != "" {
|
||||||
|
var transport adapter.DNSTransport
|
||||||
|
if !options.DirectResolver {
|
||||||
|
var loaded bool
|
||||||
|
transport, loaded = dnsTransport.Transport(dialOptions.DomainResolver.Server)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("domain resolver not found: " + dialOptions.DomainResolver.Server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var strategy C.DomainStrategy
|
||||||
|
if dialOptions.DomainResolver.Strategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||||
|
strategy = C.DomainStrategy(dialOptions.DomainResolver.Strategy)
|
||||||
|
} else if
|
||||||
|
//nolint:staticcheck
|
||||||
|
dialOptions.DomainStrategy != option.DomainStrategy(C.DomainStrategyAsIS) {
|
||||||
|
//nolint:staticcheck
|
||||||
|
strategy = C.DomainStrategy(dialOptions.DomainStrategy)
|
||||||
|
}
|
||||||
|
server = dialOptions.DomainResolver.Server
|
||||||
|
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||||
|
Transport: transport,
|
||||||
|
Strategy: strategy,
|
||||||
|
DisableCache: dialOptions.DomainResolver.DisableCache,
|
||||||
|
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
||||||
|
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||||
|
}
|
||||||
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
|
} else if options.DirectResolver {
|
||||||
|
return nil, E.New("missing domain resolver for domain server address")
|
||||||
|
} else if defaultOptions.DomainResolver != "" {
|
||||||
|
dnsQueryOptions = defaultOptions.DomainResolveOptions
|
||||||
|
transport, loaded := dnsTransport.Transport(defaultOptions.DomainResolver)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("default domain resolver not found: " + defaultOptions.DomainResolver)
|
||||||
|
}
|
||||||
|
dnsQueryOptions.Transport = transport
|
||||||
|
resolveFallbackDelay = time.Duration(dialOptions.FallbackDelay)
|
||||||
|
} else {
|
||||||
|
deprecated.Report(options.Context, deprecated.OptionMissingDomainResolver)
|
||||||
|
}
|
||||||
dialer = NewResolveDialer(
|
dialer = NewResolveDialer(
|
||||||
router,
|
options.Context,
|
||||||
dialer,
|
dialer,
|
||||||
options.Detour == "" && !options.TCPFastOpen,
|
dialOptions.Detour == "" && !dialOptions.TCPFastOpen,
|
||||||
dns.DomainStrategy(options.DomainStrategy),
|
server,
|
||||||
time.Duration(options.FallbackDelay))
|
dnsQueryOptions,
|
||||||
|
resolveFallbackDelay,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return dialer, nil
|
return dialer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ParallelInterfaceDialer interface {
|
||||||
|
N.Dialer
|
||||||
|
DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||||
|
ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParallelNetworkDialer interface {
|
||||||
|
DialParallelNetwork(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error)
|
||||||
|
ListenSerialNetworkPacket(ctx context.Context, destination M.Socksaddr, destinationAddresses []netip.Addr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, netip.Addr, error)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,75 +3,125 @@ package dialer
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResolveDialer struct {
|
var (
|
||||||
|
_ N.Dialer = (*resolveDialer)(nil)
|
||||||
|
_ ParallelInterfaceDialer = (*resolveParallelNetworkDialer)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResolveDialer interface {
|
||||||
|
N.Dialer
|
||||||
|
QueryOptions() adapter.DNSQueryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParallelInterfaceResolveDialer interface {
|
||||||
|
ParallelInterfaceDialer
|
||||||
|
QueryOptions() adapter.DNSQueryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type resolveDialer struct {
|
||||||
|
transport adapter.DNSTransportManager
|
||||||
|
router adapter.DNSRouter
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
parallel bool
|
parallel bool
|
||||||
router adapter.Router
|
server string
|
||||||
strategy dns.DomainStrategy
|
initOnce sync.Once
|
||||||
|
initErr error
|
||||||
|
queryOptions adapter.DNSQueryOptions
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolveDialer(router adapter.Router, dialer N.Dialer, parallel bool, strategy dns.DomainStrategy, fallbackDelay time.Duration) *ResolveDialer {
|
func NewResolveDialer(ctx context.Context, dialer N.Dialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ResolveDialer {
|
||||||
return &ResolveDialer{
|
return &resolveDialer{
|
||||||
dialer,
|
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||||
parallel,
|
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
router,
|
dialer: dialer,
|
||||||
strategy,
|
parallel: parallel,
|
||||||
fallbackDelay,
|
server: server,
|
||||||
|
queryOptions: queryOptions,
|
||||||
|
fallbackDelay: fallbackDelay,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
type resolveParallelNetworkDialer struct {
|
||||||
|
resolveDialer
|
||||||
|
dialer ParallelInterfaceDialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolveParallelInterfaceDialer(ctx context.Context, dialer ParallelInterfaceDialer, parallel bool, server string, queryOptions adapter.DNSQueryOptions, fallbackDelay time.Duration) ParallelInterfaceResolveDialer {
|
||||||
|
return &resolveParallelNetworkDialer{
|
||||||
|
resolveDialer{
|
||||||
|
transport: service.FromContext[adapter.DNSTransportManager](ctx),
|
||||||
|
router: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
|
dialer: dialer,
|
||||||
|
parallel: parallel,
|
||||||
|
server: server,
|
||||||
|
queryOptions: queryOptions,
|
||||||
|
fallbackDelay: fallbackDelay,
|
||||||
|
},
|
||||||
|
dialer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveDialer) initialize() error {
|
||||||
|
d.initOnce.Do(d.initServer)
|
||||||
|
return d.initErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveDialer) initServer() {
|
||||||
|
if d.server == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
transport, loaded := d.transport.Transport(d.server)
|
||||||
|
if !loaded {
|
||||||
|
d.initErr = E.New("domain resolver not found: " + d.server)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.queryOptions.Transport = transport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
err := d.initialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.DialContext(ctx, network, destination)
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||||
metadata.Domain = ""
|
|
||||||
var addresses []netip.Addr
|
|
||||||
var err error
|
|
||||||
if d.strategy == dns.DomainStrategyAsIS {
|
|
||||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
} else {
|
|
||||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if d.parallel {
|
if d.parallel {
|
||||||
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.strategy == dns.DomainStrategyPreferIPv6, d.fallbackDelay)
|
return N.DialParallel(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, d.fallbackDelay)
|
||||||
} else {
|
} else {
|
||||||
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
return N.DialSerial(ctx, d.dialer, network, destination, addresses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *resolveDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
err := d.initialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if !destination.IsFqdn() {
|
if !destination.IsFqdn() {
|
||||||
return d.dialer.ListenPacket(ctx, destination)
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
ctx, metadata := adapter.ExtendContext(ctx)
|
|
||||||
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
metadata.Destination = destination
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||||
metadata.Domain = ""
|
|
||||||
var addresses []netip.Addr
|
|
||||||
var err error
|
|
||||||
if d.strategy == dns.DomainStrategyAsIS {
|
|
||||||
addresses, err = d.router.LookupDefault(ctx, destination.Fqdn)
|
|
||||||
} else {
|
|
||||||
addresses, err = d.router.Lookup(ctx, destination.Fqdn, d.strategy)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -82,6 +132,64 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
|||||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *ResolveDialer) Upstream() any {
|
func (d *resolveDialer) QueryOptions() adapter.DNSQueryOptions {
|
||||||
|
return d.queryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveDialer) Upstream() any {
|
||||||
|
return d.dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveParallelNetworkDialer) DialParallelInterface(ctx context.Context, network string, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.Conn, error) {
|
||||||
|
err := d.initialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !destination.IsFqdn() {
|
||||||
|
return d.dialer.DialContext(ctx, network, destination)
|
||||||
|
}
|
||||||
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = d.fallbackDelay
|
||||||
|
}
|
||||||
|
if d.parallel {
|
||||||
|
return DialParallelNetwork(ctx, d.dialer, network, destination, addresses, d.queryOptions.Strategy == C.DomainStrategyPreferIPv6, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
} else {
|
||||||
|
return DialSerialNetwork(ctx, d.dialer, network, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveParallelNetworkDialer) ListenSerialInterfacePacket(ctx context.Context, destination M.Socksaddr, strategy *C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType, fallbackDelay time.Duration) (net.PacketConn, error) {
|
||||||
|
err := d.initialize()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !destination.IsFqdn() {
|
||||||
|
return d.dialer.ListenPacket(ctx, destination)
|
||||||
|
}
|
||||||
|
ctx = log.ContextWithOverrideLevel(ctx, log.LevelDebug)
|
||||||
|
addresses, err := d.router.Lookup(ctx, destination.Fqdn, d.queryOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if fallbackDelay == 0 {
|
||||||
|
fallbackDelay = d.fallbackDelay
|
||||||
|
}
|
||||||
|
conn, destinationAddress, err := ListenSerialNetworkPacket(ctx, d.dialer, destination, addresses, strategy, interfaceType, fallbackInterfaceType, fallbackDelay)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveParallelNetworkDialer) QueryOptions() adapter.DNSQueryOptions {
|
||||||
|
return d.queryOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *resolveParallelNetworkDialer) Upstream() any {
|
||||||
return d.dialer
|
return d.dialer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,32 +7,27 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RouterDialer struct {
|
type DefaultOutboundDialer struct {
|
||||||
router adapter.Router
|
outbound adapter.OutboundManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(router adapter.Router) N.Dialer {
|
func NewDefaultOutbound(ctx context.Context) N.Dialer {
|
||||||
return &RouterDialer{router: router}
|
return &DefaultOutboundDialer{
|
||||||
}
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
|
|
||||||
func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
|
||||||
dialer, err := d.router.DefaultOutbound(network)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return dialer.DialContext(ctx, network, destination)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (d *DefaultOutboundDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
dialer, err := d.router.DefaultOutbound(N.NetworkUDP)
|
return d.outbound.Default().DialContext(ctx, network, destination)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dialer.ListenPacket(ctx, destination)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RouterDialer) Upstream() any {
|
func (d *DefaultOutboundDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return d.router
|
return d.outbound.Default().ListenPacket(ctx, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DefaultOutboundDialer) Upstream() any {
|
||||||
|
return d.outbound.Default()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,12 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WireGuardListener interface {
|
type WireGuardListener interface {
|
||||||
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var WgControlFns []control.Func
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build with_wireguard
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/wireguard-go/conn"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ WireGuardListener = (conn.Listener)(nil)
|
|
||||||
|
|
||||||
var wgControlFns = conn.ControlFns
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
//go:build !with_wireguard
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
var wgControlFns []control.Func
|
|
||||||
137
common/listener/listener.go
Normal file
137
common/listener/listener.go
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
network []string
|
||||||
|
listenOptions option.ListenOptions
|
||||||
|
connHandler adapter.ConnectionHandlerEx
|
||||||
|
packetHandler adapter.PacketHandlerEx
|
||||||
|
oobPacketHandler adapter.OOBPacketHandlerEx
|
||||||
|
threadUnsafePacketWriter bool
|
||||||
|
disablePacketOutput bool
|
||||||
|
setSystemProxy bool
|
||||||
|
systemProxySOCKS bool
|
||||||
|
|
||||||
|
tcpListener net.Listener
|
||||||
|
systemProxy settings.SystemProxy
|
||||||
|
udpConn *net.UDPConn
|
||||||
|
udpAddr M.Socksaddr
|
||||||
|
packetOutbound chan *N.PacketBuffer
|
||||||
|
packetOutboundClosed chan struct{}
|
||||||
|
shutdown atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Context context.Context
|
||||||
|
Logger logger.ContextLogger
|
||||||
|
Network []string
|
||||||
|
Listen option.ListenOptions
|
||||||
|
ConnectionHandler adapter.ConnectionHandlerEx
|
||||||
|
PacketHandler adapter.PacketHandlerEx
|
||||||
|
OOBPacketHandler adapter.OOBPacketHandlerEx
|
||||||
|
ThreadUnsafePacketWriter bool
|
||||||
|
DisablePacketOutput bool
|
||||||
|
SetSystemProxy bool
|
||||||
|
SystemProxySOCKS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(
|
||||||
|
options Options,
|
||||||
|
) *Listener {
|
||||||
|
return &Listener{
|
||||||
|
ctx: options.Context,
|
||||||
|
logger: options.Logger,
|
||||||
|
network: options.Network,
|
||||||
|
listenOptions: options.Listen,
|
||||||
|
connHandler: options.ConnectionHandler,
|
||||||
|
packetHandler: options.PacketHandler,
|
||||||
|
oobPacketHandler: options.OOBPacketHandler,
|
||||||
|
threadUnsafePacketWriter: options.ThreadUnsafePacketWriter,
|
||||||
|
disablePacketOutput: options.DisablePacketOutput,
|
||||||
|
setSystemProxy: options.SetSystemProxy,
|
||||||
|
systemProxySOCKS: options.SystemProxySOCKS,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Start() error {
|
||||||
|
if common.Contains(l.network, N.NetworkTCP) {
|
||||||
|
_, err := l.ListenTCP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go l.loopTCPIn()
|
||||||
|
}
|
||||||
|
if common.Contains(l.network, N.NetworkUDP) {
|
||||||
|
_, err := l.ListenUDP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l.packetOutboundClosed = make(chan struct{})
|
||||||
|
l.packetOutbound = make(chan *N.PacketBuffer, 64)
|
||||||
|
go l.loopUDPIn()
|
||||||
|
if !l.disablePacketOutput {
|
||||||
|
go l.loopUDPOut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.setSystemProxy {
|
||||||
|
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
|
||||||
|
var listenAddrString string
|
||||||
|
listenAddr := l.listenOptions.Listen.Build(netip.IPv4Unspecified())
|
||||||
|
if listenAddr.IsUnspecified() {
|
||||||
|
listenAddrString = "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
listenAddrString = listenAddr.String()
|
||||||
|
}
|
||||||
|
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "initialize system proxy")
|
||||||
|
}
|
||||||
|
err = systemProxy.Enable()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "set system proxy")
|
||||||
|
}
|
||||||
|
l.systemProxy = systemProxy
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.shutdown.Store(true)
|
||||||
|
var err error
|
||||||
|
if l.systemProxy != nil && l.systemProxy.IsEnabled() {
|
||||||
|
err = l.systemProxy.Disable()
|
||||||
|
}
|
||||||
|
return E.Errors(err, common.Close(
|
||||||
|
l.tcpListener,
|
||||||
|
common.PtrOrNil(l.udpConn),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) TCPListener() net.Listener {
|
||||||
|
return l.tcpListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) UDPConn() *net.UDPConn {
|
||||||
|
return l.udpConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) ListenOptions() option.ListenOptions {
|
||||||
|
return l.listenOptions
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//go:build go1.21
|
//go:build go1.21
|
||||||
|
|
||||||
package inbound
|
package listener
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
16
common/listener/listener_go123.go
Normal file
16
common/listener/listener_go123.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
//go:build go1.23
|
||||||
|
|
||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
||||||
|
listener.KeepAliveConfig = net.KeepAliveConfig{
|
||||||
|
Enable: true,
|
||||||
|
Idle: idle,
|
||||||
|
Interval: interval,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//go:build !go1.21
|
//go:build !go1.21
|
||||||
|
|
||||||
package inbound
|
package listener
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
15
common/listener/listener_nongo123.go
Normal file
15
common/listener/listener_nongo123.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build !go1.23
|
||||||
|
|
||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
|
||||||
|
listener.KeepAlive = idle
|
||||||
|
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
|
||||||
|
}
|
||||||
86
common/listener/listener_tcp.go
Normal file
86
common/listener/listener_tcp.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/metacubex/tfo-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||||
|
var err error
|
||||||
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
|
var tcpListener net.Listener
|
||||||
|
var listenConfig net.ListenConfig
|
||||||
|
if l.listenOptions.TCPKeepAlive >= 0 {
|
||||||
|
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
|
||||||
|
if keepIdle == 0 {
|
||||||
|
keepIdle = C.TCPKeepAliveInitial
|
||||||
|
}
|
||||||
|
keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval)
|
||||||
|
if keepInterval == 0 {
|
||||||
|
keepInterval = C.TCPKeepAliveInterval
|
||||||
|
}
|
||||||
|
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
|
||||||
|
}
|
||||||
|
if l.listenOptions.TCPMultiPath {
|
||||||
|
if !go121Available {
|
||||||
|
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
|
||||||
|
}
|
||||||
|
setMultiPathTCP(&listenConfig)
|
||||||
|
}
|
||||||
|
if l.listenOptions.TCPFastOpen {
|
||||||
|
var tfoConfig tfo.ListenConfig
|
||||||
|
tfoConfig.ListenConfig = listenConfig
|
||||||
|
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
|
} else {
|
||||||
|
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||||
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
||||||
|
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
||||||
|
}
|
||||||
|
l.tcpListener = tcpListener
|
||||||
|
return tcpListener, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) loopTCPIn() {
|
||||||
|
tcpListener := l.tcpListener
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
for {
|
||||||
|
conn, err := tcpListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
//nolint:staticcheck
|
||||||
|
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
|
||||||
|
l.logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.tcpListener.Close()
|
||||||
|
l.logger.Error("tcp listener closed: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundDetour = l.listenOptions.Detour
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundOptions = l.listenOptions.InboundOptions
|
||||||
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||||
|
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
|
||||||
|
ctx := log.ContextWithNewID(l.ctx)
|
||||||
|
l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
|
||||||
|
go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
154
common/listener/listener_udp.go
Normal file
154
common/listener/listener_udp.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||||
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
|
var lc net.ListenConfig
|
||||||
|
var udpFragment bool
|
||||||
|
if l.listenOptions.UDPFragment != nil {
|
||||||
|
udpFragment = *l.listenOptions.UDPFragment
|
||||||
|
} else {
|
||||||
|
udpFragment = l.listenOptions.UDPFragmentDefault
|
||||||
|
}
|
||||||
|
if !udpFragment {
|
||||||
|
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
|
||||||
|
}
|
||||||
|
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l.udpConn = udpConn.(*net.UDPConn)
|
||||||
|
l.udpAddr = bindAddr
|
||||||
|
l.logger.Info("udp server started at ", udpConn.LocalAddr())
|
||||||
|
return udpConn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) UDPAddr() M.Socksaddr {
|
||||||
|
return l.udpAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) PacketWriter() N.PacketWriter {
|
||||||
|
return (*packetWriter)(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) loopUDPIn() {
|
||||||
|
defer close(l.packetOutboundClosed)
|
||||||
|
var buffer *buf.Buffer
|
||||||
|
if !l.threadUnsafePacketWriter {
|
||||||
|
buffer = buf.NewPacket()
|
||||||
|
defer buffer.Release()
|
||||||
|
buffer.IncRef()
|
||||||
|
defer buffer.DecRef()
|
||||||
|
}
|
||||||
|
if l.oobPacketHandler != nil {
|
||||||
|
oob := make([]byte, 1024)
|
||||||
|
for {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer = buf.NewPacket()
|
||||||
|
} else {
|
||||||
|
buffer.Reset()
|
||||||
|
}
|
||||||
|
n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
|
||||||
|
if err != nil {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer.Release()
|
||||||
|
}
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
|
l.logger.Error("udp listener closed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer = buf.NewPacket()
|
||||||
|
} else {
|
||||||
|
buffer.Reset()
|
||||||
|
}
|
||||||
|
n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
if l.threadUnsafePacketWriter {
|
||||||
|
buffer.Release()
|
||||||
|
}
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
|
l.logger.Error("udp listener closed: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) loopUDPOut() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case packet := <-l.packetOutbound:
|
||||||
|
destination := packet.Destination.AddrPort()
|
||||||
|
_, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination)
|
||||||
|
packet.Buffer.Release()
|
||||||
|
N.PutPacketBuffer(packet)
|
||||||
|
if err != nil {
|
||||||
|
if l.shutdown.Load() && E.IsClosed(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.udpConn.Close()
|
||||||
|
l.logger.Error("udp listener write back: ", destination, ": ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case <-l.packetOutboundClosed:
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case packet := <-l.packetOutbound:
|
||||||
|
packet.Buffer.Release()
|
||||||
|
N.PutPacketBuffer(packet)
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetWriter Listener
|
||||||
|
|
||||||
|
func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
packet := N.NewPacketBuffer()
|
||||||
|
packet.Buffer = buffer
|
||||||
|
packet.Destination = destination
|
||||||
|
select {
|
||||||
|
case w.packetOutbound <- packet:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
buffer.Release()
|
||||||
|
N.PutPacketBuffer(packet)
|
||||||
|
if w.shutdown.Load() {
|
||||||
|
return os.ErrClosed
|
||||||
|
}
|
||||||
|
w.logger.Trace("dropped packet to ", destination)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *packetWriter) WriteIsThreadUnsafe() {
|
||||||
|
}
|
||||||
@@ -15,11 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
router adapter.ConnectionRouter
|
router adapter.ConnectionRouterEx
|
||||||
service *mux.Service
|
service *mux.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
@@ -41,10 +41,10 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context
|
|||||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||||
return log.ContextWithNewID(ctx)
|
return log.ContextWithNewID(ctx)
|
||||||
},
|
},
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Handler: adapter.NewRouteContextHandler(router, logger),
|
HandlerEx: adapter.NewRouteContextHandlerEx(router),
|
||||||
Padding: options.Padding,
|
Padding: options.Padding,
|
||||||
Brutal: brutalOptions,
|
Brutal: brutalOptions,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -52,14 +52,29 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context
|
|||||||
return &Router{router, service}, nil
|
return &Router{router, service}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RouteConnectionEx instead.
|
||||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if metadata.Destination == mux.Destination {
|
if metadata.Destination == mux.Destination {
|
||||||
|
// TODO: check if WithContext is necessary
|
||||||
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
} else {
|
} else {
|
||||||
return r.router.RouteConnection(ctx, conn, metadata)
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use RoutePacketConnectionEx instead.
|
||||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
if metadata.Destination == mux.Destination {
|
||||||
|
r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
vmess "github.com/sagernet/sing-vmess"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type V2RayLegacyRouter struct {
|
|
||||||
router adapter.ConnectionRouter
|
|
||||||
logger logger.ContextLogger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
|
|
||||||
return &V2RayLegacyRouter{router, logger}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
|
|
||||||
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
|
|
||||||
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
|
|
||||||
}
|
|
||||||
return r.router.RouteConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
@@ -23,6 +23,7 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
|
ProcessID uint32
|
||||||
ProcessPath string
|
ProcessPath string
|
||||||
PackageName string
|
PackageName string
|
||||||
User string
|
User string
|
||||||
|
|||||||
@@ -2,14 +2,11 @@ package process
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
"github.com/sagernet/sing/common/winiphlpapi"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
@@ -26,209 +23,39 @@ func NewSearcher(_ Config) (Searcher, error) {
|
|||||||
return &windowsSearcher{}, nil
|
return &windowsSearcher{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
|
|
||||||
procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable")
|
|
||||||
procGetExtendedUdpTable = modiphlpapi.NewProc("GetExtendedUdpTable")
|
|
||||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
|
|
||||||
)
|
|
||||||
|
|
||||||
func initWin32API() error {
|
func initWin32API() error {
|
||||||
err := modiphlpapi.Load()
|
return winiphlpapi.LoadExtendedTable()
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "load iphlpapi.dll")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = procGetExtendedTcpTable.Find()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "load iphlpapi::GetExtendedTcpTable")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = procGetExtendedUdpTable.Find()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "load iphlpapi::GetExtendedUdpTable")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = modkernel32.Load()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "load kernel32.dll")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = procQueryFullProcessImageNameW.Find()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "load kernel32::QueryFullProcessImageNameW")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
func (s *windowsSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||||
processName, err := findProcessName(network, source.Addr(), int(source.Port()))
|
pid, err := winiphlpapi.FindPid(network, source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Info{ProcessPath: processName, UserId: -1}, nil
|
path, err := getProcessPath(pid)
|
||||||
}
|
|
||||||
|
|
||||||
func findProcessName(network string, ip netip.Addr, srcPort int) (string, error) {
|
|
||||||
family := windows.AF_INET
|
|
||||||
if ip.Is6() {
|
|
||||||
family = windows.AF_INET6
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tcpTablePidConn = 4
|
|
||||||
udpTablePid = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
var class int
|
|
||||||
var fn uintptr
|
|
||||||
switch network {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
fn = procGetExtendedTcpTable.Addr()
|
|
||||||
class = tcpTablePidConn
|
|
||||||
case N.NetworkUDP:
|
|
||||||
fn = procGetExtendedUdpTable.Addr()
|
|
||||||
class = udpTablePid
|
|
||||||
default:
|
|
||||||
return "", os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := getTransportTable(fn, family, class)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return &Info{ProcessID: pid, UserId: -1}, err
|
||||||
}
|
}
|
||||||
|
return &Info{ProcessID: pid, ProcessPath: path, UserId: -1}, nil
|
||||||
s := newSearcher(family == windows.AF_INET, network == N.NetworkTCP)
|
|
||||||
|
|
||||||
pid, err := s.Search(buf, ip, uint16(srcPort))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return getExecPathFromPID(pid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type searcher struct {
|
func getProcessPath(pid uint32) (string, error) {
|
||||||
itemSize int
|
|
||||||
port int
|
|
||||||
ip int
|
|
||||||
ipSize int
|
|
||||||
pid int
|
|
||||||
tcpState int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error) {
|
|
||||||
n := int(readNativeUint32(b[:4]))
|
|
||||||
itemSize := s.itemSize
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
row := b[4+itemSize*i : 4+itemSize*(i+1)]
|
|
||||||
|
|
||||||
if s.tcpState >= 0 {
|
|
||||||
tcpState := readNativeUint32(row[s.tcpState : s.tcpState+4])
|
|
||||||
// MIB_TCP_STATE_ESTAB, only check established connections for TCP
|
|
||||||
if tcpState != 5 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// according to MSDN, only the lower 16 bits of dwLocalPort are used and the port number is in network endian.
|
|
||||||
// this field can be illustrated as follows depends on different machine endianess:
|
|
||||||
// little endian: [ MSB LSB 0 0 ] interpret as native uint32 is ((LSB<<8)|MSB)
|
|
||||||
// big endian: [ 0 0 MSB LSB ] interpret as native uint32 is ((MSB<<8)|LSB)
|
|
||||||
// so we need an syscall.Ntohs on the lower 16 bits after read the port as native uint32
|
|
||||||
srcPort := syscall.Ntohs(uint16(readNativeUint32(row[s.port : s.port+4])))
|
|
||||||
if srcPort != port {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
|
|
||||||
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
|
||||||
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pid := readNativeUint32(row[s.pid : s.pid+4])
|
|
||||||
return pid, nil
|
|
||||||
}
|
|
||||||
return 0, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSearcher(isV4, isTCP bool) *searcher {
|
|
||||||
var itemSize, port, ip, ipSize, pid int
|
|
||||||
tcpState := -1
|
|
||||||
switch {
|
|
||||||
case isV4 && isTCP:
|
|
||||||
// struct MIB_TCPROW_OWNER_PID
|
|
||||||
itemSize, port, ip, ipSize, pid, tcpState = 24, 8, 4, 4, 20, 0
|
|
||||||
case isV4 && !isTCP:
|
|
||||||
// struct MIB_UDPROW_OWNER_PID
|
|
||||||
itemSize, port, ip, ipSize, pid = 12, 4, 0, 4, 8
|
|
||||||
case !isV4 && isTCP:
|
|
||||||
// struct MIB_TCP6ROW_OWNER_PID
|
|
||||||
itemSize, port, ip, ipSize, pid, tcpState = 56, 20, 0, 16, 52, 48
|
|
||||||
case !isV4 && !isTCP:
|
|
||||||
// struct MIB_UDP6ROW_OWNER_PID
|
|
||||||
itemSize, port, ip, ipSize, pid = 28, 20, 0, 16, 24
|
|
||||||
}
|
|
||||||
|
|
||||||
return &searcher{
|
|
||||||
itemSize: itemSize,
|
|
||||||
port: port,
|
|
||||||
ip: ip,
|
|
||||||
ipSize: ipSize,
|
|
||||||
pid: pid,
|
|
||||||
tcpState: tcpState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
|
|
||||||
for size, buf := uint32(8), make([]byte, 8); ; {
|
|
||||||
ptr := unsafe.Pointer(&buf[0])
|
|
||||||
err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
|
|
||||||
|
|
||||||
switch err {
|
|
||||||
case 0:
|
|
||||||
return buf, nil
|
|
||||||
case uintptr(syscall.ERROR_INSUFFICIENT_BUFFER):
|
|
||||||
buf = make([]byte, size)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("syscall error: %d", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func readNativeUint32(b []byte) uint32 {
|
|
||||||
return *(*uint32)(unsafe.Pointer(&b[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getExecPathFromPID(pid uint32) (string, error) {
|
|
||||||
// kernel process starts with a colon in order to distinguish with normal processes
|
|
||||||
switch pid {
|
switch pid {
|
||||||
case 0:
|
case 0:
|
||||||
// reserved pid for system idle process
|
|
||||||
return ":System Idle Process", nil
|
return ":System Idle Process", nil
|
||||||
case 4:
|
case 4:
|
||||||
// reserved pid for windows kernel image
|
|
||||||
return ":System", nil
|
return ":System", nil
|
||||||
}
|
}
|
||||||
h, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer windows.CloseHandle(h)
|
defer windows.CloseHandle(handle)
|
||||||
|
size := uint32(syscall.MAX_LONG_PATH)
|
||||||
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
||||||
size := uint32(len(buf))
|
err = windows.QueryFullProcessImageName(handle, 0, &buf[0], &size)
|
||||||
r1, _, err := syscall.SyscallN(
|
if err != nil {
|
||||||
procQueryFullProcessImageNameW.Addr(),
|
|
||||||
uintptr(h),
|
|
||||||
uintptr(0),
|
|
||||||
uintptr(unsafe.Pointer(&buf[0])),
|
|
||||||
uintptr(unsafe.Pointer(&size)),
|
|
||||||
)
|
|
||||||
if r1 == 0 {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return syscall.UTF16ToString(buf[:size]), nil
|
return windows.UTF16ToString(buf[:size]), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ package settings
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/shell"
|
"github.com/sagernet/sing/common/shell"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DarwinSystemProxy struct {
|
type DarwinSystemProxy struct {
|
||||||
@@ -24,7 +25,7 @@ type DarwinSystemProxy struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
|
||||||
interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
|
interfaceMonitor := service.FromContext[adapter.NetworkManager](ctx).InterfaceMonitor()
|
||||||
if interfaceMonitor == nil {
|
if interfaceMonitor == nil {
|
||||||
return nil, E.New("missing interface monitor")
|
return nil, E.New("missing interface monitor")
|
||||||
}
|
}
|
||||||
@@ -33,7 +34,7 @@ func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bo
|
|||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
supportSOCKS: supportSOCKS,
|
supportSOCKS: supportSOCKS,
|
||||||
}
|
}
|
||||||
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
|
proxy.element = interfaceMonitor.RegisterCallback(proxy.routeUpdate)
|
||||||
return proxy, nil
|
return proxy, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,25 +66,22 @@ func (p *DarwinSystemProxy) Disable() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DarwinSystemProxy) update(event int) {
|
func (p *DarwinSystemProxy) routeUpdate(defaultInterface *control.Interface, flags int) {
|
||||||
if event&tun.EventInterfaceUpdate == 0 {
|
if !p.isEnabled || defaultInterface == nil {
|
||||||
return
|
|
||||||
}
|
|
||||||
if !p.isEnabled {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = p.update0()
|
_ = p.update0()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *DarwinSystemProxy) update0() error {
|
func (p *DarwinSystemProxy) update0() error {
|
||||||
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
|
newInterface := p.monitor.DefaultInterface()
|
||||||
if p.interfaceName == newInterfaceName {
|
if p.interfaceName == newInterface.Name {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if p.interfaceName != "" {
|
if p.interfaceName != "" {
|
||||||
_ = p.Disable()
|
_ = p.Disable()
|
||||||
}
|
}
|
||||||
p.interfaceName = newInterfaceName
|
p.interfaceName = newInterface.Name
|
||||||
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build !(windows || linux || darwin)
|
|
||||||
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
//go:build linux || darwin
|
|
||||||
|
|
||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
|
||||||
return unix.Settimeofday(&timeVal)
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package settings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
func SetSystemTime(nowTime time.Time) error {
|
|
||||||
var systemTime windows.Systemtime
|
|
||||||
systemTime.Year = uint16(nowTime.Year())
|
|
||||||
systemTime.Month = uint16(nowTime.Month())
|
|
||||||
systemTime.Day = uint16(nowTime.Day())
|
|
||||||
systemTime.Hour = uint16(nowTime.Hour())
|
|
||||||
systemTime.Minute = uint16(nowTime.Minute())
|
|
||||||
systemTime.Second = uint16(nowTime.Second())
|
|
||||||
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
|
||||||
|
|
||||||
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
proc := dllKernel32.NewProc("SetSystemTime")
|
|
||||||
|
|
||||||
_, _, err := proc.Call(
|
|
||||||
uintptr(unsafe.Pointer(&systemTime)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil && err.Error() != "The operation completed successfully." {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -18,5 +18,6 @@ func HTTPHost(_ context.Context, metadata *adapter.InboundContext, reader io.Rea
|
|||||||
}
|
}
|
||||||
metadata.Protocol = C.ProtocolHTTP
|
metadata.Protocol = C.ProtocolHTTP
|
||||||
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
metadata.Domain = M.ParseSocksaddr(request.Host).AddrString()
|
||||||
|
metadata.HTTPRequest = request
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ type (
|
|||||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
||||||
)
|
)
|
||||||
|
|
||||||
func Skip(metadata adapter.InboundContext) bool {
|
func Skip(metadata *adapter.InboundContext) bool {
|
||||||
// skip server first protocols
|
// skip server first protocols
|
||||||
switch metadata.Destination.Port {
|
switch metadata.Destination.Port {
|
||||||
case 25, 465, 587:
|
case 25, 465, 587:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ func TLSClientHello(ctx context.Context, metadata *adapter.InboundContext, reade
|
|||||||
if clientHello != nil {
|
if clientHello != nil {
|
||||||
metadata.Protocol = C.ProtocolTLS
|
metadata.Protocol = C.ProtocolTLS
|
||||||
metadata.Domain = clientHello.ServerName
|
metadata.Domain = clientHello.ServerName
|
||||||
|
metadata.ClientHello = clientHello
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ const (
|
|||||||
ruleItemWIFIBSSID
|
ruleItemWIFIBSSID
|
||||||
ruleItemAdGuardDomain
|
ruleItemAdGuardDomain
|
||||||
ruleItemProcessPathRegex
|
ruleItemProcessPathRegex
|
||||||
|
ruleItemNetworkType
|
||||||
|
ruleItemNetworkIsExpensive
|
||||||
|
ruleItemNetworkIsConstrained
|
||||||
ruleItemFinal uint8 = 0xFF
|
ruleItemFinal uint8 = 0xFF
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -222,6 +225,12 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
|
case ruleItemNetworkType:
|
||||||
|
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||||
|
case ruleItemNetworkIsExpensive:
|
||||||
|
rule.NetworkIsExpensive = true
|
||||||
|
case ruleItemNetworkIsConstrained:
|
||||||
|
rule.NetworkIsConstrained = true
|
||||||
case ruleItemFinal:
|
case ruleItemFinal:
|
||||||
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
|
||||||
return
|
return
|
||||||
@@ -336,6 +345,27 @@ func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule, gen
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(rule.NetworkType) > 0 {
|
||||||
|
if generateVersion < C.RuleSetVersion3 {
|
||||||
|
return E.New("network_type rule item is only supported in version 3 or later")
|
||||||
|
}
|
||||||
|
err = writeRuleItemUint8(writer, ruleItemNetworkType, rule.NetworkType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rule.NetworkIsExpensive {
|
||||||
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsExpensive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rule.NetworkIsConstrained {
|
||||||
|
err = binary.Write(writer, binary.BigEndian, ruleItemNetworkIsConstrained)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(rule.WIFISSID) > 0 {
|
if len(rule.WIFISSID) > 0 {
|
||||||
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -384,6 +414,18 @@ func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) e
|
|||||||
return varbin.Write(writer, binary.BigEndian, value)
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readRuleItemUint8[E ~uint8](reader varbin.Reader) ([]E, error) {
|
||||||
|
return varbin.ReadValue[[]E](reader, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRuleItemUint8[E ~uint8](writer varbin.Writer, itemType uint8, value []E) error {
|
||||||
|
err := writer.WriteByte(itemType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return varbin.Write(writer, binary.BigEndian, value)
|
||||||
|
}
|
||||||
|
|
||||||
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ import (
|
|||||||
|
|
||||||
cftls "github.com/sagernet/cloudflare-tls"
|
cftls "github.com/sagernet/cloudflare-tls"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-dns"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
mDNS "github.com/miekg/dns"
|
mDNS "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -99,6 +100,7 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -214,7 +216,7 @@ func fetchECHClientConfig(ctx context.Context) func(_ context.Context, serverNam
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
response, err := adapter.RouterFromContext(ctx).Exchange(ctx, message)
|
response, err := service.FromContext[adapter.DNSRouter](ctx).Exchange(ctx, message, adapter.DNSQueryOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ func (c *echServerConfig) startWatcher() error {
|
|||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
err := c.credentialsUpdated(path)
|
err := c.credentialsUpdated(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(E.Cause(err, "reload credentials from ", path))
|
c.logger.Error(E.Cause(err, "reload credentials"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ import (
|
|||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
func GenerateKeyPair(parent *x509.Certificate, parentKey any, timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||||
privateKeyPem, publicKeyPem, err := GenerateKeyPair(timeFunc, serverName, timeFunc().Add(time.Hour))
|
privateKeyPem, publicKeyPem, err := GenerateCertificate(parent, parentKey, timeFunc, serverName, timeFunc().Add(time.Hour))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -23,7 +26,7 @@ func GenerateCertificate(timeFunc func() time.Time, serverName string) (*tls.Cer
|
|||||||
return &certificate, err
|
return &certificate, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.Time) (privateKeyPem []byte, publicKeyPem []byte, err error) {
|
func GenerateCertificate(parent *x509.Certificate, parentKey any, 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
|
||||||
}
|
}
|
||||||
@@ -35,19 +38,36 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string, expire time.T
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
template := &x509.Certificate{
|
var template *x509.Certificate
|
||||||
SerialNumber: serialNumber,
|
if serverAddress := M.ParseAddr(serverName); serverAddress.IsValid() {
|
||||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
template = &x509.Certificate{
|
||||||
NotAfter: expire,
|
SerialNumber: serialNumber,
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
IPAddresses: []net.IP{serverAddress.AsSlice()},
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||||
BasicConstraintsValid: true,
|
NotAfter: expire,
|
||||||
Subject: pkix.Name{
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
CommonName: serverName,
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
},
|
BasicConstraintsValid: true,
|
||||||
DNSNames: []string{serverName},
|
}
|
||||||
|
} else {
|
||||||
|
template = &x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||||
|
NotAfter: expire,
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
CommonName: serverName,
|
||||||
|
},
|
||||||
|
DNSNames: []string{serverName},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
publicDer, err := x509.CreateCertificate(rand.Reader, template, template, key.Public(), key)
|
if parent == nil {
|
||||||
|
parent = template
|
||||||
|
parentKey = key
|
||||||
|
}
|
||||||
|
publicDer, err := x509.CreateCertificate(rand.Reader, template, parent, key.Public(), parentKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
aTLS "github.com/sagernet/sing/common/tls"
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
utls "github.com/sagernet/utls"
|
utls "github.com/sagernet/utls"
|
||||||
|
|
||||||
@@ -40,6 +42,7 @@ import (
|
|||||||
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
||||||
|
|
||||||
type RealityClientConfig struct {
|
type RealityClientConfig struct {
|
||||||
|
ctx context.Context
|
||||||
uClient *UTLSClientConfig
|
uClient *UTLSClientConfig
|
||||||
publicKey []byte
|
publicKey []byte
|
||||||
shortID [8]byte
|
shortID [8]byte
|
||||||
@@ -70,7 +73,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
@@ -180,20 +183,24 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !verifier.verified {
|
if !verifier.verified {
|
||||||
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
go realityClientFallback(e.ctx, uConn, e.uClient.ServerName(), e.uClient.id)
|
||||||
return nil, E.New("reality verification failed")
|
return nil, E.New("reality verification failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &utlsConnWrapper{uConn}, nil
|
return &realityClientConnWrapper{uConn}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
func realityClientFallback(ctx context.Context, uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
defer uConn.Close()
|
defer uConn.Close()
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Transport: &http2.Transport{
|
Transport: &http2.Transport{
|
||||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
return uConn, nil
|
return uConn, nil
|
||||||
},
|
},
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
Time: ntp.TimeFuncFromContext(ctx),
|
||||||
|
RootCAs: adapter.RootPoolFromContext(ctx),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||||
@@ -213,6 +220,7 @@ func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello [
|
|||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
func (e *RealityClientConfig) Clone() Config {
|
||||||
return &RealityClientConfig{
|
return &RealityClientConfig{
|
||||||
|
e.ctx,
|
||||||
e.uClient.Clone().(*UTLSClientConfig),
|
e.uClient.Clone().(*UTLSClientConfig),
|
||||||
e.publicKey,
|
e.publicKey,
|
||||||
e.shortID,
|
e.shortID,
|
||||||
@@ -249,3 +257,36 @@ func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChain
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type realityClientConnWrapper struct {
|
||||||
|
*utls.UConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) ConnectionState() tls.ConnectionState {
|
||||||
|
state := c.Conn.ConnectionState()
|
||||||
|
//nolint:staticcheck
|
||||||
|
return tls.ConnectionState{
|
||||||
|
Version: state.Version,
|
||||||
|
HandshakeComplete: state.HandshakeComplete,
|
||||||
|
DidResume: state.DidResume,
|
||||||
|
CipherSuite: state.CipherSuite,
|
||||||
|
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||||
|
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||||
|
ServerName: state.ServerName,
|
||||||
|
PeerCertificates: state.PeerCertificates,
|
||||||
|
VerifiedChains: state.VerifiedChains,
|
||||||
|
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||||
|
OCSPResponse: state.OCSPResponse,
|
||||||
|
TLSUnique: state.TLSUnique,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityClientConnWrapper) Upstream() any {
|
||||||
|
return c.UConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
||||||
|
// We fixed it by calling Close() directly.
|
||||||
|
func (c *realityClientConnWrapper) CloseWrite() error {
|
||||||
|
return c.Close()
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/reality"
|
"github.com/sagernet/reality"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -102,7 +101,7 @@ func NewRealityServer(ctx context.Context, logger log.Logger, options option.Inb
|
|||||||
tlsConfig.ShortIds[shortID] = true
|
tlsConfig.ShortIds[shortID] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakeDialer, err := dialer.New(adapter.RouterFromContext(ctx), options.Reality.Handshake.DialerOptions)
|
handshakeDialer, err := dialer.New(ctx, options.Reality.Handshake.DialerOptions, options.Reality.Handshake.ServerIsDomain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -195,3 +194,9 @@ func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
|||||||
func (c *realityConnWrapper) Upstream() any {
|
func (c *realityConnWrapper) Upstream() any {
|
||||||
return c.Conn
|
return c.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
||||||
|
// We fixed it by calling Close() directly.
|
||||||
|
func (c *realityConnWrapper) CloseWrite() error {
|
||||||
|
return c.Close()
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -51,9 +51,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
if _, err := netip.ParseAddr(serverName); err != nil {
|
serverName = serverAddress
|
||||||
serverName = serverAddress
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if serverName == "" && !options.Insecure {
|
if serverName == "" && !options.Insecure {
|
||||||
return nil, E.New("missing server_name or insecure=true")
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
@@ -61,6 +59,7 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ func (c *STDServerConfig) startWatcher() error {
|
|||||||
Callback: func(path string) {
|
Callback: func(path string) {
|
||||||
err := c.certificateUpdated(path)
|
err := c.certificateUpdated(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error(err)
|
c.logger.Error(E.Cause(err, "reload certificate"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -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 GenerateCertificate(ntp.TimeFuncFromContext(ctx), info.ServerName)
|
return GenerateKeyPair(nil, nil, ntp.TimeFuncFromContext(ctx), info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
22
common/tls/time_wrapper.go
Normal file
22
common/tls/time_wrapper.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/ntp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TimeServiceWrapper struct {
|
||||||
|
ntp.TimeService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TimeServiceWrapper) TimeFunc() func() time.Time {
|
||||||
|
if w.TimeService == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return w.TimeService.TimeFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TimeServiceWrapper) Upstream() any {
|
||||||
|
return w.TimeService
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -130,6 +131,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
107
common/tlsfragment/conn.go
Normal file
107
common/tlsfragment/conn.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package tf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
tcpConn *net.TCPConn
|
||||||
|
ctx context.Context
|
||||||
|
firstPacketWritten bool
|
||||||
|
fallbackDelay time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||||
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
|
return &Conn{
|
||||||
|
Conn: conn,
|
||||||
|
tcpConn: tcpConn,
|
||||||
|
ctx: ctx,
|
||||||
|
fallbackDelay: fallbackDelay,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||||
|
if !c.firstPacketWritten {
|
||||||
|
defer func() {
|
||||||
|
c.firstPacketWritten = true
|
||||||
|
}()
|
||||||
|
serverName := indexTLSServerName(b)
|
||||||
|
if serverName != nil {
|
||||||
|
if c.tcpConn != nil {
|
||||||
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
splits := strings.Split(serverName.ServerName, ".")
|
||||||
|
currentIndex := serverName.Index
|
||||||
|
if publicSuffix := publicsuffix.List.PublicSuffix(serverName.ServerName); publicSuffix != "" {
|
||||||
|
splits = splits[:len(splits)-strings.Count(serverName.ServerName, ".")]
|
||||||
|
}
|
||||||
|
if len(splits) > 1 && splits[0] == "..." {
|
||||||
|
currentIndex += len(splits[0]) + 1
|
||||||
|
splits = splits[1:]
|
||||||
|
}
|
||||||
|
var splitIndexes []int
|
||||||
|
for i, split := range splits {
|
||||||
|
splitAt := rand.Intn(len(split))
|
||||||
|
splitIndexes = append(splitIndexes, currentIndex+splitAt)
|
||||||
|
currentIndex += len(split)
|
||||||
|
if i != len(splits)-1 {
|
||||||
|
currentIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i <= len(splitIndexes); i++ {
|
||||||
|
var payload []byte
|
||||||
|
if i == 0 {
|
||||||
|
payload = b[:splitIndexes[i]]
|
||||||
|
} else if i == len(splitIndexes) {
|
||||||
|
payload = b[splitIndexes[i-1]:]
|
||||||
|
} else {
|
||||||
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
|
}
|
||||||
|
if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = c.Conn.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.tcpConn != nil {
|
||||||
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.Conn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) ReaderReplaceable() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) WriterReplaceable() bool {
|
||||||
|
return c.firstPacketWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
131
common/tlsfragment/index.go
Normal file
131
common/tlsfragment/index.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package tf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
recordLayerHeaderLen int = 5
|
||||||
|
handshakeHeaderLen int = 6
|
||||||
|
randomDataLen int = 32
|
||||||
|
sessionIDHeaderLen int = 1
|
||||||
|
cipherSuiteHeaderLen int = 2
|
||||||
|
compressMethodHeaderLen int = 1
|
||||||
|
extensionsHeaderLen int = 2
|
||||||
|
extensionHeaderLen int = 4
|
||||||
|
sniExtensionHeaderLen int = 5
|
||||||
|
contentType uint8 = 22
|
||||||
|
handshakeType uint8 = 1
|
||||||
|
sniExtensionType uint16 = 0
|
||||||
|
sniNameDNSHostnameType uint8 = 0
|
||||||
|
tlsVersionBitmask uint16 = 0xFFFC
|
||||||
|
tls13 uint16 = 0x0304
|
||||||
|
)
|
||||||
|
|
||||||
|
type myServerName struct {
|
||||||
|
Index int
|
||||||
|
Length int
|
||||||
|
ServerName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexTLSServerName(payload []byte) *myServerName {
|
||||||
|
if len(payload) < recordLayerHeaderLen || payload[0] != contentType {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
segmentLen := binary.BigEndian.Uint16(payload[3:5])
|
||||||
|
if len(payload) < recordLayerHeaderLen+int(segmentLen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
serverName := indexTLSServerNameFromHandshake(payload[recordLayerHeaderLen : recordLayerHeaderLen+int(segmentLen)])
|
||||||
|
if serverName == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
serverName.Length += recordLayerHeaderLen
|
||||||
|
return serverName
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexTLSServerNameFromHandshake(hs []byte) *myServerName {
|
||||||
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if hs[0] != handshakeType {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
handshakeLen := uint32(hs[1])<<16 | uint32(hs[2])<<8 | uint32(hs[3])
|
||||||
|
if len(hs[4:]) != int(handshakeLen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tlsVersion := uint16(hs[4])<<8 | uint16(hs[5])
|
||||||
|
if tlsVersion&tlsVersionBitmask != 0x0300 && tlsVersion != tls13 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sessionIDLen := hs[38]
|
||||||
|
if len(hs) < handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cs := hs[handshakeHeaderLen+randomDataLen+sessionIDHeaderLen+int(sessionIDLen):]
|
||||||
|
if len(cs) < cipherSuiteHeaderLen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
csLen := uint16(cs[0])<<8 | uint16(cs[1])
|
||||||
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
compressMethodLen := uint16(cs[cipherSuiteHeaderLen+int(csLen)])
|
||||||
|
if len(cs) < cipherSuiteHeaderLen+int(csLen)+compressMethodHeaderLen+int(compressMethodLen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
currentIndex := cipherSuiteHeaderLen + int(csLen) + compressMethodHeaderLen + int(compressMethodLen)
|
||||||
|
serverName := indexTLSServerNameFromExtensions(cs[currentIndex:])
|
||||||
|
if serverName == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
serverName.Index += currentIndex
|
||||||
|
return serverName
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexTLSServerNameFromExtensions(exs []byte) *myServerName {
|
||||||
|
if len(exs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(exs) < extensionsHeaderLen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
exsLen := uint16(exs[0])<<8 | uint16(exs[1])
|
||||||
|
exs = exs[extensionsHeaderLen:]
|
||||||
|
if len(exs) < int(exsLen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for currentIndex := extensionsHeaderLen; len(exs) > 0; {
|
||||||
|
if len(exs) < extensionHeaderLen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
exType := uint16(exs[0])<<8 | uint16(exs[1])
|
||||||
|
exLen := uint16(exs[2])<<8 | uint16(exs[3])
|
||||||
|
if len(exs) < extensionHeaderLen+int(exLen) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sex := exs[extensionHeaderLen : extensionHeaderLen+int(exLen)]
|
||||||
|
|
||||||
|
switch exType {
|
||||||
|
case sniExtensionType:
|
||||||
|
if len(sex) < sniExtensionHeaderLen {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sniType := sex[2]
|
||||||
|
if sniType != sniNameDNSHostnameType {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sniLen := uint16(sex[3])<<8 | uint16(sex[4])
|
||||||
|
sex = sex[sniExtensionHeaderLen:]
|
||||||
|
return &myServerName{
|
||||||
|
Index: currentIndex + extensionHeaderLen + sniExtensionHeaderLen,
|
||||||
|
Length: int(sniLen),
|
||||||
|
ServerName: string(sex),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exs = exs[4+exLen:]
|
||||||
|
currentIndex += 4 + int(exLen)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
93
common/tlsfragment/wait_darwin.go
Normal file
93
common/tlsfragment/wait_darwin.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package tf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
const tcpMaxNotifyAck = 10
|
||||||
|
|
||||||
|
type tcpNotifyAckID uint32
|
||||||
|
|
||||||
|
type tcpNotifyAckComplete struct {
|
||||||
|
NotifyPending uint32
|
||||||
|
NotifyCompleteCount uint32
|
||||||
|
NotifyCompleteID [tcpMaxNotifyAck]tcpNotifyAckID
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizeOfTCPNotifyAckComplete = int(unsafe.Sizeof(tcpNotifyAckComplete{}))
|
||||||
|
|
||||||
|
func getsockoptTCPNotifyAckComplete(fd, level, opt int) (*tcpNotifyAckComplete, error) {
|
||||||
|
var value tcpNotifyAckComplete
|
||||||
|
vallen := uint32(sizeOfTCPNotifyAckComplete)
|
||||||
|
err := getsockopt(fd, level, opt, unsafe.Pointer(&value), &vallen)
|
||||||
|
return &value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname getsockopt golang.org/x/sys/unix.getsockopt
|
||||||
|
func getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32) error
|
||||||
|
|
||||||
|
func waitAck(ctx context.Context, conn *net.TCPConn, _ time.Duration) error {
|
||||||
|
const TCP_NOTIFY_ACKNOWLEDGEMENT = 0x212
|
||||||
|
return control.Conn(conn, func(fd uintptr) error {
|
||||||
|
err := unix.SetsockoptInt(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT, 1)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, unix.EINVAL) {
|
||||||
|
return waitAckFallback(ctx, conn, 0)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
var ackComplete *tcpNotifyAckComplete
|
||||||
|
ackComplete, err = getsockoptTCPNotifyAckComplete(int(fd), unix.IPPROTO_TCP, TCP_NOTIFY_ACKNOWLEDGEMENT)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ackComplete.NotifyPending == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func writeAndWaitAck(ctx context.Context, conn *net.TCPConn, payload []byte, fallbackDelay time.Duration) error {
|
||||||
|
_, err := conn.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return control.Conn(conn, func(fd uintptr) error {
|
||||||
|
start := time.Now()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
unacked, err := unix.GetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_NWRITE)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if unacked == 0 {
|
||||||
|
if time.Since(start) <= 20*time.Millisecond {
|
||||||
|
// under transparent proxy
|
||||||
|
time.Sleep(fallbackDelay)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user