mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60d3ba3b18 | ||
|
|
4f4a815654 | ||
|
|
77e3f2d465 | ||
|
|
d012a9091d | ||
|
|
c986a48001 | ||
|
|
8550495789 | ||
|
|
06bc57d85a | ||
|
|
b54c1b1aa7 | ||
|
|
cc90156b96 | ||
|
|
593fc97c65 | ||
|
|
ef73c6f2a9 | ||
|
|
5f78036977 | ||
|
|
f846cf918e | ||
|
|
c3cab4cad1 | ||
|
|
0b22c7e314 | ||
|
|
a3b1656995 | ||
|
|
f5f3468f65 | ||
|
|
b6cd48944a | ||
|
|
8fbbb4f8dd | ||
|
|
94e0ebd3c8 | ||
|
|
32ad5710ba | ||
|
|
39fb23775c | ||
|
|
3ad4fc728f | ||
|
|
f6cb32b76e | ||
|
|
a8fcadab25 |
28
.github/renovate.json
vendored
28
.github/renovate.json
vendored
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"commitMessagePrefix": "[dependencies]",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":disableRateLimiting"
|
||||
],
|
||||
"baseBranches": [
|
||||
"dev-next"
|
||||
],
|
||||
"golang": {
|
||||
"enabled": false
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": [
|
||||
"github-actions"
|
||||
],
|
||||
"groupName": "github-actions"
|
||||
},
|
||||
{
|
||||
"matchManagers": [
|
||||
"dockerfile"
|
||||
],
|
||||
"groupName": "Dockerfile"
|
||||
}
|
||||
]
|
||||
}
|
||||
6
.github/workflows/debug.yml
vendored
6
.github/workflows/debug.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
run: |
|
||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
- name: Cache go module
|
||||
@@ -193,7 +193,7 @@ jobs:
|
||||
run: |
|
||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
run: |
|
||||
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
|
||||
6
.github/workflows/mkdocs.yml
vendored
6
.github/workflows/mkdocs.yml
vendored
@@ -14,7 +14,5 @@ jobs:
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: |
|
||||
pip install mkdocs-material=="9.*" mkdocs-static-i18n=="0.53"
|
||||
- run: |
|
||||
mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||
- run: pip install mkdocs-material mkdocs-static-i18n
|
||||
- run: mkdocs gh-deploy -m "{sha}" --force --ignore-version --no-history
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,4 +12,3 @@
|
||||
/*.aar
|
||||
/*.xcframework/
|
||||
.DS_Store
|
||||
/config.d/
|
||||
|
||||
@@ -12,8 +12,6 @@ run:
|
||||
- transport/simple-obfs
|
||||
- transport/clashssr
|
||||
- transport/cloudflaretls
|
||||
- transport/shadowtls/tls
|
||||
- transport/shadowtls/tls_go119
|
||||
|
||||
linters-settings:
|
||||
gci:
|
||||
|
||||
@@ -10,13 +10,12 @@ builds:
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||
- -s -w -buildid=
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
@@ -44,7 +43,7 @@ builds:
|
||||
gcflags:
|
||||
- all=-trimpath={{.Env.GOPATH}}
|
||||
ldflags:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }} -s -w -buildid=
|
||||
- -X "github.com/sagernet/sing-box/constant.Version={{ .Version }}" -s -w -buildid=
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
@@ -118,6 +117,7 @@ nfpms:
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
scripts:
|
||||
postinstall: release/config/postinstall.sh
|
||||
postremove: release/config/postremove.sh
|
||||
source:
|
||||
enabled: false
|
||||
|
||||
@@ -9,7 +9,7 @@ RUN set -ex \
|
||||
&& apk add git build-base \
|
||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
||||
&& go build -v -trimpath -tags with_quic,with_wireguard,with_acme \
|
||||
-o /go/bin/sing-box \
|
||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||
./cmd/sing-box
|
||||
|
||||
19
Makefile
19
Makefile
@@ -2,14 +2,9 @@ NAME = sing-box
|
||||
COMMIT = $(shell git rev-parse --short HEAD)
|
||||
TAGS ?= with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server,with_shadowsocksr
|
||||
|
||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||
|
||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||
VERSION=$(shell go run ./cmd/internal/read_tag)
|
||||
PARAMS = -v -trimpath -tags "$(TAGS)" -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$(VERSION)\" -s -w -buildid="
|
||||
MAIN = ./cmd/sing-box
|
||||
PREFIX ?= $(shell go env GOPATH)
|
||||
|
||||
.PHONY: test release
|
||||
|
||||
@@ -17,7 +12,7 @@ build:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
|
||||
install:
|
||||
go build -o $(PREFIX)/bin/$(NAME) $(PARAMS) $(MAIN)
|
||||
go install $(PARAMS) $(MAIN)
|
||||
|
||||
fmt:
|
||||
@gofumpt -l -w .
|
||||
@@ -77,14 +72,6 @@ test_stdio:
|
||||
go mod tidy && \
|
||||
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:
|
||||
rm -rf bin dist sing-box
|
||||
rm -f $(shell go env GOPATH)/sing-box
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
The universal proxy platform.
|
||||
|
||||
[](https://repology.org/project/sing-box/versions)
|
||||
|
||||
## Documentation
|
||||
|
||||
https://sing-box.sagernet.org
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
type ClashServer interface {
|
||||
Service
|
||||
PreStarter
|
||||
Mode() string
|
||||
StoreSelected() bool
|
||||
CacheFile() ClashCacheFile
|
||||
@@ -46,6 +45,6 @@ type V2RayServer interface {
|
||||
}
|
||||
|
||||
type V2RayStatsService interface {
|
||||
RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn
|
||||
RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn
|
||||
RoutedConnection(inbound string, outbound string, conn net.Conn) net.Conn
|
||||
RoutedPacketConnection(inbound string, outbound string, conn N.PacketConn) N.PacketConn
|
||||
}
|
||||
|
||||
@@ -46,10 +46,6 @@ type InboundContext struct {
|
||||
SourceGeoIPCode string
|
||||
GeoIPCode string
|
||||
ProcessInfo *process.Info
|
||||
|
||||
// dns cache
|
||||
|
||||
QueryType uint16
|
||||
}
|
||||
|
||||
type inboundContextKey struct{}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package adapter
|
||||
|
||||
type PreStarter interface {
|
||||
PreStart() error
|
||||
}
|
||||
|
||||
func PreStart(starter any) error {
|
||||
if preService, ok := starter.(PreStarter); ok {
|
||||
err := preService.PreStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -34,15 +34,12 @@ type Router interface {
|
||||
InterfaceFinder() control.InterfaceFinder
|
||||
DefaultInterface() string
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
DefaultMark() int
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
Rules() []Rule
|
||||
|
||||
TimeService
|
||||
|
||||
ClashServer() ClashServer
|
||||
SetClashServer(server ClashServer)
|
||||
|
||||
@@ -50,20 +47,6 @@ type Router interface {
|
||||
SetV2RayServer(server V2RayServer)
|
||||
}
|
||||
|
||||
type routerContextKey struct{}
|
||||
|
||||
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||
return context.WithValue(ctx, (*routerContextKey)(nil), router)
|
||||
}
|
||||
|
||||
func RouterFromContext(ctx context.Context) Router {
|
||||
metadata := ctx.Value((*routerContextKey)(nil))
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
return metadata.(Router)
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
Service
|
||||
Type() string
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package adapter
|
||||
|
||||
import "time"
|
||||
|
||||
type TimeService interface {
|
||||
Service
|
||||
TimeFunc() func() time.Time
|
||||
}
|
||||
@@ -3,10 +3,6 @@ package adapter
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type V2RayServerTransport interface {
|
||||
@@ -16,12 +12,6 @@ type V2RayServerTransport interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type V2RayServerTransportHandler interface {
|
||||
N.TCPConnectionHandler
|
||||
E.Handler
|
||||
FallbackConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error
|
||||
}
|
||||
|
||||
type V2RayClientTransport interface {
|
||||
DialContext(ctx context.Context) (net.Conn, error)
|
||||
}
|
||||
|
||||
194
box.go
194
box.go
@@ -9,9 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"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/libbox/platform"
|
||||
"github.com/sagernet/sing-box/inbound"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@@ -25,57 +23,49 @@ import (
|
||||
var _ adapter.Service = (*Box)(nil)
|
||||
|
||||
type Box struct {
|
||||
createdAt time.Time
|
||||
router adapter.Router
|
||||
inbounds []adapter.Inbound
|
||||
outbounds []adapter.Outbound
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
logFile *os.File
|
||||
preServices map[string]adapter.Service
|
||||
postServices map[string]adapter.Service
|
||||
done chan struct{}
|
||||
createdAt time.Time
|
||||
router adapter.Router
|
||||
inbounds []adapter.Inbound
|
||||
outbounds []adapter.Outbound
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
logFile *os.File
|
||||
clashServer adapter.ClashServer
|
||||
v2rayServer adapter.V2RayServer
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
|
||||
func New(ctx context.Context, options option.Options) (*Box, error) {
|
||||
createdAt := time.Now()
|
||||
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||
logOptions := common.PtrValueOrDefault(options.Log)
|
||||
|
||||
var needClashAPI bool
|
||||
var needV2RayAPI bool
|
||||
if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
|
||||
needClashAPI = true
|
||||
if options.Experimental != nil {
|
||||
if options.Experimental.ClashAPI != nil && options.Experimental.ClashAPI.ExternalController != "" {
|
||||
needClashAPI = true
|
||||
}
|
||||
if options.Experimental.V2RayAPI != nil && options.Experimental.V2RayAPI.Listen != "" {
|
||||
needV2RayAPI = true
|
||||
}
|
||||
}
|
||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||
needV2RayAPI = true
|
||||
}
|
||||
|
||||
logOptions := common.PtrValueOrDefault(options.Log)
|
||||
|
||||
var logFactory log.Factory
|
||||
var observableLogFactory log.ObservableFactory
|
||||
var logFile *os.File
|
||||
var logWriter io.Writer
|
||||
if logOptions.Disabled {
|
||||
observableLogFactory = log.NewNOPFactory()
|
||||
logFactory = observableLogFactory
|
||||
} else {
|
||||
var logWriter io.Writer
|
||||
switch logOptions.Output {
|
||||
case "":
|
||||
if platformInterface != nil {
|
||||
logWriter = io.Discard
|
||||
} else {
|
||||
logWriter = os.Stdout
|
||||
}
|
||||
case "stderr":
|
||||
case "", "stderr":
|
||||
logWriter = os.Stderr
|
||||
case "stdout":
|
||||
logWriter = os.Stdout
|
||||
default:
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,10 +79,10 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||
}
|
||||
if needClashAPI {
|
||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
|
||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter)
|
||||
logFactory = observableLogFactory
|
||||
} else {
|
||||
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
|
||||
logFactory = log.NewFactory(logFormatter, logWriter)
|
||||
}
|
||||
if logOptions.Level != "" {
|
||||
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||
@@ -110,9 +100,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
logFactory,
|
||||
common.PtrValueOrDefault(options.Route),
|
||||
common.PtrValueOrDefault(options.DNS),
|
||||
common.PtrValueOrDefault(options.NTP),
|
||||
options.Inbounds,
|
||||
platformInterface,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse route options")
|
||||
@@ -132,7 +120,6 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
inboundOptions,
|
||||
platformInterface,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||
@@ -166,57 +153,37 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
preServices := make(map[string]adapter.Service)
|
||||
postServices := make(map[string]adapter.Service)
|
||||
|
||||
var clashServer adapter.ClashServer
|
||||
var v2rayServer adapter.V2RayServer
|
||||
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 {
|
||||
return nil, E.Cause(err, "create clash api server")
|
||||
}
|
||||
router.SetClashServer(clashServer)
|
||||
preServices["clash api"] = clashServer
|
||||
}
|
||||
if needV2RayAPI {
|
||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
|
||||
v2rayServer, err = experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create v2ray api server")
|
||||
}
|
||||
router.SetV2RayServer(v2rayServer)
|
||||
preServices["v2ray api"] = v2rayServer
|
||||
}
|
||||
return &Box{
|
||||
router: router,
|
||||
inbounds: inbounds,
|
||||
outbounds: outbounds,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
logFile: logFile,
|
||||
preServices: preServices,
|
||||
postServices: postServices,
|
||||
done: make(chan struct{}),
|
||||
router: router,
|
||||
inbounds: inbounds,
|
||||
outbounds: outbounds,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
logFile: logFile,
|
||||
clashServer: clashServer,
|
||||
v2rayServer: v2rayServer,
|
||||
done: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Box) PreStart() error {
|
||||
err := s.preStart()
|
||||
if err != nil {
|
||||
// TODO: remove catch error
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
log.Error(E.Cause(err, "origin error"))
|
||||
debug.PrintStack()
|
||||
panic("panic on early close: " + fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
s.Close()
|
||||
return err
|
||||
}
|
||||
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) Start() error {
|
||||
err := s.start()
|
||||
if err != nil {
|
||||
@@ -230,19 +197,11 @@ func (s *Box) Start() error {
|
||||
}
|
||||
}()
|
||||
s.Close()
|
||||
return err
|
||||
}
|
||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Box) preStart() error {
|
||||
for serviceName, service := range s.preServices {
|
||||
err := adapter.PreStart(service)
|
||||
if err != nil {
|
||||
return E.Cause(err, "pre-start ", serviceName)
|
||||
}
|
||||
}
|
||||
func (s *Box) start() error {
|
||||
for i, out := range s.outbounds {
|
||||
if starter, isStarter := out.(common.Starter); isStarter {
|
||||
err := starter.Start()
|
||||
@@ -257,20 +216,10 @@ func (s *Box) preStart() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.router.Start()
|
||||
}
|
||||
|
||||
func (s *Box) start() error {
|
||||
err := s.preStart()
|
||||
err := s.router.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for serviceName, service := range s.preServices {
|
||||
err = service.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
for i, in := range s.inbounds {
|
||||
err = in.Start()
|
||||
if err != nil {
|
||||
@@ -283,12 +232,19 @@ func (s *Box) start() error {
|
||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
for serviceName, service := range s.postServices {
|
||||
err = service.Start()
|
||||
if s.clashServer != nil {
|
||||
err = s.clashServer.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
return E.Cause(err, "start clash api server")
|
||||
}
|
||||
}
|
||||
if s.v2rayServer != nil {
|
||||
err = s.v2rayServer.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start v2ray api server")
|
||||
}
|
||||
}
|
||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -299,43 +255,19 @@ func (s *Box) Close() error {
|
||||
default:
|
||||
close(s.done)
|
||||
}
|
||||
var errors error
|
||||
for serviceName, service := range s.postServices {
|
||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", serviceName)
|
||||
})
|
||||
for _, in := range s.inbounds {
|
||||
in.Close()
|
||||
}
|
||||
for i, in := range s.inbounds {
|
||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||
})
|
||||
for _, out := range s.outbounds {
|
||||
common.Close(out)
|
||||
}
|
||||
for i, out := range s.outbounds {
|
||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
||||
})
|
||||
}
|
||||
if err := common.Close(s.router); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close router")
|
||||
})
|
||||
}
|
||||
for serviceName, service := range s.preServices {
|
||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", serviceName)
|
||||
})
|
||||
}
|
||||
if err := common.Close(s.logFactory); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close log factory")
|
||||
})
|
||||
}
|
||||
if s.logFile != nil {
|
||||
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
||||
return E.Cause(err, "close log file")
|
||||
})
|
||||
}
|
||||
return errors
|
||||
return common.Close(
|
||||
s.router,
|
||||
s.logFactory,
|
||||
s.clashServer,
|
||||
s.v2rayServer,
|
||||
common.PtrOrNil(s.logFile),
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Box) Router() adapter.Router {
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
_ "github.com/sagernet/gomobile/event/key"
|
||||
"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()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
sharedFlags []string
|
||||
debugFlags []string
|
||||
)
|
||||
|
||||
func init() {
|
||||
sharedFlags = append(sharedFlags, "-trimpath")
|
||||
sharedFlags = append(sharedFlags, "-ldflags")
|
||||
|
||||
currentTag, err := build_shared.ReadTag()
|
||||
if err != nil {
|
||||
currentTag = "unknown"
|
||||
}
|
||||
sharedFlags = append(sharedFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||
debugFlags = append(debugFlags, "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||
}
|
||||
|
||||
func buildAndroid() {
|
||||
build_shared.FindSDK()
|
||||
|
||||
args := []string{
|
||||
"bind",
|
||||
"-v",
|
||||
"-androidapi", "21",
|
||||
"-javapkg=io.nekohasekai",
|
||||
"-libname=box",
|
||||
}
|
||||
if !debugEnabled {
|
||||
args = append(args, sharedFlags...)
|
||||
} else {
|
||||
args = append(args, debugFlags...)
|
||||
}
|
||||
|
||||
args = append(args, "-tags")
|
||||
if !debugEnabled {
|
||||
args = append(args, "with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api")
|
||||
} else {
|
||||
args = append(args, "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, sharedFlags...)
|
||||
} else {
|
||||
args = append(args, debugFlags...)
|
||||
}
|
||||
|
||||
args = append(args, "-tags")
|
||||
if !debugEnabled {
|
||||
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack")
|
||||
} else {
|
||||
args = append(args, "with_gvisor,with_quic,with_utls,with_clash_api,with_low_memory,with_conntrack,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("..", "sing-box-for-ios")
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package build_shared
|
||||
import "github.com/sagernet/sing/common/shell"
|
||||
|
||||
func ReadTag() (string, error) {
|
||||
currentTag, err := shell.Exec("git", "describe", "--tags").ReadOutput()
|
||||
currentTag, err := shell.Exec("git", "describe", "--tags").Read()
|
||||
if err != nil {
|
||||
return currentTag, err
|
||||
}
|
||||
|
||||
@@ -26,15 +26,12 @@ func init() {
|
||||
}
|
||||
|
||||
func check() error {
|
||||
options, err := readConfigAndMerge()
|
||||
options, err := readConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
instance, err := box.New(ctx, options, nil)
|
||||
if err == nil {
|
||||
instance.Close()
|
||||
}
|
||||
_, err = box.New(ctx, options)
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -33,44 +33,6 @@ func init() {
|
||||
}
|
||||
|
||||
func format() error {
|
||||
optionsList, err := readConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, optionsEntry := range optionsList {
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(optionsEntry.options)
|
||||
if err != nil {
|
||||
return E.Cause(err, "encode config")
|
||||
}
|
||||
outputPath, _ := filepath.Abs(optionsEntry.path)
|
||||
if !commandFormatFlagWrite {
|
||||
if len(optionsList) > 1 {
|
||||
os.Stdout.WriteString(outputPath + "\n")
|
||||
}
|
||||
os.Stdout.WriteString(buffer.String() + "\n")
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(optionsEntry.content, buffer.Bytes()) {
|
||||
continue
|
||||
}
|
||||
output, err := os.Create(optionsEntry.path)
|
||||
if err != nil {
|
||||
return E.Cause(err, "open output")
|
||||
}
|
||||
_, err = output.Write(buffer.Bytes())
|
||||
output.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "write output")
|
||||
}
|
||||
os.Stderr.WriteString(outputPath + "\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatOne(configPath string) error {
|
||||
configContent, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read config")
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
var commandGenerate = &cobra.Command{
|
||||
Use: "generate",
|
||||
Short: "Generate things",
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandGenerate.AddCommand(commandGenerateUUID)
|
||||
commandGenerate.AddCommand(commandGenerateRandom)
|
||||
commandGenerate.AddCommand(commandGenerateWireGuardKeyPair)
|
||||
commandGenerate.AddCommand(commandGenerateRealityKeyPair)
|
||||
mainCommand.AddCommand(commandGenerate)
|
||||
}
|
||||
|
||||
var (
|
||||
outputBase64 bool
|
||||
outputHex bool
|
||||
)
|
||||
|
||||
var commandGenerateRandom = &cobra.Command{
|
||||
Use: "rand <length>",
|
||||
Short: "Generate random bytes",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateRandom(args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandGenerateRandom.Flags().BoolVar(&outputBase64, "base64", false, "Generate base64 string")
|
||||
commandGenerateRandom.Flags().BoolVar(&outputHex, "hex", false, "Generate hex string")
|
||||
}
|
||||
|
||||
func generateRandom(args []string) error {
|
||||
length, err := strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
randomBytes := make([]byte, length)
|
||||
_, err = rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if outputBase64 {
|
||||
_, err = os.Stdout.WriteString(base64.StdEncoding.EncodeToString(randomBytes) + "\n")
|
||||
} else if outputHex {
|
||||
_, err = os.Stdout.WriteString(hex.EncodeToString(randomBytes) + "\n")
|
||||
} else {
|
||||
_, err = os.Stdout.Write(randomBytes)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var commandGenerateUUID = &cobra.Command{
|
||||
Use: "uuid",
|
||||
Short: "Generate UUID string",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateUUID()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func generateUUID() error {
|
||||
newUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = os.Stdout.WriteString(newUUID.String() + "\n")
|
||||
return err
|
||||
}
|
||||
|
||||
var commandGenerateWireGuardKeyPair = &cobra.Command{
|
||||
Use: "wg-keypair",
|
||||
Short: "Generate WireGuard key pair",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateWireGuardKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func generateWireGuardKey() error {
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n")
|
||||
os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
var commandGenerateRealityKeyPair = &cobra.Command{
|
||||
Use: "reality-keypair",
|
||||
Short: "Generate reality key pair",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := generateRealityKey()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func generateRealityKey() error {
|
||||
privateKey, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n")
|
||||
os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n")
|
||||
return nil
|
||||
}
|
||||
@@ -5,14 +5,10 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
runtimeDebug "runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/common/badjsonmerge"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
@@ -35,88 +31,29 @@ func init() {
|
||||
mainCommand.AddCommand(commandRun)
|
||||
}
|
||||
|
||||
type OptionsEntry struct {
|
||||
content []byte
|
||||
path string
|
||||
options option.Options
|
||||
}
|
||||
|
||||
func readConfigAt(path string) (*OptionsEntry, error) {
|
||||
func readConfig() (option.Options, error) {
|
||||
var (
|
||||
configContent []byte
|
||||
err error
|
||||
)
|
||||
if path == "stdin" {
|
||||
if configPath == "stdin" {
|
||||
configContent, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
configContent, err = os.ReadFile(path)
|
||||
configContent, err = os.ReadFile(configPath)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read config at ", path)
|
||||
return option.Options{}, E.Cause(err, "read config")
|
||||
}
|
||||
var options option.Options
|
||||
err = options.UnmarshalJSON(configContent)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode config at ", path)
|
||||
return option.Options{}, E.Cause(err, "decode config")
|
||||
}
|
||||
return &OptionsEntry{
|
||||
content: configContent,
|
||||
path: path,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readConfig() ([]*OptionsEntry, error) {
|
||||
var optionsList []*OptionsEntry
|
||||
for _, path := range configPaths {
|
||||
optionsEntry, err := readConfigAt(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optionsList = append(optionsList, optionsEntry)
|
||||
}
|
||||
for _, directory := range configDirectories {
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read config directory at ", directory)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optionsList = append(optionsList, optionsEntry)
|
||||
}
|
||||
}
|
||||
sort.Slice(optionsList, func(i, j int) bool {
|
||||
return optionsList[i].path < optionsList[j].path
|
||||
})
|
||||
return optionsList, nil
|
||||
}
|
||||
|
||||
func readConfigAndMerge() (option.Options, error) {
|
||||
optionsList, err := readConfig()
|
||||
if err != nil {
|
||||
return option.Options{}, err
|
||||
}
|
||||
if len(optionsList) == 1 {
|
||||
return optionsList[0].options, nil
|
||||
}
|
||||
var mergedOptions option.Options
|
||||
for _, options := range optionsList {
|
||||
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||
}
|
||||
}
|
||||
return mergedOptions, nil
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func create() (*box.Box, context.CancelFunc, error) {
|
||||
options, err := readConfigAndMerge()
|
||||
options, err := readConfig()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -127,7 +64,7 @@ func create() (*box.Box, context.CancelFunc, error) {
|
||||
options.Log.DisableColor = true
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
instance, err := box.New(ctx, options, nil)
|
||||
instance, err := box.New(ctx, options)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, nil, E.Cause(err, "create service")
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var commandToolsFlagOutbound string
|
||||
|
||||
var commandTools = &cobra.Command{
|
||||
Use: "tools",
|
||||
Short: "Experimental tools",
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound")
|
||||
mainCommand.AddCommand(commandTools)
|
||||
}
|
||||
|
||||
func createPreStartedClient() (*box.Box, error) {
|
||||
options, err := readConfigAndMerge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instance, err := box.New(context.Background(), options, nil)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create service")
|
||||
}
|
||||
err = instance.PreStart()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "start service")
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
||||
if outboundTag == "" {
|
||||
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||
if outbound == nil {
|
||||
return nil, E.New("missing default outbound")
|
||||
}
|
||||
return outbound, nil
|
||||
} else {
|
||||
outbound, loaded := instance.Router().Outbound(outboundTag)
|
||||
if !loaded {
|
||||
return nil, E.New("outbound not found: ", outboundTag)
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/task"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var commandConnectFlagNetwork string
|
||||
|
||||
var commandConnect = &cobra.Command{
|
||||
Use: "connect [address]",
|
||||
Short: "Connect to an address",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := connect(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
|
||||
commandTools.AddCommand(commandConnect)
|
||||
}
|
||||
|
||||
func connect(address string) error {
|
||||
switch N.NetworkName(commandConnectFlagNetwork) {
|
||||
case N.NetworkTCP, N.NetworkUDP:
|
||||
default:
|
||||
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
|
||||
}
|
||||
instance, err := createPreStartedClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
|
||||
if err != nil {
|
||||
return E.Cause(err, "connect to server")
|
||||
}
|
||||
var group task.Group
|
||||
group.Append("upload", func(ctx context.Context) error {
|
||||
return common.Error(bufio.Copy(conn, os.Stdin))
|
||||
})
|
||||
group.Append("download", func(ctx context.Context) error {
|
||||
return common.Error(bufio.Copy(os.Stdout, conn))
|
||||
})
|
||||
group.Cleanup(func() {
|
||||
conn.Close()
|
||||
})
|
||||
err = group.Run(context.Background())
|
||||
if E.IsClosed(err) {
|
||||
log.Info(err)
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var commandFetch = &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "Fetch an URL",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := fetch(args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandTools.AddCommand(commandFetch)
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
func fetch(args []string) error {
|
||||
instance, err := createPreStartedClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
ForceAttemptHTTP2: true,
|
||||
},
|
||||
}
|
||||
defer httpClient.CloseIdleConnections()
|
||||
for _, urlString := range args {
|
||||
parsedURL, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch parsedURL.Scheme {
|
||||
case "":
|
||||
parsedURL.Scheme = "http"
|
||||
fallthrough
|
||||
case "http", "https":
|
||||
err = fetchHTTP(parsedURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchHTTP(parsedURL *url.URL) error {
|
||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Add("User-Agent", "curl/7.88.0")
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
_, err = bufio.Copy(os.Stdout, response.Body)
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
commandSyncTimeFlagServer string
|
||||
commandSyncTimeOutputFormat string
|
||||
commandSyncTimeWrite bool
|
||||
)
|
||||
|
||||
var commandSyncTime = &cobra.Command{
|
||||
Use: "synctime",
|
||||
Short: "Sync time using the NTP protocol",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := syncTime()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, "server", "s", "time.apple.com", "Set NTP server")
|
||||
commandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, "format", "f", C.TimeLayout, "Set output format")
|
||||
commandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, "write", "w", false, "Write time to system")
|
||||
commandTools.AddCommand(commandSyncTime)
|
||||
}
|
||||
|
||||
func syncTime() error {
|
||||
instance, err := createPreStartedClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
||||
if serverAddress.Port == 0 {
|
||||
serverAddress.Port = 123
|
||||
}
|
||||
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if commandSyncTimeWrite {
|
||||
err = settings.SetSystemTime(response.Time)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write time to system")
|
||||
}
|
||||
}
|
||||
os.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat))
|
||||
return nil
|
||||
}
|
||||
@@ -25,9 +25,9 @@ func init() {
|
||||
runtime.ReadMemStats(&memStats)
|
||||
|
||||
var memObject badjson.JSONObject
|
||||
memObject.Put("heap", humanize.IBytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.IBytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("heap", humanize.Bytes(memStats.HeapInuse))
|
||||
memObject.Put("stack", humanize.Bytes(memStats.StackInuse))
|
||||
memObject.Put("idle", humanize.Bytes(memStats.HeapIdle-memStats.HeapReleased))
|
||||
memObject.Put("goroutines", runtime.NumGoroutine())
|
||||
memObject.Put("rss", rusageMaxRSS())
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "github.com/sagernet/sing-box/include"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@@ -11,10 +10,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
configPaths []string
|
||||
configDirectories []string
|
||||
workingDir string
|
||||
disableColor bool
|
||||
configPath string
|
||||
workingDir string
|
||||
disableColor bool
|
||||
)
|
||||
|
||||
var mainCommand = &cobra.Command{
|
||||
@@ -23,8 +21,7 @@ var mainCommand = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
|
||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||
}
|
||||
@@ -36,19 +33,9 @@ func main() {
|
||||
}
|
||||
|
||||
func preRun(cmd *cobra.Command, args []string) {
|
||||
if disableColor {
|
||||
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger())
|
||||
}
|
||||
if workingDir != "" {
|
||||
_, err := os.Stat(workingDir)
|
||||
if err != nil {
|
||||
os.MkdirAll(workingDir, 0o777)
|
||||
}
|
||||
if err := os.Chdir(workingDir); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||
configPaths = append(configPaths, "config.json")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package badjsonmerge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/sing-box/common/badjson"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func MergeOptions(source option.Options, destination option.Options) (option.Options, error) {
|
||||
rawSource, err := json.Marshal(source)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "marshal source")
|
||||
}
|
||||
rawDestination, err := json.Marshal(destination)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "marshal destination")
|
||||
}
|
||||
rawMerged, err := MergeJSON(rawSource, rawDestination)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "merge options")
|
||||
}
|
||||
var merged option.Options
|
||||
err = json.Unmarshal(rawMerged, &merged)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "unmarshal merged options")
|
||||
}
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
func MergeJSON(rawSource json.RawMessage, rawDestination json.RawMessage) (json.RawMessage, error) {
|
||||
source, err := badjson.Decode(rawSource)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode source")
|
||||
}
|
||||
destination, err := badjson.Decode(rawDestination)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode destination")
|
||||
}
|
||||
merged, err := mergeJSON(source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(merged)
|
||||
}
|
||||
|
||||
func mergeJSON(anySource any, anyDestination any) (any, error) {
|
||||
switch destination := anyDestination.(type) {
|
||||
case badjson.JSONArray:
|
||||
switch source := anySource.(type) {
|
||||
case badjson.JSONArray:
|
||||
destination = append(destination, source...)
|
||||
default:
|
||||
destination = append(destination, source)
|
||||
}
|
||||
return destination, nil
|
||||
case *badjson.JSONObject:
|
||||
switch source := anySource.(type) {
|
||||
case *badjson.JSONObject:
|
||||
for _, entry := range source.Entries() {
|
||||
oldValue, loaded := destination.Get(entry.Key)
|
||||
if loaded {
|
||||
var err error
|
||||
entry.Value, err = mergeJSON(entry.Value, oldValue)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "merge object item ", entry.Key)
|
||||
}
|
||||
}
|
||||
destination.Put(entry.Key, entry.Value)
|
||||
}
|
||||
default:
|
||||
return nil, E.New("cannot merge json object into ", reflect.TypeOf(destination))
|
||||
}
|
||||
return destination, nil
|
||||
default:
|
||||
return destination, nil
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package badjsonmerge
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMergeJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := option.Options{
|
||||
Log: &option.LogOptions{
|
||||
Level: "info",
|
||||
},
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Network: N.NetworkTCP,
|
||||
Outbound: "direct",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
anotherOptions := option.Options{
|
||||
Outbounds: []option.Outbound{
|
||||
{
|
||||
Type: C.TypeDirect,
|
||||
Tag: "direct",
|
||||
},
|
||||
},
|
||||
}
|
||||
thirdOptions := option.Options{
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Network: N.NetworkUDP,
|
||||
Outbound: "direct",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
mergeOptions, err := MergeOptions(options, anotherOptions)
|
||||
require.NoError(t, err)
|
||||
mergeOptions, err = MergeOptions(thirdOptions, mergeOptions)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "info", mergeOptions.Log.Level)
|
||||
require.Equal(t, 2, len(mergeOptions.Route.Rules))
|
||||
require.Equal(t, C.TypeDirect, mergeOptions.Outbounds[0].Type)
|
||||
}
|
||||
48
common/canceler/instance.go
Normal file
48
common/canceler/instance.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package canceler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
timer *time.Timer
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func New(ctx context.Context, cancelFunc context.CancelFunc, timeout time.Duration) *Instance {
|
||||
instance := &Instance{
|
||||
ctx,
|
||||
cancelFunc,
|
||||
time.NewTimer(timeout),
|
||||
timeout,
|
||||
}
|
||||
go instance.wait()
|
||||
return instance
|
||||
}
|
||||
|
||||
func (i *Instance) Update() bool {
|
||||
if !i.timer.Stop() {
|
||||
return false
|
||||
}
|
||||
if !i.timer.Reset(i.timeout) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (i *Instance) wait() {
|
||||
select {
|
||||
case <-i.timer.C:
|
||||
case <-i.ctx.Done():
|
||||
}
|
||||
i.Close()
|
||||
}
|
||||
|
||||
func (i *Instance) Close() error {
|
||||
i.timer.Stop()
|
||||
i.cancelFunc()
|
||||
return nil
|
||||
}
|
||||
49
common/canceler/packet.go
Normal file
49
common/canceler/packet.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package canceler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type PacketConn struct {
|
||||
N.PacketConn
|
||||
instance *Instance
|
||||
}
|
||||
|
||||
func NewPacketConn(ctx context.Context, conn N.PacketConn, timeout time.Duration) (context.Context, N.PacketConn) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
instance := New(ctx, cancel, timeout)
|
||||
return ctx, &PacketConn{conn, instance}
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||
destination, err = c.PacketConn.ReadPacket(buffer)
|
||||
if err == nil {
|
||||
c.instance.Update()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *PacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
err := c.PacketConn.WritePacket(buffer, destination)
|
||||
if err == nil {
|
||||
c.instance.Update()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
return common.Close(
|
||||
c.PacketConn,
|
||||
c.instance,
|
||||
)
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
net.Conn
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn) (*Conn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := killerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
element: element,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
if c.element.Value != nil {
|
||||
connAccess.Lock()
|
||||
if c.element.Value != nil {
|
||||
openConnection.Remove(c.element)
|
||||
c.element.Value = nil
|
||||
}
|
||||
connAccess.Unlock()
|
||||
}
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
func (c *Conn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
runtimeDebug "runtime/debug"
|
||||
"time"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var (
|
||||
KillerEnabled bool
|
||||
MemoryLimit int64
|
||||
killerLastCheck time.Time
|
||||
)
|
||||
|
||||
func killerCheck() error {
|
||||
if !KillerEnabled {
|
||||
return nil
|
||||
}
|
||||
nowTime := time.Now()
|
||||
if nowTime.Sub(killerLastCheck) < 3*time.Second {
|
||||
return nil
|
||||
}
|
||||
killerLastCheck = nowTime
|
||||
var memStats runtime.MemStats
|
||||
runtime.ReadMemStats(&memStats)
|
||||
inuseMemory := int64(memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased)
|
||||
if inuseMemory > MemoryLimit {
|
||||
Close()
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
runtimeDebug.FreeOSMemory()
|
||||
}()
|
||||
return E.New("out of memory")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
type PacketConn struct {
|
||||
net.PacketConn
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
if KillerEnabled {
|
||||
err := killerCheck()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &PacketConn{
|
||||
PacketConn: conn,
|
||||
element: element,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
if c.element.Value != nil {
|
||||
connAccess.Lock()
|
||||
if c.element.Value != nil {
|
||||
openConnection.Remove(c.element)
|
||||
c.element.Value = nil
|
||||
}
|
||||
connAccess.Unlock()
|
||||
}
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (c *PacketConn) Upstream() any {
|
||||
return c.PacketConn
|
||||
}
|
||||
|
||||
func (c *PacketConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *PacketConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package conntrack
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
var (
|
||||
connAccess sync.RWMutex
|
||||
openConnection list.List[io.Closer]
|
||||
)
|
||||
|
||||
func Count() int {
|
||||
return openConnection.Len()
|
||||
}
|
||||
|
||||
func List() []io.Closer {
|
||||
connAccess.RLock()
|
||||
defer connAccess.RUnlock()
|
||||
connList := make([]io.Closer, 0, openConnection.Len())
|
||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||
connList = append(connList, element.Value)
|
||||
}
|
||||
return connList
|
||||
}
|
||||
|
||||
func Close() {
|
||||
connAccess.Lock()
|
||||
defer connAccess.Unlock()
|
||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||
common.Close(element.Value)
|
||||
element.Value = nil
|
||||
}
|
||||
openConnection.Init()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build !with_conntrack
|
||||
|
||||
package conntrack
|
||||
|
||||
const Enabled = false
|
||||
@@ -1,5 +0,0 @@
|
||||
//go:build with_conntrack
|
||||
|
||||
package conntrack
|
||||
|
||||
const Enabled = true
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/common/warning"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
@@ -14,7 +13,8 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/tfo-go"
|
||||
|
||||
"github.com/database64128/tfo-go/v2"
|
||||
)
|
||||
|
||||
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
||||
@@ -71,7 +71,15 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} 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)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.DefaultInterface() != "" {
|
||||
@@ -160,30 +168,16 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
}
|
||||
}
|
||||
if !address.IsIPv6() {
|
||||
return trackConn(DialSlowContext(&d.dialer4, ctx, network, address))
|
||||
return DialSlowContext(&d.dialer4, ctx, network, address)
|
||||
} else {
|
||||
return trackConn(DialSlowContext(&d.dialer6, ctx, network, address))
|
||||
return DialSlowContext(&d.dialer6, ctx, network, address)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if !destination.IsIPv6() {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4))
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||
} else {
|
||||
return trackPacketConn(d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6))
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||
}
|
||||
}
|
||||
|
||||
func trackConn(conn net.Conn, err error) (net.Conn, error) {
|
||||
if !conntrack.Enabled || err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return conntrack.NewConn(conn)
|
||||
}
|
||||
|
||||
func trackPacketConn(conn net.PacketConn, err error) (net.PacketConn, error) {
|
||||
if !conntrack.Enabled || err != nil {
|
||||
return conn, err
|
||||
}
|
||||
return conntrack.NewPacketConn(conn)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import (
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/tfo-go"
|
||||
|
||||
"github.com/database64128/tfo-go/v2"
|
||||
)
|
||||
|
||||
type slowOpenConn struct {
|
||||
@@ -119,10 +120,6 @@ func (c *slowOpenConn) LazyHeadroom() bool {
|
||||
return c.conn == nil
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) NeedHandshake() bool {
|
||||
return c.conn == nil
|
||||
}
|
||||
|
||||
func (c *slowOpenConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
if c.conn != nil {
|
||||
return bufio.Copy(c.conn, r)
|
||||
|
||||
@@ -24,13 +24,13 @@ func (l *Listener) Accept() (net.Conn, error) {
|
||||
bufReader := std_bufio.NewReader(conn)
|
||||
header, err := proxyproto.Read(bufReader)
|
||||
if err != nil && !(l.AcceptNoHeader && err == proxyproto.ErrNoProxyProtocol) {
|
||||
return nil, &Error{err}
|
||||
return nil, err
|
||||
}
|
||||
if bufReader.Buffered() > 0 {
|
||||
cache := buf.NewSize(bufReader.Buffered())
|
||||
_, err = cache.ReadFullFrom(bufReader, cache.FreeLen())
|
||||
if err != nil {
|
||||
return nil, &Error{err}
|
||||
return nil, err
|
||||
}
|
||||
conn = bufio.NewCachedConn(conn, cache)
|
||||
}
|
||||
@@ -42,21 +42,3 @@ func (l *Listener) Accept() (net.Conn, error) {
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
var _ net.Error = (*Error)(nil)
|
||||
|
||||
type Error struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (e *Error) Timeout() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *Error) Temporary() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
//go:build !(windows || linux || darwin)
|
||||
|
||||
package settings
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetSystemTime(nowTime time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package settings
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func SetSystemTime(nowTime time.Time) error {
|
||||
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
||||
return unix.Settimeofday(&timeVal)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func SetSystemTime(nowTime time.Time) error {
|
||||
var systemTime windows.Systemtime
|
||||
systemTime.Year = uint16(nowTime.Year())
|
||||
systemTime.Month = uint16(nowTime.Month())
|
||||
systemTime.Day = uint16(nowTime.Day())
|
||||
systemTime.Hour = uint16(nowTime.Hour())
|
||||
systemTime.Minute = uint16(nowTime.Minute())
|
||||
systemTime.Second = uint16(nowTime.Second())
|
||||
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
||||
|
||||
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
proc := dllKernel32.NewProc("SetSystemTime")
|
||||
|
||||
_, _, err := proc.Call(
|
||||
uintptr(unsafe.Pointer(&systemTime)),
|
||||
)
|
||||
|
||||
if err != nil && err.Error() != "The operation completed successfully." {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,15 +2,16 @@ package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/badtls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
)
|
||||
|
||||
func NewDialerFromOptions(router adapter.Router, dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) {
|
||||
@@ -30,19 +31,29 @@ func NewClient(router adapter.Router, serverAddress string, options option.Outbo
|
||||
}
|
||||
if options.ECH != nil && options.ECH.Enabled {
|
||||
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 {
|
||||
return NewUTLSClient(router, serverAddress, options)
|
||||
} else {
|
||||
return NewSTDClient(router, serverAddress, options)
|
||||
return NewSTDClient(serverAddress, options)
|
||||
}
|
||||
}
|
||||
|
||||
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
|
||||
tlsConn := config.Client(conn)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
defer cancel()
|
||||
return aTLS.ClientHandshake(ctx, conn, config)
|
||||
err := tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
||||
var badConn badtls.TLSConn
|
||||
badConn, err = badtls.Create(stdConn)
|
||||
if err == nil {
|
||||
return badConn, nil
|
||||
}
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
type Dialer struct {
|
||||
|
||||
@@ -1,25 +1,41 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
)
|
||||
|
||||
type (
|
||||
Config = aTLS.Config
|
||||
ConfigCompat = aTLS.ConfigCompat
|
||||
ServerConfig = aTLS.ServerConfig
|
||||
ServerConfigCompat = aTLS.ServerConfigCompat
|
||||
WithSessionIDGenerator = aTLS.WithSessionIDGenerator
|
||||
Conn = aTLS.Conn
|
||||
|
||||
STDConfig = tls.Config
|
||||
STDConn = tls.Conn
|
||||
ConnectionState = tls.ConnectionState
|
||||
STDConfig = tls.Config
|
||||
STDConn = tls.Conn
|
||||
)
|
||||
|
||||
type Config interface {
|
||||
ServerName() string
|
||||
SetServerName(serverName string)
|
||||
NextProtos() []string
|
||||
SetNextProtos(nextProto []string)
|
||||
Config() (*STDConfig, error)
|
||||
Client(conn net.Conn) Conn
|
||||
Clone() Config
|
||||
}
|
||||
|
||||
type ServerConfig interface {
|
||||
Config
|
||||
adapter.Service
|
||||
Server(conn net.Conn) Conn
|
||||
}
|
||||
|
||||
type Conn interface {
|
||||
net.Conn
|
||||
HandshakeContext(ctx context.Context) error
|
||||
ConnectionState() tls.ConnectionState
|
||||
}
|
||||
|
||||
func ParseTLSVersion(version string) (uint16, error) {
|
||||
switch version {
|
||||
case "1.0":
|
||||
|
||||
@@ -44,8 +44,8 @@ func (e *ECHClientConfig) Config() (*STDConfig, error) {
|
||||
return nil, E.New("unsupported usage for ECH")
|
||||
}
|
||||
|
||||
func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return &echConnWrapper{cftls.Client(conn, e.config)}, nil
|
||||
func (e *ECHClientConfig) Client(conn net.Conn) Conn {
|
||||
return &echConnWrapper{cftls.Client(conn, e.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) {
|
||||
var serverName string
|
||||
if options.ServerName != "" {
|
||||
@@ -94,7 +90,6 @@ func NewECHClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
}
|
||||
|
||||
var tlsConfig cftls.Config
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
|
||||
@@ -11,10 +11,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certificate, error) {
|
||||
if timeFunc == nil {
|
||||
timeFunc = time.Now
|
||||
}
|
||||
func GenerateKeyPair(serverName string) (*tls.Certificate, error) {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -25,8 +22,8 @@ func GenerateKeyPair(timeFunc func() time.Time, serverName string) (*tls.Certifi
|
||||
}
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
NotBefore: timeFunc().Add(time.Hour * -1),
|
||||
NotAfter: timeFunc().Add(time.Hour),
|
||||
NotBefore: time.Now().Add(time.Hour * -1),
|
||||
NotAfter: time.Now().Add(time.Hour),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
//go:build with_utls
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ed25519"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
mRand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"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"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var _ ConfigCompat = (*RealityClientConfig)(nil)
|
||||
|
||||
type RealityClientConfig struct {
|
||||
uClient *UTLSClientConfig
|
||||
publicKey []byte
|
||||
shortID [8]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")
|
||||
}
|
||||
var shortID [8]byte
|
||||
decodedLen, err := hex.Decode(shortID[:], []byte(options.Reality.ShortID))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode short_id")
|
||||
}
|
||||
if decodedLen > 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) {
|
||||
return ClientHandshake(context.Background(), conn, e)
|
||||
}
|
||||
|
||||
func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.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)
|
||||
}
|
||||
|
||||
err = uConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
fmt.Printf("REALITY Conn.Verified: %v\n", verifier.verified)
|
||||
}
|
||||
|
||||
if !verifier.verified {
|
||||
go realityClientFallback(uConn, e.uClient.ServerName(), e.uClient.id)
|
||||
return nil, E.New("reality verification failed")
|
||||
}
|
||||
|
||||
return &utlsConnWrapper{uConn}, nil
|
||||
}
|
||||
|
||||
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||
defer uConn.Close()
|
||||
client := &http.Client{
|
||||
Transport: &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||
return uConn, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||
request.Header.Set("User-Agent", fingerprint.Client)
|
||||
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", mRand.Intn(32)+30)})
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _ = io.Copy(io.Discard, response.Body)
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
//go:build with_reality_server
|
||||
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/reality"
|
||||
"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"
|
||||
)
|
||||
|
||||
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, shortIDString := range options.Reality.ShortID {
|
||||
var shortID [8]byte
|
||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode short_id[", i, "]: ", shortIDString)
|
||||
}
|
||||
if decodedLen > 8 {
|
||||
return nil, E.New("invalid short_id[", i, "]: ", shortIDString)
|
||||
}
|
||||
tlsConfig.ShortIds[shortID] = 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 ClientHandshake(context.Background(), conn, c)
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RealityServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||
return ServerHandshake(context.Background(), conn, c)
|
||||
}
|
||||
|
||||
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`)
|
||||
}
|
||||
@@ -2,28 +2,36 @@ package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/badtls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
)
|
||||
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
if options.Reality != nil && options.Reality.Enabled {
|
||||
return NewRealityServer(ctx, router, logger, options)
|
||||
} else {
|
||||
return NewSTDServer(ctx, router, logger, options)
|
||||
}
|
||||
return NewSTDServer(ctx, logger, options)
|
||||
}
|
||||
|
||||
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
|
||||
tlsConn := config.Server(conn)
|
||||
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
|
||||
defer cancel()
|
||||
return aTLS.ServerHandshake(ctx, conn, config)
|
||||
err := tlsConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stdConn, isSTD := tlsConn.(*tls.Conn); isSTD {
|
||||
var badConn badtls.TLSConn
|
||||
badConn, err = badtls.Create(stdConn)
|
||||
if err == nil {
|
||||
return badConn, nil
|
||||
}
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"net/netip"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
@@ -36,15 +35,15 @@ func (s *STDClientConfig) Config() (*STDConfig, error) {
|
||||
return s.config, nil
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return tls.Client(conn, s.config), nil
|
||||
func (s *STDClientConfig) Client(conn net.Conn) Conn {
|
||||
return tls.Client(conn, s.config)
|
||||
}
|
||||
|
||||
func (s *STDClientConfig) Clone() Config {
|
||||
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
|
||||
if options.ServerName != "" {
|
||||
serverName = options.ServerName
|
||||
@@ -58,7 +57,6 @@ func NewSTDClient(router adapter.Router, serverAddress string, options option.Ou
|
||||
}
|
||||
|
||||
var tlsConfig tls.Config
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
|
||||
@@ -48,12 +48,12 @@ func (c *STDServerConfig) Config() (*STDConfig, error) {
|
||||
return c.config, nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return tls.Client(conn, c.config), nil
|
||||
func (c *STDServerConfig) Client(conn net.Conn) Conn {
|
||||
return tls.Client(conn, c.config)
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Server(conn net.Conn) (Conn, error) {
|
||||
return tls.Server(conn, c.config), nil
|
||||
func (c *STDServerConfig) Server(conn net.Conn) Conn {
|
||||
return tls.Server(conn, c.config)
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) Clone() Config {
|
||||
@@ -156,7 +156,7 @@ func (c *STDServerConfig) Close() error {
|
||||
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 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -175,7 +175,6 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
||||
} else {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if 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 {
|
||||
tlsConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return GenerateKeyPair(router.TimeFunc(), info.ServerName)
|
||||
return GenerateKeyPair(info.ServerName)
|
||||
}
|
||||
} else {
|
||||
if certificate == nil {
|
||||
|
||||
@@ -5,7 +5,6 @@ package tls
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
@@ -13,8 +12,8 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
utls "github.com/sagernet/utls"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
@@ -46,19 +45,8 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||
return nil, E.New("unsupported usage for uTLS")
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
func (e *UTLSClientConfig) Client(conn net.Conn) Conn {
|
||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}
|
||||
}
|
||||
|
||||
type utlsConnWrapper struct {
|
||||
@@ -83,11 +71,14 @@ func (c *utlsConnWrapper) ConnectionState() tls.ConnectionState {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *utlsConnWrapper) Upstream() any {
|
||||
return c.UConn
|
||||
func (e *UTLSClientConfig) Clone() Config {
|
||||
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
|
||||
if options.ServerName != "" {
|
||||
serverName = options.ServerName
|
||||
@@ -101,7 +92,6 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
||||
}
|
||||
|
||||
var tlsConfig utls.Config
|
||||
tlsConfig.Time = router.TimeFunc()
|
||||
if options.DisableSNI {
|
||||
tlsConfig.ServerName = "127.0.0.1"
|
||||
} else {
|
||||
@@ -158,59 +148,28 @@ func NewUTLSClient(router adapter.Router, serverAddress string, options option.O
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
id, err := uTLSClientHelloID(options.UTLS.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var id utls.ClientHelloID
|
||||
switch options.UTLS.Fingerprint {
|
||||
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
|
||||
}
|
||||
|
||||
var (
|
||||
randomFingerprint utls.ClientHelloID
|
||||
randomizedFingerprint utls.ClientHelloID
|
||||
)
|
||||
|
||||
func init() {
|
||||
modernFingerprints := []utls.ClientHelloID{
|
||||
utls.HelloChrome_Auto,
|
||||
utls.HelloFirefox_Auto,
|
||||
utls.HelloEdge_Auto,
|
||||
utls.HelloSafari_Auto,
|
||||
utls.HelloIOS_Auto,
|
||||
}
|
||||
randomFingerprint = modernFingerprints[rand.Intn(len(modernFingerprints))]
|
||||
|
||||
weights := utls.DefaultWeights
|
||||
weights.TLSVersMax_Set_VersionTLS13 = 1
|
||||
weights.FirstKeyShare_Set_CurveP256 = 0
|
||||
randomizedFingerprint = utls.HelloRandomized
|
||||
randomizedFingerprint.Seed, _ = utls.NewPRNGSeed()
|
||||
randomizedFingerprint.Weights = &weights
|
||||
}
|
||||
|
||||
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 randomFingerprint, nil
|
||||
case "randomized":
|
||||
return randomizedFingerprint, 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) {
|
||||
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`)
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package constant
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
DHCPTTL = time.Hour
|
||||
DHCPTimeout = time.Minute
|
||||
)
|
||||
@@ -3,28 +3,13 @@ package constant
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
)
|
||||
|
||||
const dirName = "sing-box"
|
||||
|
||||
var (
|
||||
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
|
||||
}
|
||||
var resourcePaths []string
|
||||
|
||||
func FindPath(name string) (string, bool) {
|
||||
name = os.ExpandEnv(name)
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package constant
|
||||
|
||||
const TimeLayout = "2006-01-02 15:04:05 -0700"
|
||||
@@ -1,3 +1,3 @@
|
||||
package constant
|
||||
|
||||
var Version = "unknown"
|
||||
var Version = "1.1.6"
|
||||
|
||||
35
debug.go
35
debug.go
@@ -1,35 +0,0 @@
|
||||
//go:build go1.19
|
||||
|
||||
package box
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func applyDebugOptions(options option.DebugOptions) {
|
||||
if options.GCPercent != nil {
|
||||
debug.SetGCPercent(*options.GCPercent)
|
||||
}
|
||||
if options.MaxStack != nil {
|
||||
debug.SetMaxStack(*options.MaxStack)
|
||||
}
|
||||
if options.MaxThreads != nil {
|
||||
debug.SetMaxThreads(*options.MaxThreads)
|
||||
}
|
||||
if options.PanicOnFault != nil {
|
||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||
}
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
//go:build !go1.19
|
||||
|
||||
package box
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func applyDebugOptions(options option.DebugOptions) {
|
||||
if options.GCPercent != nil {
|
||||
debug.SetGCPercent(*options.GCPercent)
|
||||
}
|
||||
if options.MaxStack != nil {
|
||||
debug.SetMaxStack(*options.MaxStack)
|
||||
}
|
||||
if options.MaxThreads != nil {
|
||||
debug.SetMaxThreads(*options.MaxThreads)
|
||||
}
|
||||
if options.PanicOnFault != nil {
|
||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||
}
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
<svg width="1027" height="1109" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" overflow="hidden">
|
||||
<defs>
|
||||
<filter id="fx0" x="-10%" y="-10%" width="120%" height="120%" filterUnits="userSpaceOnUse" primitiveUnits="userSpaceOnUse">
|
||||
<feComponentTransfer color-interpolation-filters="sRGB">
|
||||
<feFuncR type="discrete" tableValues="0 0" />
|
||||
<feFuncG type="discrete" tableValues="0 0" />
|
||||
<feFuncB type="discrete" tableValues="0 0" />
|
||||
<feFuncA type="linear" slope="0.4" intercept="0" />
|
||||
</feComponentTransfer>
|
||||
<feGaussianBlur stdDeviation="4.58333 4.58333" />
|
||||
</filter>
|
||||
<clipPath id="clip1">
|
||||
<rect x="692" y="855" width="1027" height="1109" />
|
||||
</clipPath>
|
||||
<clipPath id="clip2">
|
||||
<rect x="-2" y="-2" width="541" height="786" />
|
||||
</clipPath>
|
||||
<clipPath id="clip3">
|
||||
<rect x="0" y="0" width="535" height="782" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#clip1)" transform="translate(-692 -855)">
|
||||
<path d="M692 1191 692 1575.69C692 1640.41 731.499 1651.19 731.499 1651.19L1148.03 1931.62C1212.66 1974.77 1194.71 1881.29 1194.71 1881.29L1194.71 1528.96 692 1191Z" fill="#37474F" fill-rule="evenodd" />
|
||||
<g clip-path="url(#clip2)" filter="url(#fx0)" transform="translate(1184 1182)">
|
||||
<g clip-path="url(#clip3)">
|
||||
<path d="M520.482 15.4819 520.482 400.176C520.482 464.89 480.983 475.676 480.983 475.676 480.983 475.676 129.086 712.963 64.4523 756.106-0.181814 799.25 17.7721 705.773 17.7721 705.773L17.7721 353.437 520.482 15.4819Z" fill="#455A64" fill-rule="evenodd" />
|
||||
</g>
|
||||
</g>
|
||||
<path d="M1698 1191 1698 1575.69C1698 1640.41 1658.5 1651.19 1658.5 1651.19 1658.5 1651.19 1306.6 1888.48 1241.97 1931.62 1177.34 1974.77 1195.29 1881.29 1195.29 1881.29L1195.29 1528.96 1698 1191Z" fill="#455A64" fill-rule="evenodd" />
|
||||
<path d="M1241.71 868.473C1212.96 850.509 1169.85 850.509 1144.7 868.473L713.557 1163.07C684.814 1181.04 684.814 1213.37 713.557 1231.33L1144.7 1529.53C1173.44 1547.49 1216.56 1547.49 1241.71 1529.53L1676.44 1227.74C1705.19 1209.78 1705.19 1177.44 1676.44 1159.48L1241.71 868.473Z" fill="#546E7A" fill-rule="evenodd" />
|
||||
<path d="M1195 1949C1173.4 1949 1159 1935.19 1159 1917.92L1159 1531.08C1159 1513.82 1173.4 1500 1195 1500 1216.6 1500 1231 1513.82 1231 1531.08L1231 1914.46C1231 1935.19 1216.6 1949 1195 1949Z" fill="#546E7A" fill-rule="evenodd" />
|
||||
<path d="M1553.92 1435.92C1553.92 1471.89 1557.5 1486.27 1518.03 1511.45L1428.32 1568.99C1388.85 1594.17 1374.5 1572.59 1374.5 1540.22L1374.5 1446.71C1374.5 1439.52 1374.5 1435.92 1363.73 1428.73 1270.43 1363.99 911.591 1115.84 847 1069.09L1012.07 954C1058.72 982.772 1399.61 1209.35 1539.56 1306.45 1546.74 1310.05 1550.33 1317.24 1550.33 1320.84L1550.33 1435.92Z" fill="#99AAB5" fill-rule="evenodd" />
|
||||
<path d="M1543.41 1310.21C1399.82 1213.17 1058.79 986.752 1015.72 958L951.103 997.534 847 1069.41C911.615 1116.14 1270.59 1360.53 1363.92 1425.22 1371.1 1428.81 1371.1 1432.41 1371.1 1436L1547 1313.8C1547 1313.8 1547 1310.21 1543.41 1310.21Z" fill="#CCD6DD" fill-rule="evenodd" />
|
||||
<path d="M1554.9 1435.48 1554.9 1324.19C1554.9 1317.01 1551.3 1313.42 1544.11 1309.83 1400.28 1212.89 1058.67 986.721 1015.51 958L940 1008.26C1062.26 1090.83 1389.49 1306.24 1475.79 1367.27 1486.58 1374.45 1486.58 1381.63 1486.58 1385.22L1486.58 1536 1522.54 1510.87C1558.5 1485.74 1554.9 1467.79 1554.9 1435.48Z" fill="#CCD6DD" fill-rule="evenodd" />
|
||||
<path d="M1543.23 1309.95C1399.6 1212.98 1058.49 986.731 1015.4 958L940 1008.28C1062.08 1090.88 1388.83 1306.36 1475.01 1367.41 1475.01 1367.41 1478.6 1371 1478.6 1371L1554 1317.13C1546.82 1313.54 1546.82 1309.95 1543.23 1309.95Z" fill="#E1E8ED" fill-rule="evenodd" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,83 +1,9 @@
|
||||
#### 1.2.0
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
Important changes since 1.1:
|
||||
|
||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
||||
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
||||
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support
|
||||
* Add [reality TLS](/configuration/shared/tls) support
|
||||
* Add [NTP service](/configuration/ntp)
|
||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
||||
* Add SSH [host key validation](/configuration/outbound/ssh) support
|
||||
* Add [query_type](/configuration/dns/rule) DNS rule item
|
||||
* Add fallback support for v2ray transport
|
||||
* Add custom TLS server support for http based v2ray transports
|
||||
* Add health check support for http-based v2ray transports
|
||||
* Add multiple configuration support
|
||||
|
||||
#### 1.2-rc1
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
#### 1.2-beta10
|
||||
|
||||
* Add multiple configuration support **1**
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
*1*:
|
||||
|
||||
Now you can pass the parameter `--config` or `-c` multiple times, or use the new parameter `--config-directory` or `-C`
|
||||
to load all configuration files in a directory.
|
||||
|
||||
Loaded configuration files are sorted by name. If you want to control the merge order, add a numeric prefix to the file
|
||||
name.
|
||||
|
||||
#### 1.1.7
|
||||
|
||||
* Improve the stability of the VMESS server
|
||||
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
#### 1.2-beta9
|
||||
|
||||
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp)
|
||||
* Add health check support for http-based v2ray transports
|
||||
* Remove length limit on short_id for reality TLS config
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
#### 1.2-beta8
|
||||
|
||||
* Update reality and uTLS libraries
|
||||
* Fix `auto_detect_interface` incorrectly identifying the default interface on Windows
|
||||
|
||||
#### 1.2-beta7
|
||||
|
||||
* Fix the compatibility issue between VLESS's vision sub-protocol and the Xray-core client
|
||||
* Improve the stability of the VMESS server
|
||||
|
||||
#### 1.2-beta6
|
||||
|
||||
* Introducing our [new iOS client application](/installation/clients/sfi)
|
||||
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound
|
||||
* Add custom TLS server support for http based v2ray transports
|
||||
* Add generate commands
|
||||
* Enable XUDP by default in VLESS
|
||||
* Update reality server
|
||||
* Update vision protocol
|
||||
* Fixed [user flow in vless server](/configuration/inbound/vless#usersflow)
|
||||
* Bug fixes
|
||||
* Update dependencies
|
||||
|
||||
#### 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
|
||||
@@ -88,37 +14,6 @@ name.
|
||||
* 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
|
||||
|
||||
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
|
||||
* Add fallback support for v2ray transport
|
||||
* Fix parse hysteria UDP message
|
||||
* Fix socks connect response
|
||||
* Disable vmess header protection if transport enabled
|
||||
|
||||
#### 1.2-beta1
|
||||
|
||||
* Add [DHCP DNS server](/configuration/dns/server) support
|
||||
* Add SSH [host key validation](/configuration/outbound/ssh) support
|
||||
* Add [query_type](/configuration/dns/rule) DNS rule item
|
||||
* Add v2ray [user stats](/configuration/experimental#statsusers) api
|
||||
* Add new clash DNS query api
|
||||
* Improve vmess request
|
||||
* Fix ipv6 redirect on Linux
|
||||
* Fix match geoip private
|
||||
|
||||
#### 1.1.5
|
||||
|
||||
* Add Go 1.20 support
|
||||
@@ -166,7 +61,7 @@ Important changes since 1.0:
|
||||
* Add VLESS outbound and XUDP
|
||||
* Skip wait for hysteria tcp handshake response
|
||||
* Add v2ray mux support for all inbound
|
||||
* Add XUDP support for VMess
|
||||
* Add XUDP support for VMess
|
||||
* Improve websocket writer
|
||||
* Refine tproxy write back
|
||||
* Fix DNS leak caused by
|
||||
|
||||
@@ -9,11 +9,6 @@
|
||||
"mixed-in"
|
||||
],
|
||||
"ip_version": 6,
|
||||
"query_type": [
|
||||
"A",
|
||||
"HTTPS",
|
||||
32768
|
||||
],
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
@@ -124,10 +119,6 @@ Tags of [Inbound](/configuration/inbound).
|
||||
|
||||
Not limited if empty.
|
||||
|
||||
#### query_type
|
||||
|
||||
DNS query type. Values can be integers or type name strings.
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` or `udp`.
|
||||
|
||||
@@ -9,11 +9,6 @@
|
||||
"mixed-in"
|
||||
],
|
||||
"ip_version": 6,
|
||||
"query_type": [
|
||||
"A",
|
||||
"HTTPS",
|
||||
32768
|
||||
],
|
||||
"network": "tcp",
|
||||
"auth_user": [
|
||||
"usera",
|
||||
@@ -123,10 +118,6 @@
|
||||
|
||||
默认不限制。
|
||||
|
||||
#### query_type
|
||||
|
||||
DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### network
|
||||
|
||||
`tcp` 或 `udp`。
|
||||
|
||||
@@ -30,17 +30,16 @@ The tag of the dns server.
|
||||
|
||||
The address of the dns server.
|
||||
|
||||
| Protocol | Format |
|
||||
|----------|-------------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
| `DHCP` | `dhcp://auto` or `dhcp://en0` |
|
||||
| Protocol | Format |
|
||||
|----------|-----------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
@@ -54,10 +53,6 @@ The address of the dns server.
|
||||
|
||||
the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
DHCP transport is not included by default, see [Installation](/#installation).
|
||||
|
||||
| RCode | Description |
|
||||
|-------------------|-----------------------|
|
||||
| `success` | `No error` |
|
||||
|
||||
@@ -30,17 +30,16 @@ DNS 服务器的标签。
|
||||
|
||||
DNS 服务器的地址。
|
||||
|
||||
| 协议 | 格式 |
|
||||
|----------|------------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
| `DHCP` | `dhcp://auto` 或 `dhcp://en0` |
|
||||
| 协议 | 格式 |
|
||||
|----------|-----------------------------|
|
||||
| `System` | `local` |
|
||||
| `TCP` | `tcp://1.0.0.1` |
|
||||
| `UDP` | `8.8.8.8` `udp://8.8.4.4` |
|
||||
| `TLS` | `tls://dns.google` |
|
||||
| `HTTPS` | `https://1.1.1.1/dns-query` |
|
||||
| `QUIC` | `quic://dns.adguard.com` |
|
||||
| `HTTP3` | `h3://8.8.8.8/dns-query` |
|
||||
| `RCode` | `rcode://refused` |
|
||||
|
||||
!!! warning ""
|
||||
|
||||
@@ -54,10 +53,6 @@ DNS 服务器的地址。
|
||||
|
||||
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
默认安装不包含 DHCP 传输层,请参阅 [安装](/zh/#_2)。
|
||||
|
||||
| RCode | 描述 |
|
||||
|-------------------|----------|
|
||||
| `success` | `无错误` |
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "folder",
|
||||
"secret": "",
|
||||
"direct_io": false,
|
||||
"default_mode": "rule",
|
||||
"store_selected": false,
|
||||
"cache_file": "cache.db"
|
||||
@@ -17,15 +18,13 @@
|
||||
"listen": "127.0.0.1:8080",
|
||||
"stats": {
|
||||
"enabled": true,
|
||||
"direct_io": false,
|
||||
"inbounds": [
|
||||
"socks-in"
|
||||
],
|
||||
"outbounds": [
|
||||
"proxy",
|
||||
"direct"
|
||||
],
|
||||
"users": [
|
||||
"sekai"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -59,6 +58,10 @@ Secret for the RESTful API (optional)
|
||||
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
|
||||
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
|
||||
|
||||
#### direct_io
|
||||
|
||||
Allows lossless relays like splice without real-time traffic reporting.
|
||||
|
||||
#### default_mode
|
||||
|
||||
Default mode in clash, `rule` will be used if empty.
|
||||
@@ -95,6 +98,10 @@ Traffic statistics service settings.
|
||||
|
||||
Enable statistics service.
|
||||
|
||||
#### stats.direct_io
|
||||
|
||||
Allows lossless relays like splice without real-time traffic reporting.
|
||||
|
||||
#### stats.inbounds
|
||||
|
||||
Inbound list to count traffic.
|
||||
@@ -102,7 +109,3 @@ Inbound list to count traffic.
|
||||
#### stats.outbounds
|
||||
|
||||
Outbound list to count traffic.
|
||||
|
||||
#### stats.users
|
||||
|
||||
User list to count traffic.
|
||||
@@ -9,6 +9,7 @@
|
||||
"external_controller": "127.0.0.1:9090",
|
||||
"external_ui": "folder",
|
||||
"secret": "",
|
||||
"direct_io": false,
|
||||
"default_mode": "rule",
|
||||
"store_selected": false,
|
||||
"cache_file": "cache.db"
|
||||
@@ -17,15 +18,13 @@
|
||||
"listen": "127.0.0.1:8080",
|
||||
"stats": {
|
||||
"enabled": true,
|
||||
"direct_io": false,
|
||||
"inbounds": [
|
||||
"socks-in"
|
||||
],
|
||||
"outbounds": [
|
||||
"proxy",
|
||||
"direct"
|
||||
],
|
||||
"users": [
|
||||
"sekai"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -57,6 +56,10 @@ RESTful API 的密钥(可选)
|
||||
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
|
||||
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
|
||||
|
||||
#### direct_io
|
||||
|
||||
允许像 splice 这样的没有实时流量报告的无损中继。
|
||||
|
||||
#### default_mode
|
||||
|
||||
Clash 中的默认模式,默认使用 `rule`。
|
||||
@@ -93,6 +96,10 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
||||
|
||||
启用统计服务。
|
||||
|
||||
#### stats.direct_io
|
||||
|
||||
允许像 splice 这样的没有实时流量报告的无损中继。
|
||||
|
||||
#### stats.inbounds
|
||||
|
||||
统计流量的入站列表。
|
||||
@@ -100,7 +107,3 @@ gRPC API 监听地址。如果为空,则禁用 V2Ray API。
|
||||
#### stats.outbounds
|
||||
|
||||
统计流量的出站列表。
|
||||
|
||||
#### stats.users
|
||||
|
||||
统计流量的用户列表。
|
||||
@@ -40,8 +40,4 @@ No authentication required if empty.
|
||||
|
||||
Only supported on Linux, Android, Windows, and macOS.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
||||
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
|
||||
@@ -40,8 +40,4 @@ HTTP 用户
|
||||
|
||||
仅支持 Linux、Android、Windows 和 macOS。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
||||
|
||||
启动时自动设置系统代理,停止时自动清理。
|
||||
@@ -74,10 +74,14 @@ Hysteria users
|
||||
|
||||
#### users.auth
|
||||
|
||||
==Required if `auth_str` is empty==
|
||||
|
||||
Authentication password, in base64.
|
||||
|
||||
#### users.auth_str
|
||||
|
||||
==Required if `auth` is empty==
|
||||
|
||||
Authentication password.
|
||||
|
||||
#### recv_window_conn
|
||||
|
||||
@@ -74,10 +74,14 @@ Hysteria 用户
|
||||
|
||||
#### users.auth
|
||||
|
||||
==与 auth_str 必填一个==
|
||||
|
||||
base64 编码的认证密码。
|
||||
|
||||
#### users.auth_str
|
||||
|
||||
==与 auth 必填一个==
|
||||
|
||||
认证密码。
|
||||
|
||||
#### recv_window_conn
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
| `trojan` | [Trojan](./trojan) | TCP |
|
||||
| `naive` | [Naive](./naive) | X |
|
||||
| `hysteria` | [Hysteria](./hysteria) | X |
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP |
|
||||
| `vless` | [VLESS](./vless) | TCP |
|
||||
| `tun` | [Tun](./tun) | X |
|
||||
| `redirect` | [Redirect](./redirect) | X |
|
||||
| `tproxy` | [TProxy](./tproxy) | X |
|
||||
|
||||
@@ -37,8 +37,4 @@ No authentication required if empty.
|
||||
|
||||
Only supported on Linux, Android, Windows, and macOS.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
||||
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
@@ -37,8 +37,4 @@ SOCKS 和 HTTP 用户
|
||||
|
||||
仅支持 Linux、Android、Windows 和 macOS。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
||||
|
||||
启动时自动设置系统代理,停止时自动清理。
|
||||
@@ -77,11 +77,11 @@ Both if empty.
|
||||
|
||||
==Required==
|
||||
|
||||
| Method | Password Format |
|
||||
|---------------|------------------------------------------------|
|
||||
| none | / |
|
||||
| 2022 methods | `sing-box generate rand --base64 <Key Length>` |
|
||||
| other methods | any string |
|
||||
| Method | Password Format |
|
||||
|---------------|-------------------------------------|
|
||||
| none | / |
|
||||
| 2022 methods | `openssl rand -base64 <Key Length>` |
|
||||
| other methods | any string |
|
||||
|
||||
### Listen Fields
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
==必填==
|
||||
|
||||
| 方法 | 密码格式 |
|
||||
|---------------|------------------------------------------|
|
||||
| none | / |
|
||||
| 2022 methods | `sing-box generate rand --base64 <密钥长度>` |
|
||||
| other methods | 任意字符串 |
|
||||
| 方法 | 密码格式 |
|
||||
|---------------|-------------------------------|
|
||||
| none | / |
|
||||
| 2022 methods | `openssl rand -base64 <密钥长度>` |
|
||||
| other methods | 任意字符串 |
|
||||
@@ -7,29 +7,14 @@
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"version": 3,
|
||||
"version": 2,
|
||||
"password": "fuck me till the daylight",
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
],
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // Dial Fields
|
||||
},
|
||||
"handshake_for_server_name": {
|
||||
"example.com": {
|
||||
"server": "example.com",
|
||||
"server_port": 443,
|
||||
|
||||
... // 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) |
|
||||
| `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
|
||||
|
||||
ShadowTLS password.
|
||||
Set password.
|
||||
|
||||
Only available in the ShadowTLS protocol 2.
|
||||
|
||||
|
||||
#### users
|
||||
|
||||
ShadowTLS users.
|
||||
|
||||
Only available in the ShadowTLS protocol 3.
|
||||
Only available in the ShadowTLS v2 protocol.
|
||||
|
||||
#### handshake
|
||||
|
||||
==Required==
|
||||
|
||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||
|
||||
#### handshake_for_server_name
|
||||
|
||||
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.
|
||||
Handshake server address and [Dial options](/configuration/shared/dial).
|
||||
@@ -7,29 +7,14 @@
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"version": 3,
|
||||
"version": 2,
|
||||
"password": "fuck me till the daylight",
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"password": "8JCsPssfgS8tiRwiMlhARg=="
|
||||
}
|
||||
],
|
||||
"handshake": {
|
||||
"server": "google.com",
|
||||
"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) |
|
||||
| `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
|
||||
|
||||
ShadowTLS 密码。
|
||||
设置密码。
|
||||
|
||||
仅在 ShadowTLS 协议版本 2 中可用。
|
||||
|
||||
#### users
|
||||
|
||||
ShadowTLS 用户。
|
||||
|
||||
仅在 ShadowTLS 协议版本 3 中可用。
|
||||
仅在 ShadowTLS v2 协议中可用。
|
||||
|
||||
#### handshake
|
||||
|
||||
==必填==
|
||||
|
||||
握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||
|
||||
#### handshake_for_server_name
|
||||
|
||||
==必填==
|
||||
|
||||
对于特定服务器名称的握手服务器地址和 [拨号参数](/zh/configuration/shared/dial/)。
|
||||
|
||||
仅在 ShadowTLS 协议版本 2/3 中可用。
|
||||
|
||||
#### strict_mode
|
||||
|
||||
ShadowTLS 严格模式。
|
||||
|
||||
仅在 ShadowTLS 协议版本 3 中可用。
|
||||
|
||||
@@ -46,15 +46,8 @@
|
||||
"exclude_package": [
|
||||
"com.android.captiveportallogin"
|
||||
],
|
||||
"platform": {
|
||||
"http_proxy": {
|
||||
"enabled": false,
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080
|
||||
}
|
||||
},
|
||||
|
||||
... // Listen Fields
|
||||
...
|
||||
// Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
@@ -194,14 +187,6 @@ Limit android packages in route.
|
||||
|
||||
Exclude android packages in route.
|
||||
|
||||
#### platform
|
||||
|
||||
Platform-specific settings, provided by client applications.
|
||||
|
||||
#### platform.http_proxy
|
||||
|
||||
System HTTP proxy settings.
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
@@ -46,15 +46,8 @@
|
||||
"exclude_package": [
|
||||
"com.android.captiveportallogin"
|
||||
],
|
||||
"platform": {
|
||||
"http_proxy": {
|
||||
"enabled": false,
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080
|
||||
}
|
||||
},
|
||||
|
||||
... // 监听字段
|
||||
...
|
||||
// 监听字段
|
||||
}
|
||||
```
|
||||
|
||||
@@ -155,19 +148,19 @@ TCP/IP 栈。
|
||||
|
||||
UID 规则仅在 Linux 下被支持,并且需要 `auto_route`。
|
||||
|
||||
限制被路由的用户。默认不限制。
|
||||
限制被路由的的用户。默认不限制。
|
||||
|
||||
#### include_uid_range
|
||||
|
||||
限制被路由的用户范围。
|
||||
限制被路由的的用户范围。
|
||||
|
||||
#### exclude_uid
|
||||
|
||||
排除路由的用户。
|
||||
排除路由的的用户。
|
||||
|
||||
#### exclude_uid_range
|
||||
|
||||
排除路由的用户范围。
|
||||
排除路由的的用户范围。
|
||||
|
||||
#### include_android_user
|
||||
|
||||
@@ -190,14 +183,6 @@ TCP/IP 栈。
|
||||
|
||||
排除路由的 Android 应用包名。
|
||||
|
||||
#### platform
|
||||
|
||||
平台特定的设置,由客户端应用提供。
|
||||
|
||||
#### platform.http_proxy
|
||||
|
||||
系统 HTTP 代理设置。
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-in",
|
||||
|
||||
... // Listen Fields
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": ""
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen) for details.
|
||||
|
||||
### Fields
|
||||
|
||||
#### users
|
||||
|
||||
==Required==
|
||||
|
||||
VLESS users.
|
||||
|
||||
#### users.uuid
|
||||
|
||||
==Required==
|
||||
|
||||
VLESS user id.
|
||||
|
||||
#### users.flow
|
||||
|
||||
VLESS Sub-protocol.
|
||||
|
||||
Available values:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
|
||||
#### tls
|
||||
|
||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
||||
|
||||
#### transport
|
||||
|
||||
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport).
|
||||
@@ -1,54 +0,0 @@
|
||||
### 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "vless",
|
||||
"tag": "vless-in",
|
||||
|
||||
... // 监听字段
|
||||
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": ""
|
||||
}
|
||||
],
|
||||
"tls": {},
|
||||
"transport": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
### 字段
|
||||
|
||||
#### users
|
||||
|
||||
==必填==
|
||||
|
||||
VLESS 用户。
|
||||
|
||||
#### users.uuid
|
||||
|
||||
==必填==
|
||||
|
||||
VLESS 用户 ID。
|
||||
|
||||
#### users.flow
|
||||
|
||||
VLESS 子协议。
|
||||
|
||||
可用值:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
|
||||
#### 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": {},
|
||||
"dns": {},
|
||||
"ntp": {},
|
||||
"inbounds": [],
|
||||
"outbounds": [],
|
||||
"route": {},
|
||||
@@ -22,7 +21,6 @@ sing-box uses JSON for configuration files.
|
||||
|----------------|--------------------------------|
|
||||
| `log` | [Log](./log) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
| `ntp` | [NTP](./ntp) |
|
||||
| `inbounds` | [Inbound](./inbound) |
|
||||
| `outbounds` | [Outbound](./outbound) |
|
||||
| `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) |
|
||||
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
|
||||
| `vless` | [VLESS](./vless) |
|
||||
| `shadowtls` | [ShadowTLS](./shadowtls) |
|
||||
| `tor` | [Tor](./tor) |
|
||||
| `ssh` | [SSH](./ssh) |
|
||||
| `dns` | [DNS](./dns) |
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"plugin": "",
|
||||
"plugin_opts": "",
|
||||
"network": "udp",
|
||||
"udp_over_tcp": false | {},
|
||||
"udp_over_tcp": false,
|
||||
"multiplex": {},
|
||||
|
||||
... // Dial Fields
|
||||
@@ -87,9 +87,7 @@ Both is enabled by default.
|
||||
|
||||
#### udp_over_tcp
|
||||
|
||||
UDP over TCP configuration.
|
||||
|
||||
See [UDP Over TCP](/configuration/shared/udp-over-tcp) for details.
|
||||
Enable the UDP over TCP protocol.
|
||||
|
||||
Conflict with `multiplex`.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"plugin": "",
|
||||
"plugin_opts": "",
|
||||
"network": "udp",
|
||||
"udp_over_tcp": false | {},
|
||||
"udp_over_tcp": false,
|
||||
"multiplex": {},
|
||||
|
||||
... // 拨号字段
|
||||
@@ -87,9 +87,7 @@ Shadowsocks SIP003 插件参数。
|
||||
|
||||
#### udp_over_tcp
|
||||
|
||||
UDP over TCP 配置。
|
||||
|
||||
参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp)。
|
||||
启用 UDP over TCP 协议。
|
||||
|
||||
与 `multiplex` 冲突。
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"version": 3,
|
||||
"version": 2,
|
||||
"password": "fuck me till the daylight",
|
||||
"tls": {},
|
||||
|
||||
@@ -37,13 +37,12 @@ ShadowTLS protocol version.
|
||||
|---------------|-----------------------------------------------------------------------------------------|
|
||||
| `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) |
|
||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||
|
||||
#### password
|
||||
|
||||
Set password.
|
||||
|
||||
Only available in the ShadowTLS v2/v3 protocol.
|
||||
Only available in the ShadowTLS v2 protocol.
|
||||
|
||||
#### tls
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"version": 3,
|
||||
"version": 2,
|
||||
"password": "fuck me till the daylight",
|
||||
"tls": {},
|
||||
|
||||
@@ -37,13 +37,12 @@ ShadowTLS 协议版本。
|
||||
|---------------|-----------------------------------------------------------------------------------------|
|
||||
| `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) |
|
||||
| `3` | [ShadowTLS v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) |
|
||||
|
||||
#### password
|
||||
|
||||
设置密码。
|
||||
|
||||
仅在 ShadowTLS v2/v3 协议中可用。
|
||||
仅在 ShadowTLS v2 协议中可用。
|
||||
|
||||
#### tls
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"username": "sekai",
|
||||
"password": "admin",
|
||||
"network": "udp",
|
||||
"udp_over_tcp": false | {},
|
||||
"udp_over_tcp": false,
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
@@ -57,9 +57,7 @@ Both is enabled by default.
|
||||
|
||||
#### udp_over_tcp
|
||||
|
||||
UDP over TCP protocol settings.
|
||||
|
||||
See [UDP Over TCP](/configuration/shared/udp-over-tcp) for details.
|
||||
Enable the UDP over TCP protocol.
|
||||
|
||||
### Dial Fields
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"username": "sekai",
|
||||
"password": "admin",
|
||||
"network": "udp",
|
||||
"udp_over_tcp": false | {},
|
||||
"udp_over_tcp": false,
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
@@ -57,9 +57,7 @@ SOCKS5 密码。
|
||||
|
||||
#### udp_over_tcp
|
||||
|
||||
UDP over TCP 配置。
|
||||
|
||||
参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp)。
|
||||
启用 UDP over TCP 协议。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"private_key": "",
|
||||
"private_key_path": "$HOME/.ssh/id_rsa",
|
||||
"private_key_passphrase": "",
|
||||
"host_key": [
|
||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
|
||||
],
|
||||
"host_key_algorithms": [],
|
||||
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
||||
|
||||
@@ -54,10 +51,6 @@ Private key path.
|
||||
|
||||
Private key passphrase.
|
||||
|
||||
#### host_key
|
||||
|
||||
Host key. Accept any if empty.
|
||||
|
||||
#### host_key_algorithms
|
||||
|
||||
Host key algorithms.
|
||||
|
||||
@@ -12,9 +12,6 @@
|
||||
"private_key": "",
|
||||
"private_key_path": "$HOME/.ssh/id_rsa",
|
||||
"private_key_passphrase": "",
|
||||
"host_key": [
|
||||
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..."
|
||||
],
|
||||
"host_key_algorithms": [],
|
||||
"client_version": "SSH-2.0-OpenSSH_7.4p1",
|
||||
|
||||
@@ -54,10 +51,6 @@ SSH 用户, 默认使用 root。
|
||||
|
||||
密钥密码。
|
||||
|
||||
#### host_key
|
||||
|
||||
主机密钥,留空接受所有。
|
||||
|
||||
#### host_key_algorithms
|
||||
|
||||
主机密钥算法。
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"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
|
||||
|
||||
#### server
|
||||
@@ -36,15 +39,7 @@ The server port.
|
||||
|
||||
==Required==
|
||||
|
||||
VLESS user id.
|
||||
|
||||
#### flow
|
||||
|
||||
VLESS Sub-protocol.
|
||||
|
||||
Available values:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
The VLESS user id.
|
||||
|
||||
#### network
|
||||
|
||||
@@ -60,8 +55,6 @@ TLS configuration, see [TLS](/configuration/shared/tls/#outbound).
|
||||
|
||||
#### packet_encoding
|
||||
|
||||
UDP packet encoding, xudp is used by default.
|
||||
|
||||
| Encoding | Description |
|
||||
|------------|-----------------------|
|
||||
| (none) | Disabled |
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 1080,
|
||||
"uuid": "bf000d23-0752-40b4-affe-68f7707a9661",
|
||||
"flow": "xtls-rprx-vision",
|
||||
"network": "tcp",
|
||||
"tls": {},
|
||||
"packet_encoding": "",
|
||||
@@ -18,6 +17,10 @@
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning ""
|
||||
|
||||
VLESS 协议与 v2ray 架构耦合且无人维护。 提供此出站仅出于兼容性目的。
|
||||
|
||||
### 字段
|
||||
|
||||
#### server
|
||||
@@ -38,14 +41,6 @@
|
||||
|
||||
VLESS 用户 ID。
|
||||
|
||||
#### flow
|
||||
|
||||
VLESS 子协议。
|
||||
|
||||
可用值:
|
||||
|
||||
* `xtls-rprx-vision`
|
||||
|
||||
#### network
|
||||
|
||||
启用的网络协议。
|
||||
@@ -60,8 +55,6 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。
|
||||
|
||||
#### packet_encoding
|
||||
|
||||
UDP 包编码,默认使用 xudp。
|
||||
|
||||
| 编码 | 描述 |
|
||||
|------------|---------------|
|
||||
| (空) | 禁用 |
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user