mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 12:48:28 +10:00
Compare commits
23 Commits
v1.2-beta9
...
dev-l3-rou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9092139e73 | ||
|
|
9324a39d4e | ||
|
|
84904c5206 | ||
|
|
fe4b429fc2 | ||
|
|
f680d0acaf | ||
|
|
4baff5aeb1 | ||
|
|
f25296fb23 | ||
|
|
e717852c73 | ||
|
|
13dc70f649 | ||
|
|
46040a71c3 | ||
|
|
0558b3fc5c | ||
|
|
99b2ab5526 | ||
|
|
e5f3bb6344 | ||
|
|
c7f89ad88e | ||
|
|
e0d9f79445 | ||
|
|
b6dbb69fc4 | ||
|
|
b76fabee65 | ||
|
|
872bcfd1c0 | ||
|
|
b033c13ca2 | ||
|
|
2db188f3a1 | ||
|
|
11de271c8f | ||
|
|
40c800c57c | ||
|
|
91b0540e95 |
6
.github/workflows/debug.yml
vendored
6
.github/workflows/debug.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.10
|
go-version: 1.18.10
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
@@ -193,7 +193,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ steps.version.outputs.go_version }}
|
go-version: ${{ steps.version.outputs.go_version }}
|
||||||
- name: Cache go module
|
- name: Cache go module
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@
|
|||||||
/*.aar
|
/*.aar
|
||||||
/*.xcframework/
|
/*.xcframework/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/config.d/
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ builds:
|
|||||||
- with_quic
|
- with_quic
|
||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
|
- with_reality_server
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
@@ -117,7 +118,6 @@ nfpms:
|
|||||||
- src: LICENSE
|
- src: LICENSE
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
dst: /usr/share/licenses/sing-box/LICENSE
|
||||||
scripts:
|
scripts:
|
||||||
postinstall: release/config/postinstall.sh
|
|
||||||
postremove: release/config/postremove.sh
|
postremove: release/config/postremove.sh
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ RUN set -ex \
|
|||||||
&& apk add git build-base \
|
&& apk add git build-base \
|
||||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
|
&& go build -v -trimpath -tags with_quic,with_wireguard,with_reality_server,with_acme \
|
||||||
-o /go/bin/sing-box \
|
-o /go/bin/sing-box \
|
||||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||||
./cmd/sing-box
|
./cmd/sing-box
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -7,7 +7,7 @@ GOHOSTOS = $(shell go env GOHOSTOS)
|
|||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$(VERSION)\" -s -w -buildid="
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
PREFIX ?= $(shell go env GOPATH)
|
PREFIX ?= $(shell go env GOPATH)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
The universal proxy platform.
|
The universal proxy platform.
|
||||||
|
|
||||||
|
[](https://repology.org/project/sing-box/versions)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
https://sing-box.sagernet.org
|
https://sing-box.sagernet.org
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
type ClashServer interface {
|
type ClashServer interface {
|
||||||
Service
|
Service
|
||||||
|
PreStarter
|
||||||
Mode() string
|
Mode() string
|
||||||
StoreSelected() bool
|
StoreSelected() bool
|
||||||
CacheFile() ClashCacheFile
|
CacheFile() ClashCacheFile
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ type InjectableInbound interface {
|
|||||||
type InboundContext struct {
|
type InboundContext struct {
|
||||||
Inbound string
|
Inbound string
|
||||||
InboundType string
|
InboundType string
|
||||||
IPVersion int
|
IPVersion uint8
|
||||||
Network string
|
Network string
|
||||||
Source M.Socksaddr
|
Source M.Socksaddr
|
||||||
Destination M.Socksaddr
|
Destination M.Socksaddr
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,3 +18,8 @@ type Outbound interface {
|
|||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPOutbound interface {
|
||||||
|
Outbound
|
||||||
|
NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) (tun.DirectDestination, error)
|
||||||
|
}
|
||||||
|
|||||||
15
adapter/prestart.go
Normal file
15
adapter/prestart.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package adapter
|
||||||
|
|
||||||
|
type PreStarter interface {
|
||||||
|
PreStart() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func PreStart(starter any) error {
|
||||||
|
if preService, ok := starter.(PreStarter); ok {
|
||||||
|
err := preService.PreStart()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -23,6 +23,9 @@ type Router interface {
|
|||||||
|
|
||||||
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
|
RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata InboundContext) tun.RouteAction
|
||||||
|
|
||||||
|
NatRequired(outbound string) bool
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -39,7 +42,9 @@ type Router interface {
|
|||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
|
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
IPRules() []IPRule
|
||||||
|
|
||||||
TimeService
|
TimeService
|
||||||
|
|
||||||
@@ -78,6 +83,11 @@ type DNSRule interface {
|
|||||||
DisableCache() bool
|
DisableCache() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPRule interface {
|
||||||
|
Rule
|
||||||
|
Action() tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
InterfaceUpdated() error
|
InterfaceUpdated() error
|
||||||
}
|
}
|
||||||
|
|||||||
193
box.go
193
box.go
@@ -25,32 +25,34 @@ import (
|
|||||||
var _ adapter.Service = (*Box)(nil)
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
router adapter.Router
|
router adapter.Router
|
||||||
inbounds []adapter.Inbound
|
inbounds []adapter.Inbound
|
||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
logFile *os.File
|
logFile *os.File
|
||||||
clashServer adapter.ClashServer
|
preServices map[string]adapter.Service
|
||||||
v2rayServer adapter.V2RayServer
|
postServices map[string]adapter.Service
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
|
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
logOptions := common.PtrValueOrDefault(options.Log)
|
|
||||||
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
|
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||||
|
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
if options.Experimental != nil {
|
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
|
||||||
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
needClashAPI = true
|
||||||
needClashAPI = true
|
|
||||||
}
|
|
||||||
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
|
||||||
needV2RayAPI = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
|
needV2RayAPI = true
|
||||||
|
}
|
||||||
|
|
||||||
|
logOptions := common.PtrValueOrDefault(options.Log)
|
||||||
|
|
||||||
var logFactory log.Factory
|
var logFactory log.Factory
|
||||||
var observableLogFactory log.ObservableFactory
|
var observableLogFactory log.ObservableFactory
|
||||||
@@ -164,37 +166,57 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
preServices := make(map[string]adapter.Service)
|
||||||
var clashServer adapter.ClashServer
|
postServices := make(map[string]adapter.Service)
|
||||||
var v2rayServer adapter.V2RayServer
|
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
clashServer, err := experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create clash api server")
|
return nil, E.Cause(err, "create clash api server")
|
||||||
}
|
}
|
||||||
router.SetClashServer(clashServer)
|
router.SetClashServer(clashServer)
|
||||||
|
preServices["clash api"] = clashServer
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create v2ray api server")
|
return nil, E.Cause(err, "create v2ray api server")
|
||||||
}
|
}
|
||||||
router.SetV2RayServer(v2rayServer)
|
router.SetV2RayServer(v2rayServer)
|
||||||
|
preServices["v2ray api"] = v2rayServer
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
router: router,
|
||||||
inbounds: inbounds,
|
inbounds: inbounds,
|
||||||
outbounds: outbounds,
|
outbounds: outbounds,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
logFactory: logFactory,
|
logFactory: logFactory,
|
||||||
logger: logFactory.Logger(),
|
logger: logFactory.Logger(),
|
||||||
logFile: logFile,
|
logFile: logFile,
|
||||||
clashServer: clashServer,
|
preServices: preServices,
|
||||||
v2rayServer: v2rayServer,
|
postServices: postServices,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Box) PreStart() error {
|
||||||
|
err := s.preStart()
|
||||||
|
if err != nil {
|
||||||
|
// TODO: remove catch error
|
||||||
|
defer func() {
|
||||||
|
v := recover()
|
||||||
|
if v != nil {
|
||||||
|
log.Error(E.Cause(err, "origin error"))
|
||||||
|
debug.PrintStack()
|
||||||
|
panic("panic on early close: " + fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
s.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Box) Start() error {
|
func (s *Box) Start() error {
|
||||||
err := s.start()
|
err := s.start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -208,55 +230,70 @@ func (s *Box) Start() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
s.Close()
|
s.Close()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return err
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) start() error {
|
func (s *Box) preStart() error {
|
||||||
if s.clashServer != nil {
|
for serviceName, service := range s.preServices {
|
||||||
err := s.clashServer.Start()
|
s.logger.Trace("pre-starting ", serviceName)
|
||||||
|
err := adapter.PreStart(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start clash api server")
|
return E.Cause(err, "pre-start ", serviceName)
|
||||||
}
|
|
||||||
}
|
|
||||||
if s.v2rayServer != nil {
|
|
||||||
err := s.v2rayServer.Start()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "start v2ray api server")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
if starter, isStarter := out.(common.Starter); isStarter {
|
if starter, isStarter := out.(common.Starter); isStarter {
|
||||||
|
var tag string
|
||||||
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("initializing outbound ", tag)
|
||||||
err := starter.Start()
|
err := starter.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var tag string
|
|
||||||
if out.Tag() == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = out.Tag()
|
|
||||||
}
|
|
||||||
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := s.router.Start()
|
return s.router.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Box) start() error {
|
||||||
|
err := s.preStart()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
for serviceName, service := range s.preServices {
|
||||||
|
s.logger.Trace("starting ", serviceName)
|
||||||
|
err = service.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
for i, in := range s.inbounds {
|
for i, in := range s.inbounds {
|
||||||
|
var tag string
|
||||||
|
if in.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = in.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("initializing inbound ", tag)
|
||||||
err = in.Start()
|
err = in.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var tag string
|
|
||||||
if in.Tag() == "" {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
} else {
|
|
||||||
tag = in.Tag()
|
|
||||||
}
|
|
||||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for serviceName, service := range s.postServices {
|
||||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
s.logger.Trace("start ", serviceName)
|
||||||
|
err = service.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "starting ", serviceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,36 +305,54 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
var errors error
|
var errors error
|
||||||
|
for serviceName, service := range s.postServices {
|
||||||
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
|
s.logger.Trace("closing ", serviceName)
|
||||||
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
})
|
||||||
|
}
|
||||||
for i, in := range s.inbounds {
|
for i, in := range s.inbounds {
|
||||||
|
var tag string
|
||||||
|
if in.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = in.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("closing inbound ", tag)
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
for i, out := range s.outbounds {
|
||||||
|
var tag string
|
||||||
|
if out.Tag() == "" {
|
||||||
|
tag = F.ToString(i)
|
||||||
|
} else {
|
||||||
|
tag = out.Tag()
|
||||||
|
}
|
||||||
|
s.logger.Trace("closing outbound ", tag)
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||||
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
s.logger.Trace("closing router")
|
||||||
if err := common.Close(s.router); err != nil {
|
if err := common.Close(s.router); err != nil {
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
return E.Cause(err, "close router")
|
return E.Cause(err, "close router")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
for serviceName, service := range s.preServices {
|
||||||
|
s.logger.Trace("closing ", serviceName)
|
||||||
|
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||||
|
return E.Cause(err, "close ", serviceName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
s.logger.Trace("closing logger")
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
if err := common.Close(s.logFactory); err != nil {
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
errors = E.Append(errors, err, func(err error) error {
|
||||||
return E.Cause(err, "close log factory")
|
return E.Cause(err, "close log factory")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if err := common.Close(s.clashServer); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close clash api server")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err := common.Close(s.v2rayServer); err != nil {
|
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
|
||||||
return E.Cause(err, "close v2ray api server")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if s.logFile != nil {
|
if s.logFile != nil {
|
||||||
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close log file")
|
return E.Cause(err, "close log file")
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func check() error {
|
func check() error {
|
||||||
options, err := readConfig()
|
options, err := readConfigAndMerge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,44 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func format() error {
|
func format() error {
|
||||||
|
optionsList, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, optionsEntry := range optionsList {
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
encoder := json.NewEncoder(buffer)
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
err = encoder.Encode(optionsEntry.options)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "encode config")
|
||||||
|
}
|
||||||
|
outputPath, _ := filepath.Abs(optionsEntry.path)
|
||||||
|
if !commandFormatFlagWrite {
|
||||||
|
if len(optionsList) > 1 {
|
||||||
|
os.Stdout.WriteString(outputPath + "\n")
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(buffer.String() + "\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if bytes.Equal(optionsEntry.content, buffer.Bytes()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
output, err := os.Create(optionsEntry.path)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output")
|
||||||
|
}
|
||||||
|
_, err = output.Write(buffer.Bytes())
|
||||||
|
output.Close()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write output")
|
||||||
|
}
|
||||||
|
os.Stderr.WriteString(outputPath + "\n")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatOne(configPath string) error {
|
||||||
configContent, err := os.ReadFile(configPath)
|
configContent, err := os.ReadFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "read config")
|
return E.Cause(err, "read config")
|
||||||
|
|||||||
@@ -5,10 +5,14 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
runtimeDebug "runtime/debug"
|
runtimeDebug "runtime/debug"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
|
"github.com/sagernet/sing-box/common/badjsonmerge"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"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"
|
||||||
@@ -31,29 +35,88 @@ func init() {
|
|||||||
mainCommand.AddCommand(commandRun)
|
mainCommand.AddCommand(commandRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readConfig() (option.Options, error) {
|
type OptionsEntry struct {
|
||||||
|
content []byte
|
||||||
|
path string
|
||||||
|
options option.Options
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfigAt(path string) (*OptionsEntry, error) {
|
||||||
var (
|
var (
|
||||||
configContent []byte
|
configContent []byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if configPath == "stdin" {
|
if path == "stdin" {
|
||||||
configContent, err = io.ReadAll(os.Stdin)
|
configContent, err = io.ReadAll(os.Stdin)
|
||||||
} else {
|
} else {
|
||||||
configContent, err = os.ReadFile(configPath)
|
configContent, err = os.ReadFile(path)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "read config")
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
}
|
}
|
||||||
var options option.Options
|
var options option.Options
|
||||||
err = options.UnmarshalJSON(configContent)
|
err = options.UnmarshalJSON(configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "decode config")
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
}
|
}
|
||||||
return options, nil
|
return &OptionsEntry{
|
||||||
|
content: configContent,
|
||||||
|
path: path,
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readConfig() ([]*OptionsEntry, error) {
|
||||||
|
var optionsList []*OptionsEntry
|
||||||
|
for _, path := range configPaths {
|
||||||
|
optionsEntry, err := readConfigAt(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
optionsList = append(optionsList, optionsEntry)
|
||||||
|
}
|
||||||
|
for _, directory := range configDirectories {
|
||||||
|
entries, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read config directory at ", directory)
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
optionsEntry, err := readConfigAt(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 readConfigAndMerge() (option.Options, error) {
|
||||||
|
optionsList, err := readConfig()
|
||||||
|
if err != nil {
|
||||||
|
return option.Options{}, err
|
||||||
|
}
|
||||||
|
if len(optionsList) == 1 {
|
||||||
|
return optionsList[0].options, nil
|
||||||
|
}
|
||||||
|
var mergedOptions option.Options
|
||||||
|
for _, options := range optionsList {
|
||||||
|
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
|
||||||
|
if err != nil {
|
||||||
|
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mergedOptions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func create() (*box.Box, context.CancelFunc, error) {
|
func create() (*box.Box, context.CancelFunc, error) {
|
||||||
options, err := readConfig()
|
options, err := readConfigAndMerge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
55
cmd/sing-box/cmd_tools.go
Normal file
55
cmd/sing-box/cmd_tools.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandToolsFlagOutbound string
|
||||||
|
|
||||||
|
var commandTools = &cobra.Command{
|
||||||
|
Use: "tools",
|
||||||
|
Short: "Experimental tools",
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
|
||||||
|
mainCommand.AddCommand(commandTools)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPreStartedClient() (*box.Box, error) {
|
||||||
|
options, err := readConfigAndMerge()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
instance, err := box.New(context.Background(), options, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create service")
|
||||||
|
}
|
||||||
|
err = instance.PreStart()
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "start service")
|
||||||
|
}
|
||||||
|
return instance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
||||||
|
if outboundTag == "" {
|
||||||
|
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||||
|
if outbound == nil {
|
||||||
|
return nil, E.New("missing default outbound")
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
} else {
|
||||||
|
outbound, loaded := instance.Router().Outbound(outboundTag)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("outbound not found: ", outboundTag)
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
73
cmd/sing-box/cmd_tools_connect.go
Normal file
73
cmd/sing-box/cmd_tools_connect.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandConnectFlagNetwork string
|
||||||
|
|
||||||
|
var commandConnect = &cobra.Command{
|
||||||
|
Use: "connect [address]",
|
||||||
|
Short: "Connect to an address",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := connect(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
|
||||||
|
commandTools.AddCommand(commandConnect)
|
||||||
|
}
|
||||||
|
|
||||||
|
func connect(address string) error {
|
||||||
|
switch N.NetworkName(commandConnectFlagNetwork) {
|
||||||
|
case N.NetworkTCP, N.NetworkUDP:
|
||||||
|
default:
|
||||||
|
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
|
||||||
|
}
|
||||||
|
instance, err := createPreStartedClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer instance.Close()
|
||||||
|
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "connect to server")
|
||||||
|
}
|
||||||
|
var group task.Group
|
||||||
|
group.Append("upload", func(ctx context.Context) error {
|
||||||
|
return common.Error(bufio.Copy(conn, os.Stdin))
|
||||||
|
})
|
||||||
|
group.Append("download", func(ctx context.Context) error {
|
||||||
|
return common.Error(bufio.Copy(os.Stdout, conn))
|
||||||
|
})
|
||||||
|
group.Cleanup(func() {
|
||||||
|
conn.Close()
|
||||||
|
})
|
||||||
|
err = group.Run(context.Background())
|
||||||
|
if E.IsClosed(err) {
|
||||||
|
log.Info(err)
|
||||||
|
} else {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
91
cmd/sing-box/cmd_tools_fetch.go
Normal file
91
cmd/sing-box/cmd_tools_fetch.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandFetch = &cobra.Command{
|
||||||
|
Use: "fetch",
|
||||||
|
Short: "Fetch an URL",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := fetch(args)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandTools.AddCommand(commandFetch)
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpClient *http.Client
|
||||||
|
|
||||||
|
func fetch(args []string) error {
|
||||||
|
instance, err := createPreStartedClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer instance.Close()
|
||||||
|
httpClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
for _, urlString := range args {
|
||||||
|
parsedURL, err := url.Parse(urlString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch parsedURL.Scheme {
|
||||||
|
case "":
|
||||||
|
parsedURL.Scheme = "http"
|
||||||
|
fallthrough
|
||||||
|
case "http", "https":
|
||||||
|
err = fetchHTTP(parsedURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchHTTP(parsedURL *url.URL) error {
|
||||||
|
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
request.Header.Add("User-Agent", "curl/7.88.0")
|
||||||
|
response, err := httpClient.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = bufio.Copy(os.Stdout, response.Body)
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
69
cmd/sing-box/cmd_tools_synctime.go
Normal file
69
cmd/sing-box/cmd_tools_synctime.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/settings"
|
||||||
|
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/sagernet/sing/common/ntp"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
commandSyncTimeFlagServer string
|
||||||
|
commandSyncTimeOutputFormat string
|
||||||
|
commandSyncTimeWrite bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var commandSyncTime = &cobra.Command{
|
||||||
|
Use: "synctime",
|
||||||
|
Short: "Sync time using the NTP protocol",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
err := syncTime()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, "server", "s", "time.apple.com", "Set NTP server")
|
||||||
|
commandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, "format", "f", C.TimeLayout, "Set output format")
|
||||||
|
commandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, "write", "w", false, "Write time to system")
|
||||||
|
commandTools.AddCommand(commandSyncTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncTime() error {
|
||||||
|
instance, err := createPreStartedClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer instance.Close()
|
||||||
|
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
||||||
|
if serverAddress.Port == 0 {
|
||||||
|
serverAddress.Port = 123
|
||||||
|
}
|
||||||
|
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if commandSyncTimeWrite {
|
||||||
|
err = settings.SetSystemTime(response.Time)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "write time to system")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
os.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -11,9 +11,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
configPath string
|
configPaths []string
|
||||||
workingDir string
|
configDirectories []string
|
||||||
disableColor bool
|
workingDir string
|
||||||
|
disableColor bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var mainCommand = &cobra.Command{
|
var mainCommand = &cobra.Command{
|
||||||
@@ -22,7 +23,8 @@ var mainCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
|
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||||
|
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||||
}
|
}
|
||||||
@@ -38,8 +40,15 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger())
|
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger())
|
||||||
}
|
}
|
||||||
if workingDir != "" {
|
if workingDir != "" {
|
||||||
|
_, err := os.Stat(workingDir)
|
||||||
|
if err != nil {
|
||||||
|
os.MkdirAll(workingDir, 0o777)
|
||||||
|
}
|
||||||
if err := os.Chdir(workingDir); err != nil {
|
if err := os.Chdir(workingDir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
|
configPaths = append(configPaths, "config.json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
80
common/badjsonmerge/merge.go
Normal file
80
common/badjsonmerge/merge.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package badjsonmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/badjson"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MergeOptions(source option.Options, destination option.Options) (option.Options, error) {
|
||||||
|
rawSource, err := json.Marshal(source)
|
||||||
|
if err != nil {
|
||||||
|
return option.Options{}, E.Cause(err, "marshal source")
|
||||||
|
}
|
||||||
|
rawDestination, err := json.Marshal(destination)
|
||||||
|
if err != nil {
|
||||||
|
return option.Options{}, E.Cause(err, "marshal destination")
|
||||||
|
}
|
||||||
|
rawMerged, err := MergeJSON(rawSource, rawDestination)
|
||||||
|
if err != nil {
|
||||||
|
return option.Options{}, E.Cause(err, "merge options")
|
||||||
|
}
|
||||||
|
var merged option.Options
|
||||||
|
err = json.Unmarshal(rawMerged, &merged)
|
||||||
|
if err != nil {
|
||||||
|
return option.Options{}, E.Cause(err, "unmarshal merged options")
|
||||||
|
}
|
||||||
|
return merged, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MergeJSON(rawSource json.RawMessage, rawDestination json.RawMessage) (json.RawMessage, error) {
|
||||||
|
source, err := badjson.Decode(rawSource)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode source")
|
||||||
|
}
|
||||||
|
destination, err := badjson.Decode(rawDestination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "decode destination")
|
||||||
|
}
|
||||||
|
merged, err := mergeJSON(source, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(merged)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeJSON(anySource any, anyDestination any) (any, error) {
|
||||||
|
switch destination := anyDestination.(type) {
|
||||||
|
case badjson.JSONArray:
|
||||||
|
switch source := anySource.(type) {
|
||||||
|
case badjson.JSONArray:
|
||||||
|
destination = append(destination, source...)
|
||||||
|
default:
|
||||||
|
destination = append(destination, source)
|
||||||
|
}
|
||||||
|
return destination, nil
|
||||||
|
case *badjson.JSONObject:
|
||||||
|
switch source := anySource.(type) {
|
||||||
|
case *badjson.JSONObject:
|
||||||
|
for _, entry := range source.Entries() {
|
||||||
|
oldValue, loaded := destination.Get(entry.Key)
|
||||||
|
if loaded {
|
||||||
|
var err error
|
||||||
|
entry.Value, err = mergeJSON(entry.Value, oldValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "merge object item ", entry.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
destination.Put(entry.Key, entry.Value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("cannot merge json object into ", reflect.TypeOf(destination))
|
||||||
|
}
|
||||||
|
return destination, nil
|
||||||
|
default:
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
59
common/badjsonmerge/merge_test.go
Normal file
59
common/badjsonmerge/merge_test.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package badjsonmerge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeJSON(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
options := option.Options{
|
||||||
|
Log: &option.LogOptions{
|
||||||
|
Level: "info",
|
||||||
|
},
|
||||||
|
Route: &option.RouteOptions{
|
||||||
|
Rules: []option.Rule{
|
||||||
|
{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
Network: N.NetworkTCP,
|
||||||
|
Outbound: "direct",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
anotherOptions := option.Options{
|
||||||
|
Outbounds: []option.Outbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeDirect,
|
||||||
|
Tag: "direct",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
thirdOptions := option.Options{
|
||||||
|
Route: &option.RouteOptions{
|
||||||
|
Rules: []option.Rule{
|
||||||
|
{
|
||||||
|
Type: C.RuleTypeDefault,
|
||||||
|
DefaultOptions: option.DefaultRule{
|
||||||
|
Network: N.NetworkUDP,
|
||||||
|
Outbound: "direct",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mergeOptions, err := MergeOptions(options, anotherOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
mergeOptions, err = MergeOptions(thirdOptions, mergeOptions)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "info", mergeOptions.Log.Level)
|
||||||
|
require.Equal(t, 2, len(mergeOptions.Route.Rules))
|
||||||
|
require.Equal(t, C.TypeDirect, mergeOptions.Outbounds[0].Type)
|
||||||
|
}
|
||||||
@@ -27,7 +27,12 @@ type slowOpenConn struct {
|
|||||||
|
|
||||||
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func DialSlowContext(dialer *tfo.Dialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
if dialer.DisableTFO || N.NetworkName(network) != N.NetworkTCP {
|
||||||
return dialer.DialContext(ctx, network, destination.String(), nil)
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP, N.NetworkUDP:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.String())
|
||||||
|
default:
|
||||||
|
return dialer.Dialer.DialContext(ctx, network, destination.AddrString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &slowOpenConn{
|
return &slowOpenConn{
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
|
|||||||
12
common/settings/time_stub.go
Normal file
12
common/settings/time_stub.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build !(windows || linux || darwin)
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
14
common/settings/time_unix.go
Normal file
14
common/settings/time_unix.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build linux || darwin
|
||||||
|
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
||||||
|
return unix.Settimeofday(&timeVal)
|
||||||
|
}
|
||||||
32
common/settings/time_windows.go
Normal file
32
common/settings/time_windows.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetSystemTime(nowTime time.Time) error {
|
||||||
|
var systemTime windows.Systemtime
|
||||||
|
systemTime.Year = uint16(nowTime.Year())
|
||||||
|
systemTime.Month = uint16(nowTime.Month())
|
||||||
|
systemTime.Day = uint16(nowTime.Day())
|
||||||
|
systemTime.Hour = uint16(nowTime.Hour())
|
||||||
|
systemTime.Minute = uint16(nowTime.Minute())
|
||||||
|
systemTime.Second = uint16(nowTime.Second())
|
||||||
|
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
||||||
|
|
||||||
|
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
proc := dllKernel32.NewProc("SetSystemTime")
|
||||||
|
|
||||||
|
_, _, err := proc.Call(
|
||||||
|
uintptr(unsafe.Pointer(&systemTime)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil && err.Error() != "The operation completed successfully." {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
3
constant/time.go
Normal file
3
constant/time.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
const TimeLayout = "2006-01-02 15:04:05 -0700"
|
||||||
35
debug.go
Normal file
35
debug.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//go:build go1.19
|
||||||
|
|
||||||
|
package box
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func applyDebugOptions(options option.DebugOptions) {
|
||||||
|
if options.GCPercent != nil {
|
||||||
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
|
}
|
||||||
|
if options.MaxStack != nil {
|
||||||
|
debug.SetMaxStack(*options.MaxStack)
|
||||||
|
}
|
||||||
|
if options.MaxThreads != nil {
|
||||||
|
debug.SetMaxThreads(*options.MaxThreads)
|
||||||
|
}
|
||||||
|
if options.PanicOnFault != nil {
|
||||||
|
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||||
|
}
|
||||||
|
if options.TraceBack != "" {
|
||||||
|
debug.SetTraceback(options.TraceBack)
|
||||||
|
}
|
||||||
|
if options.MemoryLimit != 0 {
|
||||||
|
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||||
|
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||||
|
}
|
||||||
|
if options.OOMKiller != nil {
|
||||||
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
}
|
||||||
|
}
|
||||||
35
debug_go118.go
Normal file
35
debug_go118.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//go:build !go1.19
|
||||||
|
|
||||||
|
package box
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func applyDebugOptions(options option.DebugOptions) {
|
||||||
|
if options.GCPercent != nil {
|
||||||
|
debug.SetGCPercent(*options.GCPercent)
|
||||||
|
}
|
||||||
|
if options.MaxStack != nil {
|
||||||
|
debug.SetMaxStack(*options.MaxStack)
|
||||||
|
}
|
||||||
|
if options.MaxThreads != nil {
|
||||||
|
debug.SetMaxThreads(*options.MaxThreads)
|
||||||
|
}
|
||||||
|
if options.PanicOnFault != nil {
|
||||||
|
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||||
|
}
|
||||||
|
if options.TraceBack != "" {
|
||||||
|
debug.SetTraceback(options.TraceBack)
|
||||||
|
}
|
||||||
|
if options.MemoryLimit != 0 {
|
||||||
|
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||||
|
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||||
|
}
|
||||||
|
if options.OOMKiller != nil {
|
||||||
|
conntrack.KillerEnabled = *options.OOMKiller
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
#### 1.2-beta10
|
||||||
|
|
||||||
|
* Add multiple configuration support **1**
|
||||||
|
* Fix bugs and update dependencies
|
||||||
|
|
||||||
|
*1*:
|
||||||
|
|
||||||
|
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C` to load all configuration files in a directory.
|
||||||
|
|
||||||
|
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file name.
|
||||||
|
|
||||||
|
#### 1.1.7
|
||||||
|
|
||||||
|
* Improve the stability of the VMESS server
|
||||||
|
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
|
||||||
|
* Fix bugs and update dependencies
|
||||||
|
|
||||||
#### 1.2-beta9
|
#### 1.2-beta9
|
||||||
|
|
||||||
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
||||||
|
|||||||
@@ -74,14 +74,10 @@ Hysteria users
|
|||||||
|
|
||||||
#### users.auth
|
#### users.auth
|
||||||
|
|
||||||
==Required if `auth_str` is empty==
|
|
||||||
|
|
||||||
Authentication password, in base64.
|
Authentication password, in base64.
|
||||||
|
|
||||||
#### users.auth_str
|
#### users.auth_str
|
||||||
|
|
||||||
==Required if `auth` is empty==
|
|
||||||
|
|
||||||
Authentication password.
|
Authentication password.
|
||||||
|
|
||||||
#### recv_window_conn
|
#### recv_window_conn
|
||||||
|
|||||||
@@ -74,14 +74,10 @@ Hysteria 用户
|
|||||||
|
|
||||||
#### users.auth
|
#### users.auth
|
||||||
|
|
||||||
==与 auth_str 必填一个==
|
|
||||||
|
|
||||||
base64 编码的认证密码。
|
base64 编码的认证密码。
|
||||||
|
|
||||||
#### users.auth_str
|
#### users.auth_str
|
||||||
|
|
||||||
==与 auth 必填一个==
|
|
||||||
|
|
||||||
认证密码。
|
认证密码。
|
||||||
|
|
||||||
#### recv_window_conn
|
#### recv_window_conn
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ The protocol version, `1` or `2`.
|
|||||||
|
|
||||||
### Application support
|
### Application support
|
||||||
|
|
||||||
| Project | UoT v1 | UoT v2 |
|
| Project | UoT v1 | UoT v2 |
|
||||||
|--------------|----------------------|------------|
|
|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||||
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
||||||
| Xray-core | v1.5.7 (2022/06/05) | / |
|
| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) |
|
||||||
| Clash.Meta | v1.12.0 (2022/07/02) | / |
|
| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) |
|
||||||
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
||||||
|
|
||||||
### Protocol details
|
### Protocol details
|
||||||
|
|
||||||
@@ -64,8 +64,6 @@ Protocol version 2 uses a new magic address: `sp.v2.udp-over-tcp.arpa`
|
|||||||
|-----------|------|----------|-------|
|
|-----------|------|----------|-------|
|
||||||
| u8 | u8 | variable | u16be |
|
| u8 | u8 | variable | u16be |
|
||||||
|
|
||||||
**version**: Fixed to 2.
|
|
||||||
|
|
||||||
**isConnect**: Set to 1 to indicates that the stream uses the connect format, 0 to disable.
|
**isConnect**: Set to 1 to indicates that the stream uses the connect format, 0 to disable.
|
||||||
|
|
||||||
**ATYP / address / port**: Request destination, uses the SOCKS address format.
|
**ATYP / address / port**: Request destination, uses the SOCKS address format.
|
||||||
|
|||||||
@@ -36,4 +36,4 @@ sing-box version
|
|||||||
```
|
```
|
||||||
|
|
||||||
It is also recommended to use systemd to manage sing-box service,
|
It is also recommended to use systemd to manage sing-box service,
|
||||||
see [Linux server installation example](./examples/linux-server-installation).
|
see [Linux server installation example](/examples/linux-server-installation).
|
||||||
@@ -36,4 +36,4 @@ sing-box version
|
|||||||
```
|
```
|
||||||
|
|
||||||
同时推荐使用 systemd 来管理 sing-box 服务器实例。
|
同时推荐使用 systemd 来管理 sing-box 服务器实例。
|
||||||
参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。
|
参阅 [Linux 服务器安装示例](/examples/linux-server-installation)。
|
||||||
@@ -114,7 +114,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start() error {
|
func (s *Server) PreStart() error {
|
||||||
if s.cacheFilePath != "" {
|
if s.cacheFilePath != "" {
|
||||||
cacheFile, err := cachefile.Open(s.cacheFilePath)
|
cacheFile, err := cachefile.Open(s.cacheFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -122,6 +122,10 @@ func (s *Server) Start() error {
|
|||||||
}
|
}
|
||||||
s.cacheFile = cacheFile
|
s.cacheFile = cacheFile
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "external controller listen error")
|
return E.Cause(err, "external controller listen error")
|
||||||
|
|||||||
8
go.mod
8
go.mod
@@ -4,7 +4,7 @@ go 1.18
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
berty.tech/go-libtor v1.0.385
|
berty.tech/go-libtor v1.0.385
|
||||||
github.com/Dreamacro/clash v1.13.0
|
github.com/Dreamacro/clash v1.14.0
|
||||||
github.com/caddyserver/certmagic v0.17.2
|
github.com/caddyserver/certmagic v0.17.2
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
@@ -25,11 +25,11 @@ require (
|
|||||||
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
|
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32
|
||||||
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8
|
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8
|
||||||
github.com/sagernet/sing v0.1.9-0.20230317044231-85a9429eadb6
|
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d
|
||||||
github.com/sagernet/sing-dns v0.1.4
|
github.com/sagernet/sing-dns v0.1.4
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9
|
github.com/sagernet/sing-shadowsocks v0.2.0
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0
|
github.com/sagernet/sing-shadowtls v0.1.0
|
||||||
github.com/sagernet/sing-tun v0.1.3-0.20230315134716-fe89bbded22d
|
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558
|
||||||
github.com/sagernet/sing-vmess v0.1.3
|
github.com/sagernet/sing-vmess v0.1.3
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -1,7 +1,7 @@
|
|||||||
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw=
|
||||||
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw=
|
||||||
github.com/Dreamacro/clash v1.13.0 h1:gF0E0TluE1LCmuhhg0/bjqABYDmSnXkUjXjRhZxyrm8=
|
github.com/Dreamacro/clash v1.14.0 h1:ehJ/C/1m9LEjmME72WSE/Y2YqbR3Q54AbjqiRCvtyW4=
|
||||||
github.com/Dreamacro/clash v1.13.0/go.mod h1:hf0RkWPsQ0e8oS8WVJBIRocY/1ILYzQQg9zeMwd8LsM=
|
github.com/Dreamacro/clash v1.14.0/go.mod h1:ia2CU7V713H1QdCqMwOLK9U9V5Ay8X0voj3yQr2tk+I=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
@@ -111,16 +111,16 @@ github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 h1:4M3+0/kqvJuTsi
|
|||||||
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.1.9-0.20230317044231-85a9429eadb6 h1:h1wGLPBJLjujj9kYSbLiP1Tt6+IQnZ7Ok7jQd4u3xxk=
|
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d h1:ktk03rtgPqTDyUd2dWg1uzyr5RnptX8grSMvIzedJlQ=
|
||||||
github.com/sagernet/sing v0.1.9-0.20230317044231-85a9429eadb6/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
|
github.com/sagernet/sing v0.2.1-0.20230321172705-3e60222a1a7d/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
|
||||||
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
|
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
|
||||||
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
|
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0=
|
github.com/sagernet/sing-shadowsocks v0.2.0 h1:ILDWL7pwWfkPLEbviE/MyCgfjaBmJY/JVVY+5jhSb58=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9/go.mod h1:f3mHTy5shnVM9l8UocMlJgC/1G/zdj5FuEuVXhDinGU=
|
github.com/sagernet/sing-shadowsocks v0.2.0/go.mod h1:ysYzszRLpNzJSorvlWRMuzU6Vchsp7sd52q+JNY4axw=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
||||||
github.com/sagernet/sing-tun v0.1.3-0.20230315134716-fe89bbded22d h1:1gt4Hu2fHCrmL2NZYCNJ3nCgeczuhK09oCMni9mZmZk=
|
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558 h1:c5Rm6BTOclEeayS6G9+1rI1kTeilCsn0ALSFbOdlgRE=
|
||||||
github.com/sagernet/sing-tun v0.1.3-0.20230315134716-fe89bbded22d/go.mod h1:KnRkwaDHbb06zgeNPu0LQ8A+vA9myMxKEgHN1brCPHg=
|
github.com/sagernet/sing-tun v0.1.3-0.20230321172818-56bedd2f0558/go.mod h1:cqnZEm+2ArgP4Gq1NcQfVFm9CZaeGw21mG9AcnYOTiU=
|
||||||
github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
|
github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
|
||||||
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
|
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package inbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/proxyproto"
|
"github.com/sagernet/sing-box/common/proxyproto"
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
|
func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
|
||||||
var err error
|
var err error
|
||||||
bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort)
|
||||||
var tcpListener net.Listener
|
var tcpListener net.Listener
|
||||||
if !a.listenOptions.TCPFastOpen {
|
if !a.listenOptions.TCPFastOpen {
|
||||||
tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
|
tcpListener, err = net.ListenTCP(M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.TCPAddr())
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package inbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) {
|
func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) {
|
||||||
bindAddr := M.SocksaddrFrom(netip.Addr(a.listenOptions.Listen), a.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort)
|
||||||
var lc net.ListenConfig
|
var lc net.ListenConfig
|
||||||
var udpFragment bool
|
var udpFragment bool
|
||||||
if a.listenOptions.UDPFragment != nil {
|
if a.listenOptions.UDPFragment != nil {
|
||||||
|
|||||||
@@ -187,20 +187,24 @@ func (h *Hysteria) accept(ctx context.Context, conn quic.Connection) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
userIndex := slices.Index(h.authKey, string(clientHello.Auth))
|
if len(h.authKey) > 0 {
|
||||||
if userIndex == -1 {
|
userIndex := slices.Index(h.authKey, string(clientHello.Auth))
|
||||||
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
|
if userIndex == -1 {
|
||||||
Message: "wrong password",
|
err = hysteria.WriteServerHello(controlStream, hysteria.ServerHello{
|
||||||
})
|
Message: "wrong password",
|
||||||
return E.Errors(E.New("wrong password: ", string(clientHello.Auth)), err)
|
})
|
||||||
}
|
return E.Errors(E.New("wrong password: ", string(clientHello.Auth)), err)
|
||||||
user := h.authUser[userIndex]
|
}
|
||||||
if user == "" {
|
user := h.authUser[userIndex]
|
||||||
user = F.ToString(userIndex)
|
if user == "" {
|
||||||
|
user = F.ToString(userIndex)
|
||||||
|
} else {
|
||||||
|
ctx = auth.ContextWithUser(ctx, user)
|
||||||
|
}
|
||||||
|
h.logger.InfoContext(ctx, "[", user, "] inbound connection from ", conn.RemoteAddr())
|
||||||
} else {
|
} else {
|
||||||
ctx = auth.ContextWithUser(ctx, user)
|
h.logger.InfoContext(ctx, "inbound connection from ", conn.RemoteAddr())
|
||||||
}
|
}
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection from ", conn.RemoteAddr())
|
|
||||||
h.logger.DebugContext(ctx, "peer send speed: ", clientHello.SendBPS/1024/1024, " MBps, peer recv speed: ", clientHello.RecvBPS/1024/1024, " MBps")
|
h.logger.DebugContext(ctx, "peer send speed: ", clientHello.SendBPS/1024/1024, " MBps, peer recv speed: ", clientHello.RecvBPS/1024/1024, " MBps")
|
||||||
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
|
if clientHello.SendBPS == 0 || clientHello.RecvBPS == 0 {
|
||||||
return E.New("invalid rate from client")
|
return E.New("invalid rate from client")
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common/ranges"
|
"github.com/sagernet/sing/common/ranges"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.Inbound = (*Tun)(nil)
|
var (
|
||||||
|
_ adapter.Inbound = (*Tun)(nil)
|
||||||
|
_ tun.Router = (*Tun)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
type Tun struct {
|
type Tun struct {
|
||||||
tag string
|
tag string
|
||||||
@@ -40,10 +43,6 @@ type Tun struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*Tun, error) {
|
||||||
tunName := options.InterfaceName
|
|
||||||
if tunName == "" {
|
|
||||||
tunName = tun.CalculateInterfaceName("")
|
|
||||||
}
|
|
||||||
tunMTU := options.MTU
|
tunMTU := options.MTU
|
||||||
if tunMTU == 0 {
|
if tunMTU == 0 {
|
||||||
tunMTU = 9000
|
tunMTU = 9000
|
||||||
@@ -77,7 +76,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
inboundOptions: options.InboundOptions,
|
inboundOptions: options.InboundOptions,
|
||||||
tunOptions: tun.Options{
|
tunOptions: tun.Options{
|
||||||
Name: tunName,
|
Name: options.InterfaceName,
|
||||||
MTU: tunMTU,
|
MTU: tunMTU,
|
||||||
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
Inet4Address: common.Map(options.Inet4Address, option.ListenPrefix.Build),
|
||||||
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
Inet6Address: common.Map(options.Inet6Address, option.ListenPrefix.Build),
|
||||||
@@ -143,12 +142,17 @@ func (t *Tun) Tag() string {
|
|||||||
|
|
||||||
func (t *Tun) Start() error {
|
func (t *Tun) Start() error {
|
||||||
if C.IsAndroid && t.platformInterface == nil {
|
if C.IsAndroid && t.platformInterface == nil {
|
||||||
|
t.logger.Trace("building android rules")
|
||||||
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
||||||
}
|
}
|
||||||
|
if t.tunOptions.Name == "" {
|
||||||
|
t.tunOptions.Name = tun.CalculateInterfaceName("")
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
tunInterface tun.Tun
|
tunInterface tun.Tun
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
t.logger.Trace("opening interface")
|
||||||
if t.platformInterface != nil {
|
if t.platformInterface != nil {
|
||||||
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
|
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions, t.platformOptions)
|
||||||
} else {
|
} else {
|
||||||
@@ -157,7 +161,12 @@ func (t *Tun) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "configure tun interface")
|
return E.Cause(err, "configure tun interface")
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("creating stack")
|
||||||
t.tunIf = tunInterface
|
t.tunIf = tunInterface
|
||||||
|
var tunRouter tun.Router
|
||||||
|
if len(t.router.IPRules()) > 0 {
|
||||||
|
tunRouter = t
|
||||||
|
}
|
||||||
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
t.tunStack, err = tun.NewStack(t.stack, tun.StackOptions{
|
||||||
Context: t.ctx,
|
Context: t.ctx,
|
||||||
Tun: tunInterface,
|
Tun: tunInterface,
|
||||||
@@ -167,6 +176,7 @@ func (t *Tun) Start() error {
|
|||||||
Inet6Address: t.tunOptions.Inet6Address,
|
Inet6Address: t.tunOptions.Inet6Address,
|
||||||
EndpointIndependentNat: t.endpointIndependentNat,
|
EndpointIndependentNat: t.endpointIndependentNat,
|
||||||
UDPTimeout: t.udpTimeout,
|
UDPTimeout: t.udpTimeout,
|
||||||
|
Router: tunRouter,
|
||||||
Handler: t,
|
Handler: t,
|
||||||
Logger: t.logger,
|
Logger: t.logger,
|
||||||
UnderPlatform: t.platformInterface != nil,
|
UnderPlatform: t.platformInterface != nil,
|
||||||
@@ -174,6 +184,7 @@ func (t *Tun) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
t.logger.Trace("starting stack")
|
||||||
err = t.tunStack.Start()
|
err = t.tunStack.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -189,6 +200,21 @@ func (t *Tun) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tun) RouteConnection(session tun.RouteSession, conn tun.RouteContext) tun.RouteAction {
|
||||||
|
ctx := log.ContextWithNewID(t.ctx)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
metadata.Inbound = t.tag
|
||||||
|
metadata.InboundType = C.TypeTun
|
||||||
|
metadata.IPVersion = session.IPVersion
|
||||||
|
metadata.Network = tun.NetworkName(session.Network)
|
||||||
|
metadata.Source = M.SocksaddrFromNetIP(session.Source)
|
||||||
|
metadata.Destination = M.SocksaddrFromNetIP(session.Destination)
|
||||||
|
metadata.InboundOptions = t.inboundOptions
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection from ", metadata.Source)
|
||||||
|
t.logger.DebugContext(ctx, "incoming connection to ", metadata.Destination)
|
||||||
|
return t.router.RouteIPConnection(ctx, conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
func (t *Tun) NewConnection(ctx context.Context, conn net.Conn, upstreamMetadata M.Metadata) error {
|
||||||
ctx = log.ContextWithNewID(ctx)
|
ctx = log.ContextWithNewID(ctx)
|
||||||
var metadata adapter.InboundContext
|
var metadata adapter.InboundContext
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"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/common/settings"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"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/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
@@ -14,19 +16,17 @@ import (
|
|||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const timeLayout = "2006-01-02 15:04:05 -0700"
|
|
||||||
|
|
||||||
var _ adapter.TimeService = (*Service)(nil)
|
var _ adapter.TimeService = (*Service)(nil)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
server M.Socksaddr
|
server M.Socksaddr
|
||||||
dialer N.Dialer
|
writeToSystem bool
|
||||||
logger logger.Logger
|
dialer N.Dialer
|
||||||
|
logger logger.Logger
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
clockOffset time.Duration
|
clockOffset time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service {
|
func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service {
|
||||||
@@ -42,12 +42,13 @@ func NewService(ctx context.Context, router adapter.Router, logger logger.Logger
|
|||||||
interval = 30 * time.Minute
|
interval = 30 * time.Minute
|
||||||
}
|
}
|
||||||
return &Service{
|
return &Service{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
cancel: cancel,
|
cancel: cancel,
|
||||||
server: server,
|
server: server,
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
writeToSystem: options.WriteToSystem,
|
||||||
logger: logger,
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
ticker: time.NewTicker(interval),
|
logger: logger,
|
||||||
|
ticker: time.NewTicker(interval),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ func (s *Service) Start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "initialize time")
|
return E.Cause(err, "initialize time")
|
||||||
}
|
}
|
||||||
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
|
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
|
||||||
go s.loopUpdate()
|
go s.loopUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -82,7 +83,7 @@ func (s *Service) loopUpdate() {
|
|||||||
}
|
}
|
||||||
err := s.update()
|
err := s.update()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
|
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(C.TimeLayout))
|
||||||
} else {
|
} else {
|
||||||
s.logger.Warn("update time: ", err)
|
s.logger.Warn("update time: ", err)
|
||||||
}
|
}
|
||||||
@@ -95,5 +96,11 @@ func (s *Service) update() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.clockOffset = response.ClockOffset
|
s.clockOffset = response.ClockOffset
|
||||||
|
if s.writeToSystem {
|
||||||
|
writeErr := settings.SetSystemTime(s.TimeFunc()())
|
||||||
|
if writeErr != nil {
|
||||||
|
s.logger.Warn("write time to system: ", writeErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
43
option/debug.go
Normal file
43
option/debug.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DebugOptions struct {
|
||||||
|
GCPercent *int `json:"gc_percent,omitempty"`
|
||||||
|
MaxStack *int `json:"max_stack,omitempty"`
|
||||||
|
MaxThreads *int `json:"max_threads,omitempty"`
|
||||||
|
PanicOnFault *bool `json:"panic_on_fault,omitempty"`
|
||||||
|
TraceBack string `json:"trace_back,omitempty"`
|
||||||
|
MemoryLimit BytesLength `json:"memory_limit,omitempty"`
|
||||||
|
OOMKiller *bool `json:"oom_killer,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BytesLength int64
|
||||||
|
|
||||||
|
func (l BytesLength) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(humanize.IBytes(uint64(l)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BytesLength) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var valueInteger int64
|
||||||
|
err := json.Unmarshal(bytes, &valueInteger)
|
||||||
|
if err == nil {
|
||||||
|
*l = BytesLength(valueInteger)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var valueString string
|
||||||
|
err = json.Unmarshal(bytes, &valueString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
parsedValue, err := humanize.ParseBytes(valueString)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*l = BytesLength(parsedValue)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
103
option/dns.go
103
option/dns.go
@@ -1,14 +1,5 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DNSOptions struct {
|
type DNSOptions struct {
|
||||||
Servers []DNSServerOptions `json:"servers,omitempty"`
|
Servers []DNSServerOptions `json:"servers,omitempty"`
|
||||||
Rules []DNSRule `json:"rules,omitempty"`
|
Rules []DNSRule `json:"rules,omitempty"`
|
||||||
@@ -31,97 +22,3 @@ type DNSServerOptions struct {
|
|||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSRule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultDNSRule `json:"-"`
|
|
||||||
LogicalOptions LogicalDNSRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DNSRule _DNSRule
|
|
||||||
|
|
||||||
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_DNSRule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "dns route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
|
||||||
Network string `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
Outbound Listable[string] `json:"outbound,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultDNSRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultDNSRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Server = r.Server
|
|
||||||
defaultValue.DisableCache = r.DisableCache
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Server string `json:"server,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalDNSRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,4 +3,5 @@ package option
|
|||||||
type ExperimentalOptions struct {
|
type ExperimentalOptions struct {
|
||||||
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
ClashAPI *ClashAPIOptions `json:"clash_api,omitempty"`
|
||||||
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
V2RayAPI *V2RayAPIOptions `json:"v2ray_api,omitempty"`
|
||||||
|
Debug *DebugOptions `json:"debug,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,14 +117,14 @@ type InboundOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ListenOptions struct {
|
type ListenOptions struct {
|
||||||
Listen ListenAddress `json:"listen"`
|
Listen *ListenAddress `json:"listen,omitempty"`
|
||||||
ListenPort uint16 `json:"listen_port,omitempty"`
|
ListenPort uint16 `json:"listen_port,omitempty"`
|
||||||
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
|
||||||
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
UDPFragment *bool `json:"udp_fragment,omitempty"`
|
||||||
UDPFragmentDefault bool `json:"-"`
|
UDPFragmentDefault bool `json:"-"`
|
||||||
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
UDPTimeout int64 `json:"udp_timeout,omitempty"`
|
||||||
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
|
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
|
||||||
ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"`
|
ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"`
|
||||||
Detour string `json:"detour,omitempty"`
|
Detour string `json:"detour,omitempty"`
|
||||||
InboundOptions
|
InboundOptions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
type NTPOptions struct {
|
type NTPOptions struct {
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Interval Duration `json:"interval,omitempty"`
|
Interval Duration `json:"interval,omitempty"`
|
||||||
|
WriteToSystem bool `json:"write_to_system,omitempty"`
|
||||||
ServerOptions
|
ServerOptions
|
||||||
DialerOptions
|
DialerOptions
|
||||||
}
|
}
|
||||||
|
|||||||
101
option/route.go
101
option/route.go
@@ -1,17 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RouteOptions struct {
|
type RouteOptions struct {
|
||||||
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
GeoIP *GeoIPOptions `json:"geoip,omitempty"`
|
||||||
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
Geosite *GeositeOptions `json:"geosite,omitempty"`
|
||||||
|
IPRules []IPRule `json:"ip_rules,omitempty"`
|
||||||
Rules []Rule `json:"rules,omitempty"`
|
Rules []Rule `json:"rules,omitempty"`
|
||||||
Final string `json:"final,omitempty"`
|
Final string `json:"final,omitempty"`
|
||||||
FindProcess bool `json:"find_process,omitempty"`
|
FindProcess bool `json:"find_process,omitempty"`
|
||||||
@@ -32,94 +24,3 @@ type GeositeOptions struct {
|
|||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type _Rule struct {
|
|
||||||
Type string `json:"type,omitempty"`
|
|
||||||
DefaultOptions DefaultRule `json:"-"`
|
|
||||||
LogicalOptions LogicalRule `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Rule _Rule
|
|
||||||
|
|
||||||
func (r Rule) MarshalJSON() ([]byte, error) {
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
r.Type = ""
|
|
||||||
v = r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
return MarshallObjects((_Rule)(r), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := json.Unmarshal(bytes, (*_Rule)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var v any
|
|
||||||
switch r.Type {
|
|
||||||
case "", C.RuleTypeDefault:
|
|
||||||
r.Type = C.RuleTypeDefault
|
|
||||||
v = &r.DefaultOptions
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
v = &r.LogicalOptions
|
|
||||||
default:
|
|
||||||
return E.New("unknown rule type: " + r.Type)
|
|
||||||
}
|
|
||||||
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "route rule")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DefaultRule struct {
|
|
||||||
Inbound Listable[string] `json:"inbound,omitempty"`
|
|
||||||
IPVersion int `json:"ip_version,omitempty"`
|
|
||||||
Network string `json:"network,omitempty"`
|
|
||||||
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
|
||||||
Protocol Listable[string] `json:"protocol,omitempty"`
|
|
||||||
Domain Listable[string] `json:"domain,omitempty"`
|
|
||||||
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
|
||||||
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
|
||||||
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
|
||||||
Geosite Listable[string] `json:"geosite,omitempty"`
|
|
||||||
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
|
||||||
GeoIP Listable[string] `json:"geoip,omitempty"`
|
|
||||||
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
|
||||||
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
|
||||||
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
|
||||||
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
|
||||||
Port Listable[uint16] `json:"port,omitempty"`
|
|
||||||
PortRange Listable[string] `json:"port_range,omitempty"`
|
|
||||||
ProcessName Listable[string] `json:"process_name,omitempty"`
|
|
||||||
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
|
||||||
PackageName Listable[string] `json:"package_name,omitempty"`
|
|
||||||
User Listable[string] `json:"user,omitempty"`
|
|
||||||
UserID Listable[int32] `json:"user_id,omitempty"`
|
|
||||||
ClashMode string `json:"clash_mode,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r DefaultRule) IsValid() bool {
|
|
||||||
var defaultValue DefaultRule
|
|
||||||
defaultValue.Invert = r.Invert
|
|
||||||
defaultValue.Outbound = r.Outbound
|
|
||||||
return !reflect.DeepEqual(r, defaultValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogicalRule struct {
|
|
||||||
Mode string `json:"mode"`
|
|
||||||
Rules []DefaultRule `json:"rules,omitempty"`
|
|
||||||
Invert bool `json:"invert,omitempty"`
|
|
||||||
Outbound string `json:"outbound,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r LogicalRule) IsValid() bool {
|
|
||||||
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
|
||||||
}
|
|
||||||
|
|||||||
101
option/rule.go
Normal file
101
option/rule.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _Rule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultRule `json:"-"`
|
||||||
|
LogicalOptions LogicalRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rule _Rule
|
||||||
|
|
||||||
|
func (r Rule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_Rule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_Rule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
GeoIP Listable[string] `json:"geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
IPCIDR Listable[string] `json:"ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultRule.IsValid)
|
||||||
|
}
|
||||||
104
option/rule_dns.go
Normal file
104
option/rule_dns.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _DNSRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultDNSRule `json:"-"`
|
||||||
|
LogicalOptions LogicalDNSRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DNSRule _DNSRule
|
||||||
|
|
||||||
|
func (r DNSRule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_DNSRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_DNSRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "dns route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultDNSRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
AuthUser Listable[string] `json:"auth_user,omitempty"`
|
||||||
|
Protocol Listable[string] `json:"protocol,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
ProcessName Listable[string] `json:"process_name,omitempty"`
|
||||||
|
ProcessPath Listable[string] `json:"process_path,omitempty"`
|
||||||
|
PackageName Listable[string] `json:"package_name,omitempty"`
|
||||||
|
User Listable[string] `json:"user,omitempty"`
|
||||||
|
UserID Listable[int32] `json:"user_id,omitempty"`
|
||||||
|
Outbound Listable[string] `json:"outbound,omitempty"`
|
||||||
|
ClashMode string `json:"clash_mode,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultDNSRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultDNSRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Server = r.Server
|
||||||
|
defaultValue.DisableCache = r.DisableCache
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalDNSRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultDNSRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalDNSRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultDNSRule.IsValid)
|
||||||
|
}
|
||||||
125
option/rule_ip.go
Normal file
125
option/rule_ip.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type _IPRule struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
DefaultOptions DefaultIPRule `json:"-"`
|
||||||
|
LogicalOptions LogicalIPRule `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IPRule _IPRule
|
||||||
|
|
||||||
|
func (r IPRule) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
r.Type = ""
|
||||||
|
v = r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_IPRule)(r), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IPRule) UnmarshalJSON(bytes []byte) error {
|
||||||
|
err := json.Unmarshal(bytes, (*_IPRule)(r))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch r.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
r.Type = C.RuleTypeDefault
|
||||||
|
v = &r.DefaultOptions
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
v = &r.LogicalOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown rule type: " + r.Type)
|
||||||
|
}
|
||||||
|
err = UnmarshallExcluded(bytes, (*_IPRule)(r), v)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "ip route rule")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultIPRule struct {
|
||||||
|
Inbound Listable[string] `json:"inbound,omitempty"`
|
||||||
|
IPVersion int `json:"ip_version,omitempty"`
|
||||||
|
Network Listable[string] `json:"network,omitempty"`
|
||||||
|
Domain Listable[string] `json:"domain,omitempty"`
|
||||||
|
DomainSuffix Listable[string] `json:"domain_suffix,omitempty"`
|
||||||
|
DomainKeyword Listable[string] `json:"domain_keyword,omitempty"`
|
||||||
|
DomainRegex Listable[string] `json:"domain_regex,omitempty"`
|
||||||
|
Geosite Listable[string] `json:"geosite,omitempty"`
|
||||||
|
SourceGeoIP Listable[string] `json:"source_geoip,omitempty"`
|
||||||
|
SourceIPCIDR Listable[string] `json:"source_ip_cidr,omitempty"`
|
||||||
|
SourcePort Listable[uint16] `json:"source_port,omitempty"`
|
||||||
|
SourcePortRange Listable[string] `json:"source_port_range,omitempty"`
|
||||||
|
Port Listable[uint16] `json:"port,omitempty"`
|
||||||
|
PortRange Listable[string] `json:"port_range,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouteAction tun.ActionType
|
||||||
|
|
||||||
|
func (a RouteAction) MarshalJSON() ([]byte, error) {
|
||||||
|
switch tun.ActionType(a) {
|
||||||
|
case tun.ActionTypeReject, tun.ActionTypeDirect:
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown action: ", a)
|
||||||
|
}
|
||||||
|
return json.Marshal(tun.ActionTypeName(tun.ActionType(a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *RouteAction) UnmarshalJSON(bytes []byte) error {
|
||||||
|
var value string
|
||||||
|
err := json.Unmarshal(bytes, &value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
actionType, err := tun.ParseActionType(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch actionType {
|
||||||
|
case tun.ActionTypeReject, tun.ActionTypeDirect:
|
||||||
|
default:
|
||||||
|
return E.New("unknown action: ", a)
|
||||||
|
}
|
||||||
|
*a = RouteAction(actionType)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r DefaultIPRule) IsValid() bool {
|
||||||
|
var defaultValue DefaultIPRule
|
||||||
|
defaultValue.Invert = r.Invert
|
||||||
|
defaultValue.Action = r.Action
|
||||||
|
defaultValue.Outbound = r.Outbound
|
||||||
|
return !reflect.DeepEqual(r, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogicalIPRule struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
Rules []DefaultIPRule `json:"rules,omitempty"`
|
||||||
|
Invert bool `json:"invert,omitempty"`
|
||||||
|
Action RouteAction `json:"action,omitempty"`
|
||||||
|
Outbound string `json:"outbound,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r LogicalIPRule) IsValid() bool {
|
||||||
|
return len(r.Rules) > 0 && common.All(r.Rules, DefaultIPRule.IsValid)
|
||||||
|
}
|
||||||
@@ -16,6 +16,11 @@ import (
|
|||||||
|
|
||||||
type ListenAddress netip.Addr
|
type ListenAddress netip.Addr
|
||||||
|
|
||||||
|
func NewListenAddress(addr netip.Addr) *ListenAddress {
|
||||||
|
address := ListenAddress(addr)
|
||||||
|
return &address
|
||||||
|
}
|
||||||
|
|
||||||
func (a ListenAddress) MarshalJSON() ([]byte, error) {
|
func (a ListenAddress) MarshalJSON() ([]byte, error) {
|
||||||
addr := netip.Addr(a)
|
addr := netip.Addr(a)
|
||||||
if !addr.IsValid() {
|
if !addr.IsValid() {
|
||||||
@@ -38,8 +43,11 @@ func (a *ListenAddress) UnmarshalJSON(content []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a ListenAddress) Build() netip.Addr {
|
func (a *ListenAddress) Build() netip.Addr {
|
||||||
return (netip.Addr)(a)
|
if a == nil {
|
||||||
|
return netip.AddrFrom4([4]byte{127, 0, 0, 1})
|
||||||
|
}
|
||||||
|
return (netip.Addr)(*a)
|
||||||
}
|
}
|
||||||
|
|
||||||
type NetworkList string
|
type NetworkList string
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ type WireGuardOutboundOptions struct {
|
|||||||
Workers int `json:"workers,omitempty"`
|
Workers int `json:"workers,omitempty"`
|
||||||
MTU uint32 `json:"mtu,omitempty"`
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
IPRewrite bool `json:"ip_rewrite,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
@@ -26,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ adapter.Outbound = (*WireGuard)(nil)
|
_ adapter.IPOutbound = (*WireGuard)(nil)
|
||||||
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ type WireGuard struct {
|
|||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
bind *wireguard.ClientBind
|
bind *wireguard.ClientBind
|
||||||
device *device.Device
|
device *device.Device
|
||||||
|
natDevice wireguard.NatDevice
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,17 +109,25 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
if mtu == 0 {
|
if mtu == 0 {
|
||||||
mtu = 1408
|
mtu = 1408
|
||||||
}
|
}
|
||||||
var wireTunDevice wireguard.Device
|
var tunDevice wireguard.Device
|
||||||
var err error
|
var err error
|
||||||
if !options.SystemInterface && tun.WithGVisor {
|
if !options.SystemInterface && tun.WithGVisor {
|
||||||
wireTunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu)
|
tunDevice, err = wireguard.NewStackDevice(localPrefixes, mtu, options.IPRewrite)
|
||||||
} else {
|
} else {
|
||||||
wireTunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
tunDevice, err = wireguard.NewSystemDevice(router, options.InterfaceName, localPrefixes, mtu)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create WireGuard device")
|
return nil, E.Cause(err, "create WireGuard device")
|
||||||
}
|
}
|
||||||
wgDevice := device.NewDevice(wireTunDevice, outbound.bind, &device.Logger{
|
natDevice, isNatDevice := tunDevice.(wireguard.NatDevice)
|
||||||
|
if !isNatDevice && router.NatRequired(tag) {
|
||||||
|
natDevice = wireguard.NewNATDevice(tunDevice, options.IPRewrite)
|
||||||
|
}
|
||||||
|
deviceInput := tunDevice
|
||||||
|
if natDevice != nil {
|
||||||
|
deviceInput = natDevice
|
||||||
|
}
|
||||||
|
wgDevice := device.NewDevice(deviceInput, outbound.bind, &device.Logger{
|
||||||
Verbosef: func(format string, args ...interface{}) {
|
Verbosef: func(format string, args ...interface{}) {
|
||||||
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
logger.Debug(fmt.Sprintf(strings.ToLower(format), args...))
|
||||||
},
|
},
|
||||||
@@ -132,7 +143,8 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
return nil, E.Cause(err, "setup wireguard")
|
return nil, E.Cause(err, "setup wireguard")
|
||||||
}
|
}
|
||||||
outbound.device = wgDevice
|
outbound.device = wgDevice
|
||||||
outbound.tunDevice = wireTunDevice
|
outbound.natDevice = natDevice
|
||||||
|
outbound.tunDevice = tunDevice
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +183,27 @@ func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn,
|
|||||||
return NewPacketConnection(ctx, w, conn, metadata)
|
return NewPacketConnection(ctx, w, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *WireGuard) NewIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) (tun.DirectDestination, error) {
|
||||||
|
if w.natDevice == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
session := tun.RouteSession{
|
||||||
|
IPVersion: metadata.IPVersion,
|
||||||
|
Network: tun.NetworkFromName(metadata.Network),
|
||||||
|
Source: metadata.Source.AddrPort(),
|
||||||
|
Destination: metadata.Destination.AddrPort(),
|
||||||
|
}
|
||||||
|
switch session.Network {
|
||||||
|
case syscall.IPPROTO_TCP:
|
||||||
|
w.logger.InfoContext(ctx, "linked connection to ", metadata.Destination)
|
||||||
|
case syscall.IPPROTO_UDP:
|
||||||
|
w.logger.InfoContext(ctx, "linked packet connection to ", metadata.Destination)
|
||||||
|
default:
|
||||||
|
w.logger.InfoContext(ctx, "linked ", metadata.Network, " connection to ", metadata.Destination.AddrString())
|
||||||
|
}
|
||||||
|
return w.natDevice.CreateDestination(session, conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *WireGuard) Start() error {
|
func (w *WireGuard) Start() error {
|
||||||
return w.tunDevice.Start()
|
return w.tunDevice.Start()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"listen": "::",
|
"listen": "::",
|
||||||
"listen_port": 8080,
|
"listen_port": 8080,
|
||||||
"sniff": true,
|
"sniff": true,
|
||||||
|
"network": "tcp",
|
||||||
"method": "2022-blake3-aes-128-gcm",
|
"method": "2022-blake3-aes-128-gcm",
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
mkdir -p /var/lib/sing-box
|
|
||||||
@@ -14,7 +14,6 @@ go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguar
|
|||||||
popd
|
popd
|
||||||
|
|
||||||
sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/
|
sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/
|
||||||
sudo mkdir -p /var/lib/sing-box
|
|
||||||
sudo mkdir -p /usr/local/etc/sing-box
|
sudo mkdir -p /usr/local/etc/sing-box
|
||||||
sudo cp $PROJECT/release/config/config.json /usr/local/etc/sing-box/config.json
|
sudo cp $PROJECT/release/config/config.json /usr/local/etc/sing-box/config.json
|
||||||
sudo cp $DIR/sing-box.service /etc/systemd/system
|
sudo cp $DIR/sing-box.service /etc/systemd/system
|
||||||
|
|||||||
278
route/router.go
278
route/router.go
@@ -2,14 +2,11 @@ package route
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -37,7 +34,6 @@ import (
|
|||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
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/rw"
|
|
||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,6 +68,7 @@ type Router struct {
|
|||||||
outbounds []adapter.Outbound
|
outbounds []adapter.Outbound
|
||||||
outboundByTag map[string]adapter.Outbound
|
outboundByTag map[string]adapter.Outbound
|
||||||
rules []adapter.Rule
|
rules []adapter.Rule
|
||||||
|
ipRules []adapter.IPRule
|
||||||
defaultDetour string
|
defaultDetour string
|
||||||
defaultOutboundForConnection adapter.Outbound
|
defaultOutboundForConnection adapter.Outbound
|
||||||
defaultOutboundForPacketConnection adapter.Outbound
|
defaultOutboundForPacketConnection adapter.Outbound
|
||||||
@@ -128,6 +125,7 @@ func NewRouter(
|
|||||||
dnsLogger: logFactory.NewLogger("dns"),
|
dnsLogger: logFactory.NewLogger("dns"),
|
||||||
outboundByTag: make(map[string]adapter.Outbound),
|
outboundByTag: make(map[string]adapter.Outbound),
|
||||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||||
|
ipRules: make([]adapter.IPRule, 0, len(options.IPRules)),
|
||||||
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
dnsRules: make([]adapter.DNSRule, 0, len(dnsOptions.Rules)),
|
||||||
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
needGeoIPDatabase: hasRule(options.Rules, isGeoIPRule) || hasDNSRule(dnsOptions.Rules, isGeoIPDNSRule),
|
||||||
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
needGeositeDatabase: hasRule(options.Rules, isGeositeRule) || hasDNSRule(dnsOptions.Rules, isGeositeDNSRule),
|
||||||
@@ -149,6 +147,13 @@ func NewRouter(
|
|||||||
}
|
}
|
||||||
router.rules = append(router.rules, routeRule)
|
router.rules = append(router.rules, routeRule)
|
||||||
}
|
}
|
||||||
|
for i, ipRuleOptions := range options.IPRules {
|
||||||
|
ipRule, err := NewIPRule(router, router.logger, ipRuleOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse ip rule[", i, "]")
|
||||||
|
}
|
||||||
|
router.ipRules = append(router.ipRules, ipRule)
|
||||||
|
}
|
||||||
for i, dnsRuleOptions := range dnsOptions.Rules {
|
for i, dnsRuleOptions := range dnsOptions.Rules {
|
||||||
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
dnsRule, err := NewDNSRule(router, router.logger, dnsRuleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -156,6 +161,7 @@ func NewRouter(
|
|||||||
}
|
}
|
||||||
router.dnsRules = append(router.dnsRules, dnsRule)
|
router.dnsRules = append(router.dnsRules, dnsRule)
|
||||||
}
|
}
|
||||||
|
|
||||||
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
transports := make([]dns.Transport, len(dnsOptions.Servers))
|
||||||
dummyTransportMap := make(map[string]dns.Transport)
|
dummyTransportMap := make(map[string]dns.Transport)
|
||||||
transportMap := make(map[string]dns.Transport)
|
transportMap := make(map[string]dns.Transport)
|
||||||
@@ -169,6 +175,9 @@ func NewRouter(
|
|||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
|
if transportTagMap[tag] {
|
||||||
|
return nil, E.New("duplicate dns server tag: ", tag)
|
||||||
|
}
|
||||||
transportTags[i] = tag
|
transportTags[i] = tag
|
||||||
transportTagMap[tag] = true
|
transportTagMap[tag] = true
|
||||||
}
|
}
|
||||||
@@ -241,6 +250,9 @@ func NewRouter(
|
|||||||
}), func(index int, server option.DNSServerOptions) string {
|
}), func(index int, server option.DNSServerOptions) string {
|
||||||
return transportTags[index]
|
return transportTags[index]
|
||||||
})
|
})
|
||||||
|
if len(unresolvedTags) == 0 {
|
||||||
|
panic(F.ToString("unexpected unresolved dns servers: ", len(transports), " ", len(dummyTransportMap), " ", len(transportMap)))
|
||||||
|
}
|
||||||
return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " "))
|
return nil, E.New("found circular reference in dns servers: ", strings.Join(unresolvedTags, " "))
|
||||||
}
|
}
|
||||||
var defaultTransport dns.Transport
|
var defaultTransport dns.Transport
|
||||||
@@ -510,27 +522,6 @@ func (r *Router) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) GeoIPReader() *geoip.Reader {
|
|
||||||
return r.geoIPReader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
|
||||||
rule, cached := r.geositeCache[code]
|
|
||||||
if cached {
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
items, err := r.geositeReader.Read(code)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.geositeCache[code] = rule
|
|
||||||
return rule, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||||
outbound, loaded := r.outboundByTag[tag]
|
outbound, loaded := r.outboundByTag[tag]
|
||||||
return outbound, loaded
|
return outbound, loaded
|
||||||
@@ -805,6 +796,10 @@ func (r *Router) Rules() []adapter.Rule {
|
|||||||
return r.rules
|
return r.rules
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) IPRules() []adapter.IPRule {
|
||||||
|
return r.ipRules
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
func (r *Router) NetworkMonitor() tun.NetworkUpdateMonitor {
|
||||||
return r.networkMonitor
|
return r.networkMonitor
|
||||||
}
|
}
|
||||||
@@ -840,239 +835,6 @@ func (r *Router) SetV2RayServer(server adapter.V2RayServer) {
|
|||||||
r.v2rayServer = server
|
r.v2rayServer = server
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
for _, subRule := range rule.LogicalOptions.Rules {
|
|
||||||
if cond(subRule) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.Geosite) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessRule(rule option.DefaultRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
|
||||||
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func notPrivateNode(code string) bool {
|
|
||||||
return code != "private"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeoIPDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geoIPOptions.Path != "" {
|
|
||||||
geoPath = r.geoIPOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geoip.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geoip database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeoIPDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geoip database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geoip.Open(geoPath)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open geoip database")
|
|
||||||
}
|
|
||||||
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
|
||||||
r.geoIPReader = geoReader
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) prepareGeositeDatabase() error {
|
|
||||||
var geoPath string
|
|
||||||
if r.geositeOptions.Path != "" {
|
|
||||||
geoPath = r.geositeOptions.Path
|
|
||||||
} else {
|
|
||||||
geoPath = "geosite.db"
|
|
||||||
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
|
||||||
geoPath = foundPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoPath = C.BasePath(geoPath)
|
|
||||||
if !rw.FileExists(geoPath) {
|
|
||||||
r.logger.Warn("geosite database not exists: ", geoPath)
|
|
||||||
var err error
|
|
||||||
for attempts := 0; attempts < 3; attempts++ {
|
|
||||||
err = r.downloadGeositeDatabase(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r.logger.Error("download geosite database: ", err)
|
|
||||||
os.Remove(geoPath)
|
|
||||||
// time.Sleep(10 * time.Second)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geoReader, codes, err := geosite.Open(geoPath)
|
|
||||||
if err == nil {
|
|
||||||
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
|
||||||
r.geositeReader = geoReader
|
|
||||||
} else {
|
|
||||||
return E.Cause(err, "open geosite database")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geoIPOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geoIPOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geoip database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geoIPOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
|
||||||
var downloadURL string
|
|
||||||
if r.geositeOptions.DownloadURL != "" {
|
|
||||||
downloadURL = r.geositeOptions.DownloadURL
|
|
||||||
} else {
|
|
||||||
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
|
||||||
}
|
|
||||||
r.logger.Info("downloading geosite database")
|
|
||||||
var detour adapter.Outbound
|
|
||||||
if r.geositeOptions.DownloadDetour != "" {
|
|
||||||
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
|
||||||
if !loaded {
|
|
||||||
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
|
||||||
}
|
|
||||||
detour = outbound
|
|
||||||
} else {
|
|
||||||
detour = r.defaultOutboundForConnection
|
|
||||||
}
|
|
||||||
|
|
||||||
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
|
||||||
os.MkdirAll(parentDir, 0o755)
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "open output file: ", downloadURL)
|
|
||||||
}
|
|
||||||
defer saveFile.Close()
|
|
||||||
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: 5 * time.Second,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
defer httpClient.CloseIdleConnections()
|
|
||||||
response, err := httpClient.Get(downloadURL)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
_, err = io.Copy(saveFile, response.Body)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
func (r *Router) OnPackagesUpdated(packages int, sharedUsers int) {
|
||||||
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
r.logger.Info("updated packages list: ", packages, " packages, ", sharedUsers, " shared users")
|
||||||
}
|
}
|
||||||
|
|||||||
283
route/router_geo_resources.go
Normal file
283
route/router_geo_resources.go
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
|
"github.com/sagernet/sing-box/common/geosite"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Router) GeoIPReader() *geoip.Reader {
|
||||||
|
return r.geoIPReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) LoadGeosite(code string) (adapter.Rule, error) {
|
||||||
|
rule, cached := r.geositeCache[code]
|
||||||
|
if cached {
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
items, err := r.geositeReader.Read(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rule, err = NewDefaultRule(r, nil, geosite.Compile(items))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.geositeCache[code] = rule
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeoIPDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geoIPOptions.Path != "" {
|
||||||
|
geoPath = r.geoIPOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geoip.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if rw.FileExists(geoPath) {
|
||||||
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
||||||
|
r.geoIPReader = geoReader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geoip database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeoIPDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geoip database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geoip.Open(geoPath)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open geoip database")
|
||||||
|
}
|
||||||
|
r.logger.Info("loaded geoip database: ", len(codes), " codes")
|
||||||
|
r.geoIPReader = geoReader
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) prepareGeositeDatabase() error {
|
||||||
|
var geoPath string
|
||||||
|
if r.geositeOptions.Path != "" {
|
||||||
|
geoPath = r.geositeOptions.Path
|
||||||
|
} else {
|
||||||
|
geoPath = "geosite.db"
|
||||||
|
if foundPath, loaded := C.FindPath(geoPath); loaded {
|
||||||
|
geoPath = foundPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoPath = C.BasePath(geoPath)
|
||||||
|
if !rw.FileExists(geoPath) {
|
||||||
|
r.logger.Warn("geosite database not exists: ", geoPath)
|
||||||
|
var err error
|
||||||
|
for attempts := 0; attempts < 3; attempts++ {
|
||||||
|
err = r.downloadGeositeDatabase(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.logger.Error("download geosite database: ", err)
|
||||||
|
os.Remove(geoPath)
|
||||||
|
// time.Sleep(10 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
geoReader, codes, err := geosite.Open(geoPath)
|
||||||
|
if err == nil {
|
||||||
|
r.logger.Info("loaded geosite database: ", len(codes), " codes")
|
||||||
|
r.geositeReader = geoReader
|
||||||
|
} else {
|
||||||
|
return E.Cause(err, "open geosite database")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeoIPDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geoIPOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geoIPOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geoip database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geoIPOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geoIPOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geoIPOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) downloadGeositeDatabase(savePath string) error {
|
||||||
|
var downloadURL string
|
||||||
|
if r.geositeOptions.DownloadURL != "" {
|
||||||
|
downloadURL = r.geositeOptions.DownloadURL
|
||||||
|
} else {
|
||||||
|
downloadURL = "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db"
|
||||||
|
}
|
||||||
|
r.logger.Info("downloading geosite database")
|
||||||
|
var detour adapter.Outbound
|
||||||
|
if r.geositeOptions.DownloadDetour != "" {
|
||||||
|
outbound, loaded := r.Outbound(r.geositeOptions.DownloadDetour)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("detour outbound not found: ", r.geositeOptions.DownloadDetour)
|
||||||
|
}
|
||||||
|
detour = outbound
|
||||||
|
} else {
|
||||||
|
detour = r.defaultOutboundForConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentDir := filepath.Dir(savePath); parentDir != "" {
|
||||||
|
os.MkdirAll(parentDir, 0o755)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFile, err := os.OpenFile(savePath, os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "open output file: ", downloadURL)
|
||||||
|
}
|
||||||
|
defer saveFile.Close()
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer httpClient.CloseIdleConnections()
|
||||||
|
response, err := httpClient.Get(downloadURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
_, err = io.Copy(saveFile, response.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasRule(rules []option.Rule, cond func(rule option.DefaultRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasDNSRule(rules []option.DNSRule, cond func(rule option.DefaultDNSRule) bool) bool {
|
||||||
|
for _, rule := range rules {
|
||||||
|
switch rule.Type {
|
||||||
|
case C.RuleTypeDefault:
|
||||||
|
if cond(rule.DefaultOptions) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
for _, subRule := range rule.LogicalOptions.Rules {
|
||||||
|
if cond(subRule) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode) || len(rule.GeoIP) > 0 && common.Any(rule.GeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeoIPDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.SourceGeoIP) > 0 && common.Any(rule.SourceGeoIP, notPrivateNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGeositeDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.Geosite) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessRule(rule option.DefaultRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func isProcessDNSRule(rule option.DefaultDNSRule) bool {
|
||||||
|
return len(rule.ProcessName) > 0 || len(rule.ProcessPath) > 0 || len(rule.PackageName) > 0 || len(rule.User) > 0 || len(rule.UserID) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func notPrivateNode(code string) bool {
|
||||||
|
return code != "private"
|
||||||
|
}
|
||||||
47
route/router_ip.go
Normal file
47
route/router_ip.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *Router) RouteIPConnection(ctx context.Context, conn tun.RouteContext, metadata adapter.InboundContext) tun.RouteAction {
|
||||||
|
for i, rule := range r.ipRules {
|
||||||
|
if rule.Match(&metadata) {
|
||||||
|
if rule.Action() == tun.ActionTypeReject {
|
||||||
|
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => reject")
|
||||||
|
return (*tun.ActionReject)(nil)
|
||||||
|
}
|
||||||
|
detour := rule.Outbound()
|
||||||
|
r.logger.InfoContext(ctx, "match[", i, "] ", rule.String(), " => ", detour)
|
||||||
|
outbound, loaded := r.Outbound(detour)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "outbound not found: ", detour)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ipOutbound, loaded := outbound.(adapter.IPOutbound)
|
||||||
|
if !loaded {
|
||||||
|
r.logger.ErrorContext(ctx, "outbound have no ip connection support: ", detour)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
destination, err := ipOutbound.NewIPConnection(ctx, conn, metadata)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return &tun.ActionDirect{DirectDestination: destination}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*tun.ActionReturn)(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) NatRequired(outbound string) bool {
|
||||||
|
for _, ipRule := range r.ipRules {
|
||||||
|
if ipRule.Outbound() == outbound {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
199
route/rule_abstract.go
Normal file
199
route/rule_abstract.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
type abstractDefaultRule struct {
|
||||||
|
items []RuleItem
|
||||||
|
sourceAddressItems []RuleItem
|
||||||
|
sourcePortItems []RuleItem
|
||||||
|
destinationAddressItems []RuleItem
|
||||||
|
destinationPortItems []RuleItem
|
||||||
|
allItems []RuleItem
|
||||||
|
invert bool
|
||||||
|
outbound string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Type() string {
|
||||||
|
return C.RuleTypeDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Start() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
err := common.Start(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Close() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
err := common.Close(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) UpdateGeosite() error {
|
||||||
|
for _, item := range r.allItems {
|
||||||
|
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
||||||
|
err := geositeItem.Update()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
for _, item := range r.items {
|
||||||
|
if !item.Match(metadata) {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.sourceAddressItems) > 0 {
|
||||||
|
var sourceAddressMatch bool
|
||||||
|
for _, item := range r.sourceAddressItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
sourceAddressMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sourceAddressMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.sourcePortItems) > 0 {
|
||||||
|
var sourcePortMatch bool
|
||||||
|
for _, item := range r.sourcePortItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
sourcePortMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !sourcePortMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.destinationAddressItems) > 0 {
|
||||||
|
var destinationAddressMatch bool
|
||||||
|
for _, item := range r.destinationAddressItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
destinationAddressMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !destinationAddressMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(r.destinationPortItems) > 0 {
|
||||||
|
var destinationPortMatch bool
|
||||||
|
for _, item := range r.destinationPortItems {
|
||||||
|
if item.Match(metadata) {
|
||||||
|
destinationPortMatch = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !destinationPortMatch {
|
||||||
|
return r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !r.invert
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) Outbound() string {
|
||||||
|
return r.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractDefaultRule) String() string {
|
||||||
|
return strings.Join(F.MapToString(r.allItems), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type abstractLogicalRule struct {
|
||||||
|
rules []adapter.Rule
|
||||||
|
mode string
|
||||||
|
invert bool
|
||||||
|
outbound string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Type() string {
|
||||||
|
return C.RuleTypeLogical
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) UpdateGeosite() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.UpdateGeosite()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Start() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.Start()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Close() error {
|
||||||
|
for _, rule := range r.rules {
|
||||||
|
err := rule.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
if r.mode == C.LogicalTypeAnd {
|
||||||
|
return common.All(r.rules, func(it adapter.Rule) bool {
|
||||||
|
return it.Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
} else {
|
||||||
|
return common.Any(r.rules, func(it adapter.Rule) bool {
|
||||||
|
return it.Match(metadata)
|
||||||
|
}) != r.invert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) Outbound() string {
|
||||||
|
return r.outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *abstractLogicalRule) String() string {
|
||||||
|
var op string
|
||||||
|
switch r.mode {
|
||||||
|
case C.LogicalTypeAnd:
|
||||||
|
op = "&&"
|
||||||
|
case C.LogicalTypeOr:
|
||||||
|
op = "||"
|
||||||
|
}
|
||||||
|
if !r.invert {
|
||||||
|
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
||||||
|
} else {
|
||||||
|
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
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/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) {
|
func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rule) (adapter.Rule, error) {
|
||||||
@@ -39,14 +34,7 @@ func NewRule(router adapter.Router, logger log.ContextLogger, options option.Rul
|
|||||||
var _ adapter.Rule = (*DefaultRule)(nil)
|
var _ adapter.Rule = (*DefaultRule)(nil)
|
||||||
|
|
||||||
type DefaultRule struct {
|
type DefaultRule struct {
|
||||||
items []RuleItem
|
abstractDefaultRule
|
||||||
sourceAddressItems []RuleItem
|
|
||||||
sourcePortItems []RuleItem
|
|
||||||
destinationAddressItems []RuleItem
|
|
||||||
destinationPortItems []RuleItem
|
|
||||||
allItems []RuleItem
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleItem interface {
|
type RuleItem interface {
|
||||||
@@ -56,8 +44,10 @@ type RuleItem interface {
|
|||||||
|
|
||||||
func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
|
func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options option.DefaultRule) (*DefaultRule, error) {
|
||||||
rule := &DefaultRule{
|
rule := &DefaultRule{
|
||||||
invert: options.Invert,
|
abstractDefaultRule{
|
||||||
outbound: options.Outbound,
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if len(options.Inbound) > 0 {
|
if len(options.Inbound) > 0 {
|
||||||
item := NewInboundRule(options.Inbound)
|
item := NewInboundRule(options.Inbound)
|
||||||
@@ -74,15 +64,10 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
return nil, E.New("invalid ip version: ", options.IPVersion)
|
return nil, E.New("invalid ip version: ", options.IPVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Network != "" {
|
if len(options.Network) > 0 {
|
||||||
switch options.Network {
|
item := NewNetworkItem(options.Network)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
rule.items = append(rule.items, item)
|
||||||
item := NewNetworkItem(options.Network)
|
rule.allItems = append(rule.allItems, item)
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
default:
|
|
||||||
return nil, E.New("invalid network: ", options.Network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(options.AuthUser) > 0 {
|
if len(options.AuthUser) > 0 {
|
||||||
item := NewAuthUserItem(options.AuthUser)
|
item := NewAuthUserItem(options.AuthUser)
|
||||||
@@ -202,130 +187,19 @@ func NewDefaultRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultRule) Type() string {
|
|
||||||
return C.RuleTypeDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Start() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Start(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Close() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Close(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) UpdateGeosite() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
|
||||||
err := geositeItem.Update()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
for _, item := range r.items {
|
|
||||||
if !item.Match(metadata) {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourceAddressItems) > 0 {
|
|
||||||
var sourceAddressMatch bool
|
|
||||||
for _, item := range r.sourceAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourceAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourceAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourcePortItems) > 0 {
|
|
||||||
var sourcePortMatch bool
|
|
||||||
for _, item := range r.sourcePortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourcePortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourcePortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 {
|
|
||||||
var destinationAddressMatch bool
|
|
||||||
for _, item := range r.destinationAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 {
|
|
||||||
var destinationPortMatch bool
|
|
||||||
for _, item := range r.destinationPortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationPortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationPortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !r.invert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultRule) String() string {
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.allItems), " ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.allItems), " ") + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.Rule = (*LogicalRule)(nil)
|
var _ adapter.Rule = (*LogicalRule)(nil)
|
||||||
|
|
||||||
type LogicalRule struct {
|
type LogicalRule struct {
|
||||||
mode string
|
abstractLogicalRule
|
||||||
rules []*DefaultRule
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options option.LogicalRule) (*LogicalRule, error) {
|
||||||
r := &LogicalRule{
|
r := &LogicalRule{
|
||||||
rules: make([]*DefaultRule, len(options.Rules)),
|
abstractLogicalRule{
|
||||||
invert: options.Invert,
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
outbound: options.Outbound,
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
switch options.Mode {
|
switch options.Mode {
|
||||||
case C.LogicalTypeAnd:
|
case C.LogicalTypeAnd:
|
||||||
@@ -344,68 +218,3 @@ func NewLogicalRule(router adapter.Router, logger log.ContextLogger, options opt
|
|||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalRule) Type() string {
|
|
||||||
return C.RuleTypeLogical
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) UpdateGeosite() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.UpdateGeosite()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Start() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Close() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
if r.mode == C.LogicalTypeAnd {
|
|
||||||
return common.All(r.rules, func(it *DefaultRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
} else {
|
|
||||||
return common.Any(r.rules, func(it *DefaultRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalRule) String() string {
|
|
||||||
var op string
|
|
||||||
switch r.mode {
|
|
||||||
case C.LogicalTypeAnd:
|
|
||||||
op = "&&"
|
|
||||||
case C.LogicalTypeOr:
|
|
||||||
op = "||"
|
|
||||||
}
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
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/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) {
|
func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.DNSRule) (adapter.DNSRule, error) {
|
||||||
@@ -39,21 +34,16 @@ func NewDNSRule(router adapter.Router, logger log.ContextLogger, options option.
|
|||||||
var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
|
var _ adapter.DNSRule = (*DefaultDNSRule)(nil)
|
||||||
|
|
||||||
type DefaultDNSRule struct {
|
type DefaultDNSRule struct {
|
||||||
items []RuleItem
|
abstractDefaultRule
|
||||||
sourceAddressItems []RuleItem
|
disableCache bool
|
||||||
sourcePortItems []RuleItem
|
|
||||||
destinationAddressItems []RuleItem
|
|
||||||
destinationPortItems []RuleItem
|
|
||||||
allItems []RuleItem
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
disableCache bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options option.DefaultDNSRule) (*DefaultDNSRule, error) {
|
||||||
rule := &DefaultDNSRule{
|
rule := &DefaultDNSRule{
|
||||||
invert: options.Invert,
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
outbound: options.Server,
|
invert: options.Invert,
|
||||||
|
outbound: options.Server,
|
||||||
|
},
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
}
|
}
|
||||||
if len(options.Inbound) > 0 {
|
if len(options.Inbound) > 0 {
|
||||||
@@ -76,15 +66,10 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
|||||||
rule.items = append(rule.items, item)
|
rule.items = append(rule.items, item)
|
||||||
rule.allItems = append(rule.allItems, item)
|
rule.allItems = append(rule.allItems, item)
|
||||||
}
|
}
|
||||||
if options.Network != "" {
|
if len(options.Network) > 0 {
|
||||||
switch options.Network {
|
item := NewNetworkItem(options.Network)
|
||||||
case N.NetworkTCP, N.NetworkUDP:
|
rule.items = append(rule.items, item)
|
||||||
item := NewNetworkItem(options.Network)
|
rule.allItems = append(rule.allItems, item)
|
||||||
rule.items = append(rule.items, item)
|
|
||||||
rule.allItems = append(rule.allItems, item)
|
|
||||||
default:
|
|
||||||
return nil, E.New("invalid network: ", options.Network)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(options.AuthUser) > 0 {
|
if len(options.AuthUser) > 0 {
|
||||||
item := NewAuthUserItem(options.AuthUser)
|
item := NewAuthUserItem(options.AuthUser)
|
||||||
@@ -196,131 +181,24 @@ func NewDefaultDNSRule(router adapter.Router, logger log.ContextLogger, options
|
|||||||
return rule, nil
|
return rule, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Type() string {
|
|
||||||
return C.RuleTypeDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Start() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Start(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Close() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
err := common.Close(item)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) UpdateGeosite() error {
|
|
||||||
for _, item := range r.allItems {
|
|
||||||
if geositeItem, isSite := item.(*GeositeItem); isSite {
|
|
||||||
err := geositeItem.Update()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
for _, item := range r.items {
|
|
||||||
if !item.Match(metadata) {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourceAddressItems) > 0 {
|
|
||||||
var sourceAddressMatch bool
|
|
||||||
for _, item := range r.sourceAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourceAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourceAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.sourcePortItems) > 0 {
|
|
||||||
var sourcePortMatch bool
|
|
||||||
for _, item := range r.sourcePortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
sourcePortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !sourcePortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationAddressItems) > 0 {
|
|
||||||
var destinationAddressMatch bool
|
|
||||||
for _, item := range r.destinationAddressItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationAddressMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationAddressMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.destinationPortItems) > 0 {
|
|
||||||
var destinationPortMatch bool
|
|
||||||
for _, item := range r.destinationPortItems {
|
|
||||||
if item.Match(metadata) {
|
|
||||||
destinationPortMatch = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !destinationPortMatch {
|
|
||||||
return r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !r.invert
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *DefaultDNSRule) DisableCache() bool {
|
func (r *DefaultDNSRule) DisableCache() bool {
|
||||||
return r.disableCache
|
return r.disableCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefaultDNSRule) String() string {
|
|
||||||
return strings.Join(F.MapToString(r.allItems), " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
var _ adapter.DNSRule = (*LogicalDNSRule)(nil)
|
||||||
|
|
||||||
type LogicalDNSRule struct {
|
type LogicalDNSRule struct {
|
||||||
mode string
|
abstractLogicalRule
|
||||||
rules []*DefaultDNSRule
|
|
||||||
invert bool
|
|
||||||
outbound string
|
|
||||||
disableCache bool
|
disableCache bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options option.LogicalDNSRule) (*LogicalDNSRule, error) {
|
||||||
r := &LogicalDNSRule{
|
r := &LogicalDNSRule{
|
||||||
rules: make([]*DefaultDNSRule, len(options.Rules)),
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
invert: options.Invert,
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
outbound: options.Server,
|
invert: options.Invert,
|
||||||
|
outbound: options.Server,
|
||||||
|
},
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
}
|
}
|
||||||
switch options.Mode {
|
switch options.Mode {
|
||||||
@@ -341,71 +219,6 @@ func NewLogicalDNSRule(router adapter.Router, logger log.ContextLogger, options
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Type() string {
|
|
||||||
return C.RuleTypeLogical
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) UpdateGeosite() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.UpdateGeosite()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Start() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Start()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Close() error {
|
|
||||||
for _, rule := range r.rules {
|
|
||||||
err := rule.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
if r.mode == C.LogicalTypeAnd {
|
|
||||||
return common.All(r.rules, func(it *DefaultDNSRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
} else {
|
|
||||||
return common.Any(r.rules, func(it *DefaultDNSRule) bool {
|
|
||||||
return it.Match(metadata)
|
|
||||||
}) != r.invert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) Outbound() string {
|
|
||||||
return r.outbound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *LogicalDNSRule) DisableCache() bool {
|
func (r *LogicalDNSRule) DisableCache() bool {
|
||||||
return r.disableCache
|
return r.disableCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LogicalDNSRule) String() string {
|
|
||||||
var op string
|
|
||||||
switch r.mode {
|
|
||||||
case C.LogicalTypeAnd:
|
|
||||||
op = "&&"
|
|
||||||
case C.LogicalTypeOr:
|
|
||||||
op = "||"
|
|
||||||
}
|
|
||||||
if !r.invert {
|
|
||||||
return strings.Join(F.MapToString(r.rules), " "+op+" ")
|
|
||||||
} else {
|
|
||||||
return "!(" + strings.Join(F.MapToString(r.rules), " "+op+" ") + ")"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
176
route/rule_ip.go
Normal file
176
route/rule_ip.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
tun "github.com/sagernet/sing-tun"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewIPRule(router adapter.Router, logger log.ContextLogger, options option.IPRule) (adapter.IPRule, error) {
|
||||||
|
switch options.Type {
|
||||||
|
case "", C.RuleTypeDefault:
|
||||||
|
if !options.DefaultOptions.IsValid() {
|
||||||
|
return nil, E.New("missing conditions")
|
||||||
|
}
|
||||||
|
if common.IsEmpty(options.DefaultOptions.Action) {
|
||||||
|
return nil, E.New("missing action")
|
||||||
|
}
|
||||||
|
return NewDefaultIPRule(router, logger, options.DefaultOptions)
|
||||||
|
case C.RuleTypeLogical:
|
||||||
|
if !options.LogicalOptions.IsValid() {
|
||||||
|
return nil, E.New("missing conditions")
|
||||||
|
}
|
||||||
|
if common.IsEmpty(options.DefaultOptions.Action) {
|
||||||
|
return nil, E.New("missing action")
|
||||||
|
}
|
||||||
|
return NewLogicalIPRule(router, logger, options.LogicalOptions)
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown rule type: ", options.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.IPRule = (*DefaultIPRule)(nil)
|
||||||
|
|
||||||
|
type DefaultIPRule struct {
|
||||||
|
abstractDefaultRule
|
||||||
|
action tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultIPRule(router adapter.Router, logger log.ContextLogger, options option.DefaultIPRule) (*DefaultIPRule, error) {
|
||||||
|
rule := &DefaultIPRule{
|
||||||
|
abstractDefaultRule: abstractDefaultRule{
|
||||||
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
|
action: tun.ActionType(options.Action),
|
||||||
|
}
|
||||||
|
if len(options.Inbound) > 0 {
|
||||||
|
item := NewInboundRule(options.Inbound)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if options.IPVersion > 0 {
|
||||||
|
switch options.IPVersion {
|
||||||
|
case 4, 6:
|
||||||
|
item := NewIPVersionItem(options.IPVersion == 6)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
default:
|
||||||
|
return nil, E.New("invalid ip version: ", options.IPVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(options.Network) > 0 {
|
||||||
|
item := NewNetworkItem(options.Network)
|
||||||
|
rule.items = append(rule.items, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Domain) > 0 || len(options.DomainSuffix) > 0 {
|
||||||
|
item := NewDomainItem(options.Domain, options.DomainSuffix)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DomainKeyword) > 0 {
|
||||||
|
item := NewDomainKeywordItem(options.DomainKeyword)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.DomainRegex) > 0 {
|
||||||
|
item, err := NewDomainRegexItem(options.DomainRegex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "domain_regex")
|
||||||
|
}
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Geosite) > 0 {
|
||||||
|
item := NewGeositeItem(router, logger, options.Geosite)
|
||||||
|
rule.destinationAddressItems = append(rule.destinationAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourceGeoIP) > 0 {
|
||||||
|
item := NewGeoIPItem(router, logger, true, options.SourceGeoIP)
|
||||||
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourceIPCIDR) > 0 {
|
||||||
|
item, err := NewIPCIDRItem(true, options.SourceIPCIDR)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "source_ipcidr")
|
||||||
|
}
|
||||||
|
rule.sourceAddressItems = append(rule.sourceAddressItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourcePort) > 0 {
|
||||||
|
item := NewPortItem(true, options.SourcePort)
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.SourcePortRange) > 0 {
|
||||||
|
item, err := NewPortRangeItem(true, options.SourcePortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "source_port_range")
|
||||||
|
}
|
||||||
|
rule.sourcePortItems = append(rule.sourcePortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.Port) > 0 {
|
||||||
|
item := NewPortItem(false, options.Port)
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
if len(options.PortRange) > 0 {
|
||||||
|
item, err := NewPortRangeItem(false, options.PortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "port_range")
|
||||||
|
}
|
||||||
|
rule.destinationPortItems = append(rule.destinationPortItems, item)
|
||||||
|
rule.allItems = append(rule.allItems, item)
|
||||||
|
}
|
||||||
|
return rule, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DefaultIPRule) Action() tun.ActionType {
|
||||||
|
return r.action
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.IPRule = (*LogicalIPRule)(nil)
|
||||||
|
|
||||||
|
type LogicalIPRule struct {
|
||||||
|
abstractLogicalRule
|
||||||
|
action tun.ActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogicalIPRule(router adapter.Router, logger log.ContextLogger, options option.LogicalIPRule) (*LogicalIPRule, error) {
|
||||||
|
r := &LogicalIPRule{
|
||||||
|
abstractLogicalRule: abstractLogicalRule{
|
||||||
|
rules: make([]adapter.Rule, len(options.Rules)),
|
||||||
|
invert: options.Invert,
|
||||||
|
outbound: options.Outbound,
|
||||||
|
},
|
||||||
|
action: tun.ActionType(options.Action),
|
||||||
|
}
|
||||||
|
switch options.Mode {
|
||||||
|
case C.LogicalTypeAnd:
|
||||||
|
r.mode = C.LogicalTypeAnd
|
||||||
|
case C.LogicalTypeOr:
|
||||||
|
r.mode = C.LogicalTypeOr
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown logical mode: ", options.Mode)
|
||||||
|
}
|
||||||
|
for i, subRule := range options.Rules {
|
||||||
|
rule, err := NewDefaultIPRule(router, logger, subRule)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "sub rule[", i, "]")
|
||||||
|
}
|
||||||
|
r.rules[i] = rule
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LogicalIPRule) Action() tun.ActionType {
|
||||||
|
return r.action
|
||||||
|
}
|
||||||
42
route/rule_item_network.go
Normal file
42
route/rule_item_network.go
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package route
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ RuleItem = (*NetworkItem)(nil)
|
||||||
|
|
||||||
|
type NetworkItem struct {
|
||||||
|
networks []string
|
||||||
|
networkMap map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetworkItem(networks []string) *NetworkItem {
|
||||||
|
networkMap := make(map[string]bool)
|
||||||
|
for _, network := range networks {
|
||||||
|
networkMap[network] = true
|
||||||
|
}
|
||||||
|
return &NetworkItem{
|
||||||
|
networks: networks,
|
||||||
|
networkMap: networkMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
|
||||||
|
return r.networkMap[metadata.Network]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *NetworkItem) String() string {
|
||||||
|
description := "network="
|
||||||
|
|
||||||
|
pLen := len(r.networks)
|
||||||
|
if pLen == 1 {
|
||||||
|
description += F.ToString(r.networks[0])
|
||||||
|
} else {
|
||||||
|
description += "[" + strings.Join(F.MapToString(r.networks), " ") + "]"
|
||||||
|
}
|
||||||
|
return description
|
||||||
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package route
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ RuleItem = (*NetworkItem)(nil)
|
|
||||||
|
|
||||||
type NetworkItem struct {
|
|
||||||
network string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNetworkItem(network string) *NetworkItem {
|
|
||||||
return &NetworkItem{network}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool {
|
|
||||||
return r.network == metadata.Network
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *NetworkItem) String() string {
|
|
||||||
return "network=" + r.network
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,7 @@ func TestProxyProtocol(t *testing.T) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -25,7 +25,7 @@ func TestProxyProtocol(t *testing.T) {
|
|||||||
Type: C.TypeDirect,
|
Type: C.TypeDirect,
|
||||||
DirectOptions: option.DirectInboundOptions{
|
DirectOptions: option.DirectInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
ProxyProtocol: true,
|
ProxyProtocol: true,
|
||||||
},
|
},
|
||||||
|
|||||||
12
test/go.mod
12
test/go.mod
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/docker/docker v20.10.18+incompatible
|
github.com/docker/docker v20.10.18+incompatible
|
||||||
github.com/docker/go-connections v0.4.0
|
github.com/docker/go-connections v0.4.0
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/sagernet/sing v0.1.9-0.20230313033500-448948d26d1a
|
github.com/sagernet/sing v0.2.1-0.20230318094614-4bbf5f2c3046
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9
|
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9
|
||||||
github.com/spyzhov/ajson v0.7.1
|
github.com/spyzhov/ajson v0.7.1
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
@@ -30,6 +30,7 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
github.com/docker/go-units v0.4.0 // indirect
|
github.com/docker/go-units v0.4.0 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
||||||
github.com/go-chi/cors v1.2.1 // indirect
|
github.com/go-chi/cors v1.2.1 // indirect
|
||||||
@@ -52,11 +53,12 @@ require (
|
|||||||
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
|
github.com/moby/term v0.0.0-20221105221325-4eb28fa6025c // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||||
|
github.com/ooni/go-libtor v1.1.7 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
github.com/oschwald/maxminddb-golang v1.10.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
github.com/pires/go-proxyproto v0.7.0 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
@@ -70,7 +72,7 @@ require (
|
|||||||
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 // indirect
|
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 // indirect
|
||||||
github.com/sagernet/sing-dns v0.1.4 // indirect
|
github.com/sagernet/sing-dns v0.1.4 // indirect
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0 // indirect
|
github.com/sagernet/sing-shadowtls v0.1.0 // indirect
|
||||||
github.com/sagernet/sing-tun v0.1.3-0.20230313113643-839f1792e46c // indirect
|
github.com/sagernet/sing-tun v0.1.3-0.20230315134716-fe89bbded22d // indirect
|
||||||
github.com/sagernet/sing-vmess v0.1.3 // indirect
|
github.com/sagernet/sing-vmess v0.1.3 // indirect
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 // indirect
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect
|
github.com/sagernet/tfo-go v0.0.0-20230303015439-ffcfd8c41cf9 // indirect
|
||||||
@@ -86,7 +88,7 @@ require (
|
|||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 // indirect
|
go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35 // indirect
|
||||||
golang.org/x/crypto v0.7.0 // indirect
|
golang.org/x/crypto v0.7.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec // indirect
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 // indirect
|
||||||
golang.org/x/mod v0.8.0 // indirect
|
golang.org/x/mod v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.6.0 // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
golang.org/x/text v0.8.0 // indirect
|
golang.org/x/text v0.8.0 // indirect
|
||||||
@@ -94,7 +96,7 @@ require (
|
|||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||||
google.golang.org/grpc v1.53.0 // indirect
|
google.golang.org/grpc v1.53.0 // indirect
|
||||||
google.golang.org/protobuf v1.29.1 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gotest.tools/v3 v3.4.0 // indirect
|
gotest.tools/v3 v3.4.0 // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c // indirect
|
||||||
|
|||||||
24
test/go.sum
24
test/go.sum
@@ -31,6 +31,8 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
|
|||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||||
@@ -88,6 +90,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
|
|||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||||
|
github.com/ooni/go-libtor v1.1.7 h1:ooVcdEPBqDox5OfeXAfXIeQFCbqMLJVfIpO+Irr7N9A=
|
||||||
|
github.com/ooni/go-libtor v1.1.7/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||||
@@ -96,8 +100,8 @@ github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd918
|
|||||||
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
github.com/oschwald/maxminddb-golang v1.10.0/go.mod h1:Y2ELenReaLAZ0b400URyGwvYxHV1dLIxBuyOsyYjHK0=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||||
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs=
|
||||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -122,16 +126,16 @@ github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8 h1:4M3+0/kqvJuTsi
|
|||||||
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
github.com/sagernet/reality v0.0.0-20230312150606-35ea9af0e0b8/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||||
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.8/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing v0.1.9-0.20230313033500-448948d26d1a h1:JgPPxKLiqA95Z0oTp9FyYUfij8xsjS2rBWtpQ41zFTo=
|
github.com/sagernet/sing v0.2.1-0.20230318094614-4bbf5f2c3046 h1:/+ZWbxRvQmco9ES2qT5Eh/x/IiQRjAcUyRG/vQ4dpxc=
|
||||||
github.com/sagernet/sing v0.1.9-0.20230313033500-448948d26d1a/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
|
github.com/sagernet/sing v0.2.1-0.20230318094614-4bbf5f2c3046/go.mod h1:9uHswk2hITw8leDbiLS/xn0t9nzBcbePxzm9PJhwdlw=
|
||||||
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
|
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
|
||||||
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
|
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0=
|
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9/go.mod h1:f3mHTy5shnVM9l8UocMlJgC/1G/zdj5FuEuVXhDinGU=
|
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9/go.mod h1:f3mHTy5shnVM9l8UocMlJgC/1G/zdj5FuEuVXhDinGU=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
github.com/sagernet/sing-shadowtls v0.1.0 h1:05MYce8aR5xfKIn+y7xRFsdKhKt44QZTSEQW+lG5IWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
github.com/sagernet/sing-shadowtls v0.1.0/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
||||||
github.com/sagernet/sing-tun v0.1.3-0.20230313113643-839f1792e46c h1:k504OwD5yQHUb13tSsdRAB6GjtS2WjIygAThElAO6MY=
|
github.com/sagernet/sing-tun v0.1.3-0.20230315134716-fe89bbded22d h1:1gt4Hu2fHCrmL2NZYCNJ3nCgeczuhK09oCMni9mZmZk=
|
||||||
github.com/sagernet/sing-tun v0.1.3-0.20230313113643-839f1792e46c/go.mod h1:KnRkwaDHbb06zgeNPu0LQ8A+vA9myMxKEgHN1brCPHg=
|
github.com/sagernet/sing-tun v0.1.3-0.20230315134716-fe89bbded22d/go.mod h1:KnRkwaDHbb06zgeNPu0LQ8A+vA9myMxKEgHN1brCPHg=
|
||||||
github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
|
github.com/sagernet/sing-vmess v0.1.3 h1:q/+tsF46dvvapL6CpQBgPHJ6nQrDUZqEtLHCbsjO7iM=
|
||||||
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
|
github.com/sagernet/sing-vmess v0.1.3/go.mod h1:GVXqAHwe9U21uS+Voh4YBIrADQyE4F9v0ayGSixSQAE=
|
||||||
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
github.com/sagernet/smux v0.0.0-20230312102458-337ec2a5af37 h1:HuE6xSwco/Xed8ajZ+coeYLmioq0Qp1/Z2zczFaV8as=
|
||||||
@@ -187,8 +191,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||||
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec h1:pAv+d8BM2JNnNctsLJ6nnZ6NqXT8N4+eauvZSb3P0I0=
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||||
golang.org/x/exp v0.0.0-20230314191032-db074128a8ec/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -257,8 +261,8 @@ google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
|||||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM=
|
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||||
google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestHTTPSelf(t *testing.T) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -25,7 +25,7 @@ func TestHTTPSelf(t *testing.T) {
|
|||||||
Type: C.TypeMixed,
|
Type: C.TypeMixed,
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func TestHysteriaSelf(t *testing.T) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -26,7 +26,7 @@ func TestHysteriaSelf(t *testing.T) {
|
|||||||
Type: C.TypeHysteria,
|
Type: C.TypeHysteria,
|
||||||
HysteriaOptions: option.HysteriaInboundOptions{
|
HysteriaOptions: option.HysteriaInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
UpMbps: 100,
|
UpMbps: 100,
|
||||||
@@ -90,7 +90,7 @@ func TestHysteriaInbound(t *testing.T) {
|
|||||||
Type: C.TypeHysteria,
|
Type: C.TypeHysteria,
|
||||||
HysteriaOptions: option.HysteriaInboundOptions{
|
HysteriaOptions: option.HysteriaInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
UpMbps: 100,
|
UpMbps: 100,
|
||||||
@@ -139,7 +139,7 @@ func TestHysteriaOutbound(t *testing.T) {
|
|||||||
Type: C.TypeMixed,
|
Type: C.TypeMixed,
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestChainedInbound(t *testing.T) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -28,7 +28,7 @@ func TestChainedInbound(t *testing.T) {
|
|||||||
Type: C.TypeShadowsocks,
|
Type: C.TypeShadowsocks,
|
||||||
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
Detour: "detour",
|
Detour: "detour",
|
||||||
},
|
},
|
||||||
@@ -41,7 +41,7 @@ func TestChainedInbound(t *testing.T) {
|
|||||||
Tag: "detour",
|
Tag: "detour",
|
||||||
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: otherPort,
|
ListenPort: otherPort,
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func TestMuxCoolServer(t *testing.T) {
|
|||||||
Type: C.TypeVMess,
|
Type: C.TypeVMess,
|
||||||
VMessOptions: option.VMessInboundOptions{
|
VMessOptions: option.VMessInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Users: []option.VMessUser{
|
Users: []option.VMessUser{
|
||||||
@@ -85,7 +85,7 @@ func TestMuxCoolClient(t *testing.T) {
|
|||||||
Type: C.TypeMixed,
|
Type: C.TypeMixed,
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -117,7 +117,7 @@ func TestMuxCoolSelf(t *testing.T) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -126,7 +126,7 @@ func TestMuxCoolSelf(t *testing.T) {
|
|||||||
Type: C.TypeVMess,
|
Type: C.TypeVMess,
|
||||||
VMessOptions: option.VMessInboundOptions{
|
VMessOptions: option.VMessInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Users: []option.VMessUser{
|
Users: []option.VMessUser{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ func testShadowsocksMux(t *testing.T, protocol string) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -48,7 +48,7 @@ func testShadowsocksMux(t *testing.T, protocol string) {
|
|||||||
Type: C.TypeShadowsocks,
|
Type: C.TypeShadowsocks,
|
||||||
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
@@ -100,7 +100,7 @@ func testVMessMux(t *testing.T, protocol string) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -109,7 +109,7 @@ func testVMessMux(t *testing.T, protocol string) {
|
|||||||
Type: C.TypeVMess,
|
Type: C.TypeVMess,
|
||||||
VMessOptions: option.VMessInboundOptions{
|
VMessOptions: option.VMessInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Users: []option.VMessUser{
|
Users: []option.VMessUser{
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func TestNaiveInboundWithNginx(t *testing.T) {
|
|||||||
Type: C.TypeNaive,
|
Type: C.TypeNaive,
|
||||||
NaiveOptions: option.NaiveInboundOptions{
|
NaiveOptions: option.NaiveInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: otherPort,
|
ListenPort: otherPort,
|
||||||
},
|
},
|
||||||
Users: []auth.User{
|
Users: []auth.User{
|
||||||
@@ -64,7 +64,7 @@ func TestNaiveInbound(t *testing.T) {
|
|||||||
Type: C.TypeNaive,
|
Type: C.TypeNaive,
|
||||||
NaiveOptions: option.NaiveInboundOptions{
|
NaiveOptions: option.NaiveInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Users: []auth.User{
|
Users: []auth.User{
|
||||||
@@ -106,7 +106,7 @@ func TestNaiveHTTP3Inbound(t *testing.T) {
|
|||||||
Type: C.TypeNaive,
|
Type: C.TypeNaive,
|
||||||
NaiveOptions: option.NaiveInboundOptions{
|
NaiveOptions: option.NaiveInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Users: []auth.User{
|
Users: []auth.User{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestVLESSVisionReality(t *testing.T) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -29,7 +29,7 @@ func TestVLESSVisionReality(t *testing.T) {
|
|||||||
Type: C.TypeVLESS,
|
Type: C.TypeVLESS,
|
||||||
VLESSOptions: option.VLESSInboundOptions{
|
VLESSOptions: option.VLESSInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Users: []option.VLESSUser{
|
Users: []option.VLESSUser{
|
||||||
@@ -61,7 +61,7 @@ func TestVLESSVisionReality(t *testing.T) {
|
|||||||
Tag: "trojan",
|
Tag: "trojan",
|
||||||
TrojanOptions: option.TrojanInboundOptions{
|
TrojanOptions: option.TrojanInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: otherPort,
|
ListenPort: otherPort,
|
||||||
},
|
},
|
||||||
Users: []option.TrojanUser{
|
Users: []option.TrojanUser{
|
||||||
@@ -170,7 +170,7 @@ func testVLESSRealityTransport(t *testing.T, transport *option.V2RayTransportOpt
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -179,7 +179,7 @@ func testVLESSRealityTransport(t *testing.T, transport *option.V2RayTransportOpt
|
|||||||
Type: C.TypeVLESS,
|
Type: C.TypeVLESS,
|
||||||
VLESSOptions: option.VLESSInboundOptions{
|
VLESSOptions: option.VLESSInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Users: []option.VLESSUser{
|
Users: []option.VLESSUser{
|
||||||
@@ -211,7 +211,7 @@ func testVLESSRealityTransport(t *testing.T, transport *option.V2RayTransportOpt
|
|||||||
Tag: "trojan",
|
Tag: "trojan",
|
||||||
TrojanOptions: option.TrojanInboundOptions{
|
TrojanOptions: option.TrojanInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: otherPort,
|
ListenPort: otherPort,
|
||||||
},
|
},
|
||||||
Users: []option.TrojanUser{
|
Users: []option.TrojanUser{
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass
|
|||||||
Type: C.TypeShadowsocks,
|
Type: C.TypeShadowsocks,
|
||||||
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
@@ -108,7 +108,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas
|
|||||||
Type: C.TypeMixed,
|
Type: C.TypeMixed,
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -139,7 +139,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -148,7 +148,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) {
|
|||||||
Type: C.TypeShadowsocks,
|
Type: C.TypeShadowsocks,
|
||||||
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
@@ -197,7 +197,7 @@ func TestShadowsocksUoT(t *testing.T) {
|
|||||||
Tag: "mixed-in",
|
Tag: "mixed-in",
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -206,7 +206,7 @@ func TestShadowsocksUoT(t *testing.T) {
|
|||||||
Type: C.TypeShadowsocks,
|
Type: C.TypeShadowsocks,
|
||||||
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
ShadowsocksOptions: option.ShadowsocksInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: serverPort,
|
ListenPort: serverPort,
|
||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
@@ -228,7 +228,9 @@ func TestShadowsocksUoT(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Method: method,
|
Method: method,
|
||||||
Password: password,
|
Password: password,
|
||||||
UoT: true,
|
UDPOverTCPOptions: &option.UDPOverTCPOptions{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func TestShadowsocksR(t *testing.T) {
|
|||||||
Type: C.TypeMixed,
|
Type: C.TypeMixed,
|
||||||
MixedOptions: option.HTTPMixedInboundOptions{
|
MixedOptions: option.HTTPMixedInboundOptions{
|
||||||
ListenOptions: option.ListenOptions{
|
ListenOptions: option.ListenOptions{
|
||||||
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
Listen: option.NewListenAddress(netip.IPv4Unspecified()),
|
||||||
ListenPort: clientPort,
|
ListenPort: clientPort,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user