mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-05 08:04:15 +03:00
Compare commits
1 Commits
v1.2-beta5
...
dev-ssm-ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d34091f8fc |
6
.github/workflows/mkdocs.yml
vendored
6
.github/workflows/mkdocs.yml
vendored
@@ -14,7 +14,5 @@ jobs:
|
|||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
- run: |
|
- run: pip install mkdocs-material mkdocs-static-i18n
|
||||||
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
|
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||||
- run: |
|
|
||||||
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -6,8 +6,3 @@
|
|||||||
/bin/
|
/bin/
|
||||||
/dist/
|
/dist/
|
||||||
/sing-box
|
/sing-box
|
||||||
/build/
|
|
||||||
/*.jar
|
|
||||||
/*.aar
|
|
||||||
/*.xcframework/
|
|
||||||
.DS_Store
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ builds:
|
|||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_ssm_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
targets:
|
targets:
|
||||||
@@ -50,6 +51,7 @@ builds:
|
|||||||
- with_wireguard
|
- with_wireguard
|
||||||
- with_utls
|
- with_utls
|
||||||
- with_clash_api
|
- with_clash_api
|
||||||
|
- with_ssm_api
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
overrides:
|
overrides:
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -1,7 +1,7 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
|
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,with_ssm_api
|
||||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_shadowsocksr
|
||||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-s -w -buildid="
|
||||||
MAIN = ./cmd/sing-box
|
MAIN = ./cmd/sing-box
|
||||||
|
|
||||||
@@ -71,14 +71,6 @@ test_stdio:
|
|||||||
go mod tidy && \
|
go mod tidy && \
|
||||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||||
|
|
||||||
lib:
|
|
||||||
go run ./cmd/internal/build_libbox
|
|
||||||
|
|
||||||
lib_install:
|
|
||||||
go get -v -d
|
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20221130124640-349ebaa752ca
|
|
||||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20221130124640-349ebaa752ca
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf bin dist sing-box
|
rm -rf bin dist sing-box
|
||||||
rm -f $(shell go env GOPATH)/sing-box
|
rm -f $(shell go env GOPATH)/sing-box
|
||||||
|
|||||||
@@ -48,3 +48,16 @@ type V2RayStatsService interface {
|
|||||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SSMServer interface {
|
||||||
|
Service
|
||||||
|
RoutedConnection(metadata InboundContext, conn net.Conn) net.Conn
|
||||||
|
RoutedPacketConnection(metadata InboundContext, conn N.PacketConn) N.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagedShadowsocksServer interface {
|
||||||
|
Inbound
|
||||||
|
Method() string
|
||||||
|
Password() string
|
||||||
|
UpdateUsers(users []string, uPSKs []string) error
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
type Router interface {
|
type Router interface {
|
||||||
Service
|
Service
|
||||||
|
|
||||||
|
Inbound(tag string) (Inbound, bool)
|
||||||
Outbounds() []Outbound
|
Outbounds() []Outbound
|
||||||
Outbound(tag string) (Outbound, bool)
|
Outbound(tag string) (Outbound, bool)
|
||||||
DefaultOutbound(network string) Outbound
|
DefaultOutbound(network string) Outbound
|
||||||
@@ -34,20 +35,20 @@ type Router interface {
|
|||||||
InterfaceFinder() control.InterfaceFinder
|
InterfaceFinder() control.InterfaceFinder
|
||||||
DefaultInterface() string
|
DefaultInterface() string
|
||||||
AutoDetectInterface() bool
|
AutoDetectInterface() bool
|
||||||
AutoDetectInterfaceFunc() control.Func
|
|
||||||
DefaultMark() int
|
DefaultMark() int
|
||||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||||
PackageManager() tun.PackageManager
|
PackageManager() tun.PackageManager
|
||||||
Rules() []Rule
|
Rules() []Rule
|
||||||
|
|
||||||
TimeService
|
|
||||||
|
|
||||||
ClashServer() ClashServer
|
ClashServer() ClashServer
|
||||||
SetClashServer(server ClashServer)
|
SetClashServer(server ClashServer)
|
||||||
|
|
||||||
V2RayServer() V2RayServer
|
V2RayServer() V2RayServer
|
||||||
SetV2RayServer(server V2RayServer)
|
SetV2RayServer(server V2RayServer)
|
||||||
|
|
||||||
|
SSMServer() SSMServer
|
||||||
|
SetSSMServer(server SSMServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
type routerContextKey struct{}
|
type routerContextKey struct{}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type TimeService interface {
|
|
||||||
Service
|
|
||||||
TimeFunc() func() time.Time
|
|
||||||
}
|
|
||||||
87
box.go
87
box.go
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/inbound"
|
"github.com/sagernet/sing-box/inbound"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -33,6 +32,7 @@ type Box struct {
|
|||||||
logFile *os.File
|
logFile *os.File
|
||||||
clashServer adapter.ClashServer
|
clashServer adapter.ClashServer
|
||||||
v2rayServer adapter.V2RayServer
|
v2rayServer adapter.V2RayServer
|
||||||
|
ssmServer adapter.SSMServer
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,6 +42,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
|
|
||||||
var needClashAPI bool
|
var needClashAPI bool
|
||||||
var needV2RayAPI bool
|
var needV2RayAPI bool
|
||||||
|
var needSSMAPI bool
|
||||||
if options.Experimental != nil {
|
if options.Experimental != nil {
|
||||||
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
||||||
needClashAPI = true
|
needClashAPI = true
|
||||||
@@ -49,30 +50,27 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
|
if options.Experimental.SSMAPI != nil && options.Experimental.SSMAPI.Listen != "" {
|
||||||
|
needSSMAPI = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var logFactory log.Factory
|
var logFactory log.Factory
|
||||||
var observableLogFactory log.ObservableFactory
|
var observableLogFactory log.ObservableFactory
|
||||||
var logFile *os.File
|
var logFile *os.File
|
||||||
var logWriter io.Writer
|
|
||||||
if logOptions.Disabled {
|
if logOptions.Disabled {
|
||||||
observableLogFactory = log.NewNOPFactory()
|
observableLogFactory = log.NewNOPFactory()
|
||||||
logFactory = observableLogFactory
|
logFactory = observableLogFactory
|
||||||
} else {
|
} else {
|
||||||
|
var logWriter io.Writer
|
||||||
switch logOptions.Output {
|
switch logOptions.Output {
|
||||||
case "":
|
case "", "stderr":
|
||||||
if options.PlatformInterface != nil {
|
|
||||||
logWriter = io.Discard
|
|
||||||
} else {
|
|
||||||
logWriter = os.Stdout
|
|
||||||
}
|
|
||||||
case "stderr":
|
|
||||||
logWriter = os.Stderr
|
logWriter = os.Stderr
|
||||||
case "stdout":
|
case "stdout":
|
||||||
logWriter = os.Stdout
|
logWriter = os.Stdout
|
||||||
default:
|
default:
|
||||||
var err error
|
var err error
|
||||||
logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
logFile, err = os.OpenFile(logOptions.Output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -86,10 +84,10 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, options.PlatformInterface)
|
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
|
||||||
logFactory = observableLogFactory
|
logFactory = observableLogFactory
|
||||||
} else {
|
} else {
|
||||||
logFactory = log.NewFactory(logFormatter, logWriter, options.PlatformInterface)
|
logFactory = log.NewFactory(logFormatter, logWriter)
|
||||||
}
|
}
|
||||||
if logOptions.Level != "" {
|
if logOptions.Level != "" {
|
||||||
logLevel, err := log.ParseLevel(logOptions.Level)
|
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||||
@@ -107,9 +105,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
logFactory,
|
logFactory,
|
||||||
common.PtrValueOrDefault(options.Route),
|
common.PtrValueOrDefault(options.Route),
|
||||||
common.PtrValueOrDefault(options.DNS),
|
common.PtrValueOrDefault(options.DNS),
|
||||||
common.PtrValueOrDefault(options.NTP),
|
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
options.PlatformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
return nil, E.Cause(err, "parse route options")
|
||||||
@@ -129,7 +125,6 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
inboundOptions,
|
inboundOptions,
|
||||||
options.PlatformInterface,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
@@ -166,6 +161,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
|
|
||||||
var clashServer adapter.ClashServer
|
var clashServer adapter.ClashServer
|
||||||
var v2rayServer adapter.V2RayServer
|
var v2rayServer adapter.V2RayServer
|
||||||
|
var ssmServer adapter.SSMServer
|
||||||
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 {
|
||||||
@@ -180,6 +176,13 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
router.SetV2RayServer(v2rayServer)
|
router.SetV2RayServer(v2rayServer)
|
||||||
}
|
}
|
||||||
|
if needSSMAPI {
|
||||||
|
ssmServer, err = experimental.NewSSMServer(router, logFactory.NewLogger("ssm-api"), common.PtrValueOrDefault(options.Experimental.SSMAPI))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "create ssm api server")
|
||||||
|
}
|
||||||
|
router.SetSSMServer(ssmServer)
|
||||||
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
router: router,
|
router: router,
|
||||||
inbounds: inbounds,
|
inbounds: inbounds,
|
||||||
@@ -190,6 +193,7 @@ func New(ctx context.Context, options option.Options) (*Box, error) {
|
|||||||
logFile: logFile,
|
logFile: logFile,
|
||||||
clashServer: clashServer,
|
clashServer: clashServer,
|
||||||
v2rayServer: v2rayServer,
|
v2rayServer: v2rayServer,
|
||||||
|
ssmServer: ssmServer,
|
||||||
done: make(chan struct{}),
|
done: make(chan struct{}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -254,6 +258,12 @@ func (s *Box) start() error {
|
|||||||
return E.Cause(err, "start v2ray api server")
|
return E.Cause(err, "start v2ray api server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if s.ssmServer != nil {
|
||||||
|
err = s.ssmServer.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "start ssm api server")
|
||||||
|
}
|
||||||
|
}
|
||||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -265,43 +275,20 @@ func (s *Box) Close() error {
|
|||||||
default:
|
default:
|
||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
var errors error
|
for _, in := range s.inbounds {
|
||||||
for i, in := range s.inbounds {
|
in.Close()
|
||||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
for i, out := range s.outbounds {
|
for _, out := range s.outbounds {
|
||||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
common.Close(out)
|
||||||
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if err := common.Close(s.router); err != nil {
|
return common.Close(
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
s.router,
|
||||||
return E.Cause(err, "close router")
|
s.logFactory,
|
||||||
})
|
s.clashServer,
|
||||||
}
|
s.v2rayServer,
|
||||||
if err := common.Close(s.logFactory); err != nil {
|
s.ssmServer,
|
||||||
errors = E.Append(errors, err, func(err error) error {
|
common.PtrOrNil(s.logFile),
|
||||||
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 {
|
|
||||||
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close log file")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return errors
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Box) Router() adapter.Router {
|
func (s *Box) Router() adapter.Router {
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
build_shared.FindSDK()
|
findSDK()
|
||||||
|
|
||||||
command := exec.Command(os.Args[1], os.Args[2:]...)
|
command := exec.Command(os.Args[1], os.Args[2:]...)
|
||||||
command.Stdout = os.Stdout
|
command.Stdout = os.Stdout
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package build_shared
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/build"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -19,7 +18,7 @@ var (
|
|||||||
androidNDKPath string
|
androidNDKPath string
|
||||||
)
|
)
|
||||||
|
|
||||||
func FindSDK() {
|
func findSDK() {
|
||||||
searchPath := []string{
|
searchPath := []string{
|
||||||
"$ANDROID_HOME",
|
"$ANDROID_HOME",
|
||||||
"$HOME/Android/Sdk",
|
"$HOME/Android/Sdk",
|
||||||
@@ -80,13 +79,3 @@ func findNDK() bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var GoBinPath string
|
|
||||||
|
|
||||||
func FindMobile() {
|
|
||||||
goBin := filepath.Join(build.Default.GOPATH, "bin")
|
|
||||||
if !rw.FileExists(goBin + "/" + "gobind") {
|
|
||||||
log.Fatal("missing gomobile installation")
|
|
||||||
}
|
|
||||||
GoBinPath = goBin
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
_ "github.com/sagernet/gomobile/asset"
|
|
||||||
"github.com/sagernet/sing-box/cmd/internal/build_shared"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
debugEnabled bool
|
|
||||||
target string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
build_shared.FindMobile()
|
|
||||||
|
|
||||||
switch target {
|
|
||||||
case "android":
|
|
||||||
buildAndroid()
|
|
||||||
case "ios":
|
|
||||||
buildiOS()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildAndroid() {
|
|
||||||
build_shared.FindSDK()
|
|
||||||
|
|
||||||
args := []string{
|
|
||||||
"bind",
|
|
||||||
"-v",
|
|
||||||
"-androidapi", "21",
|
|
||||||
"-javapkg=io.nekohasekai",
|
|
||||||
"-libname=box",
|
|
||||||
}
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args,
|
|
||||||
"-trimpath", "-ldflags=-s -w -buildid=",
|
|
||||||
"-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
args = append(args, "-tags", "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api,debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, "./experimental/libbox")
|
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
|
||||||
command.Stdout = os.Stdout
|
|
||||||
command.Stderr = os.Stderr
|
|
||||||
err := command.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const name = "libbox.aar"
|
|
||||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
|
||||||
if rw.FileExists(copyPath) {
|
|
||||||
copyPath, _ = filepath.Abs(copyPath)
|
|
||||||
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Info("copied to ", copyPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildiOS() {
|
|
||||||
args := []string{
|
|
||||||
"bind",
|
|
||||||
"-v",
|
|
||||||
"-target", "ios,iossimulator,macos",
|
|
||||||
"-libname=box",
|
|
||||||
}
|
|
||||||
if !debugEnabled {
|
|
||||||
args = append(args,
|
|
||||||
"-trimpath", "-ldflags=-s -w -buildid=",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
args = append(args, "-tags", "debug")
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, "./experimental/libbox")
|
|
||||||
|
|
||||||
command := exec.Command(build_shared.GoBinPath+"/gomobile", args...)
|
|
||||||
command.Stdout = os.Stdout
|
|
||||||
command.Stderr = os.Stderr
|
|
||||||
err := command.Run()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
copyPath := filepath.Join("..", "sfi")
|
|
||||||
if rw.FileExists(copyPath) {
|
|
||||||
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
|
||||||
targetDir, _ = filepath.Abs(targetDir)
|
|
||||||
os.RemoveAll(targetDir)
|
|
||||||
os.Rename("Libbox.xcframework", targetDir)
|
|
||||||
log.Info("copied to ", targetDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -70,7 +70,15 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
|||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router.AutoDetectInterface() {
|
} else if router.AutoDetectInterface() {
|
||||||
bindFunc := router.AutoDetectInterfaceFunc()
|
const useInterfaceName = C.IsLinux
|
||||||
|
bindFunc := control.BindToInterfaceFunc(router.InterfaceFinder(), func(network string, address string) (interfaceName string, interfaceIndex int) {
|
||||||
|
remoteAddr := M.ParseSocksaddr(address).Addr
|
||||||
|
if C.IsLinux {
|
||||||
|
return router.InterfaceMonitor().DefaultInterfaceName(remoteAddr), -1
|
||||||
|
} else {
|
||||||
|
return "", router.InterfaceMonitor().DefaultInterfaceIndex(remoteAddr)
|
||||||
|
}
|
||||||
|
})
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
} else if router.DefaultInterface() != "" {
|
} else if router.DefaultInterface() != "" {
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import (
|
|||||||
const (
|
const (
|
||||||
VersionDraft29 = 0xff00001d
|
VersionDraft29 = 0xff00001d
|
||||||
Version1 = 0x1
|
Version1 = 0x1
|
||||||
Version2 = 0x6b3343cf
|
Version2 = 0x709a50c4
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
SaltOld = []byte{0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}
|
||||||
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
SaltV1 = []byte{0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}
|
||||||
SaltV2 = []byte{0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}
|
SaltV2 = []byte{0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -31,23 +31,18 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
|
|||||||
}
|
}
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return NewECHClient(router, serverAddress, options)
|
return NewECHClient(router, serverAddress, options)
|
||||||
} else if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return NewRealityClient(router, serverAddress, options)
|
|
||||||
} else if options.UTLS != nil && options.UTLS.Enabled {
|
} else if options.UTLS != nil && options.UTLS.Enabled {
|
||||||
return NewUTLSClient(router, serverAddress, options)
|
return NewUTLSClient(router, serverAddress, options)
|
||||||
} else {
|
} else {
|
||||||
return NewSTDClient(router, serverAddress, options)
|
return NewSTDClient(serverAddress, options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||||
|
tlsConn := config.Client(conn)
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
tlsConn, err := config.Client(conn)
|
err := tlsConn.HandshakeContext(ctx)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,23 +21,14 @@ type Config interface {
|
|||||||
NextProtos() []string
|
NextProtos() []string
|
||||||
SetNextProtos(nextProto []string)
|
SetNextProtos(nextProto []string)
|
||||||
Config() (*STDConfig, error)
|
Config() (*STDConfig, error)
|
||||||
Client(conn net.Conn) (Conn, error)
|
Client(conn net.Conn) Conn
|
||||||
Clone() Config
|
Clone() Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigWithSessionIDGenerator interface {
|
|
||||||
SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfig interface {
|
type ServerConfig interface {
|
||||||
Config
|
Config
|
||||||
adapter.Service
|
adapter.Service
|
||||||
Server(conn net.Conn) (Conn, error)
|
Server(conn net.Conn) Conn
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfigCompat interface {
|
|
||||||
ServerConfig
|
|
||||||
ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Conn interface {
|
type Conn interface {
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
|||||||
return nil, E.New("unsupported usage for ECH")
|
return nil, E.New("unsupported usage for ECH")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
|
||||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
return &echConnWrapper{cftls.Client(conn, e.config)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ECHClientConfig) Clone() Config {
|
func (e *ECHClientConfig) Clone() Config {
|
||||||
@@ -76,10 +76,6 @@ func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *echConnWrapper) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
@@ -94,7 +90,6 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig cftls.Config
|
var tlsConfig cftls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
||||||
if timeFunc == nil {
|
|
||||||
timeFunc = time.Now
|
|
||||||
}
|
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -25,8 +22,8 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certifi
|
|||||||
}
|
}
|
||||||
template := &x509.Certificate{
|
template := &x509.Certificate{
|
||||||
SerialNumber: serialNumber,
|
SerialNumber: serialNumber,
|
||||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
NotBefore: time.Now().Add(time.Hour * -1),
|
||||||
NotAfter: timeFunc().Add(time.Hour),
|
NotAfter: time.Now().Add(time.Hour),
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
|
|||||||
@@ -1,187 +0,0 @@
|
|||||||
//go:build with_utls
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/ed25519"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/debug"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/hkdf"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ Config = (*RealityClientConfig)(nil)
|
|
||||||
|
|
||||||
type RealityClientConfig struct {
|
|
||||||
uClient *UTLSClientConfig
|
|
||||||
publicKey []byte
|
|
||||||
shortID []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*RealityClientConfig, error) {
|
|
||||||
if options.UTLS == nil || !options.UTLS.Enabled {
|
|
||||||
return nil, E.New("uTLS is required by reality client")
|
|
||||||
}
|
|
||||||
|
|
||||||
uClient, err := NewUTLSClient(router, serverAddress, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
publicKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PublicKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode public_key")
|
|
||||||
}
|
|
||||||
if len(publicKey) != 32 {
|
|
||||||
return nil, E.New("invalid public_key")
|
|
||||||
}
|
|
||||||
shortID, err := hex.DecodeString(options.Reality.ShortID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode short_id")
|
|
||||||
}
|
|
||||||
if len(shortID) != 8 {
|
|
||||||
return nil, E.New("invalid short_id")
|
|
||||||
}
|
|
||||||
return &RealityClientConfig{uClient, publicKey, shortID}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
|
||||||
return e.uClient.ServerName()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetServerName(serverName string) {
|
|
||||||
e.uClient.SetServerName(serverName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) NextProtos() []string {
|
|
||||||
return e.uClient.NextProtos()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetNextProtos(nextProto []string) {
|
|
||||||
e.uClient.SetNextProtos(nextProto)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Config() (*STDConfig, error) {
|
|
||||||
return nil, E.New("unsupported usage for reality")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Client(conn net.Conn) (Conn, error) {
|
|
||||||
verifier := &realityVerifier{
|
|
||||||
serverName: e.uClient.ServerName(),
|
|
||||||
}
|
|
||||||
uConfig := e.uClient.config.Clone()
|
|
||||||
uConfig.InsecureSkipVerify = true
|
|
||||||
uConfig.SessionTicketsDisabled = true
|
|
||||||
uConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate
|
|
||||||
uConn := utls.UClient(conn, uConfig, e.uClient.id)
|
|
||||||
verifier.UConn = uConn
|
|
||||||
err := uConn.BuildHandshakeState()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hello := uConn.HandshakeState.Hello
|
|
||||||
hello.SessionId = make([]byte, 32)
|
|
||||||
copy(hello.Raw[39:], hello.SessionId)
|
|
||||||
|
|
||||||
var nowTime time.Time
|
|
||||||
if uConfig.Time != nil {
|
|
||||||
nowTime = uConfig.Time()
|
|
||||||
} else {
|
|
||||||
nowTime = time.Now()
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
|
||||||
|
|
||||||
hello.SessionId[0] = 1
|
|
||||||
hello.SessionId[1] = 7
|
|
||||||
hello.SessionId[2] = 5
|
|
||||||
copy(hello.SessionId[8:], e.shortID)
|
|
||||||
|
|
||||||
if debug.Enabled {
|
|
||||||
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
|
|
||||||
}
|
|
||||||
|
|
||||||
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey)
|
|
||||||
if authKey == nil {
|
|
||||||
return nil, E.New("nil auth_key")
|
|
||||||
}
|
|
||||||
verifier.authKey = authKey
|
|
||||||
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
aesBlock, _ := aes.NewCipher(authKey)
|
|
||||||
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
|
||||||
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
|
||||||
copy(hello.Raw[39:], hello.SessionId)
|
|
||||||
if debug.Enabled {
|
|
||||||
fmt.Printf("REALITY hello.sessionId: %v\n", hello.SessionId)
|
|
||||||
fmt.Printf("REALITY uConn.AuthKey: %v\n", authKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &utlsConnWrapper{uConn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
|
||||||
e.uClient.config.SessionIDGenerator = generator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *RealityClientConfig) Clone() Config {
|
|
||||||
return &RealityClientConfig{
|
|
||||||
e.uClient.Clone().(*UTLSClientConfig),
|
|
||||||
e.publicKey,
|
|
||||||
e.shortID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type realityVerifier struct {
|
|
||||||
*utls.UConn
|
|
||||||
serverName string
|
|
||||||
authKey []byte
|
|
||||||
verified bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
|
||||||
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
|
||||||
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
|
|
||||||
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
|
|
||||||
h := hmac.New(sha512.New, c.authKey)
|
|
||||||
h.Write(pub)
|
|
||||||
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
|
|
||||||
c.verified = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
opts := x509.VerifyOptions{
|
|
||||||
DNSName: c.serverName,
|
|
||||||
Intermediates: x509.NewCertPool(),
|
|
||||||
}
|
|
||||||
for _, cert := range certs[1:] {
|
|
||||||
opts.Intermediates.AddCert(cert)
|
|
||||||
}
|
|
||||||
if _, err := certs[0].Verify(opts); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !c.verified {
|
|
||||||
return E.New("reality verification failed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
//go:build with_reality_server
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing/common/debug"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"github.com/nekohasekai/reality"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ ServerConfigCompat = (*RealityServerConfig)(nil)
|
|
||||||
|
|
||||||
type RealityServerConfig struct {
|
|
||||||
config *reality.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (*RealityServerConfig, error) {
|
|
||||||
var tlsConfig reality.Config
|
|
||||||
|
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
|
||||||
return nil, E.New("acme is unavailable in reality")
|
|
||||||
}
|
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.ServerName != "" {
|
|
||||||
tlsConfig.ServerName = options.ServerName
|
|
||||||
}
|
|
||||||
if len(options.ALPN) > 0 {
|
|
||||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
|
||||||
}
|
|
||||||
if options.MinVersion != "" {
|
|
||||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse min_version")
|
|
||||||
}
|
|
||||||
tlsConfig.MinVersion = minVersion
|
|
||||||
}
|
|
||||||
if options.MaxVersion != "" {
|
|
||||||
maxVersion, err := ParseTLSVersion(options.MaxVersion)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "parse max_version")
|
|
||||||
}
|
|
||||||
tlsConfig.MaxVersion = maxVersion
|
|
||||||
}
|
|
||||||
if options.CipherSuites != nil {
|
|
||||||
find:
|
|
||||||
for _, cipherSuite := range options.CipherSuites {
|
|
||||||
for _, tlsCipherSuite := range tls.CipherSuites() {
|
|
||||||
if cipherSuite == tlsCipherSuite.Name {
|
|
||||||
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
|
||||||
continue find
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if options.Certificate != "" || options.CertificatePath != "" {
|
|
||||||
return nil, E.New("certificate is unavailable in reality")
|
|
||||||
}
|
|
||||||
if options.Key != "" || options.KeyPath != "" {
|
|
||||||
return nil, E.New("key is unavailable in reality")
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsConfig.SessionTicketsDisabled = true
|
|
||||||
tlsConfig.Type = N.NetworkTCP
|
|
||||||
tlsConfig.Dest = options.Reality.Handshake.ServerOptions.Build().String()
|
|
||||||
|
|
||||||
tlsConfig.ServerNames = map[string]bool{options.ServerName: true}
|
|
||||||
privateKey, err := base64.RawURLEncoding.DecodeString(options.Reality.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode private key")
|
|
||||||
}
|
|
||||||
if len(privateKey) != 32 {
|
|
||||||
return nil, E.New("invalid private key")
|
|
||||||
}
|
|
||||||
tlsConfig.PrivateKey = privateKey
|
|
||||||
tlsConfig.MaxTimeDiff = time.Duration(options.Reality.MaxTimeDifference)
|
|
||||||
|
|
||||||
tlsConfig.ShortIds = make(map[[8]byte]bool)
|
|
||||||
for i, shortID := range options.Reality.ShortID {
|
|
||||||
var shortIDBytesArray [8]byte
|
|
||||||
decodedLen, err := hex.Decode(shortIDBytesArray[:], []byte(shortID))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortID)
|
|
||||||
}
|
|
||||||
if decodedLen != 8 {
|
|
||||||
return nil, E.New("invalid short_id[", i, "]: ", shortID)
|
|
||||||
}
|
|
||||||
tlsConfig.ShortIds[shortIDBytesArray] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
handshakeDialer := dialer.New(router, options.Reality.Handshake.DialerOptions)
|
|
||||||
tlsConfig.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return handshakeDialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
}
|
|
||||||
|
|
||||||
if debug.Enabled {
|
|
||||||
tlsConfig.Show = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RealityServerConfig{&tlsConfig}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) ServerName() string {
|
|
||||||
return c.config.ServerName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) SetServerName(serverName string) {
|
|
||||||
c.config.ServerName = serverName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) NextProtos() []string {
|
|
||||||
return c.config.NextProtos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) SetNextProtos(nextProto []string) {
|
|
||||||
c.config.NextProtos = nextProto
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Config() (*tls.Config, error) {
|
|
||||||
return nil, E.New("unsupported usage for reality")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Client(conn net.Conn) (Conn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Start() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) ServerHandshake(ctx context.Context, conn net.Conn) (Conn, error) {
|
|
||||||
tlsConn, err := reality.Server(ctx, conn, c.config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &realityConnWrapper{Conn: tlsConn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RealityServerConfig) Clone() Config {
|
|
||||||
return &RealityServerConfig{
|
|
||||||
config: c.config.Clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Conn = (*realityConnWrapper)(nil)
|
|
||||||
|
|
||||||
type realityConnWrapper struct {
|
|
||||||
*reality.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityConnWrapper) ConnectionState() ConnectionState {
|
|
||||||
state := c.Conn.ConnectionState()
|
|
||||||
return tls.ConnectionState{
|
|
||||||
Version: state.Version,
|
|
||||||
HandshakeComplete: state.HandshakeComplete,
|
|
||||||
DidResume: state.DidResume,
|
|
||||||
CipherSuite: state.CipherSuite,
|
|
||||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
|
||||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
|
||||||
ServerName: state.ServerName,
|
|
||||||
PeerCertificates: state.PeerCertificates,
|
|
||||||
VerifiedChains: state.VerifiedChains,
|
|
||||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
|
||||||
OCSPResponse: state.OCSPResponse,
|
|
||||||
TLSUnique: state.TLSUnique,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *realityConnWrapper) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build !with_reality_server
|
|
||||||
|
|
||||||
package tls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRealityServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
|
||||||
return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`)
|
|
||||||
}
|
|
||||||
@@ -5,35 +5,24 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/badtls"
|
"github.com/sagernet/sing-box/common/badtls"
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
return NewSTDServer(ctx, logger, options)
|
||||||
return NewRealityServer(ctx, router, logger, options)
|
|
||||||
} else {
|
|
||||||
return NewSTDServer(ctx, router, logger, options)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||||
|
tlsConn := config.Server(conn)
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if compatServer, isCompat := config.(ServerConfigCompat); isCompat {
|
err := tlsConn.HandshakeContext(ctx)
|
||||||
return compatServer.ServerHandshake(ctx, conn)
|
|
||||||
}
|
|
||||||
tlsConn, err := config.Server(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = tlsConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
@@ -36,15 +35,15 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
|
|||||||
return s.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) Conn {
|
||||||
return tls.Client(conn, s.config), nil
|
return tls.Client(conn, s.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{s.config.Clone()}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -58,7 +57,6 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
|
|||||||
return c.config, nil
|
return c.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
|
func (c *STDServerConfig) Client(conn net.Conn) Conn {
|
||||||
return tls.Client(conn, c.config), nil
|
return tls.Client(conn, c.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
func (c *STDServerConfig) Server(conn net.Conn) Conn {
|
||||||
return tls.Server(conn, c.config), nil
|
return tls.Server(conn, c.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDServerConfig) Clone() Config {
|
func (c *STDServerConfig) Clone() Config {
|
||||||
@@ -156,7 +156,7 @@ func (c *STDServerConfig) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
func NewSTDServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,6 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
} else {
|
} else {
|
||||||
tlsConfig = &tls.Config{}
|
tlsConfig = &tls.Config{}
|
||||||
}
|
}
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
tlsConfig.ServerName = options.ServerName
|
tlsConfig.ServerName = options.ServerName
|
||||||
}
|
}
|
||||||
@@ -231,7 +230,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
|||||||
}
|
}
|
||||||
if certificate == nil && key == nil && options.Insecure {
|
if certificate == nil && key == nil && options.Insecure {
|
||||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
|
return GenerateKeyPair(info.ServerName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if certificate == nil {
|
if certificate == nil {
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
utls "github.com/sagernet/utls"
|
|
||||||
|
utls "github.com/refraction-networking/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
@@ -40,19 +41,8 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
|||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
|
||||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
|
||||||
e.config.SessionIDGenerator = generator
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UTLSClientConfig) Clone() Config {
|
|
||||||
return &UTLSClientConfig{
|
|
||||||
config: e.config.Clone(),
|
|
||||||
id: e.id,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
@@ -77,11 +67,14 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *utlsConnWrapper) Upstream() any {
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
return c.UConn
|
return &UTLSClientConfig{
|
||||||
|
config: e.config.Clone(),
|
||||||
|
id: e.id,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
@@ -95,7 +88,6 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = router.TimeFunc()
|
|
||||||
if options.DisableSNI {
|
if options.DisableSNI {
|
||||||
tlsConfig.ServerName = "127.0.0.1"
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
} else {
|
} else {
|
||||||
@@ -152,34 +144,28 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
var id utls.ClientHelloID
|
||||||
if err != nil {
|
switch options.UTLS.Fingerprint {
|
||||||
return nil, err
|
case "chrome", "":
|
||||||
|
id = utls.HelloChrome_Auto
|
||||||
|
case "firefox":
|
||||||
|
id = utls.HelloFirefox_Auto
|
||||||
|
case "edge":
|
||||||
|
id = utls.HelloEdge_Auto
|
||||||
|
case "safari":
|
||||||
|
id = utls.HelloSafari_Auto
|
||||||
|
case "360":
|
||||||
|
id = utls.Hello360_Auto
|
||||||
|
case "qq":
|
||||||
|
id = utls.HelloQQ_Auto
|
||||||
|
case "ios":
|
||||||
|
id = utls.HelloIOS_Auto
|
||||||
|
case "android":
|
||||||
|
id = utls.HelloAndroid_11_OkHttp
|
||||||
|
case "random":
|
||||||
|
id = utls.HelloRandomized
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown uTLS fingerprint: ", options.UTLS.Fingerprint)
|
||||||
}
|
}
|
||||||
return &UTLSClientConfig{&tlsConfig, id}, nil
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
|
||||||
switch name {
|
|
||||||
case "chrome", "":
|
|
||||||
return utls.HelloChrome_Auto, nil
|
|
||||||
case "firefox":
|
|
||||||
return utls.HelloFirefox_Auto, nil
|
|
||||||
case "edge":
|
|
||||||
return utls.HelloEdge_Auto, nil
|
|
||||||
case "safari":
|
|
||||||
return utls.HelloSafari_Auto, nil
|
|
||||||
case "360":
|
|
||||||
return utls.Hello360_Auto, nil
|
|
||||||
case "qq":
|
|
||||||
return utls.HelloQQ_Auto, nil
|
|
||||||
case "ios":
|
|
||||||
return utls.HelloIOS_Auto, nil
|
|
||||||
case "android":
|
|
||||||
return utls.HelloAndroid_11_OkHttp, nil
|
|
||||||
case "random":
|
|
||||||
return utls.HelloRandomized, nil
|
|
||||||
default:
|
|
||||||
return utls.ClientHelloID{}, E.New("unknown uTLS fingerprint: ", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,3 @@ import (
|
|||||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
|
return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRealityClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
|
||||||
return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,28 +3,13 @@ package constant
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const dirName = "sing-box"
|
const dirName = "sing-box"
|
||||||
|
|
||||||
var (
|
var resourcePaths []string
|
||||||
basePath string
|
|
||||||
resourcePaths []string
|
|
||||||
)
|
|
||||||
|
|
||||||
func BasePath(name string) string {
|
|
||||||
if basePath == "" || strings.HasPrefix(name, "/") {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return filepath.Join(basePath, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
|
||||||
basePath = path
|
|
||||||
}
|
|
||||||
|
|
||||||
func FindPath(name string) (string, bool) {
|
func FindPath(name string) (string, bool) {
|
||||||
name = os.ExpandEnv(name)
|
name = os.ExpandEnv(name)
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
var Version = "1.2-beta5"
|
var Version = "1.2-beta2"
|
||||||
|
|||||||
@@ -1,31 +1,3 @@
|
|||||||
#### 1.2-beta5
|
|
||||||
|
|
||||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
|
||||||
* Add [reality TLS](/configuration/shared/tls) support
|
|
||||||
* Fix match private address
|
|
||||||
|
|
||||||
#### 1.1.6
|
|
||||||
|
|
||||||
* Improve vmess request
|
|
||||||
* Fix ipv6 redirect on Linux
|
|
||||||
* Fix match geoip private
|
|
||||||
* Fix parse hysteria UDP message
|
|
||||||
* Fix socks connect response
|
|
||||||
* Disable vmess header protection if transport enabled
|
|
||||||
* Update QUIC v2 version number and initial salt
|
|
||||||
|
|
||||||
#### 1.2-beta4
|
|
||||||
|
|
||||||
* Add [NTP service](/configuration/ntp)
|
|
||||||
* Add Add multiple server names and multi-user support for shadowtls
|
|
||||||
* Add strict mode support for shadowtls v3
|
|
||||||
* Add uTLS support for shadowtls v3
|
|
||||||
|
|
||||||
#### 1.2-beta3
|
|
||||||
|
|
||||||
* Update QUIC v2 version number and initial salt
|
|
||||||
* Fix shadowtls v3 implementation
|
|
||||||
|
|
||||||
#### 1.2-beta2
|
#### 1.2-beta2
|
||||||
|
|
||||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
||||||
|
|||||||
@@ -26,8 +26,6 @@
|
|||||||
| `trojan` | [Trojan](./trojan) | TCP |
|
| `trojan` | [Trojan](./trojan) | TCP |
|
||||||
| `naive` | [Naive](./naive) | X |
|
| `naive` | [Naive](./naive) | X |
|
||||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||||
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
|
||||||
| `vless` | [VLESS](./vless) | TCP |
|
|
||||||
| `tun` | [Tun](./tun) | X |
|
| `tun` | [Tun](./tun) | X |
|
||||||
| `redirect` | [Redirect](./redirect) | X |
|
| `redirect` | [Redirect](./redirect) | X |
|
||||||
| `tproxy` | [TProxy](./tproxy) | X |
|
| `tproxy` | [TProxy](./tproxy) | X |
|
||||||
|
|||||||
@@ -7,29 +7,14 @@
|
|||||||
|
|
||||||
... // Listen Fields
|
... // Listen Fields
|
||||||
|
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443,
|
"server_port": 443,
|
||||||
|
|
||||||
... // Dial Fields
|
|
||||||
},
|
|
||||||
"handshake_for_server_name": {
|
|
||||||
"example.com": {
|
|
||||||
"server": "example.com",
|
|
||||||
"server_port": 443,
|
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"strict_mode": false
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -47,35 +32,15 @@ ShadowTLS protocol version.
|
|||||||
|---------------|-----------------------------------------------------------------------------------------|
|
|---------------|-----------------------------------------------------------------------------------------|
|
||||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
|
||||||
|
|
||||||
#### password
|
#### password
|
||||||
|
|
||||||
ShadowTLS password.
|
Set password.
|
||||||
|
|
||||||
Only available in the ShadowTLS protocol 2.
|
Only available in the ShadowTLS v2 protocol.
|
||||||
|
|
||||||
|
|
||||||
#### users
|
|
||||||
|
|
||||||
ShadowTLS users.
|
|
||||||
|
|
||||||
Only available in the ShadowTLS protocol 3.
|
|
||||||
|
|
||||||
#### handshake
|
#### handshake
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||||
|
|
||||||
#### handshake
|
|
||||||
|
|
||||||
Handshake server address and [Dial options](/configuration/shared/dial) for specific server name.
|
|
||||||
|
|
||||||
Only available in the ShadowTLS protocol 2/3.
|
|
||||||
|
|
||||||
#### strict_mode
|
|
||||||
|
|
||||||
ShadowTLS strict mode.
|
|
||||||
|
|
||||||
Only available in the ShadowTLS protocol 3.
|
|
||||||
|
|||||||
@@ -7,29 +7,14 @@
|
|||||||
|
|
||||||
... // 监听字段
|
... // 监听字段
|
||||||
|
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443,
|
"server_port": 443,
|
||||||
|
|
||||||
... // 拨号字段
|
|
||||||
},
|
|
||||||
"handshake_for_server_name": {
|
|
||||||
"example.com": {
|
|
||||||
"server": "example.com",
|
|
||||||
"server_port": 443,
|
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"strict_mode": false
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -47,36 +32,15 @@ ShadowTLS 协议版本。
|
|||||||
|---------------|-----------------------------------------------------------------------------------------|
|
|---------------|-----------------------------------------------------------------------------------------|
|
||||||
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
| `1` (default) | [ShadowTLS v1](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v1) |
|
||||||
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
| `2` | [ShadowTLS v2](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-en.md#v2) |
|
||||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
|
||||||
|
|
||||||
#### password
|
#### password
|
||||||
|
|
||||||
ShadowTLS 密码。
|
设置密码。
|
||||||
|
|
||||||
仅在 ShadowTLS 协议版本 2 中可用。
|
仅在 ShadowTLS v2 协议中可用。
|
||||||
|
|
||||||
#### users
|
|
||||||
|
|
||||||
ShadowTLS 用户。
|
|
||||||
|
|
||||||
仅在 ShadowTLS 协议版本 3 中可用。
|
|
||||||
|
|
||||||
#### handshake
|
#### handshake
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||||
|
|
||||||
#### handshake
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
对于特定服务器名称的握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
|
||||||
|
|
||||||
仅在 ShadowTLS 协议版本 2/3 中可用。
|
|
||||||
|
|
||||||
#### strict_mode
|
|
||||||
|
|
||||||
ShadowTLS 严格模式。
|
|
||||||
|
|
||||||
仅在 ShadowTLS 协议版本 3 中可用。
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "vless",
|
|
||||||
"tag": "vless-in",
|
|
||||||
|
|
||||||
... // Listen Fields
|
|
||||||
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls": {},
|
|
||||||
"transport": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen) for details.
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### users
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
VLESS users.
|
|
||||||
|
|
||||||
#### tls
|
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|
||||||
|
|
||||||
#### transport
|
|
||||||
|
|
||||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "vless",
|
|
||||||
"tag": "vless-in",
|
|
||||||
|
|
||||||
... // 监听字段
|
|
||||||
|
|
||||||
"users": [
|
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tls": {},
|
|
||||||
"transport": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 监听字段
|
|
||||||
|
|
||||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### users
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
VLESS 用户。
|
|
||||||
|
|
||||||
#### tls
|
|
||||||
|
|
||||||
TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
|
|
||||||
|
|
||||||
#### transport
|
|
||||||
|
|
||||||
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。
|
|
||||||
@@ -8,7 +8,6 @@ sing-box uses JSON for configuration files.
|
|||||||
{
|
{
|
||||||
"log": {},
|
"log": {},
|
||||||
"dns": {},
|
"dns": {},
|
||||||
"ntp": {},
|
|
||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
@@ -22,7 +21,6 @@ sing-box uses JSON for configuration files.
|
|||||||
|----------------|--------------------------------|
|
|----------------|--------------------------------|
|
||||||
| `log` | [Log](./log) |
|
| `log` | [Log](./log) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
| `ntp` | [NTP](./ntp) |
|
|
||||||
| `inbounds` | [Inbound](./inbound) |
|
| `inbounds` | [Inbound](./inbound) |
|
||||||
| `outbounds` | [Outbound](./outbound) |
|
| `outbounds` | [Outbound](./outbound) |
|
||||||
| `route` | [Route](./route) |
|
| `route` | [Route](./route) |
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
# NTP
|
|
||||||
|
|
||||||
Built-in NTP client service.
|
|
||||||
|
|
||||||
If enabled, it will provide time for protocols like TLS/Shadowsocks/VMess, which is useful for environments where time
|
|
||||||
synchronization is not possible.
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ntp": {
|
|
||||||
"enabled": false,
|
|
||||||
"server": "time.apple.com",
|
|
||||||
"server_port": 123,
|
|
||||||
"interval": "30m",
|
|
||||||
|
|
||||||
... // Dial Fields
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### enabled
|
|
||||||
|
|
||||||
Enable NTP service.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
NTP server address.
|
|
||||||
|
|
||||||
#### server_port
|
|
||||||
|
|
||||||
NTP server port.
|
|
||||||
|
|
||||||
123 is used by default.
|
|
||||||
|
|
||||||
#### interval
|
|
||||||
|
|
||||||
Time synchronization interval.
|
|
||||||
|
|
||||||
30 minutes is used by default.
|
|
||||||
|
|
||||||
### Dial Fields
|
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial) for details.
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# NTP
|
|
||||||
|
|
||||||
内建的 NTP 客户端服务。
|
|
||||||
|
|
||||||
如果启用,它将为像 TLS/Shadowsocks/VMess 这样的协议提供时间,这对于无法进行时间同步的环境很有用。
|
|
||||||
|
|
||||||
### 结构
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"ntp": {
|
|
||||||
"enabled": false,
|
|
||||||
"server": "time.apple.com",
|
|
||||||
"server_port": 123,
|
|
||||||
"interval": "30m",
|
|
||||||
|
|
||||||
... // 拨号字段
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### 字段
|
|
||||||
|
|
||||||
#### enabled
|
|
||||||
|
|
||||||
启用 NTP 服务。
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
NTP 服务器地址。
|
|
||||||
|
|
||||||
#### server_port
|
|
||||||
|
|
||||||
NTP 服务器端口。
|
|
||||||
|
|
||||||
默认使用 123。
|
|
||||||
|
|
||||||
#### interval
|
|
||||||
|
|
||||||
时间同步间隔。
|
|
||||||
|
|
||||||
默认使用 30 分钟。
|
|
||||||
|
|
||||||
### 拨号字段
|
|
||||||
|
|
||||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
| `hysteria` | [Hysteria](./hysteria) |
|
| `hysteria` | [Hysteria](./hysteria) |
|
||||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||||
| `vless` | [VLESS](./vless) |
|
| `vless` | [VLESS](./vless) |
|
||||||
| `shadowtls` | [ShadowTLS](./shadowtls) |
|
|
||||||
| `tor` | [Tor](./tor) |
|
| `tor` | [Tor](./tor) |
|
||||||
| `ssh` | [SSH](./ssh) |
|
| `ssh` | [SSH](./ssh) |
|
||||||
| `dns` | [DNS](./dns) |
|
| `dns` | [DNS](./dns) |
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
"flow": "xtls-rprx-vision",
|
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"packet_encoding": "",
|
"packet_encoding": "",
|
||||||
@@ -18,6 +17,10 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
The VLESS protocol is architecturally coupled to v2ray and is unmaintained. This outbound is provided for compatibility purposes only.
|
||||||
|
|
||||||
### Fields
|
### Fields
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
@@ -38,14 +41,6 @@ The server port.
|
|||||||
|
|
||||||
The VLESS user id.
|
The VLESS user id.
|
||||||
|
|
||||||
#### flow
|
|
||||||
|
|
||||||
VLESS Sub-protocol.
|
|
||||||
|
|
||||||
Available values:
|
|
||||||
|
|
||||||
* `xtls-rprx-vision`
|
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
Enabled network
|
Enabled network
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 1080,
|
"server_port": 1080,
|
||||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||||
"flow": "xtls-rprx-vision",
|
|
||||||
"network": "tcp",
|
"network": "tcp",
|
||||||
"tls": {},
|
"tls": {},
|
||||||
"packet_encoding": "",
|
"packet_encoding": "",
|
||||||
@@ -18,6 +17,10 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! warning ""
|
||||||
|
|
||||||
|
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
|
||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
#### server
|
#### server
|
||||||
@@ -38,14 +41,6 @@
|
|||||||
|
|
||||||
VLESS 用户 ID。
|
VLESS 用户 ID。
|
||||||
|
|
||||||
#### flow
|
|
||||||
|
|
||||||
VLESS 子协议。
|
|
||||||
|
|
||||||
可用值:
|
|
||||||
|
|
||||||
* `xtls-rprx-vision`
|
|
||||||
|
|
||||||
#### network
|
#### network
|
||||||
|
|
||||||
启用的网络协议。
|
启用的网络协议。
|
||||||
|
|||||||
@@ -26,20 +26,6 @@
|
|||||||
"key_id": "",
|
"key_id": "",
|
||||||
"mac_key": ""
|
"mac_key": ""
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"reality": {
|
|
||||||
"enabled": false,
|
|
||||||
"handshake": {
|
|
||||||
"server": "google.com",
|
|
||||||
"server_port": 443,
|
|
||||||
|
|
||||||
... // Dial Fields
|
|
||||||
},
|
|
||||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
|
||||||
"short_id": [
|
|
||||||
"0123456789abcdef"
|
|
||||||
],
|
|
||||||
"max_time_difference": "1m"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -67,11 +53,6 @@
|
|||||||
"utls": {
|
"utls": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"fingerprint": ""
|
"fingerprint": ""
|
||||||
},
|
|
||||||
"reality": {
|
|
||||||
"enabled": false,
|
|
||||||
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
|
||||||
"short_id": "0123456789abcdef"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -294,54 +275,6 @@ The key identifier.
|
|||||||
|
|
||||||
The MAC key.
|
The MAC key.
|
||||||
|
|
||||||
### Reality Fields
|
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
reality server is not included by default, see [Installation](/#installation).
|
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
uTLS, which is required by reality client is not included by default, see [Installation](/#installation).
|
|
||||||
|
|
||||||
#### handshake
|
|
||||||
|
|
||||||
==Server only==
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
|
||||||
|
|
||||||
#### private_key
|
|
||||||
|
|
||||||
==Server only==
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Private key, generated by `./xray x25519`.
|
|
||||||
|
|
||||||
#### public_key
|
|
||||||
|
|
||||||
==Client only==
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Public key, generated by `./xray x25519`.
|
|
||||||
|
|
||||||
#### short_id
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
A 8-bit hex string.
|
|
||||||
|
|
||||||
#### max_time_difference
|
|
||||||
|
|
||||||
==Server only==
|
|
||||||
|
|
||||||
The maximum time difference between the server and the client.
|
|
||||||
|
|
||||||
Check disabled if empty.
|
|
||||||
|
|
||||||
### Reload
|
### Reload
|
||||||
|
|
||||||
For server configuration, certificate and key will be automatically reloaded if modified.
|
For server configuration, certificate and key will be automatically reloaded if modified.
|
||||||
@@ -26,20 +26,6 @@
|
|||||||
"key_id": "",
|
"key_id": "",
|
||||||
"mac_key": ""
|
"mac_key": ""
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"reality": {
|
|
||||||
"enabled": false,
|
|
||||||
"handshake": {
|
|
||||||
"server": "google.com",
|
|
||||||
"server_port": 443,
|
|
||||||
|
|
||||||
... // 拨号字段
|
|
||||||
},
|
|
||||||
"private_key": "UuMBgl7MXTPx9inmQp2UC7Jcnwc6XYbwDNebonM-FCc",
|
|
||||||
"short_id": [
|
|
||||||
"0123456789abcdef"
|
|
||||||
],
|
|
||||||
"max_time_difference": "1m"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -67,11 +53,6 @@
|
|||||||
"utls": {
|
"utls": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"fingerprint": ""
|
"fingerprint": ""
|
||||||
},
|
|
||||||
"reality": {
|
|
||||||
"enabled": false,
|
|
||||||
"public_key": "jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0",
|
|
||||||
"short_id": "0123456789abcdef"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -290,52 +271,6 @@ EAB(外部帐户绑定)包含将 ACME 帐户绑定或映射到其他已知
|
|||||||
|
|
||||||
MAC 密钥。
|
MAC 密钥。
|
||||||
|
|
||||||
### Reality 字段
|
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
默认安装不包含 reality 服务器,参阅 [安装](/zh/#_2)。
|
|
||||||
|
|
||||||
!!! warning ""
|
|
||||||
|
|
||||||
默认安装不包含被 reality 客户端需要的 uTLS, 参阅 [安装](/zh/#_2)。
|
|
||||||
|
|
||||||
#### handshake
|
|
||||||
|
|
||||||
==仅服务器==
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
|
||||||
|
|
||||||
#### private_key
|
|
||||||
|
|
||||||
==仅服务器==
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
私钥,由 `./xray x25519` 生成。
|
|
||||||
|
|
||||||
#### public_key
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
公钥,由 `./xray x25519` 生成。
|
|
||||||
|
|
||||||
#### short_id
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
一个八位十六进制的字符串。
|
|
||||||
|
|
||||||
#### max_time_difference
|
|
||||||
|
|
||||||
服务器与和客户端之间允许的最大时间差。
|
|
||||||
|
|
||||||
默认禁用检查。
|
|
||||||
|
|
||||||
### 重载
|
### 重载
|
||||||
|
|
||||||
对于服务器配置,如果修改,证书和密钥将自动重新加载。
|
对于服务器配置,如果修改,证书和密钥将自动重新加载。
|
||||||
@@ -7,13 +7,8 @@
|
|||||||
"type": "shadowtls",
|
"type": "shadowtls",
|
||||||
"listen": "::",
|
"listen": "::",
|
||||||
"listen_port": 4443,
|
"listen_port": 4443,
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"users": [
|
"password": "fuck me till the daylight",
|
||||||
{
|
|
||||||
"name": "sekai",
|
|
||||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"handshake": {
|
"handshake": {
|
||||||
"server": "google.com",
|
"server": "google.com",
|
||||||
"server_port": 443
|
"server_port": 443
|
||||||
@@ -52,15 +47,11 @@
|
|||||||
"tag": "shadowtls-out",
|
"tag": "shadowtls-out",
|
||||||
"server": "127.0.0.1",
|
"server": "127.0.0.1",
|
||||||
"server_port": 4443,
|
"server_port": 4443,
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"password": "fuck me till the daylight",
|
"password": "fuck me till the daylight",
|
||||||
"tls": {
|
"tls": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"server_name": "google.com",
|
"server_name": "google.com"
|
||||||
"utls": {
|
|
||||||
"enabled": true,
|
|
||||||
"fingerprint": "chrome"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ type Server struct {
|
|||||||
httpServer *http.Server
|
httpServer *http.Server
|
||||||
trafficManager *trafficontrol.Manager
|
trafficManager *trafficontrol.Manager
|
||||||
urlTestHistory *urltest.HistoryStorage
|
urlTestHistory *urltest.HistoryStorage
|
||||||
|
tcpListener net.Listener
|
||||||
mode string
|
mode string
|
||||||
storeSelected bool
|
storeSelected bool
|
||||||
cacheFile adapter.ClashCacheFile
|
cacheFile adapter.ClashCacheFile
|
||||||
@@ -70,11 +71,6 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
if cachePath == "" {
|
if cachePath == "" {
|
||||||
cachePath = "cache.db"
|
cachePath = "cache.db"
|
||||||
}
|
}
|
||||||
if foundPath, loaded := C.FindPath(cachePath); loaded {
|
|
||||||
cachePath = foundPath
|
|
||||||
} else {
|
|
||||||
cachePath = C.BasePath(cachePath)
|
|
||||||
}
|
|
||||||
cacheFile, err := cachefile.Open(cachePath)
|
cacheFile, err := cachefile.Open(cachePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "open cache file")
|
return nil, E.Cause(err, "open cache file")
|
||||||
@@ -107,7 +103,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
|||||||
})
|
})
|
||||||
if options.ExternalUI != "" {
|
if options.ExternalUI != "" {
|
||||||
chiRouter.Group(func(r chi.Router) {
|
chiRouter.Group(func(r chi.Router) {
|
||||||
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(C.BasePath(os.ExpandEnv(options.ExternalUI)))))
|
fs := http.StripPrefix("/ui", http.FileServer(http.Dir(os.ExpandEnv(options.ExternalUI))))
|
||||||
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
r.Get("/ui", http.RedirectHandler("/ui/", http.StatusTemporaryRedirect).ServeHTTP)
|
||||||
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/ui/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fs.ServeHTTP(w, r)
|
fs.ServeHTTP(w, r)
|
||||||
@@ -123,6 +119,7 @@ func (s *Server) Start() error {
|
|||||||
return E.Cause(err, "external controller listen error")
|
return E.Cause(err, "external controller listen error")
|
||||||
}
|
}
|
||||||
s.logger.Info("restful api listening at ", listener.Addr())
|
s.logger.Info("restful api listening at ", listener.Addr())
|
||||||
|
s.tcpListener = listener
|
||||||
go func() {
|
go func() {
|
||||||
err = s.httpServer.Serve(listener)
|
err = s.httpServer.Serve(listener)
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
@@ -135,6 +132,7 @@ func (s *Server) Start() error {
|
|||||||
func (s *Server) Close() error {
|
func (s *Server) Close() error {
|
||||||
return common.Close(
|
return common.Close(
|
||||||
common.PtrOrNil(s.httpServer),
|
common.PtrOrNil(s.httpServer),
|
||||||
|
s.tcpListener,
|
||||||
s.trafficManager,
|
s.trafficManager,
|
||||||
s.cacheFile,
|
s.cacheFile,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseConfig(configContent string) (option.Options, error) {
|
|
||||||
var options option.Options
|
|
||||||
err := options.UnmarshalJSON([]byte(configContent))
|
|
||||||
if err != nil {
|
|
||||||
return option.Options{}, E.Cause(err, "decode config")
|
|
||||||
}
|
|
||||||
return options, nil
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
package procfs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
netIndexOfLocal = -1
|
|
||||||
netIndexOfUid = -1
|
|
||||||
nativeEndian binary.ByteOrder
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var x uint32 = 0x01020304
|
|
||||||
if *(*byte)(unsafe.Pointer(&x)) == 0x01 {
|
|
||||||
nativeEndian = binary.BigEndian
|
|
||||||
} else {
|
|
||||||
nativeEndian = binary.LittleEndian
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResolveSocketByProcSearch(network string, source, _ netip.AddrPort) int32 {
|
|
||||||
if netIndexOfLocal < 0 || netIndexOfUid < 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
path := "/proc/net/"
|
|
||||||
|
|
||||||
if network == N.NetworkTCP {
|
|
||||||
path += "tcp"
|
|
||||||
} else {
|
|
||||||
path += "udp"
|
|
||||||
}
|
|
||||||
|
|
||||||
if source.Addr().Is6() {
|
|
||||||
path += "6"
|
|
||||||
}
|
|
||||||
|
|
||||||
sIP := source.Addr().AsSlice()
|
|
||||||
if len(sIP) == 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes [2]byte
|
|
||||||
binary.BigEndian.PutUint16(bytes[:], source.Port())
|
|
||||||
local := fmt.Sprintf("%s:%s", hex.EncodeToString(nativeEndianIP(sIP)), hex.EncodeToString(bytes[:]))
|
|
||||||
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
|
|
||||||
for {
|
|
||||||
row, _, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Fields(string(row))
|
|
||||||
|
|
||||||
if len(fields) <= netIndexOfLocal || len(fields) <= netIndexOfUid {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.EqualFold(local, fields[netIndexOfLocal]) {
|
|
||||||
uid, err := strconv.Atoi(fields[netIndexOfUid])
|
|
||||||
if err != nil {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return int32(uid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func nativeEndianIP(ip net.IP) []byte {
|
|
||||||
result := make([]byte, len(ip))
|
|
||||||
|
|
||||||
for i := 0; i < len(ip); i += 4 {
|
|
||||||
value := binary.BigEndian.Uint32(ip[i:])
|
|
||||||
|
|
||||||
nativeEndian.PutUint32(result[i:], value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
file, err := os.Open("/proc/net/tcp")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
reader := bufio.NewReader(file)
|
|
||||||
|
|
||||||
header, _, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
columns := strings.Fields(string(header))
|
|
||||||
|
|
||||||
var txQueue, rxQueue, tr, tmWhen bool
|
|
||||||
|
|
||||||
for idx, col := range columns {
|
|
||||||
offset := 0
|
|
||||||
|
|
||||||
if txQueue && rxQueue {
|
|
||||||
offset--
|
|
||||||
}
|
|
||||||
|
|
||||||
if tr && tmWhen {
|
|
||||||
offset--
|
|
||||||
}
|
|
||||||
|
|
||||||
switch col {
|
|
||||||
case "tx_queue":
|
|
||||||
txQueue = true
|
|
||||||
case "rx_queue":
|
|
||||||
rxQueue = true
|
|
||||||
case "tr":
|
|
||||||
tr = true
|
|
||||||
case "tm->when":
|
|
||||||
tmWhen = true
|
|
||||||
case "local_address":
|
|
||||||
netIndexOfLocal = idx + offset
|
|
||||||
case "uid":
|
|
||||||
netIndexOfUid = idx + offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import "github.com/sagernet/sing/common"
|
|
||||||
|
|
||||||
type StringIterator interface {
|
|
||||||
Next() string
|
|
||||||
HasNext() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ StringIterator = (*iterator[string])(nil)
|
|
||||||
|
|
||||||
type iterator[T any] struct {
|
|
||||||
values []T
|
|
||||||
}
|
|
||||||
|
|
||||||
func newIterator[T any](values []T) *iterator[T] {
|
|
||||||
return &iterator[T]{values}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *iterator[T]) Next() T {
|
|
||||||
if len(i.values) == 0 {
|
|
||||||
return common.DefaultValue[T]()
|
|
||||||
}
|
|
||||||
nextValue := i.values[0]
|
|
||||||
i.values = i.values[1:]
|
|
||||||
return nextValue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *iterator[T]) HasNext() bool {
|
|
||||||
return len(i.values) > 0
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StandardOutput interface {
|
|
||||||
WriteOutput(message string)
|
|
||||||
WriteErrorOutput(message string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetOutput(output StandardOutput) {
|
|
||||||
log.SetOutput(logWriter{output})
|
|
||||||
pipeIn, pipeOut, err := os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
os.Stdout = os.NewFile(pipeOut.Fd(), "stdout")
|
|
||||||
go lineLog(pipeIn, output.WriteOutput)
|
|
||||||
|
|
||||||
pipeIn, pipeOut, err = os.Pipe()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
os.Stderr = os.NewFile(pipeOut.Fd(), "srderr")
|
|
||||||
go lineLog(pipeIn, output.WriteErrorOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
type logWriter struct {
|
|
||||||
output StandardOutput
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w logWriter) Write(p []byte) (n int, err error) {
|
|
||||||
w.output.WriteOutput(string(p))
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func lineLog(f *os.File, output func(string)) {
|
|
||||||
const logSize = 1024 // matches android/log.h.
|
|
||||||
r := bufio.NewReaderSize(f, logSize)
|
|
||||||
for {
|
|
||||||
line, _, err := r.ReadLine()
|
|
||||||
str := string(line)
|
|
||||||
if err != nil {
|
|
||||||
str += " " + err.Error()
|
|
||||||
}
|
|
||||||
output(str)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LogClient struct {
|
|
||||||
sockPath string
|
|
||||||
handler LogClientHandler
|
|
||||||
conn net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogClientHandler interface {
|
|
||||||
Connected()
|
|
||||||
Disconnected()
|
|
||||||
WriteLog(message string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogClient(sharedDirectory string, handler LogClientHandler) *LogClient {
|
|
||||||
return &LogClient{
|
|
||||||
sockPath: filepath.Join(sharedDirectory, "log.sock"),
|
|
||||||
handler: handler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LogClient) Connect() error {
|
|
||||||
conn, err := net.DialUnix("unix", nil, &net.UnixAddr{
|
|
||||||
Name: c.sockPath,
|
|
||||||
Net: "unix",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.conn = conn
|
|
||||||
go c.loopConnection(&messageConn{conn})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LogClient) Disconnect() error {
|
|
||||||
return common.Close(c.conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *LogClient) loopConnection(conn *messageConn) {
|
|
||||||
c.handler.Connected()
|
|
||||||
defer c.handler.Disconnected()
|
|
||||||
for {
|
|
||||||
message, err := conn.Read()
|
|
||||||
if err != nil {
|
|
||||||
c.handler.WriteLog("(log client error) " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.handler.WriteLog(string(message))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
//go:build ios
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/observable"
|
|
||||||
"github.com/sagernet/sing/common/x/list"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LogServer struct {
|
|
||||||
sockPath string
|
|
||||||
listener net.Listener
|
|
||||||
|
|
||||||
access sync.Mutex
|
|
||||||
savedLines *list.List[string]
|
|
||||||
subscriber *observable.Subscriber[string]
|
|
||||||
observer *observable.Observer[string]
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogServer(sharedDirectory string) *LogServer {
|
|
||||||
server := &LogServer{
|
|
||||||
sockPath: filepath.Join(sharedDirectory, "log.sock"),
|
|
||||||
savedLines: new(list.List[string]),
|
|
||||||
subscriber: observable.NewSubscriber[string](128),
|
|
||||||
}
|
|
||||||
server.observer = observable.NewObserver[string](server.subscriber, 64)
|
|
||||||
return server
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LogServer) Start() error {
|
|
||||||
os.Remove(s.sockPath)
|
|
||||||
listener, err := net.ListenUnix("unix", &net.UnixAddr{
|
|
||||||
Name: s.sockPath,
|
|
||||||
Net: "unix",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go s.loopConnection(listener)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LogServer) Close() error {
|
|
||||||
return s.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LogServer) WriteMessage(message string) {
|
|
||||||
s.subscriber.Emit(message)
|
|
||||||
s.access.Lock()
|
|
||||||
s.savedLines.PushBack(message)
|
|
||||||
if s.savedLines.Len() > 100 {
|
|
||||||
s.savedLines.Remove(s.savedLines.Front())
|
|
||||||
}
|
|
||||||
s.access.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LogServer) loopConnection(listener net.Listener) {
|
|
||||||
for {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
hErr := s.handleConnection(&messageConn{conn})
|
|
||||||
if hErr != nil && !E.IsClosed(err) {
|
|
||||||
log.Warn("log-server: process connection: ", hErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *LogServer) handleConnection(conn *messageConn) error {
|
|
||||||
var savedLines []string
|
|
||||||
s.access.Lock()
|
|
||||||
savedLines = make([]string, 0, s.savedLines.Len())
|
|
||||||
for element := s.savedLines.Front(); element != nil; element = element.Next() {
|
|
||||||
savedLines = append(savedLines, element.Value)
|
|
||||||
}
|
|
||||||
s.access.Unlock()
|
|
||||||
subscription, done, err := s.observer.Subscribe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer s.observer.UnSubscribe(subscription)
|
|
||||||
for _, line := range savedLines {
|
|
||||||
err = conn.Write([]byte(line))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case message := <-subscription:
|
|
||||||
err = conn.Write([]byte(message))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-done:
|
|
||||||
conn.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type messageConn struct {
|
|
||||||
net.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *messageConn) Read() ([]byte, error) {
|
|
||||||
var messageLength uint16
|
|
||||||
err := binary.Read(c.Conn, binary.BigEndian, &messageLength)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := make([]byte, messageLength)
|
|
||||||
_, err = io.ReadFull(c.Conn, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *messageConn) Write(message []byte) error {
|
|
||||||
err := binary.Write(c.Conn, binary.BigEndian, uint16(len(message)))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = c.Conn.Write(message)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
type PlatformInterface interface {
|
|
||||||
AutoDetectInterfaceControl(fd int32) error
|
|
||||||
OpenTun(options TunOptions) (TunInterface, error)
|
|
||||||
WriteLog(message string)
|
|
||||||
UseProcFS() bool
|
|
||||||
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
|
||||||
PackageNameByUid(uid int32) (string, error)
|
|
||||||
UIDByPackageName(packageName string) (int32, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TunInterface interface {
|
|
||||||
FileDescriptor() int32
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package platform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Interface interface {
|
|
||||||
AutoDetectInterfaceControl() control.Func
|
|
||||||
OpenTun(options tun.Options) (tun.Tun, error)
|
|
||||||
process.Searcher
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
//go:build debug
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PProfServer struct {
|
|
||||||
server *http.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPProfServer(port int) *PProfServer {
|
|
||||||
return &PProfServer{
|
|
||||||
&http.Server{
|
|
||||||
Addr: ":" + strconv.Itoa(port),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PProfServer) Start() error {
|
|
||||||
ln, err := net.Listen("tcp", s.server.Addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go s.server.Serve(ln)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PProfServer) Close() error {
|
|
||||||
return s.server.Close()
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
//go:build !debug
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PProfServer struct{}
|
|
||||||
|
|
||||||
func NewPProfServer(port int) *PProfServer {
|
|
||||||
return &PProfServer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PProfServer) Start() error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PProfServer) Close() error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BoxService struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
instance *box.Box
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
|
||||||
options, err := parseConfig(configContent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
platformInterface.WriteLog("Hello " + runtime.GOOS + "/" + runtime.GOARCH)
|
|
||||||
options.PlatformInterface = &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()}
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
instance, err := box.New(ctx, options)
|
|
||||||
if err != nil {
|
|
||||||
cancel()
|
|
||||||
return nil, E.Cause(err, "create service")
|
|
||||||
}
|
|
||||||
return &BoxService{
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
instance: instance,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BoxService) Start() error {
|
|
||||||
return s.instance.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BoxService) Close() error {
|
|
||||||
s.cancel()
|
|
||||||
return s.instance.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ platform.Interface = (*platformInterfaceWrapper)(nil)
|
|
||||||
|
|
||||||
type platformInterfaceWrapper struct {
|
|
||||||
iif PlatformInterface
|
|
||||||
useProcFS bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
|
||||||
return func(network, address string, conn syscall.RawConn) error {
|
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
|
||||||
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) OpenTun(options tun.Options) (tun.Tun, error) {
|
|
||||||
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
|
||||||
return nil, E.New("android: unsupported uid options")
|
|
||||||
}
|
|
||||||
if len(options.IncludeAndroidUser) > 0 {
|
|
||||||
return nil, E.New("android: unsupported android_user option")
|
|
||||||
}
|
|
||||||
|
|
||||||
optionsWrapper := tunOptions(options)
|
|
||||||
tunInterface, err := w.iif.OpenTun(&optionsWrapper)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
tunFd := tunInterface.FileDescriptor()
|
|
||||||
return &nativeTun{
|
|
||||||
tunFd: int(tunFd),
|
|
||||||
tunFile: os.NewFile(uintptr(tunFd), "tun"),
|
|
||||||
tunMTU: options.MTU,
|
|
||||||
closer: tunInterface,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) Write(p []byte) (n int, err error) {
|
|
||||||
w.iif.WriteLog(string(p))
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {
|
|
||||||
var uid int32
|
|
||||||
if w.useProcFS {
|
|
||||||
uid = procfs.ResolveSocketByProcSearch(network, source, destination)
|
|
||||||
if uid == -1 {
|
|
||||||
return nil, E.New("procfs: not found")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var ipProtocol int32
|
|
||||||
switch N.NetworkName(network) {
|
|
||||||
case N.NetworkTCP:
|
|
||||||
ipProtocol = syscall.IPPROTO_TCP
|
|
||||||
case N.NetworkUDP:
|
|
||||||
ipProtocol = syscall.IPPROTO_UDP
|
|
||||||
default:
|
|
||||||
return nil, E.New("unknown network: ", network)
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
uid, err = w.iif.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packageName, _ := w.iif.PackageNameByUid(uid)
|
|
||||||
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import C "github.com/sagernet/sing-box/constant"
|
|
||||||
|
|
||||||
func SetBasePath(path string) {
|
|
||||||
C.SetBasePath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Version() string {
|
|
||||||
return C.Version
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/netip"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TunOptions interface {
|
|
||||||
GetInet4Address() RoutePrefixIterator
|
|
||||||
GetInet6Address() RoutePrefixIterator
|
|
||||||
GetDNSServerAddress() (string, error)
|
|
||||||
GetMTU() int32
|
|
||||||
GetAutoRoute() bool
|
|
||||||
GetStrictRoute() bool
|
|
||||||
GetInet4RouteAddress() RoutePrefixIterator
|
|
||||||
GetInet6RouteAddress() RoutePrefixIterator
|
|
||||||
GetIncludePackage() StringIterator
|
|
||||||
GetExcludePackage() StringIterator
|
|
||||||
}
|
|
||||||
|
|
||||||
type RoutePrefix struct {
|
|
||||||
Address string
|
|
||||||
Prefix int32
|
|
||||||
}
|
|
||||||
|
|
||||||
type RoutePrefixIterator interface {
|
|
||||||
Next() *RoutePrefix
|
|
||||||
HasNext() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapRoutePrefix(prefixes []netip.Prefix) RoutePrefixIterator {
|
|
||||||
return newIterator(common.Map(prefixes, func(prefix netip.Prefix) *RoutePrefix {
|
|
||||||
return &RoutePrefix{
|
|
||||||
Address: prefix.Addr().String(),
|
|
||||||
Prefix: int32(prefix.Bits()),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ TunOptions = (*tunOptions)(nil)
|
|
||||||
|
|
||||||
type tunOptions tun.Options
|
|
||||||
|
|
||||||
func (o *tunOptions) GetInet4Address() RoutePrefixIterator {
|
|
||||||
return mapRoutePrefix(o.Inet4Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetInet6Address() RoutePrefixIterator {
|
|
||||||
return mapRoutePrefix(o.Inet6Address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetDNSServerAddress() (string, error) {
|
|
||||||
if len(o.Inet4Address) == 0 || o.Inet4Address[0].Bits() == 32 {
|
|
||||||
return "", E.New("need one more IPv4 address for DNS hijacking")
|
|
||||||
}
|
|
||||||
return o.Inet4Address[0].Addr().Next().String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetMTU() int32 {
|
|
||||||
return int32(o.MTU)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetAutoRoute() bool {
|
|
||||||
return o.AutoRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetStrictRoute() bool {
|
|
||||||
return o.StrictRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetInet4RouteAddress() RoutePrefixIterator {
|
|
||||||
return mapRoutePrefix(o.Inet4RouteAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetInet6RouteAddress() RoutePrefixIterator {
|
|
||||||
return mapRoutePrefix(o.Inet6RouteAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetIncludePackage() StringIterator {
|
|
||||||
return newIterator(o.IncludePackage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *tunOptions) GetExcludePackage() StringIterator {
|
|
||||||
return newIterator(o.ExcludePackage)
|
|
||||||
}
|
|
||||||
|
|
||||||
type nativeTun struct {
|
|
||||||
tunFd int
|
|
||||||
tunFile *os.File
|
|
||||||
tunMTU uint32
|
|
||||||
closer io.Closer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *nativeTun) Read(p []byte) (n int, err error) {
|
|
||||||
return t.tunFile.Read(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *nativeTun) Write(p []byte) (n int, err error) {
|
|
||||||
return t.tunFile.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *nativeTun) Close() error {
|
|
||||||
return t.closer.Close()
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
//go:build with_gvisor && linux
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sagernet/sing-tun"
|
|
||||||
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/link/fdbased"
|
|
||||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ tun.GVisorTun = (*nativeTun)(nil)
|
|
||||||
|
|
||||||
func (t *nativeTun) NewEndpoint() (stack.LinkEndpoint, error) {
|
|
||||||
return fdbased.New(&fdbased.Options{
|
|
||||||
FDs: []int{t.tunFd},
|
|
||||||
MTU: t.tunMTU,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
24
experimental/ssmapi.go
Normal file
24
experimental/ssmapi.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package experimental
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SSMServerConstructor = func(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error)
|
||||||
|
|
||||||
|
var ssmServerConstructor SSMServerConstructor
|
||||||
|
|
||||||
|
func RegisterSSMServerConstructor(constructor SSMServerConstructor) {
|
||||||
|
ssmServerConstructor = constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSSMServer(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error) {
|
||||||
|
if ssmServerConstructor == nil {
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
return ssmServerConstructor(router, logger, options)
|
||||||
|
}
|
||||||
214
experimental/ssmapi/api.go
Normal file
214
experimental/ssmapi/api.go
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
package ssmapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) setupRoutes(r chi.Router) {
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Get("/", s.getServerInfo)
|
||||||
|
|
||||||
|
r.Get("/nodes", s.getNodes)
|
||||||
|
r.Post("/nodes", s.addNode)
|
||||||
|
r.Get("/nodes/{id}", s.getNode)
|
||||||
|
r.Put("/nodes/{id}", s.updateNode)
|
||||||
|
r.Delete("/nodes/{id}", s.deleteNode)
|
||||||
|
|
||||||
|
r.Get("/users", s.listUser)
|
||||||
|
r.Post("/users", s.addUser)
|
||||||
|
r.Get("/users/{username}", s.getUser)
|
||||||
|
r.Put("/users/{username}", s.updateUser)
|
||||||
|
r.Delete("/users/{username}", s.deleteUser)
|
||||||
|
|
||||||
|
r.Get("/stats", s.getStats)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getServerInfo(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
render.JSON(writer, request, render.M{
|
||||||
|
"server": "sing-box",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"_sing_box_version": C.Version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getNodes(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var response struct {
|
||||||
|
Protocols []string `json:"protocols"`
|
||||||
|
Shadowsocks []ShadowsocksNodeObject `json:"shadowsocks,omitempty"`
|
||||||
|
}
|
||||||
|
for _, node := range s.nodes {
|
||||||
|
if !common.Contains(response.Protocols, node.Protocol()) {
|
||||||
|
response.Protocols = append(response.Protocols, node.Protocol())
|
||||||
|
}
|
||||||
|
switch node.Protocol() {
|
||||||
|
case C.TypeShadowsocks:
|
||||||
|
response.Shadowsocks = append(response.Shadowsocks, node.Shadowsocks())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render.JSON(writer, request, &response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) addNode(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getNode(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
nodeID := chi.URLParam(request, "id")
|
||||||
|
if nodeID == "" {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, node := range s.nodes {
|
||||||
|
if nodeID == node.ID() {
|
||||||
|
render.JSON(writer, request, render.M{
|
||||||
|
node.Protocol(): node.Object(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) updateNode(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) deleteNode(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSMUserObject struct {
|
||||||
|
UserName string `json:"username"`
|
||||||
|
Password string `json:"uPSK,omitempty"`
|
||||||
|
DownlinkBytes int64 `json:"downlinkBytes"`
|
||||||
|
UplinkBytes int64 `json:"uplinkBytes"`
|
||||||
|
|
||||||
|
DownlinkPackets int64 `json:"downlinkPackets"`
|
||||||
|
UplinkPackets int64 `json:"uplinkPackets"`
|
||||||
|
TCPSessions int64 `json:"tcpSessions"`
|
||||||
|
UDPSessions int64 `json:"udpSessions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) listUser(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
render.JSON(writer, request, render.M{
|
||||||
|
"users": s.userManager.List(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) addUser(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
var addRequest struct {
|
||||||
|
UserName string `json:"username"`
|
||||||
|
Password string `json:"uPSK"`
|
||||||
|
}
|
||||||
|
err := render.DecodeJSON(request.Body, &addRequest)
|
||||||
|
if err != nil {
|
||||||
|
render.Status(request, http.StatusBadRequest)
|
||||||
|
render.PlainText(writer, request, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.userManager.Add(addRequest.UserName, addRequest.Password)
|
||||||
|
if err != nil {
|
||||||
|
render.Status(request, http.StatusBadRequest)
|
||||||
|
render.PlainText(writer, request, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.WriteHeader(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getUser(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
userName := chi.URLParam(request, "username")
|
||||||
|
if userName == "" {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uPSK, loaded := s.userManager.Get(userName)
|
||||||
|
if !loaded {
|
||||||
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := SSMUserObject{
|
||||||
|
UserName: userName,
|
||||||
|
Password: uPSK,
|
||||||
|
}
|
||||||
|
s.trafficManager.ReadUser(&user)
|
||||||
|
render.JSON(writer, request, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) updateUser(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
userName := chi.URLParam(request, "username")
|
||||||
|
if userName == "" {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var updateRequest struct {
|
||||||
|
Password string `json:"uPSK"`
|
||||||
|
}
|
||||||
|
err := render.DecodeJSON(request.Body, &updateRequest)
|
||||||
|
if err != nil {
|
||||||
|
render.Status(request, http.StatusBadRequest)
|
||||||
|
render.PlainText(writer, request, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, loaded := s.userManager.Get(userName)
|
||||||
|
if !loaded {
|
||||||
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = s.userManager.Update(userName, updateRequest.Password)
|
||||||
|
if err != nil {
|
||||||
|
render.Status(request, http.StatusBadRequest)
|
||||||
|
render.PlainText(writer, request, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) deleteUser(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
userName := chi.URLParam(request, "username")
|
||||||
|
if userName == "" {
|
||||||
|
writer.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, loaded := s.userManager.Get(userName)
|
||||||
|
if !loaded {
|
||||||
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err := s.userManager.Delete(userName)
|
||||||
|
if err != nil {
|
||||||
|
render.Status(request, http.StatusBadRequest)
|
||||||
|
render.PlainText(writer, request, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) getStats(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
requireClear := chi.URLParam(request, "clear") == "true"
|
||||||
|
|
||||||
|
users := s.userManager.List()
|
||||||
|
s.trafficManager.ReadUsers(users)
|
||||||
|
for i := range users {
|
||||||
|
users[i].Password = ""
|
||||||
|
}
|
||||||
|
uplinkBytes, downlinkBytes, uplinkPackets, downlinkPackets, tcpSessions, udpSessions := s.trafficManager.ReadGlobal()
|
||||||
|
|
||||||
|
if requireClear {
|
||||||
|
s.trafficManager.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
render.JSON(writer, request, render.M{
|
||||||
|
"uplinkBytes": uplinkBytes,
|
||||||
|
"downlinkBytes": downlinkBytes,
|
||||||
|
"uplinkPackets": uplinkPackets,
|
||||||
|
"downlinkPackets": downlinkPackets,
|
||||||
|
"tcpSessions": tcpSessions,
|
||||||
|
"udpSessions": udpSessions,
|
||||||
|
"users": users,
|
||||||
|
})
|
||||||
|
}
|
||||||
117
experimental/ssmapi/server.go
Normal file
117
experimental/ssmapi/server.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package ssmapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/experimental"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
experimental.RegisterSSMServerConstructor(NewServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ adapter.SSMServer = (*Server)(nil)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
router adapter.Router
|
||||||
|
logger log.Logger
|
||||||
|
httpServer *http.Server
|
||||||
|
tcpListener net.Listener
|
||||||
|
|
||||||
|
nodes []Node
|
||||||
|
userManager *UserManager
|
||||||
|
trafficManager *TrafficManager
|
||||||
|
}
|
||||||
|
|
||||||
|
type Node interface {
|
||||||
|
Protocol() string
|
||||||
|
ID() string
|
||||||
|
Shadowsocks() ShadowsocksNodeObject
|
||||||
|
Object() any
|
||||||
|
Tag() string
|
||||||
|
UpdateUsers(users []string, uPSKs []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error) {
|
||||||
|
chiRouter := chi.NewRouter()
|
||||||
|
server := &Server{
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
httpServer: &http.Server{
|
||||||
|
Addr: options.Listen,
|
||||||
|
Handler: chiRouter,
|
||||||
|
},
|
||||||
|
nodes: make([]Node, 0, len(options.Nodes)),
|
||||||
|
}
|
||||||
|
for i, nodeOptions := range options.Nodes {
|
||||||
|
switch nodeOptions.Type {
|
||||||
|
case C.TypeShadowsocks:
|
||||||
|
ssOptions := nodeOptions.ShadowsocksOptions
|
||||||
|
inbound, loaded := router.Inbound(ssOptions.Inbound)
|
||||||
|
if !loaded {
|
||||||
|
return nil, E.New("parse SSM node[", i, "]: inbound", ssOptions.Inbound, "not found")
|
||||||
|
}
|
||||||
|
ssInbound, isSS := inbound.(adapter.ManagedShadowsocksServer)
|
||||||
|
if !isSS {
|
||||||
|
return nil, E.New("parse SSM node[", i, "]: inbound", ssOptions.Inbound, "is not a shadowsocks inbound")
|
||||||
|
}
|
||||||
|
node := &ShadowsocksNode{
|
||||||
|
ssOptions,
|
||||||
|
ssInbound,
|
||||||
|
}
|
||||||
|
server.nodes = append(server.nodes, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.trafficManager = NewTrafficManager(server.nodes)
|
||||||
|
server.userManager = NewUserManager(server.nodes, server.trafficManager)
|
||||||
|
listenPrefix := options.ListenPrefix
|
||||||
|
if !strings.HasPrefix(listenPrefix, "/") {
|
||||||
|
listenPrefix = "/" + listenPrefix
|
||||||
|
}
|
||||||
|
chiRouter.Route(listenPrefix+"server/v1", server.setupRoutes)
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() error {
|
||||||
|
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.logger.Info("ssm-api started at ", listener.Addr())
|
||||||
|
s.tcpListener = listener
|
||||||
|
go func() {
|
||||||
|
err = s.httpServer.Serve(listener)
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
s.logger.Error("ssm-api serve error: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
common.PtrOrNil(s.httpServer),
|
||||||
|
s.tcpListener,
|
||||||
|
s.trafficManager,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RoutedConnection(metadata adapter.InboundContext, conn net.Conn) net.Conn {
|
||||||
|
return s.trafficManager.RoutedConnection(metadata, conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RoutedPacketConnection(metadata adapter.InboundContext, conn N.PacketConn) N.PacketConn {
|
||||||
|
return s.trafficManager.RoutedPacketConnection(metadata, conn)
|
||||||
|
}
|
||||||
54
experimental/ssmapi/shadowsocks.go
Normal file
54
experimental/ssmapi/shadowsocks.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package ssmapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Node = (*ShadowsocksNode)(nil)
|
||||||
|
|
||||||
|
type ShadowsocksNode struct {
|
||||||
|
node option.SSMShadowsocksNode
|
||||||
|
inbound adapter.ManagedShadowsocksServer
|
||||||
|
}
|
||||||
|
|
||||||
|
type ShadowsocksNodeObject struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Endpoint string `json:"endpoint,omitempty"`
|
||||||
|
Method string `json:"method,omitempty"`
|
||||||
|
Passwords []string `json:"iPSKs,omitempty"`
|
||||||
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ShadowsocksNode) Protocol() string {
|
||||||
|
return C.TypeShadowsocks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ShadowsocksNode) ID() string {
|
||||||
|
return n.node.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ShadowsocksNode) Shadowsocks() ShadowsocksNodeObject {
|
||||||
|
return ShadowsocksNodeObject{
|
||||||
|
ID: n.node.ID,
|
||||||
|
Name: n.node.Name,
|
||||||
|
Endpoint: n.node.Address,
|
||||||
|
Method: n.inbound.Method(),
|
||||||
|
Passwords: []string{n.inbound.Password()},
|
||||||
|
Tags: n.node.Tags,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ShadowsocksNode) Object() any {
|
||||||
|
return n.Shadowsocks()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ShadowsocksNode) Tag() string {
|
||||||
|
return n.inbound.Tag()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ShadowsocksNode) UpdateUsers(users []string, uPSKs []string) error {
|
||||||
|
return n.inbound.UpdateUsers(users, uPSKs)
|
||||||
|
}
|
||||||
227
experimental/ssmapi/traffic.go
Normal file
227
experimental/ssmapi/traffic.go
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
package ssmapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/experimental/trackerconn"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrafficManager struct {
|
||||||
|
nodeTags map[string]bool
|
||||||
|
nodeUsers map[string]bool
|
||||||
|
globalUplink *atomic.Int64
|
||||||
|
globalDownlink *atomic.Int64
|
||||||
|
globalUplinkPackets *atomic.Int64
|
||||||
|
globalDownlinkPackets *atomic.Int64
|
||||||
|
globalTCPSessions *atomic.Int64
|
||||||
|
globalUDPSessions *atomic.Int64
|
||||||
|
userAccess sync.Mutex
|
||||||
|
userUplink map[string]*atomic.Int64
|
||||||
|
userDownlink map[string]*atomic.Int64
|
||||||
|
userUplinkPackets map[string]*atomic.Int64
|
||||||
|
userDownlinkPackets map[string]*atomic.Int64
|
||||||
|
userTCPSessions map[string]*atomic.Int64
|
||||||
|
userUDPSessions map[string]*atomic.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrafficManager(nodes []Node) *TrafficManager {
|
||||||
|
manager := &TrafficManager{
|
||||||
|
nodeTags: make(map[string]bool),
|
||||||
|
globalUplink: atomic.NewInt64(0),
|
||||||
|
globalDownlink: atomic.NewInt64(0),
|
||||||
|
globalUplinkPackets: atomic.NewInt64(0),
|
||||||
|
globalDownlinkPackets: atomic.NewInt64(0),
|
||||||
|
globalTCPSessions: atomic.NewInt64(0),
|
||||||
|
globalUDPSessions: atomic.NewInt64(0),
|
||||||
|
userUplink: make(map[string]*atomic.Int64),
|
||||||
|
userDownlink: make(map[string]*atomic.Int64),
|
||||||
|
userUplinkPackets: make(map[string]*atomic.Int64),
|
||||||
|
userDownlinkPackets: make(map[string]*atomic.Int64),
|
||||||
|
userTCPSessions: make(map[string]*atomic.Int64),
|
||||||
|
userUDPSessions: make(map[string]*atomic.Int64),
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
manager.nodeTags[node.Tag()] = true
|
||||||
|
}
|
||||||
|
return manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) UpdateUsers(users []string) {
|
||||||
|
nodeUsers := make(map[string]bool)
|
||||||
|
for _, user := range users {
|
||||||
|
nodeUsers[user] = true
|
||||||
|
}
|
||||||
|
s.nodeUsers = nodeUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) userCounter(user string) (*atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64, *atomic.Int64) {
|
||||||
|
s.userAccess.Lock()
|
||||||
|
defer s.userAccess.Unlock()
|
||||||
|
upCounter, loaded := s.userUplink[user]
|
||||||
|
if !loaded {
|
||||||
|
upCounter = atomic.NewInt64(0)
|
||||||
|
s.userUplink[user] = upCounter
|
||||||
|
}
|
||||||
|
downCounter, loaded := s.userDownlink[user]
|
||||||
|
if !loaded {
|
||||||
|
downCounter = atomic.NewInt64(0)
|
||||||
|
s.userDownlink[user] = downCounter
|
||||||
|
}
|
||||||
|
upPacketsCounter, loaded := s.userUplinkPackets[user]
|
||||||
|
if !loaded {
|
||||||
|
upPacketsCounter = atomic.NewInt64(0)
|
||||||
|
s.userUplinkPackets[user] = upPacketsCounter
|
||||||
|
}
|
||||||
|
downPacketsCounter, loaded := s.userDownlinkPackets[user]
|
||||||
|
if !loaded {
|
||||||
|
downPacketsCounter = atomic.NewInt64(0)
|
||||||
|
s.userDownlinkPackets[user] = downPacketsCounter
|
||||||
|
}
|
||||||
|
tcpSessionsCounter, loaded := s.userTCPSessions[user]
|
||||||
|
if !loaded {
|
||||||
|
tcpSessionsCounter = atomic.NewInt64(0)
|
||||||
|
s.userTCPSessions[user] = tcpSessionsCounter
|
||||||
|
}
|
||||||
|
udpSessionsCounter, loaded := s.userUDPSessions[user]
|
||||||
|
if !loaded {
|
||||||
|
udpSessionsCounter = atomic.NewInt64(0)
|
||||||
|
s.userUDPSessions[user] = udpSessionsCounter
|
||||||
|
}
|
||||||
|
return upCounter, downCounter, upPacketsCounter, downPacketsCounter, tcpSessionsCounter, udpSessionsCounter
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCounter(counterList []*atomic.Int64, packetCounterList []*atomic.Int64) func(n int64) {
|
||||||
|
return func(n int64) {
|
||||||
|
for _, counter := range counterList {
|
||||||
|
counter.Add(n)
|
||||||
|
}
|
||||||
|
for _, counter := range packetCounterList {
|
||||||
|
counter.Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) RoutedConnection(metadata adapter.InboundContext, conn net.Conn) net.Conn {
|
||||||
|
s.globalTCPSessions.Inc()
|
||||||
|
|
||||||
|
var readCounter []*atomic.Int64
|
||||||
|
var writeCounter []*atomic.Int64
|
||||||
|
|
||||||
|
if s.nodeTags[metadata.Inbound] {
|
||||||
|
readCounter = append(readCounter, s.globalUplink)
|
||||||
|
writeCounter = append(writeCounter, s.globalDownlink)
|
||||||
|
}
|
||||||
|
if s.nodeUsers[metadata.User] {
|
||||||
|
upCounter, downCounter, _, _, tcpSessionCounter, _ := s.userCounter(metadata.User)
|
||||||
|
readCounter = append(readCounter, upCounter)
|
||||||
|
writeCounter = append(writeCounter, downCounter)
|
||||||
|
tcpSessionCounter.Inc()
|
||||||
|
}
|
||||||
|
if len(readCounter) > 0 {
|
||||||
|
return trackerconn.New(conn, readCounter, writeCounter)
|
||||||
|
}
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) RoutedPacketConnection(metadata adapter.InboundContext, conn N.PacketConn) N.PacketConn {
|
||||||
|
s.globalUDPSessions.Inc()
|
||||||
|
|
||||||
|
var readCounter []*atomic.Int64
|
||||||
|
var readPacketCounter []*atomic.Int64
|
||||||
|
var writeCounter []*atomic.Int64
|
||||||
|
var writePacketCounter []*atomic.Int64
|
||||||
|
|
||||||
|
if s.nodeTags[metadata.Inbound] {
|
||||||
|
readCounter = append(readCounter, s.globalUplink)
|
||||||
|
writeCounter = append(writeCounter, s.globalDownlink)
|
||||||
|
readPacketCounter = append(readPacketCounter, s.globalUplinkPackets)
|
||||||
|
writePacketCounter = append(writePacketCounter, s.globalDownlinkPackets)
|
||||||
|
}
|
||||||
|
if s.nodeUsers[metadata.User] {
|
||||||
|
upCounter, downCounter, upPacketsCounter, downPacketsCounter, _, udpSessionCounter := s.userCounter(metadata.User)
|
||||||
|
readCounter = append(readCounter, upCounter)
|
||||||
|
writeCounter = append(writeCounter, downCounter)
|
||||||
|
readPacketCounter = append(readPacketCounter, upPacketsCounter)
|
||||||
|
writePacketCounter = append(writePacketCounter, downPacketsCounter)
|
||||||
|
udpSessionCounter.Inc()
|
||||||
|
}
|
||||||
|
if len(readCounter) > 0 {
|
||||||
|
return trackerconn.NewHookPacket(conn, createCounter(readCounter, readPacketCounter), createCounter(writeCounter, writePacketCounter))
|
||||||
|
}
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) ReadUser(user *SSMUserObject) {
|
||||||
|
s.userAccess.Lock()
|
||||||
|
defer s.userAccess.Unlock()
|
||||||
|
|
||||||
|
s.readUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) readUser(user *SSMUserObject) {
|
||||||
|
if counter, loaded := s.userUplink[user.UserName]; loaded {
|
||||||
|
user.UplinkBytes = counter.Load()
|
||||||
|
}
|
||||||
|
if counter, loaded := s.userDownlink[user.UserName]; loaded {
|
||||||
|
user.DownlinkBytes = counter.Load()
|
||||||
|
}
|
||||||
|
if counter, loaded := s.userUplinkPackets[user.UserName]; loaded {
|
||||||
|
user.UplinkPackets = counter.Load()
|
||||||
|
}
|
||||||
|
if counter, loaded := s.userDownlinkPackets[user.UserName]; loaded {
|
||||||
|
user.DownlinkPackets = counter.Load()
|
||||||
|
}
|
||||||
|
if counter, loaded := s.userTCPSessions[user.UserName]; loaded {
|
||||||
|
user.TCPSessions = counter.Load()
|
||||||
|
}
|
||||||
|
if counter, loaded := s.userUDPSessions[user.UserName]; loaded {
|
||||||
|
user.UDPSessions = counter.Load()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) ReadUsers(users []*SSMUserObject) {
|
||||||
|
s.userAccess.Lock()
|
||||||
|
defer s.userAccess.Unlock()
|
||||||
|
for _, user := range users {
|
||||||
|
s.readUser(user)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) ReadGlobal() (
|
||||||
|
uplinkBytes int64,
|
||||||
|
downlinkBytes int64,
|
||||||
|
uplinkPackets int64,
|
||||||
|
downlinkPackets int64,
|
||||||
|
tcpSessions int64,
|
||||||
|
udpSessions int64,
|
||||||
|
) {
|
||||||
|
return s.globalUplink.Load(),
|
||||||
|
s.globalDownlink.Load(),
|
||||||
|
s.globalUplinkPackets.Load(),
|
||||||
|
s.globalDownlinkPackets.Load(),
|
||||||
|
s.globalTCPSessions.Load(),
|
||||||
|
s.globalUDPSessions.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *TrafficManager) Clear() {
|
||||||
|
s.globalUplink.Store(0)
|
||||||
|
s.globalDownlink.Store(0)
|
||||||
|
s.globalUplinkPackets.Store(0)
|
||||||
|
s.globalDownlinkPackets.Store(0)
|
||||||
|
s.globalTCPSessions.Store(0)
|
||||||
|
s.globalUDPSessions.Store(0)
|
||||||
|
s.userAccess.Lock()
|
||||||
|
defer s.userAccess.Unlock()
|
||||||
|
s.userUplink = make(map[string]*atomic.Int64)
|
||||||
|
s.userDownlink = make(map[string]*atomic.Int64)
|
||||||
|
s.userUplinkPackets = make(map[string]*atomic.Int64)
|
||||||
|
s.userDownlinkPackets = make(map[string]*atomic.Int64)
|
||||||
|
s.userTCPSessions = make(map[string]*atomic.Int64)
|
||||||
|
s.userUDPSessions = make(map[string]*atomic.Int64)
|
||||||
|
}
|
||||||
86
experimental/ssmapi/user.go
Normal file
86
experimental/ssmapi/user.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package ssmapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserManager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
usersMap map[string]string
|
||||||
|
nodes []Node
|
||||||
|
trafficManager *TrafficManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserManager(nodes []Node, trafficManager *TrafficManager) *UserManager {
|
||||||
|
return &UserManager{
|
||||||
|
usersMap: make(map[string]string),
|
||||||
|
nodes: nodes,
|
||||||
|
trafficManager: trafficManager,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserManager) postUpdate() error {
|
||||||
|
users := make([]string, 0, len(m.usersMap))
|
||||||
|
uPSKs := make([]string, 0, len(m.usersMap))
|
||||||
|
for username, password := range m.usersMap {
|
||||||
|
users = append(users, username)
|
||||||
|
uPSKs = append(uPSKs, password)
|
||||||
|
}
|
||||||
|
for _, node := range m.nodes {
|
||||||
|
err := node.UpdateUsers(users, uPSKs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.trafficManager.UpdateUsers(users)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserManager) List() []*SSMUserObject {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
|
||||||
|
users := make([]*SSMUserObject, 0, len(m.usersMap))
|
||||||
|
for username, password := range m.usersMap {
|
||||||
|
users = append(users, &SSMUserObject{
|
||||||
|
UserName: username,
|
||||||
|
Password: password,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserManager) Add(username string, password string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if _, found := m.usersMap[username]; found {
|
||||||
|
return E.New("user", username, "already exists")
|
||||||
|
}
|
||||||
|
m.usersMap[username] = password
|
||||||
|
return m.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserManager) Get(username string) (string, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
if password, found := m.usersMap[username]; found {
|
||||||
|
return password, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserManager) Update(username string, password string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.usersMap[username] = password
|
||||||
|
return m.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *UserManager) Delete(username string) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
delete(m.usersMap, username)
|
||||||
|
return m.postUpdate()
|
||||||
|
}
|
||||||
22
go.mod
22
go.mod
@@ -14,25 +14,22 @@ require (
|
|||||||
github.com/go-chi/render v1.0.2
|
github.com/go-chi/render v1.0.2
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/hashicorp/yamux v0.1.1
|
github.com/hashicorp/yamux v0.1.1
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230220010740-598984875576
|
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mholt/acmez v1.1.0
|
github.com/mholt/acmez v1.1.0
|
||||||
github.com/miekg/dns v1.1.50
|
github.com/miekg/dns v1.1.50
|
||||||
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd
|
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0
|
github.com/oschwald/maxminddb-golang v1.10.0
|
||||||
github.com/pires/go-proxyproto v0.6.2
|
github.com/pires/go-proxyproto v0.6.2
|
||||||
|
github.com/refraction-networking/utls v1.2.2
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0
|
||||||
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/sing v0.1.8-0.20230221060643-3401d210384b
|
github.com/sagernet/sing v0.1.7-0.20230209132010-5f1ef3441c13
|
||||||
github.com/sagernet/sing-dns v0.1.4
|
github.com/sagernet/sing-dns v0.1.2-0.20230209132355-3c2e2957b455
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9
|
github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7
|
||||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587
|
|
||||||
github.com/sagernet/sing-tun v0.1.1
|
github.com/sagernet/sing-tun v0.1.1
|
||||||
github.com/sagernet/sing-vmess v0.1.2
|
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
||||||
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01
|
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
||||||
github.com/spf13/cobra v1.6.1
|
github.com/spf13/cobra v1.6.1
|
||||||
@@ -50,9 +47,11 @@ require (
|
|||||||
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
gvisor.dev/gvisor v0.0.0-20220901235040-6ca97ef2ce1c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//replace github.com/sagernet/sing => ../sing
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect
|
github.com/cloudflare/circl v1.2.1-0.20221019164342-6ab4dfed8f3c // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
@@ -67,7 +66,6 @@ require (
|
|||||||
github.com/libdns/libdns v0.2.1 // indirect
|
github.com/libdns/libdns v0.2.1 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // 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
|
||||||
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
github.com/quic-go/qtls-go1-18 v0.2.0 // indirect
|
||||||
@@ -76,7 +74,7 @@ require (
|
|||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 // indirect
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20230215032506-9aa6f7e2d72c // indirect
|
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f // indirect
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
golang.org/x/mod v0.6.0 // indirect
|
golang.org/x/mod v0.6.0 // indirect
|
||||||
|
|||||||
40
go.sum
40
go.sum
@@ -4,8 +4,8 @@ github.com/Dreamacro/clash v1.13.0 h1:gF0E0TluE1LCmuhhg0/bjqABYDmSnXkUjXjRhZxyrm
|
|||||||
github.com/Dreamacro/clash v1.13.0/go.mod h1:hf0RkWPsQ0e8oS8WVJBIRocY/1ILYzQQg9zeMwd8LsM=
|
github.com/Dreamacro/clash v1.13.0/go.mod h1:hf0RkWPsQ0e8oS8WVJBIRocY/1ILYzQQg9zeMwd8LsM=
|
||||||
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.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
|
github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
|
||||||
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
|
github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
|
||||||
@@ -59,8 +59,8 @@ github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Go
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230220010740-598984875576 h1:ehee0+xI4xtJTRJhN+PVA2GzCLB6KRgHRoZMg2lHwJU=
|
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 h1:Z72DOke2yOK0Ms4Z2LK1E1OrRJXOxSj5DllTz2FYTRg=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20230220010740-598984875576/go.mod h1:yGKD3yZIGAjEZXiIjVQ0SQ09Y/RzETOoqEOi6nXqX0Y=
|
github.com/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8/go.mod h1:m5WMe03WCvWcXjRnhvaAbAAXdCnu20J5P+mmH44ZzpE=
|
||||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
@@ -92,8 +92,6 @@ github.com/mholt/acmez v1.1.0 h1:IQ9CGHKOHokorxnffsqDvmmE30mDenO1lptYZ1AYkHY=
|
|||||||
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
github.com/mholt/acmez v1.1.0/go.mod h1:zwo5+fbLLTowAX8o8ETfQzbDtwGEXnPhkmGdKIP+bgs=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||||
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd h1:vd4qbG9ZTW10e1uqo8PDLshe5XL2yPhdINhGlJYaOoQ=
|
|
||||||
github.com/nekohasekai/reality v0.0.0-20230225080858-d70c703b04cd/go.mod h1:C+iqSNDBQ8qMhlNZ0JSUO9POEWq8qX87hukGfmO7/fA=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
|
||||||
@@ -101,8 +99,6 @@ github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7
|
|||||||
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
github.com/oschwald/maxminddb-golang v1.10.0 h1:Xp1u0ZhqkSuopaKmk1WwHtjF0H9Hd9181uj2MQ5Vndg=
|
||||||
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/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
|
||||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -116,37 +112,33 @@ github.com/quic-go/qtls-go1-19 v0.2.0 h1:Cvn2WdhyViFUHoOqK52i51k4nDX8EwIh5VJiVM4
|
|||||||
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
github.com/quic-go/qtls-go1-19 v0.2.0/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV54oAI=
|
||||||
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||||
|
github.com/refraction-networking/utls v1.2.2 h1:uBE6V173CwG8MQrSBpNZHAix1fxOvuLKYyjFAu3uqo0=
|
||||||
|
github.com/refraction-networking/utls v1.2.2/go.mod h1:L1goe44KvhnTfctUffM2isnJpSjPlYShrhXDeZaoYKw=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM=
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0 h1:KyhtFFt1Jtp5vW2ohNvstvQffTOQ/s5vENuGXzdA+TM=
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
|
github.com/sagernet/cloudflare-tls v0.0.0-20221031050923-d70792f4c3a0/go.mod h1:D4SFEOkJK+4W1v86ZhX0jPM0rAL498fyQAChqMtes/I=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61 h1:5+m7c6AkmAylhauulqN/c5dnh8/KssrE9c93TQrXldA=
|
||||||
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
github.com/sagernet/go-tun2socks v1.16.12-0.20220818015926-16cb67876a61/go.mod h1:QUQ4RRHD6hGGHdFMEtR8T2P6GS6R3D/CXKdaYHKKXms=
|
||||||
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca h1:w56+kf8BeqLqllrRJ1tdwKc3sCdWOn/DuNHpY9fAiqs=
|
|
||||||
github.com/sagernet/gomobile v0.0.0-20221130124640-349ebaa752ca/go.mod h1:5YE39YkJkCcMsfq1jMKkjsrM2GfBoF9JVWnvU89hmvU=
|
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE=
|
||||||
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32 h1:tztuJB+giOWNRKQEBVY2oI3PsheTooMdh+/yxemYQYY=
|
||||||
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8=
|
github.com/sagernet/quic-go v0.0.0-20230202071646-a8c8afb18b32/go.mod h1:QMCkxXAC3CvBgDZVIJp43NWTuwGBScCzMLVLynjERL8=
|
||||||
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
github.com/sagernet/sing v0.0.0-20220812082120-05f9836bff8f/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.0.0-20220817130738-ce854cda8522/go.mod h1:QVsS5L/ZA2Q5UhQwLrn0Trw+msNd/NPGEhBKR/ioWiY=
|
||||||
github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b h1:Ji2AfGlc4j9AitobOx4k3BCj7eS5nSxL1cgaL81zvlo=
|
github.com/sagernet/sing v0.1.7-0.20230209132010-5f1ef3441c13 h1:S/+YvJCEChwnckGhzqSrE/Q2m6aVWhkt1I4Pv2yCMVw=
|
||||||
github.com/sagernet/sing v0.1.8-0.20230221060643-3401d210384b/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
github.com/sagernet/sing v0.1.7-0.20230209132010-5f1ef3441c13/go.mod h1:jt1w2u7lJQFFSGLiRrRIs5YWmx4kAPfWuOejuDW9qMk=
|
||||||
github.com/sagernet/sing-dns v0.1.4 h1:7VxgeoSCiiazDSaXXQVcvrTBxFpOePPq/4XdgnUDN+0=
|
github.com/sagernet/sing-dns v0.1.2-0.20230209132355-3c2e2957b455 h1:VA/j2Jg+JURgKw2C1Dw2tpjfOZwbLXRy8PJRbJS/HEU=
|
||||||
github.com/sagernet/sing-dns v0.1.4/go.mod h1:1+6pCa48B1AI78lD+/i/dLgpw4MwfnsSpZo0Ds8wzzk=
|
github.com/sagernet/sing-dns v0.1.2-0.20230209132355-3c2e2957b455/go.mod h1:nonvn66ja+UNrQl3jzJy0EFRn15QUaCFAVXTXf6TgJ4=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9 h1:qS39eA4C7x+zhEkySbASrtmb6ebdy5v0y2M6mgkmSO0=
|
github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7 h1:Plup6oEiyLzY3HDqQ+QsUBzgBGdVmcsgf3t8h940z9U=
|
||||||
github.com/sagernet/sing-shadowsocks v0.1.2-0.20230221080503-769c01d6bba9/go.mod h1:f3mHTy5shnVM9l8UocMlJgC/1G/zdj5FuEuVXhDinGU=
|
github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7/go.mod h1:O5LtOs8Ivw686FqLpO0Zu+A0ROVE15VeqEK3yDRRAms=
|
||||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587 h1:OjIXlHT2bblZfp+ciupM4xY9+Ccpj9FsuHRtKRBv+Pg=
|
|
||||||
github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
|
|
||||||
github.com/sagernet/sing-tun v0.1.1 h1:2Hg3GAyJWzQ7Ua1j74dE+mI06vaqSBO9yD4tkTjggn4=
|
github.com/sagernet/sing-tun v0.1.1 h1:2Hg3GAyJWzQ7Ua1j74dE+mI06vaqSBO9yD4tkTjggn4=
|
||||||
github.com/sagernet/sing-tun v0.1.1/go.mod h1:WzW/SkT+Nh9uJn/FIYUE2YJHYuPwfbh8sATOzU9QDGw=
|
github.com/sagernet/sing-tun v0.1.1/go.mod h1:WzW/SkT+Nh9uJn/FIYUE2YJHYuPwfbh8sATOzU9QDGw=
|
||||||
github.com/sagernet/sing-vmess v0.1.2 h1:RbOZNAId2LrCai8epMoQXlf0XTrou0bfcw08hNBg6lM=
|
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb h1:oyd3w17fXNmWVYFUe17YVHJW5CLW9X2mxJFDP/IWrAM=
|
||||||
github.com/sagernet/sing-vmess v0.1.2/go.mod h1:9NSj8mZTx1JIY/HF9LaYRppUsVkysIN5tEFpNZujXxY=
|
github.com/sagernet/sing-vmess v0.1.1-0.20230212211128-cb4e47dd0acb/go.mod h1:9KkmnQzTL4Gvv8U2TRAH2BOITCGsGPpHtUPP5sxn5sY=
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195 h1:5VBIbVw9q7aKbrFdT83mjkyvQ+VaRsQ6yflTepfln38=
|
||||||
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
github.com/sagernet/smux v0.0.0-20220831015742-e0f1988e3195/go.mod h1:yedWtra8nyGJ+SyI+ziwuaGMzBatbB10P1IOOZbbSK8=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
||||||
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 h1:m4MI13+NRKddIvbdSN0sFHK8w5ROTa60Zi9diZ7EE08=
|
|
||||||
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e h1:7uw2njHFGE+VpWamge6o56j2RWk4omF6uLKKxMmcWvs=
|
||||||
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
github.com/sagernet/websocket v0.0.0-20220913015213-615516348b4e/go.mod h1:45TUl8+gH4SIKr4ykREbxKWTxkDlSzFENzctB1dVRRY=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
@@ -168,8 +160,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/u-root/uio v0.0.0-20230215032506-9aa6f7e2d72c h1:PHoGTnweZP+KIg/8Zc6+iOesrIF5yHkpb4GBDxHm7yE=
|
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f h1:dpx1PHxYqAnXzbryJrWP1NQLzEjwcVgFLhkknuFQ7ww=
|
||||||
github.com/u-root/uio v0.0.0-20230215032506-9aa6f7e2d72c/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/u-root/uio v0.0.0-20221213070652-c3537552635f/go.mod h1:IogEAUBXDEwX7oR/BMmCctShYs80ql4hF0ySdzGxf7E=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg=
|
||||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
|||||||
@@ -5,19 +5,18 @@ import (
|
|||||||
|
|
||||||
"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/experimental/libbox/platform"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) {
|
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, options option.Inbound) (adapter.Inbound, error) {
|
||||||
if options.Type == "" {
|
if options.Type == "" {
|
||||||
return nil, E.New("missing inbound type")
|
return nil, E.New("missing inbound type")
|
||||||
}
|
}
|
||||||
switch options.Type {
|
switch options.Type {
|
||||||
case C.TypeTun:
|
case C.TypeTun:
|
||||||
return NewTun(ctx, router, logger, options.Tag, options.TunOptions, platformInterface)
|
return NewTun(ctx, router, logger, options.Tag, options.TunOptions)
|
||||||
case C.TypeRedirect:
|
case C.TypeRedirect:
|
||||||
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
|
return NewRedirect(ctx, router, logger, options.Tag, options.RedirectOptions), nil
|
||||||
case C.TypeTProxy:
|
case C.TypeTProxy:
|
||||||
@@ -42,8 +41,6 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
|||||||
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
|
return NewHysteria(ctx, router, logger, options.Tag, options.HysteriaOptions)
|
||||||
case C.TypeShadowTLS:
|
case C.TypeShadowTLS:
|
||||||
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
return NewShadowTLS(ctx, router, logger, options.Tag, options.ShadowTLSOptions)
|
||||||
case C.TypeVLESS:
|
|
||||||
return NewVLESS(ctx, router, logger, options.Tag, options.VLESSOptions)
|
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", options.Type)
|
return nil, E.New("unknown inbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
|
|||||||
authenticator: auth.NewAuthenticator(options.Users),
|
authenticator: auth.NewAuthenticator(options.Users),
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if len(options.TLS.ALPN) == 0 {
|
if len(options.TLS.ALPN) == 0 {
|
||||||
options.TLS.ALPN = []string{hysteria.DefaultALPN}
|
options.TLS.ALPN = []string{hysteria.DefaultALPN}
|
||||||
}
|
}
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return nil, E.New("missing users")
|
return nil, E.New("missing users")
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
if len(options.Users) > 0 && len(options.Destinations) > 0 {
|
if len(options.Users) > 0 && len(options.Destinations) > 0 {
|
||||||
return nil, E.New("users and destinations options must not be combined")
|
return nil, E.New("users and destinations options must not be combined")
|
||||||
}
|
}
|
||||||
if len(options.Users) > 0 {
|
if len(options.Users) > 0 || options.Managed {
|
||||||
return newShadowsocksMulti(ctx, router, logger, tag, options)
|
return newShadowsocksMulti(ctx, router, logger, tag, options)
|
||||||
} else if len(options.Destinations) > 0 {
|
} else if len(options.Destinations) > 0 {
|
||||||
return newShadowsocksRelay(ctx, router, logger, tag, options)
|
return newShadowsocksRelay(ctx, router, logger, tag, options)
|
||||||
@@ -34,6 +34,7 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
var (
|
var (
|
||||||
_ adapter.Inbound = (*Shadowsocks)(nil)
|
_ adapter.Inbound = (*Shadowsocks)(nil)
|
||||||
_ adapter.InjectableInbound = (*Shadowsocks)(nil)
|
_ adapter.InjectableInbound = (*Shadowsocks)(nil)
|
||||||
|
_ adapter.ManagedShadowsocksServer = (*Shadowsocks)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Shadowsocks struct {
|
type Shadowsocks struct {
|
||||||
@@ -68,7 +69,7 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
case common.Contains(shadowaead.List, options.Method):
|
case common.Contains(shadowaead.List, options.Method):
|
||||||
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, inbound.upstreamContextHandler())
|
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, udpTimeout, inbound.upstreamContextHandler())
|
||||||
case common.Contains(shadowaead_2022.List, options.Method):
|
case common.Contains(shadowaead_2022.List, options.Method):
|
||||||
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, inbound.upstreamContextHandler(), router.TimeFunc())
|
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, udpTimeout, inbound.upstreamContextHandler())
|
||||||
default:
|
default:
|
||||||
err = E.New("unsupported method: ", options.Method)
|
err = E.New("unsupported method: ", options.Method)
|
||||||
}
|
}
|
||||||
@@ -76,6 +77,18 @@ func newShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
|
|||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Shadowsocks) Method() string {
|
||||||
|
return h.service.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Shadowsocks) Password() string {
|
||||||
|
return h.service.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Shadowsocks) UpdateUsers(names []string, uPSKs []string) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
_ adapter.Inbound = (*ShadowsocksMulti)(nil)
|
_ adapter.Inbound = (*ShadowsocksMulti)(nil)
|
||||||
_ adapter.InjectableInbound = (*ShadowsocksMulti)(nil)
|
_ adapter.InjectableInbound = (*ShadowsocksMulti)(nil)
|
||||||
|
_ adapter.ManagedShadowsocksServer = (*ShadowsocksMulti)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowsocksMulti struct {
|
type ShadowsocksMulti struct {
|
||||||
@@ -57,16 +58,17 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
|||||||
options.Password,
|
options.Password,
|
||||||
udpTimeout,
|
udpTimeout,
|
||||||
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
|
||||||
router.TimeFunc(),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(options.Users) > 0 {
|
||||||
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
|
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Users, func(index int, user option.ShadowsocksUser) int {
|
||||||
return index
|
return index
|
||||||
}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
|
}), common.Map(options.Users, func(user option.ShadowsocksUser) string {
|
||||||
return user.Password
|
return user.Password
|
||||||
}))
|
}))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -76,6 +78,29 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.
|
|||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *ShadowsocksMulti) Method() string {
|
||||||
|
return h.service.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ShadowsocksMulti) Password() string {
|
||||||
|
return h.service.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ShadowsocksMulti) UpdateUsers(users []string, uPSKs []string) error {
|
||||||
|
err := h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user string) int {
|
||||||
|
return index
|
||||||
|
}), uPSKs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.users = common.Map(users, func(user string) option.ShadowsocksUser {
|
||||||
|
return option.ShadowsocksUser{
|
||||||
|
Name: user,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *ShadowsocksMulti) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
_ adapter.Inbound = (*ShadowsocksRelay)(nil)
|
_ adapter.Inbound = (*ShadowsocksRelay)(nil)
|
||||||
_ adapter.InjectableInbound = (*ShadowsocksRelay)(nil)
|
_ adapter.InjectableInbound = (*ShadowsocksRelay)(nil)
|
||||||
|
_ adapter.ManagedShadowsocksServer = (*ShadowsocksRelay)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowsocksRelay struct {
|
type ShadowsocksRelay struct {
|
||||||
@@ -71,6 +72,18 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.
|
|||||||
return inbound, err
|
return inbound, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *ShadowsocksRelay) Method() string {
|
||||||
|
return h.service.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ShadowsocksRelay) Password() string {
|
||||||
|
return h.service.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ShadowsocksRelay) UpdateUsers(users []string, uPSKs []string) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
"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"
|
||||||
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-shadowtls"
|
"github.com/sagernet/sing-box/transport/shadowtls"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"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"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/task"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShadowTLS struct {
|
type ShadowTLS struct {
|
||||||
myInboundAdapter
|
myInboundAdapter
|
||||||
service *shadowtls.Service
|
handshakeDialer N.Dialer
|
||||||
|
handshakeAddr M.Socksaddr
|
||||||
|
version int
|
||||||
|
password string
|
||||||
|
fallbackAfter int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) {
|
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) {
|
||||||
@@ -30,41 +46,231 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
tag: tag,
|
tag: tag,
|
||||||
listenOptions: options.ListenOptions,
|
listenOptions: options.ListenOptions,
|
||||||
},
|
},
|
||||||
|
handshakeDialer: dialer.New(router, options.Handshake.DialerOptions),
|
||||||
|
handshakeAddr: options.Handshake.ServerOptions.Build(),
|
||||||
|
password: options.Password,
|
||||||
}
|
}
|
||||||
|
inbound.version = options.Version
|
||||||
var handshakeForServerName map[string]shadowtls.HandshakeConfig
|
switch options.Version {
|
||||||
if options.Version > 1 {
|
case 0:
|
||||||
handshakeForServerName = make(map[string]shadowtls.HandshakeConfig)
|
fallthrough
|
||||||
for serverName, serverOptions := range options.HandshakeForServerName {
|
case 1:
|
||||||
handshakeForServerName[serverName] = shadowtls.HandshakeConfig{
|
case 2:
|
||||||
Server: serverOptions.ServerOptions.Build(),
|
if options.FallbackAfter == nil {
|
||||||
Dialer: dialer.New(router, serverOptions.DialerOptions),
|
inbound.fallbackAfter = 2
|
||||||
|
} else {
|
||||||
|
inbound.fallbackAfter = *options.FallbackAfter
|
||||||
}
|
}
|
||||||
|
case 3:
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
service, err := shadowtls.NewService(shadowtls.ServiceConfig{
|
|
||||||
Version: options.Version,
|
|
||||||
Password: options.Password,
|
|
||||||
Users: common.Map(options.Users, func(it option.ShadowTLSUser) shadowtls.User {
|
|
||||||
return (shadowtls.User)(it)
|
|
||||||
}),
|
|
||||||
Handshake: shadowtls.HandshakeConfig{
|
|
||||||
Server: options.Handshake.ServerOptions.Build(),
|
|
||||||
Dialer: dialer.New(router, options.Handshake.DialerOptions),
|
|
||||||
},
|
|
||||||
HandshakeForServerName: handshakeForServerName,
|
|
||||||
StrictMode: options.StrictMode,
|
|
||||||
Handler: inbound.upstreamContextHandler(),
|
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
inbound.service = service
|
|
||||||
inbound.connHandler = inbound
|
inbound.connHandler = inbound
|
||||||
return inbound, nil
|
return inbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (s *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
handshakeConn, err := s.handshakeDialer.DialContext(ctx, N.NetworkTCP, s.handshakeAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch s.version {
|
||||||
|
case 1:
|
||||||
|
var handshake task.Group
|
||||||
|
handshake.Append("client handshake", func(ctx context.Context) error {
|
||||||
|
return s.copyUntilHandshakeFinished(handshakeConn, conn)
|
||||||
|
})
|
||||||
|
handshake.Append("server handshake", func(ctx context.Context) error {
|
||||||
|
return s.copyUntilHandshakeFinished(conn, handshakeConn)
|
||||||
|
})
|
||||||
|
handshake.FastFail()
|
||||||
|
handshake.Cleanup(func() {
|
||||||
|
handshakeConn.Close()
|
||||||
|
})
|
||||||
|
err = handshake.Run(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.newConnection(ctx, conn, metadata)
|
||||||
|
case 2:
|
||||||
|
hashConn := shadowtls.NewHashWriteConn(conn, s.password)
|
||||||
|
go bufio.Copy(hashConn, handshakeConn)
|
||||||
|
var request *buf.Buffer
|
||||||
|
request, err = s.copyUntilHandshakeFinishedV2(ctx, handshakeConn, conn, hashConn, s.fallbackAfter)
|
||||||
|
if err == nil {
|
||||||
|
handshakeConn.Close()
|
||||||
|
return s.newConnection(ctx, bufio.NewCachedConn(shadowtls.NewConn(conn), request), metadata)
|
||||||
|
} else if err == os.ErrPermission {
|
||||||
|
s.logger.WarnContext(ctx, "fallback connection")
|
||||||
|
hashConn.Fallback()
|
||||||
|
return common.Error(bufio.Copy(handshakeConn, conn))
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fallthrough
|
||||||
|
case 3:
|
||||||
|
var clientHelloFrame *buf.Buffer
|
||||||
|
clientHelloFrame, err = shadowtls.ExtractFrame(conn)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read client handshake")
|
||||||
|
}
|
||||||
|
_, err = handshakeConn.Write(clientHelloFrame.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
clientHelloFrame.Release()
|
||||||
|
return E.Cause(err, "write client handshake")
|
||||||
|
}
|
||||||
|
err = shadowtls.VerifyClientHello(clientHelloFrame.Bytes(), s.password)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.WarnContext(ctx, E.Cause(err, "client hello verify failed"))
|
||||||
|
return bufio.CopyConn(ctx, conn, handshakeConn)
|
||||||
|
}
|
||||||
|
s.logger.TraceContext(ctx, "client hello verify success")
|
||||||
|
clientHelloFrame.Release()
|
||||||
|
|
||||||
|
var serverHelloFrame *buf.Buffer
|
||||||
|
serverHelloFrame, err = shadowtls.ExtractFrame(handshakeConn)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "read server handshake")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Write(serverHelloFrame.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
serverHelloFrame.Release()
|
||||||
|
return E.Cause(err, "write server handshake")
|
||||||
|
}
|
||||||
|
|
||||||
|
serverRandom := shadowtls.ExtractServerRandom(serverHelloFrame.Bytes())
|
||||||
|
|
||||||
|
if serverRandom == nil {
|
||||||
|
s.logger.WarnContext(ctx, "server random extract failed, will copy bidirectional")
|
||||||
|
return bufio.CopyConn(ctx, conn, handshakeConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shadowtls.IsServerHelloSupportTLS13(serverHelloFrame.Bytes()) {
|
||||||
|
s.logger.WarnContext(ctx, "TLS 1.3 is not supported, will copy bidirectional")
|
||||||
|
return bufio.CopyConn(ctx, conn, handshakeConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverHelloFrame.Release()
|
||||||
|
s.logger.TraceContext(ctx, "client authenticated. server random extracted: ", hex.EncodeToString(serverRandom))
|
||||||
|
|
||||||
|
hmacWrite := hmac.New(sha1.New, []byte(s.password))
|
||||||
|
hmacWrite.Write(serverRandom)
|
||||||
|
|
||||||
|
hmacAdd := hmac.New(sha1.New, []byte(s.password))
|
||||||
|
hmacAdd.Write(serverRandom)
|
||||||
|
hmacAdd.Write([]byte("S"))
|
||||||
|
|
||||||
|
hmacVerify := hmac.New(sha1.New, []byte(s.password))
|
||||||
|
hmacVerifyReset := func() {
|
||||||
|
hmacVerify.Reset()
|
||||||
|
hmacVerify.Write(serverRandom)
|
||||||
|
hmacVerify.Write([]byte("C"))
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientFirstFrame *buf.Buffer
|
||||||
|
var group task.Group
|
||||||
|
var handshakeFinished bool
|
||||||
|
group.Append("client handshake relay", func(ctx context.Context) error {
|
||||||
|
clientFrame, cErr := shadowtls.CopyByFrameUntilHMACMatches(conn, handshakeConn, hmacVerify, hmacVerifyReset)
|
||||||
|
if cErr == nil {
|
||||||
|
clientFirstFrame = clientFrame
|
||||||
|
handshakeFinished = true
|
||||||
|
handshakeConn.Close()
|
||||||
|
}
|
||||||
|
return cErr
|
||||||
|
})
|
||||||
|
group.Append("server handshake relay", func(ctx context.Context) error {
|
||||||
|
cErr := shadowtls.CopyByFrameWithModification(handshakeConn, conn, s.password, serverRandom, hmacWrite)
|
||||||
|
if E.IsClosedOrCanceled(cErr) && handshakeFinished {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return cErr
|
||||||
|
})
|
||||||
|
group.Cleanup(func() {
|
||||||
|
handshakeConn.Close()
|
||||||
|
})
|
||||||
|
err = group.Run(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "handshake relay")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.TraceContext(ctx, "handshake relay finished")
|
||||||
|
return s.newConnection(ctx, bufio.NewCachedConn(shadowtls.NewVerifiedConn(conn, hmacAdd, hmacVerify, nil), clientFirstFrame), metadata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShadowTLS) copyUntilHandshakeFinished(dst io.Writer, src io.Reader) error {
|
||||||
|
const handshake = 0x16
|
||||||
|
const changeCipherSpec = 0x14
|
||||||
|
var hasSeenChangeCipherSpec bool
|
||||||
|
var tlsHdr [5]byte
|
||||||
|
for {
|
||||||
|
_, err := io.ReadFull(src, tlsHdr[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
length := binary.BigEndian.Uint16(tlsHdr[3:])
|
||||||
|
_, err = io.Copy(dst, io.MultiReader(bytes.NewReader(tlsHdr[:]), io.LimitReader(src, int64(length))))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tlsHdr[0] != handshake {
|
||||||
|
if tlsHdr[0] != changeCipherSpec {
|
||||||
|
return E.New("unexpected tls frame type: ", tlsHdr[0])
|
||||||
|
}
|
||||||
|
if !hasSeenChangeCipherSpec {
|
||||||
|
hasSeenChangeCipherSpec = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hasSeenChangeCipherSpec {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ShadowTLS) copyUntilHandshakeFinishedV2(ctx context.Context, dst net.Conn, src io.Reader, hash *shadowtls.HashWriteConn, fallbackAfter int) (*buf.Buffer, error) {
|
||||||
|
const applicationData = 0x17
|
||||||
|
var tlsHdr [5]byte
|
||||||
|
var applicationDataCount int
|
||||||
|
for {
|
||||||
|
_, err := io.ReadFull(src, tlsHdr[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
length := binary.BigEndian.Uint16(tlsHdr[3:])
|
||||||
|
if tlsHdr[0] == applicationData {
|
||||||
|
data := buf.NewSize(int(length))
|
||||||
|
_, err = data.ReadFullFrom(src, int(length))
|
||||||
|
if err != nil {
|
||||||
|
data.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if hash.HasContent() && length >= 8 {
|
||||||
|
checksum := hash.Sum()
|
||||||
|
if bytes.Equal(data.To(8), checksum) {
|
||||||
|
s.logger.TraceContext(ctx, "match current hashcode")
|
||||||
|
data.Advance(8)
|
||||||
|
return data, nil
|
||||||
|
} else if hash.LastSum() != nil && bytes.Equal(data.To(8), hash.LastSum()) {
|
||||||
|
s.logger.TraceContext(ctx, "match last hashcode")
|
||||||
|
data.Advance(8)
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = io.Copy(dst, io.MultiReader(bytes.NewReader(tlsHdr[:]), data))
|
||||||
|
data.Release()
|
||||||
|
applicationDataCount++
|
||||||
|
} else {
|
||||||
|
_, err = io.Copy(dst, io.MultiReader(bytes.NewReader(tlsHdr[:]), io.LimitReader(src, int64(length))))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if applicationDataCount > fallbackAfter {
|
||||||
|
return nil, os.ErrPermission
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,10 +100,9 @@ type tproxyPacketWriter struct {
|
|||||||
|
|
||||||
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
conn := w.conn
|
if w.destination == destination && w.conn != nil {
|
||||||
if w.destination == destination && conn != nil {
|
_, err := w.conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))
|
||||||
_, err := conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))
|
if err == nil {
|
||||||
if err != nil {
|
|
||||||
w.conn = nil
|
w.conn = nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
|
|||||||
users: options.Users,
|
users: options.Users,
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
tlsConfig, err := tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/canceler"
|
"github.com/sagernet/sing-box/common/canceler"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@@ -35,10 +34,9 @@ type Tun struct {
|
|||||||
stack string
|
stack string
|
||||||
tunIf tun.Tun
|
tunIf tun.Tun
|
||||||
tunStack tun.Stack
|
tunStack tun.Stack
|
||||||
platformInterface platform.Interface
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) (*Tun, error) {
|
||||||
tunName := options.InterfaceName
|
tunName := options.InterfaceName
|
||||||
if tunName == "" {
|
if tunName == "" {
|
||||||
tunName = tun.CalculateInterfaceName("")
|
tunName = tun.CalculateInterfaceName("")
|
||||||
@@ -95,7 +93,6 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
|||||||
endpointIndependentNat: options.EndpointIndependentNat,
|
endpointIndependentNat: options.EndpointIndependentNat,
|
||||||
udpTimeout: udpTimeout,
|
udpTimeout: udpTimeout,
|
||||||
stack: options.Stack,
|
stack: options.Stack,
|
||||||
platformInterface: platformInterface,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,25 +137,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.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
t.tunOptions.BuildAndroidRules(t.router.PackageManager(), t)
|
||||||
}
|
}
|
||||||
var (
|
tunIf, err := tun.Open(t.tunOptions)
|
||||||
tunInterface tun.Tun
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if t.platformInterface != nil {
|
|
||||||
tunInterface, err = t.platformInterface.OpenTun(t.tunOptions)
|
|
||||||
} else {
|
|
||||||
tunInterface, err = tun.Open(t.tunOptions)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "configure tun interface")
|
return E.Cause(err, "configure tun interface")
|
||||||
}
|
}
|
||||||
t.tunIf = tunInterface
|
t.tunIf = tunIf
|
||||||
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: tunIf,
|
||||||
MTU: t.tunOptions.MTU,
|
MTU: t.tunOptions.MTU,
|
||||||
Name: t.tunOptions.Name,
|
Name: t.tunOptions.Name,
|
||||||
Inet4Address: t.tunOptions.Inet4Address,
|
Inet4Address: t.tunOptions.Inet4Address,
|
||||||
|
|||||||
193
inbound/vless.go
193
inbound/vless.go
@@ -1,193 +0,0 @@
|
|||||||
package inbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
"github.com/sagernet/sing-box/transport/v2ray"
|
|
||||||
"github.com/sagernet/sing-box/transport/vless"
|
|
||||||
"github.com/sagernet/sing-vmess"
|
|
||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/auth"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ adapter.Inbound = (*VLESS)(nil)
|
|
||||||
_ adapter.InjectableInbound = (*VLESS)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type VLESS struct {
|
|
||||||
myInboundAdapter
|
|
||||||
ctx context.Context
|
|
||||||
users []option.VLESSUser
|
|
||||||
service *vless.Service[int]
|
|
||||||
tlsConfig tls.ServerConfig
|
|
||||||
transport adapter.V2RayServerTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (*VLESS, error) {
|
|
||||||
inbound := &VLESS{
|
|
||||||
myInboundAdapter: myInboundAdapter{
|
|
||||||
protocol: C.TypeVLESS,
|
|
||||||
network: []string{N.NetworkTCP},
|
|
||||||
ctx: ctx,
|
|
||||||
router: router,
|
|
||||||
logger: logger,
|
|
||||||
tag: tag,
|
|
||||||
listenOptions: options.ListenOptions,
|
|
||||||
},
|
|
||||||
ctx: ctx,
|
|
||||||
users: options.Users,
|
|
||||||
}
|
|
||||||
service := vless.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
|
|
||||||
service.UpdateUsers(common.MapIndexed(inbound.users, func(index int, _ option.VLESSUser) int {
|
|
||||||
return index
|
|
||||||
}), common.Map(inbound.users, func(it option.VLESSUser) string {
|
|
||||||
return it.UUID
|
|
||||||
}))
|
|
||||||
inbound.service = service
|
|
||||||
var err error
|
|
||||||
if options.TLS != nil {
|
|
||||||
inbound.tlsConfig, err = tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if options.Transport != nil {
|
|
||||||
inbound.transport, err = v2ray.NewServerTransport(ctx, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound))
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "create server transport: ", options.Transport.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inbound.connHandler = inbound
|
|
||||||
return inbound, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) Start() error {
|
|
||||||
err := common.Start(
|
|
||||||
h.service,
|
|
||||||
h.tlsConfig,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if h.transport == nil {
|
|
||||||
return h.myInboundAdapter.Start()
|
|
||||||
}
|
|
||||||
if common.Contains(h.transport.Network(), N.NetworkTCP) {
|
|
||||||
tcpListener, err := h.myInboundAdapter.ListenTCP()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
sErr := h.transport.Serve(tcpListener)
|
|
||||||
if sErr != nil && !E.IsClosed(sErr) {
|
|
||||||
h.logger.Error("transport serve error: ", sErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
if common.Contains(h.transport.Network(), N.NetworkUDP) {
|
|
||||||
udpConn, err := h.myInboundAdapter.ListenUDP()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
sErr := h.transport.ServePacket(udpConn)
|
|
||||||
if sErr != nil && !E.IsClosed(sErr) {
|
|
||||||
h.logger.Error("transport serve error: ", sErr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) Close() error {
|
|
||||||
return common.Close(
|
|
||||||
h.service,
|
|
||||||
&h.myInboundAdapter,
|
|
||||||
h.tlsConfig,
|
|
||||||
h.transport,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) newTransportConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
h.injectTCP(conn, metadata)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
var err error
|
|
||||||
if h.tlsConfig != nil && h.transport == nil {
|
|
||||||
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
|
||||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
|
||||||
if !loaded {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
user := h.users[userIndex].Name
|
|
||||||
if user == "" {
|
|
||||||
user = F.ToString(userIndex)
|
|
||||||
} else {
|
|
||||||
metadata.User = user
|
|
||||||
}
|
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
|
||||||
return h.router.RouteConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
|
||||||
userIndex, loaded := auth.UserFromContext[int](ctx)
|
|
||||||
if !loaded {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
user := h.users[userIndex].Name
|
|
||||||
if user == "" {
|
|
||||||
user = F.ToString(userIndex)
|
|
||||||
} else {
|
|
||||||
metadata.User = user
|
|
||||||
}
|
|
||||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
|
||||||
metadata.Destination = M.Socksaddr{}
|
|
||||||
conn = packetaddr.NewConn(conn.(vmess.PacketConn), metadata.Destination)
|
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet addr connection")
|
|
||||||
} else {
|
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
|
||||||
}
|
|
||||||
return h.router.RoutePacketConnection(ctx, conn, metadata)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil)
|
|
||||||
|
|
||||||
type vlessTransportHandler VLESS
|
|
||||||
|
|
||||||
func (t *vlessTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
return (*VLESS)(t).newTransportConnection(ctx, conn, adapter.InboundContext{
|
|
||||||
Source: metadata.Source,
|
|
||||||
Destination: metadata.Destination,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *vlessTransportHandler) FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
@@ -50,9 +50,6 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
users: options.Users,
|
users: options.Users,
|
||||||
}
|
}
|
||||||
var serviceOptions []vmess.ServiceOption
|
var serviceOptions []vmess.ServiceOption
|
||||||
if timeFunc := router.TimeFunc(); timeFunc != nil {
|
|
||||||
serviceOptions = append(serviceOptions, vmess.ServiceWithTimeFunc(timeFunc))
|
|
||||||
}
|
|
||||||
if options.Transport != nil && options.Transport.Type != "" {
|
if options.Transport != nil && options.Transport.Type != "" {
|
||||||
serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection())
|
serviceOptions = append(serviceOptions, vmess.ServiceWithDisableHeaderProtection())
|
||||||
}
|
}
|
||||||
@@ -69,7 +66,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if options.TLS != nil {
|
if options.TLS != nil {
|
||||||
inbound.tlsConfig, err = tls.NewServer(ctx, router, logger, common.PtrValueOrDefault(options.TLS))
|
inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
5
include/ssmapi.go
Normal file
5
include/ssmapi.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
//go:build with_ssm_api
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import _ "github.com/sagernet/sing-box/experimental/ssmapi"
|
||||||
17
include/ssmapi_stub.go
Normal file
17
include/ssmapi_stub.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//go:build !with_ssm_api
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/experimental"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
experimental.RegisterSSMServerConstructor(func(router adapter.Router, logger log.Logger, options option.SSMAPIOptions) (adapter.SSMServer, error) {
|
||||||
|
return nil, E.New(`SSM api is not included in this build, rebuild with -tags with_ssm_api`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,21 +13,14 @@ var _ Factory = (*simpleFactory)(nil)
|
|||||||
|
|
||||||
type simpleFactory struct {
|
type simpleFactory struct {
|
||||||
formatter Formatter
|
formatter Formatter
|
||||||
platformFormatter Formatter
|
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
platformWriter io.Writer
|
|
||||||
level Level
|
level Level
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFactory(formatter Formatter, writer io.Writer, platformWriter io.Writer) Factory {
|
func NewFactory(formatter Formatter, writer io.Writer) Factory {
|
||||||
return &simpleFactory{
|
return &simpleFactory{
|
||||||
formatter: formatter,
|
formatter: formatter,
|
||||||
platformFormatter: Formatter{
|
|
||||||
BaseTime: formatter.BaseTime,
|
|
||||||
DisableColors: C.IsIos,
|
|
||||||
},
|
|
||||||
writer: writer,
|
writer: writer,
|
||||||
platformWriter: platformWriter,
|
|
||||||
level: LevelTrace,
|
level: LevelTrace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,8 +53,7 @@ func (l *simpleLogger) Log(ctx context.Context, level Level, args []any) {
|
|||||||
if level > l.level {
|
if level > l.level {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nowTime := time.Now()
|
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), time.Now())
|
||||||
message := l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)
|
|
||||||
if level == LevelPanic {
|
if level == LevelPanic {
|
||||||
panic(message)
|
panic(message)
|
||||||
}
|
}
|
||||||
@@ -70,9 +61,6 @@ func (l *simpleLogger) Log(ctx context.Context, level Level, args []any) {
|
|||||||
if level == LevelFatal {
|
if level == LevelFatal {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if l.platformWriter != nil {
|
|
||||||
l.platformWriter.Write([]byte(l.platformFormatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *simpleLogger) Trace(args ...any) {
|
func (l *simpleLogger) Trace(args ...any) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
var std ContextLogger
|
var std ContextLogger
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
std = NewFactory(Formatter{BaseTime: time.Now()}, os.Stderr, nil).Logger()
|
std = NewFactory(Formatter{BaseTime: time.Now()}, os.Stderr).Logger()
|
||||||
}
|
}
|
||||||
|
|
||||||
func StdLogger() ContextLogger {
|
func StdLogger() ContextLogger {
|
||||||
|
|||||||
@@ -15,22 +15,16 @@ var _ Factory = (*observableFactory)(nil)
|
|||||||
|
|
||||||
type observableFactory struct {
|
type observableFactory struct {
|
||||||
formatter Formatter
|
formatter Formatter
|
||||||
platformFormatter Formatter
|
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
platformWriter io.Writer
|
|
||||||
level Level
|
level Level
|
||||||
subscriber *observable.Subscriber[Entry]
|
subscriber *observable.Subscriber[Entry]
|
||||||
observer *observable.Observer[Entry]
|
observer *observable.Observer[Entry]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewObservableFactory(formatter Formatter, writer io.Writer, platformWriter io.Writer) ObservableFactory {
|
func NewObservableFactory(formatter Formatter, writer io.Writer) ObservableFactory {
|
||||||
factory := &observableFactory{
|
factory := &observableFactory{
|
||||||
formatter: formatter,
|
formatter: formatter,
|
||||||
platformFormatter: Formatter{
|
|
||||||
BaseTime: formatter.BaseTime,
|
|
||||||
},
|
|
||||||
writer: writer,
|
writer: writer,
|
||||||
platformWriter: platformWriter,
|
|
||||||
level: LevelTrace,
|
level: LevelTrace,
|
||||||
subscriber: observable.NewSubscriber[Entry](128),
|
subscriber: observable.NewSubscriber[Entry](128),
|
||||||
}
|
}
|
||||||
@@ -80,8 +74,7 @@ func (l *observableLogger) Log(ctx context.Context, level Level, args []any) {
|
|||||||
if level > l.level {
|
if level > l.level {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
nowTime := time.Now()
|
message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), time.Now())
|
||||||
message, messageSimple := l.formatter.FormatWithSimple(ctx, level, l.tag, F.ToString(args...), nowTime)
|
|
||||||
if level == LevelPanic {
|
if level == LevelPanic {
|
||||||
panic(message)
|
panic(message)
|
||||||
}
|
}
|
||||||
@@ -90,9 +83,6 @@ func (l *observableLogger) Log(ctx context.Context, level Level, args []any) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
l.subscriber.Emit(Entry{level, messageSimple})
|
l.subscriber.Emit(Entry{level, messageSimple})
|
||||||
if l.platformWriter != nil {
|
|
||||||
l.platformWriter.Write([]byte(l.formatter.Format(ctx, level, l.tag, F.ToString(args...), nowTime)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *observableLogger) Trace(args ...any) {
|
func (l *observableLogger) Trace(args ...any) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func ContextWithOverrideLevel(ctx context.Context, level Level) context.Context
|
|||||||
|
|
||||||
func OverrideLevelFromContext(origin Level, ctx context.Context) Level {
|
func OverrideLevelFromContext(origin Level, ctx context.Context) Level {
|
||||||
level, loaded := ctx.Value((*overrideLevelKey)(nil)).(Level)
|
level, loaded := ctx.Value((*overrideLevelKey)(nil)).(Level)
|
||||||
if !loaded || origin > level {
|
if !loaded || origin < level {
|
||||||
return origin
|
return origin
|
||||||
}
|
}
|
||||||
return level
|
return level
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ nav:
|
|||||||
- configuration/dns/index.md
|
- configuration/dns/index.md
|
||||||
- DNS Server: configuration/dns/server.md
|
- DNS Server: configuration/dns/server.md
|
||||||
- DNS Rule: configuration/dns/rule.md
|
- DNS Rule: configuration/dns/rule.md
|
||||||
- NTP:
|
|
||||||
- configuration/ntp/index.md
|
|
||||||
- Route:
|
- Route:
|
||||||
- configuration/route/index.md
|
- configuration/route/index.md
|
||||||
- GeoIP: configuration/route/geoip.md
|
- GeoIP: configuration/route/geoip.md
|
||||||
@@ -71,7 +69,6 @@ nav:
|
|||||||
- Naive: configuration/inbound/naive.md
|
- Naive: configuration/inbound/naive.md
|
||||||
- Hysteria: configuration/inbound/hysteria.md
|
- Hysteria: configuration/inbound/hysteria.md
|
||||||
- ShadowTLS: configuration/inbound/shadowtls.md
|
- ShadowTLS: configuration/inbound/shadowtls.md
|
||||||
- VLESS: configuration/inbound/vless.md
|
|
||||||
- Tun: configuration/inbound/tun.md
|
- Tun: configuration/inbound/tun.md
|
||||||
- Redirect: configuration/inbound/redirect.md
|
- Redirect: configuration/inbound/redirect.md
|
||||||
- TProxy: configuration/inbound/tproxy.md
|
- TProxy: configuration/inbound/tproxy.md
|
||||||
@@ -152,27 +149,21 @@ plugins:
|
|||||||
Features: 特性
|
Features: 特性
|
||||||
Support: 支持
|
Support: 支持
|
||||||
Change Log: 更新日志
|
Change Log: 更新日志
|
||||||
|
|
||||||
Configuration: 配置
|
Configuration: 配置
|
||||||
Log: 日志
|
Log: 日志
|
||||||
DNS Server: DNS 服务器
|
DNS Server: DNS 服务器
|
||||||
DNS Rule: DNS 规则
|
DNS Rule: DNS 规则
|
||||||
|
|
||||||
Route: 路由
|
Route: 路由
|
||||||
Route Rule: 路由规则
|
Route Rule: 路由规则
|
||||||
Protocol Sniff: 协议探测
|
Protocol Sniff: 协议探测
|
||||||
|
|
||||||
Experimental: 实验性
|
Experimental: 实验性
|
||||||
|
|
||||||
Shared: 通用
|
Shared: 通用
|
||||||
Listen Fields: 监听字段
|
Listen Fields: 监听字段
|
||||||
Dial Fields: 拨号字段
|
Dial Fields: 拨号字段
|
||||||
Multiplex: 多路复用
|
Multiplex: 多路复用
|
||||||
V2Ray Transport: V2Ray 传输层
|
V2Ray Transport: V2Ray 传输层
|
||||||
|
|
||||||
Inbound: 入站
|
Inbound: 入站
|
||||||
Outbound: 出站
|
Outbound: 出站
|
||||||
|
|
||||||
FAQ: 常见问题
|
FAQ: 常见问题
|
||||||
Known Issues: 已知问题
|
Known Issues: 已知问题
|
||||||
Examples: 示例
|
Examples: 示例
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
package ntp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
"github.com/sagernet/sing/common/ntp"
|
|
||||||
)
|
|
||||||
|
|
||||||
const timeLayout = "2006-01-02 15:04:05 -0700"
|
|
||||||
|
|
||||||
var _ adapter.TimeService = (*Service)(nil)
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
server M.Socksaddr
|
|
||||||
dialer N.Dialer
|
|
||||||
logger logger.Logger
|
|
||||||
|
|
||||||
ticker *time.Ticker
|
|
||||||
clockOffset time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewService(ctx context.Context, router adapter.Router, logger logger.Logger, options option.NTPOptions) *Service {
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
server := options.ServerOptions.Build()
|
|
||||||
if server.Port == 0 {
|
|
||||||
server.Port = 123
|
|
||||||
}
|
|
||||||
var interval time.Duration
|
|
||||||
if options.Interval > 0 {
|
|
||||||
interval = time.Duration(options.Interval)
|
|
||||||
} else {
|
|
||||||
interval = 30 * time.Minute
|
|
||||||
}
|
|
||||||
return &Service{
|
|
||||||
ctx: ctx,
|
|
||||||
cancel: cancel,
|
|
||||||
server: server,
|
|
||||||
dialer: dialer.New(router, options.DialerOptions),
|
|
||||||
logger: logger,
|
|
||||||
ticker: time.NewTicker(interval),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Start() error {
|
|
||||||
err := s.update()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "initialize time")
|
|
||||||
}
|
|
||||||
s.logger.Info("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
|
|
||||||
go s.loopUpdate()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Close() error {
|
|
||||||
s.ticker.Stop()
|
|
||||||
s.cancel()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) TimeFunc() func() time.Time {
|
|
||||||
return func() time.Time {
|
|
||||||
return time.Now().Add(s.clockOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) loopUpdate() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-s.ctx.Done():
|
|
||||||
return
|
|
||||||
case <-s.ticker.C:
|
|
||||||
}
|
|
||||||
err := s.update()
|
|
||||||
if err == nil {
|
|
||||||
s.logger.Debug("updated time: ", s.TimeFunc()().Local().Format(timeLayout))
|
|
||||||
} else {
|
|
||||||
s.logger.Warn("update time: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) update() error {
|
|
||||||
response, err := ntp.Exchange(s.ctx, s.dialer, s.server)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.clockOffset = response.ClockOffset
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -5,19 +5,16 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/json"
|
"github.com/sagernet/sing-box/common/json"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type _Options struct {
|
type _Options struct {
|
||||||
Log *LogOptions `json:"log,omitempty"`
|
Log *LogOptions `json:"log,omitempty"`
|
||||||
DNS *DNSOptions `json:"dns,omitempty"`
|
DNS *DNSOptions `json:"dns,omitempty"`
|
||||||
NTP *NTPOptions `json:"ntp,omitempty"`
|
|
||||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||||
Route *RouteOptions `json:"route,omitempty"`
|
Route *RouteOptions `json:"route,omitempty"`
|
||||||
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
||||||
PlatformInterface platform.Interface `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options _Options
|
type Options _Options
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
SSMAPI *SSMAPIOptions `json:"ssm_api,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ type _Inbound struct {
|
|||||||
NaiveOptions NaiveInboundOptions `json:"-"`
|
NaiveOptions NaiveInboundOptions `json:"-"`
|
||||||
HysteriaOptions HysteriaInboundOptions `json:"-"`
|
HysteriaOptions HysteriaInboundOptions `json:"-"`
|
||||||
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
|
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
|
||||||
VLESSOptions VLESSInboundOptions `json:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inbound _Inbound
|
type Inbound _Inbound
|
||||||
@@ -56,8 +55,6 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
|
|||||||
v = h.HysteriaOptions
|
v = h.HysteriaOptions
|
||||||
case C.TypeShadowTLS:
|
case C.TypeShadowTLS:
|
||||||
v = h.ShadowTLSOptions
|
v = h.ShadowTLSOptions
|
||||||
case C.TypeVLESS:
|
|
||||||
v = h.VLESSOptions
|
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", h.Type)
|
return nil, E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
@@ -97,8 +94,6 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
|||||||
v = &h.HysteriaOptions
|
v = &h.HysteriaOptions
|
||||||
case C.TypeShadowTLS:
|
case C.TypeShadowTLS:
|
||||||
v = &h.ShadowTLSOptions
|
v = &h.ShadowTLSOptions
|
||||||
case C.TypeVLESS:
|
|
||||||
v = &h.VLESSOptions
|
|
||||||
default:
|
default:
|
||||||
return E.New("unknown inbound type: ", h.Type)
|
return E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
type NTPOptions struct {
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
Interval Duration `json:"interval,omitempty"`
|
|
||||||
ServerOptions
|
|
||||||
DialerOptions
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ type ShadowsocksInboundOptions struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Users []ShadowsocksUser `json:"users,omitempty"`
|
Users []ShadowsocksUser `json:"users,omitempty"`
|
||||||
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
Destinations []ShadowsocksDestination `json:"destinations,omitempty"`
|
||||||
|
Managed bool `json:"managed,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowsocksUser struct {
|
type ShadowsocksUser struct {
|
||||||
|
|||||||
@@ -4,15 +4,8 @@ type ShadowTLSInboundOptions struct {
|
|||||||
ListenOptions
|
ListenOptions
|
||||||
Version int `json:"version,omitempty"`
|
Version int `json:"version,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
Users []ShadowTLSUser `json:"users,omitempty"`
|
FallbackAfter *int `json:"fallback_after,omitempty"`
|
||||||
Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"`
|
Handshake ShadowTLSHandshakeOptions `json:"handshake"`
|
||||||
HandshakeForServerName map[string]ShadowTLSHandshakeOptions `json:"handshake_for_server_name,omitempty"`
|
|
||||||
StrictMode bool `json:"strict_mode,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ShadowTLSUser struct {
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShadowTLSHandshakeOptions struct {
|
type ShadowTLSHandshakeOptions struct {
|
||||||
|
|||||||
54
option/ssmapi.go
Normal file
54
option/ssmapi.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/common/json"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SSMAPIOptions struct {
|
||||||
|
Listen string `json:"listen,omitempty"`
|
||||||
|
ListenPrefix string `json:"listen_prefix,omitempty"`
|
||||||
|
Nodes []SSMNode `json:"nodes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type _SSMNode struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
ShadowsocksOptions SSMShadowsocksNode `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSMNode _SSMNode
|
||||||
|
|
||||||
|
func (h SSMNode) MarshalJSON() ([]byte, error) {
|
||||||
|
var v any
|
||||||
|
switch h.Type {
|
||||||
|
case C.TypeShadowsocks:
|
||||||
|
v = h.ShadowsocksOptions
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown ssm node type: ", h.Type)
|
||||||
|
}
|
||||||
|
return MarshallObjects((_SSMNode)(h), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SSMNode) UnmarshalJSON(data []byte) error {
|
||||||
|
err := json.Unmarshal(data, (*_SSMNode)(h))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch h.Type {
|
||||||
|
case C.TypeShadowsocks:
|
||||||
|
v = &h.ShadowsocksOptions
|
||||||
|
default:
|
||||||
|
return E.New("unknown ssm node type: ", h.Type)
|
||||||
|
}
|
||||||
|
return UnmarshallExcluded(data, (*_SSMNode)(h), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSMShadowsocksNode struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Address string `json:"address"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Inbound string `json:"inbound"`
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ type InboundTLSOptions struct {
|
|||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundTLSOptions struct {
|
type OutboundTLSOptions struct {
|
||||||
@@ -29,20 +28,6 @@ type OutboundTLSOptions struct {
|
|||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
ECH *OutboundECHOptions `json:"ech,omitempty"`
|
||||||
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
UTLS *OutboundUTLSOptions `json:"utls,omitempty"`
|
||||||
Reality *OutboundRealityOptions `json:"reality,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InboundRealityOptions struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
Handshake InboundRealityHandshakeOptions `json:"handshake,omitempty"`
|
|
||||||
PrivateKey string `json:"private_key,omitempty"`
|
|
||||||
ShortID Listable[string] `json:"short_id,omitempty"`
|
|
||||||
MaxTimeDifference Duration `json:"max_time_difference,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InboundRealityHandshakeOptions struct {
|
|
||||||
ServerOptions
|
|
||||||
DialerOptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundECHOptions struct {
|
type OutboundECHOptions struct {
|
||||||
@@ -56,9 +41,3 @@ type OutboundUTLSOptions struct {
|
|||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundRealityOptions struct {
|
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
|
||||||
PublicKey string `json:"public_key,omitempty"`
|
|
||||||
ShortID string `json:"short_id,omitempty"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,22 +1,9 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
type VLESSInboundOptions struct {
|
|
||||||
ListenOptions
|
|
||||||
Users []VLESSUser `json:"users,omitempty"`
|
|
||||||
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
|
||||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VLESSUser struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type VLESSOutboundOptions struct {
|
type VLESSOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
Flow string `json:"flow,omitempty"`
|
|
||||||
Network NetworkList `json:"network,omitempty"`
|
Network NetworkList `json:"network,omitempty"`
|
||||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
||||||
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
Transport *V2RayTransportOptions `json:"transport,omitempty"`
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ type Shadowsocks struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) {
|
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) {
|
||||||
method, err := shadowimpl.FetchMethod(options.Method, options.Password, router.TimeFunc())
|
method, err := shadowimpl.FetchMethod(options.Method, options.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package outbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha1"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -11,8 +13,9 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-shadowtls"
|
"github.com/sagernet/sing-box/transport/shadowtls"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
@@ -21,7 +24,11 @@ var _ adapter.Outbound = (*ShadowTLS)(nil)
|
|||||||
|
|
||||||
type ShadowTLS struct {
|
type ShadowTLS struct {
|
||||||
myOutboundAdapter
|
myOutboundAdapter
|
||||||
client *shadowtls.Client
|
dialer N.Dialer
|
||||||
|
serverAddr M.Socksaddr
|
||||||
|
tlsConfig tls.Config
|
||||||
|
version int
|
||||||
|
password string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) {
|
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) {
|
||||||
@@ -33,61 +40,86 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
},
|
},
|
||||||
|
dialer: dialer.New(router, options.DialerOptions),
|
||||||
|
serverAddr: options.ServerOptions.Build(),
|
||||||
|
password: options.Password,
|
||||||
}
|
}
|
||||||
if options.TLS == nil || !options.TLS.Enabled {
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
return nil, C.ErrTLSRequired
|
return nil, C.ErrTLSRequired
|
||||||
}
|
}
|
||||||
if options.Version == 1 {
|
outbound.version = options.Version
|
||||||
|
switch options.Version {
|
||||||
|
case 0:
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
options.TLS.MinVersion = "1.2"
|
options.TLS.MinVersion = "1.2"
|
||||||
options.TLS.MaxVersion = "1.2"
|
options.TLS.MaxVersion = "1.2"
|
||||||
}
|
case 2:
|
||||||
tlsConfig, err := tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tlsHandshakeFunc shadowtls.TLSHandshakeFunc
|
|
||||||
switch options.Version {
|
|
||||||
case 1, 2:
|
|
||||||
tlsHandshakeFunc = func(ctx context.Context, conn net.Conn, _ shadowtls.TLSSessionIDGeneratorFunc) error {
|
|
||||||
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
|
|
||||||
}
|
|
||||||
case 3:
|
case 3:
|
||||||
if idConfig, loaded := tlsConfig.(tls.ConfigWithSessionIDGenerator); loaded {
|
options.TLS.MinVersion = "1.3"
|
||||||
tlsHandshakeFunc = func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
|
options.TLS.MaxVersion = "1.3"
|
||||||
idConfig.SetSessionIDGenerator(sessionIDGenerator)
|
default:
|
||||||
return common.Error(tls.ClientHandshake(ctx, conn, tlsConfig))
|
return nil, E.New("unknown shadowtls protocol version: ", options.Version)
|
||||||
}
|
}
|
||||||
|
var err error
|
||||||
|
if options.Version != 3 {
|
||||||
|
outbound.tlsConfig, err = tls.NewClient(router, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
} else {
|
} else {
|
||||||
stdTLSConfig, err := tlsConfig.Config()
|
outbound.tlsConfig, err = shadowtls.NewClientTLSConfig(options.Server, common.PtrValueOrDefault(options.TLS), options.Password)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsHandshakeFunc = shadowtls.DefaultTLSHandshakeFunc(options.Password, stdTLSConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
client, err := shadowtls.NewClient(shadowtls.ClientConfig{
|
|
||||||
Version: options.Version,
|
|
||||||
Password: options.Password,
|
|
||||||
Server: options.ServerOptions.Build(),
|
|
||||||
Dialer: dialer.New(router, options.DialerOptions),
|
|
||||||
TLSHandshake: tlsHandshakeFunc,
|
|
||||||
Logger: logger,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
outbound.client = client
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (s *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
return s.client.DialContext(ctx)
|
|
||||||
default:
|
default:
|
||||||
return nil, os.ErrInvalid
|
return nil, os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
conn, err := s.dialer.DialContext(ctx, N.NetworkTCP, s.serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch s.version {
|
||||||
|
default:
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
_, err = tls.ClientHandshake(ctx, conn, s.tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
case 2:
|
||||||
|
hashConn := shadowtls.NewHashReadConn(conn, s.password)
|
||||||
|
_, err = tls.ClientHandshake(ctx, hashConn, s.tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return shadowtls.NewClientConn(hashConn), nil
|
||||||
|
case 3:
|
||||||
|
streamWrapper := shadowtls.NewStreamWrapper(conn, s.password)
|
||||||
|
_, err = tls.ClientHandshake(ctx, streamWrapper, s.tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authorized, serverRandom, readHMAC := streamWrapper.Authorized()
|
||||||
|
if !authorized {
|
||||||
|
return nil, E.New("traffic hijacked or TLS1.3 is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
hmacAdd := hmac.New(sha1.New, []byte(s.password))
|
||||||
|
hmacAdd.Write(serverRandom)
|
||||||
|
hmacAdd.Write([]byte("C"))
|
||||||
|
|
||||||
|
hmacVerify := hmac.New(sha1.New, []byte(s.password))
|
||||||
|
hmacVerify.Write(serverRandom)
|
||||||
|
hmacVerify.Write([]byte("S"))
|
||||||
|
|
||||||
|
return shadowtls.NewVerifiedConn(conn, hmacAdd, hmacVerify, readHMAC), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (s *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/v2ray"
|
"github.com/sagernet/sing-box/transport/v2ray"
|
||||||
"github.com/sagernet/sing-box/transport/vless"
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-vmess/packetaddr"
|
"github.com/sagernet/sing-vmess/packetaddr"
|
||||||
|
"github.com/sagernet/sing-vmess/vless"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -67,7 +67,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
default:
|
default:
|
||||||
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
||||||
}
|
}
|
||||||
outbound.client, err = vless.NewClient(options.UUID, options.Flow)
|
outbound.client, err = vless.NewClient(options.UUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -92,12 +92,16 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch N.NetworkName(network) {
|
switch N.NetworkName(network) {
|
||||||
|
case N.NetworkTCP:
|
||||||
|
case N.NetworkUDP:
|
||||||
|
}
|
||||||
|
switch N.NetworkName(network) {
|
||||||
case N.NetworkTCP:
|
case N.NetworkTCP:
|
||||||
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound connection to ", destination)
|
||||||
return h.client.DialEarlyConn(conn, destination)
|
return h.client.DialEarlyConn(conn, destination), nil
|
||||||
case N.NetworkUDP:
|
case N.NetworkUDP:
|
||||||
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
|
||||||
return h.client.DialEarlyPacketConn(conn, destination)
|
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||||
default:
|
default:
|
||||||
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
return nil, E.Extend(N.ErrUnknownNetwork, network)
|
||||||
}
|
}
|
||||||
@@ -122,15 +126,11 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if h.xudp {
|
if h.xudp {
|
||||||
return h.client.DialEarlyXUDPPacketConn(conn, destination)
|
return h.client.DialEarlyXUDPPacketConn(conn, destination), nil
|
||||||
} else if h.packetAddr {
|
} else if h.packetAddr {
|
||||||
conn, err := h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress})
|
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(h.client.DialEarlyPacketConn(conn, M.Socksaddr{Fqdn: packetaddr.SeqPacketMagicAddress}), destination)), nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dialer.NewResolvePacketConn(ctx, h.router, dns.DomainStrategyAsIS, packetaddr.NewConn(conn, destination)), nil
|
|
||||||
} else {
|
} else {
|
||||||
return h.client.DialEarlyPacketConn(conn, destination)
|
return h.client.DialEarlyPacketConn(conn, destination), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,9 +74,6 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
|
|||||||
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
|
||||||
}
|
}
|
||||||
var clientOptions []vmess.ClientOption
|
var clientOptions []vmess.ClientOption
|
||||||
if timeFunc := router.TimeFunc(); timeFunc != nil {
|
|
||||||
clientOptions = append(clientOptions, vmess.ClientWithTimeFunc(timeFunc))
|
|
||||||
}
|
|
||||||
if options.GlobalPadding {
|
if options.GlobalPadding {
|
||||||
clientOptions = append(clientOptions, vmess.ClientWithGlobalPadding())
|
clientOptions = append(clientOptions, vmess.ClientWithGlobalPadding())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -e -o pipefail
|
set -e -o pipefail
|
||||||
curl -Lo go.tar.gz https://go.dev/dl/go1.20.1.linux-amd64.tar.gz
|
curl -Lo go.tar.gz https://go.dev/dl/go1.20.linux-amd64.tar.gz
|
||||||
sudo rm -rf /usr/local/go
|
sudo rm -rf /usr/local/go
|
||||||
sudo tar -C /usr/local -xzf go.tar.gz
|
sudo tar -C /usr/local -xzf go.tar.gz
|
||||||
rm go.tar.gz
|
rm go.tar.gz
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user