mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-12 01:57:18 +10:00
Compare commits
109 Commits
v1.2-beta9
...
v1.2.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c74d3a53d4 | ||
|
|
fe7ac80a6c | ||
|
|
e50b334b9a | ||
|
|
a0d8e374fb | ||
|
|
d3a67cb5ae | ||
|
|
e69e98b185 | ||
|
|
5e1499d67b | ||
|
|
e8dad1afeb | ||
|
|
6ce4e31fc8 | ||
|
|
d2d4faf520 | ||
|
|
438de36749 | ||
|
|
df0eef770e | ||
|
|
bbdd495ed5 | ||
|
|
d686172854 | ||
|
|
e1d96cb64e | ||
|
|
d5f94b65b7 | ||
|
|
ec2d0b6b3c | ||
|
|
3a92bf993d | ||
|
|
ec13965fd0 | ||
|
|
ddf747006e | ||
|
|
4382093868 | ||
|
|
a5322850b3 | ||
|
|
407b08975c | ||
|
|
c7067ff5e8 | ||
|
|
9b2384b296 | ||
|
|
b498a22972 | ||
|
|
20e9da5c67 | ||
|
|
ec8974673b | ||
|
|
5e6e7923e4 | ||
|
|
de1b5971e1 | ||
|
|
5c20d0b4d5 | ||
|
|
9df96ac7f1 | ||
|
|
87cd925144 | ||
|
|
fecb796000 | ||
|
|
cfb6c804aa | ||
|
|
11c50c7558 | ||
|
|
34cc7f176e | ||
|
|
b54da9c6af | ||
|
|
f44f86b832 | ||
|
|
4ebf40f582 | ||
|
|
53e4302143 | ||
|
|
cf778eda4f | ||
|
|
bb63429079 | ||
|
|
f7f9a7ae20 | ||
|
|
8699412a4c | ||
|
|
0d7aa19cd1 | ||
|
|
50a7295360 | ||
|
|
e57b6ae98d | ||
|
|
6843970536 | ||
|
|
62425ad3e4 | ||
|
|
e1e217854e | ||
|
|
5bf177b021 | ||
|
|
72dbf2e2b4 | ||
|
|
46c318c6fe | ||
|
|
05bb1b88c3 | ||
|
|
5176ea9fe0 | ||
|
|
36d349acd2 | ||
|
|
4feee983b5 | ||
|
|
9b12e3e389 | ||
|
|
afd3464216 | ||
|
|
8b64446274 | ||
|
|
28aa4c4d1f | ||
|
|
0be3cdc8fb | ||
|
|
f8be484019 | ||
|
|
35f03f092d | ||
|
|
c3d7401ead | ||
|
|
4db7eb9d9e | ||
|
|
fd4efd6104 | ||
|
|
19a35ec6a4 | ||
|
|
2012c0ca1e | ||
|
|
187421c754 | ||
|
|
b3fb86d415 | ||
|
|
88fafd4e30 | ||
|
|
8056932f9c | ||
|
|
c8af003bfc | ||
|
|
4999441a85 | ||
|
|
09b001e795 | ||
|
|
3b3a251008 | ||
|
|
2e4eb9aa39 | ||
|
|
77fd284703 | ||
|
|
0a4517f4b7 | ||
|
|
4395db3206 | ||
|
|
dd5b0abc67 | ||
|
|
466800aa3a | ||
|
|
4328c535a9 | ||
|
|
f9516709da | ||
|
|
5dce722879 | ||
|
|
9324a39d4e | ||
|
|
84904c5206 | ||
|
|
fe4b429fc2 | ||
|
|
f680d0acaf | ||
|
|
4baff5aeb1 | ||
|
|
f25296fb23 | ||
|
|
e717852c73 | ||
|
|
13dc70f649 | ||
|
|
46040a71c3 | ||
|
|
0558b3fc5c | ||
|
|
99b2ab5526 | ||
|
|
e5f3bb6344 | ||
|
|
c7f89ad88e | ||
|
|
e0d9f79445 | ||
|
|
b6dbb69fc4 | ||
|
|
b76fabee65 | ||
|
|
872bcfd1c0 | ||
|
|
b033c13ca2 | ||
|
|
2db188f3a1 | ||
|
|
11de271c8f | ||
|
|
40c800c57c | ||
|
|
91b0540e95 |
18
.github/workflows/debug.yml
vendored
18
.github/workflows/debug.yml
vendored
@@ -28,15 +28,9 @@ 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@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: Add cache to Go proxy
|
||||
run: |
|
||||
version=`git rev-parse HEAD`
|
||||
@@ -58,7 +52,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.18.10
|
||||
- name: Cache go module
|
||||
@@ -193,15 +187,9 @@ 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@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: Build
|
||||
id: build
|
||||
run: make
|
||||
|
||||
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -28,15 +28,9 @@ 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@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: ${{ steps.version.outputs.go_version }}
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go-${{ hashFiles('**/go.sum') }}
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v3
|
||||
with:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,3 +12,4 @@
|
||||
/*.aar
|
||||
/*.xcframework/
|
||||
.DS_Store
|
||||
/config.d/
|
||||
|
||||
@@ -16,6 +16,7 @@ builds:
|
||||
- with_quic
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_reality_server
|
||||
- with_clash_api
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
@@ -116,9 +117,6 @@ nfpms:
|
||||
dst: /etc/systemd/system/sing-box@.service
|
||||
- src: LICENSE
|
||||
dst: /usr/share/licenses/sing-box/LICENSE
|
||||
scripts:
|
||||
postinstall: release/config/postinstall.sh
|
||||
postremove: release/config/postremove.sh
|
||||
source:
|
||||
enabled: false
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||
|
||||
@@ -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_quic,with_wireguard,with_acme \
|
||||
&& go build -v -trimpath -tags with_gvisor,with_quic,with_wireguard,with_utls,with_reality_server,with_clash_api,with_acme \
|
||||
-o /go/bin/sing-box \
|
||||
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
|
||||
./cmd/sing-box
|
||||
|
||||
5
LICENSE
5
LICENSE
@@ -11,4 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, no derivative work may use the name or imply association
|
||||
with this application without prior consent.
|
||||
|
||||
17
Makefile
17
Makefile
@@ -7,7 +7,7 @@ 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="
|
||||
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)
|
||||
|
||||
@@ -22,7 +22,7 @@ install:
|
||||
fmt:
|
||||
@gofumpt -l -w .
|
||||
@gofmt -s -w .
|
||||
@gci write --custom-order -s "standard,prefix(github.com/sagernet/),default" .
|
||||
@gci write --custom-order -s standard -s "prefix(github.com/sagernet/)" -s "default" .
|
||||
|
||||
fmt_install:
|
||||
go install -v mvdan.cc/gofumpt@latest
|
||||
@@ -77,13 +77,20 @@ test_stdio:
|
||||
go mod tidy && \
|
||||
go test -v -tags "$(TAGS_TEST),force_stdio" .
|
||||
|
||||
android:
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
|
||||
ios:
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
lib:
|
||||
go run ./cmd/internal/build_libbox
|
||||
go run ./cmd/internal/build_libbox -target android
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
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
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230413023804-244d7ff07035
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230413023804-244d7ff07035
|
||||
|
||||
clean:
|
||||
rm -rf bin dist sing-box
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
The universal proxy platform.
|
||||
|
||||
[](https://repology.org/project/sing-box/versions)
|
||||
|
||||
## Documentation
|
||||
|
||||
https://sing-box.sagernet.org
|
||||
|
||||
## Support
|
||||
|
||||
https://community.sagernet.org/c/sing-box/
|
||||
|
||||
## License
|
||||
|
||||
```
|
||||
@@ -23,4 +29,7 @@ GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, no derivative work may use the name or imply association
|
||||
with this application without prior consent.
|
||||
```
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
type ClashServer interface {
|
||||
Service
|
||||
PreStarter
|
||||
Mode() string
|
||||
StoreSelected() bool
|
||||
CacheFile() ClashCacheFile
|
||||
|
||||
15
adapter/prestart.go
Normal file
15
adapter/prestart.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package adapter
|
||||
|
||||
type PreStarter interface {
|
||||
PreStart() error
|
||||
}
|
||||
|
||||
func PreStart(starter any) error {
|
||||
if preService, ok := starter.(PreStarter); ok {
|
||||
err := preService.PreStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -32,6 +32,7 @@ type Router interface {
|
||||
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
|
||||
|
||||
InterfaceFinder() control.InterfaceFinder
|
||||
UpdateInterfaces() error
|
||||
DefaultInterface() string
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
|
||||
274
box.go
274
box.go
@@ -9,7 +9,6 @@ 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"
|
||||
@@ -25,84 +24,53 @@ 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
|
||||
clashServer adapter.ClashServer
|
||||
v2rayServer adapter.V2RayServer
|
||||
done chan struct{}
|
||||
createdAt time.Time
|
||||
router adapter.Router
|
||||
inbounds []adapter.Inbound
|
||||
outbounds []adapter.Outbound
|
||||
logFactory log.Factory
|
||||
logger log.ContextLogger
|
||||
preServices map[string]adapter.Service
|
||||
postServices map[string]adapter.Service
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) {
|
||||
createdAt := time.Now()
|
||||
logOptions := common.PtrValueOrDefault(options.Log)
|
||||
type Options struct {
|
||||
option.Options
|
||||
Context context.Context
|
||||
PlatformInterface platform.Interface
|
||||
}
|
||||
|
||||
func New(options Options) (*Box, error) {
|
||||
ctx := options.Context
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
createdAt := time.Now()
|
||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
|
||||
var needClashAPI bool
|
||||
var needV2RayAPI bool
|
||||
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.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" {
|
||||
needClashAPI = true
|
||||
}
|
||||
|
||||
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 {
|
||||
switch logOptions.Output {
|
||||
case "":
|
||||
if platformInterface != nil {
|
||||
logWriter = io.Discard
|
||||
} else {
|
||||
logWriter = os.Stdout
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logWriter = logFile
|
||||
}
|
||||
logFormatter := log.Formatter{
|
||||
BaseTime: createdAt,
|
||||
DisableColors: logOptions.DisableColor || logFile != nil,
|
||||
DisableTimestamp: !logOptions.Timestamp && logFile != nil,
|
||||
FullTimestamp: logOptions.Timestamp,
|
||||
TimestampFormat: "-0700 2006-01-02 15:04:05",
|
||||
}
|
||||
if needClashAPI {
|
||||
observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface)
|
||||
logFactory = observableLogFactory
|
||||
} else {
|
||||
logFactory = log.NewFactory(logFormatter, logWriter, platformInterface)
|
||||
}
|
||||
if logOptions.Level != "" {
|
||||
logLevel, err := log.ParseLevel(logOptions.Level)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse log level")
|
||||
}
|
||||
logFactory.SetLevel(logLevel)
|
||||
} else {
|
||||
logFactory.SetLevel(log.LevelTrace)
|
||||
}
|
||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||
needV2RayAPI = true
|
||||
}
|
||||
var defaultLogWriter io.Writer
|
||||
if options.PlatformInterface != nil {
|
||||
defaultLogWriter = io.Discard
|
||||
}
|
||||
logFactory, err := log.New(log.Options{
|
||||
Options: common.PtrValueOrDefault(options.Log),
|
||||
Observable: needClashAPI,
|
||||
DefaultWriter: defaultLogWriter,
|
||||
BaseTime: createdAt,
|
||||
PlatformWriter: options.PlatformInterface,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create log factory")
|
||||
}
|
||||
|
||||
router, err := route.NewRouter(
|
||||
ctx,
|
||||
logFactory,
|
||||
@@ -110,7 +78,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
common.PtrValueOrDefault(options.DNS),
|
||||
common.PtrValueOrDefault(options.NTP),
|
||||
options.Inbounds,
|
||||
platformInterface,
|
||||
options.PlatformInterface,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse route options")
|
||||
@@ -130,7 +98,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
inboundOptions,
|
||||
platformInterface,
|
||||
options.PlatformInterface,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||
@@ -149,6 +117,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
outboundOptions)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||
@@ -156,7 +125,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
outbounds = append(outbounds, out)
|
||||
}
|
||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), option.Outbound{Type: "direct", Tag: "default"})
|
||||
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
||||
common.Must(oErr)
|
||||
outbounds = append(outbounds, out)
|
||||
return out
|
||||
@@ -164,37 +133,62 @@ func New(ctx context.Context, options option.Options, platformInterface platform
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var clashServer adapter.ClashServer
|
||||
var v2rayServer adapter.V2RayServer
|
||||
if options.PlatformInterface != nil {
|
||||
err = options.PlatformInterface.Initialize(ctx, router)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "initialize platform interface")
|
||||
}
|
||||
}
|
||||
preServices := make(map[string]adapter.Service)
|
||||
postServices := make(map[string]adapter.Service)
|
||||
if needClashAPI {
|
||||
clashServer, err = experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI))
|
||||
clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), 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,
|
||||
clashServer: clashServer,
|
||||
v2rayServer: v2rayServer,
|
||||
done: make(chan struct{}),
|
||||
router: router,
|
||||
inbounds: inbounds,
|
||||
outbounds: outbounds,
|
||||
createdAt: createdAt,
|
||||
logFactory: logFactory,
|
||||
logger: logFactory.Logger(),
|
||||
preServices: preServices,
|
||||
postServices: postServices,
|
||||
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 {
|
||||
@@ -208,55 +202,70 @@ func (s *Box) Start() error {
|
||||
}
|
||||
}()
|
||||
s.Close()
|
||||
return err
|
||||
}
|
||||
return err
|
||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) start() error {
|
||||
if s.clashServer != nil {
|
||||
err := s.clashServer.Start()
|
||||
func (s *Box) preStart() error {
|
||||
for serviceName, service := range s.preServices {
|
||||
s.logger.Trace("pre-start ", serviceName)
|
||||
err := adapter.PreStart(service)
|
||||
if err != nil {
|
||||
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")
|
||||
return E.Cause(err, "pre-starting ", serviceName)
|
||||
}
|
||||
}
|
||||
for i, out := range s.outbounds {
|
||||
var tag string
|
||||
if out.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = out.Tag()
|
||||
}
|
||||
if starter, isStarter := out.(common.Starter); isStarter {
|
||||
s.logger.Trace("initializing outbound/", out.Type(), "[", tag, "]")
|
||||
err := starter.Start()
|
||||
if err != nil {
|
||||
var tag string
|
||||
if out.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = out.Tag()
|
||||
}
|
||||
return E.Cause(err, "initialize outbound/", out.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
err := s.router.Start()
|
||||
return s.router.Start()
|
||||
}
|
||||
|
||||
func (s *Box) start() error {
|
||||
err := s.preStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for serviceName, service := range s.preServices {
|
||||
s.logger.Trace("starting ", serviceName)
|
||||
err = service.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
for i, in := range s.inbounds {
|
||||
var tag string
|
||||
if in.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = in.Tag()
|
||||
}
|
||||
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
|
||||
err = in.Start()
|
||||
if err != nil {
|
||||
var tag string
|
||||
if in.Tag() == "" {
|
||||
tag = F.ToString(i)
|
||||
} else {
|
||||
tag = in.Tag()
|
||||
}
|
||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
for serviceName, service := range s.postServices {
|
||||
s.logger.Trace("starting ", service)
|
||||
err = service.Start()
|
||||
if err != nil {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -268,41 +277,42 @@ func (s *Box) Close() error {
|
||||
close(s.done)
|
||||
}
|
||||
var errors error
|
||||
for serviceName, service := range s.postServices {
|
||||
s.logger.Trace("closing ", serviceName)
|
||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", serviceName)
|
||||
})
|
||||
}
|
||||
for i, in := range s.inbounds {
|
||||
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
|
||||
errors = E.Append(errors, in.Close(), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
|
||||
})
|
||||
}
|
||||
for i, out := range s.outbounds {
|
||||
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
|
||||
errors = E.Append(errors, common.Close(out), func(err error) error {
|
||||
return E.Cause(err, "close inbound/", out.Type(), "[", i, "]")
|
||||
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
|
||||
})
|
||||
}
|
||||
s.logger.Trace("closing router")
|
||||
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 {
|
||||
s.logger.Trace("closing ", serviceName)
|
||||
errors = E.Append(errors, service.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ", serviceName)
|
||||
})
|
||||
}
|
||||
s.logger.Trace("closing log factory")
|
||||
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 err := common.Close(s.clashServer); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close clash api server")
|
||||
})
|
||||
}
|
||||
if err := common.Close(s.v2rayServer); err != nil {
|
||||
errors = E.Append(errors, err, func(err error) error {
|
||||
return E.Cause(err, "close v2ray api server")
|
||||
})
|
||||
}
|
||||
if s.logFile != nil {
|
||||
errors = E.Append(errors, s.logFile.Close(), func(err error) error {
|
||||
return E.Cause(err, "close log file")
|
||||
})
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
|
||||
@@ -26,12 +26,15 @@ func init() {
|
||||
}
|
||||
|
||||
func check() error {
|
||||
options, err := readConfig()
|
||||
options, err := readConfigAndMerge()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
instance, err := box.New(ctx, options, nil)
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
})
|
||||
if err == nil {
|
||||
instance.Close()
|
||||
}
|
||||
|
||||
@@ -33,6 +33,44 @@ 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")
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
@@ -5,10 +5,15 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
runtimeDebug "runtime/debug"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -31,29 +36,88 @@ func init() {
|
||||
mainCommand.AddCommand(commandRun)
|
||||
}
|
||||
|
||||
func readConfig() (option.Options, error) {
|
||||
type OptionsEntry struct {
|
||||
content []byte
|
||||
path string
|
||||
options option.Options
|
||||
}
|
||||
|
||||
func readConfigAt(path string) (*OptionsEntry, error) {
|
||||
var (
|
||||
configContent []byte
|
||||
err error
|
||||
)
|
||||
if configPath == "stdin" {
|
||||
if path == "stdin" {
|
||||
configContent, err = io.ReadAll(os.Stdin)
|
||||
} else {
|
||||
configContent, err = os.ReadFile(configPath)
|
||||
configContent, err = os.ReadFile(path)
|
||||
}
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "read config")
|
||||
return nil, E.Cause(err, "read config at ", path)
|
||||
}
|
||||
var options option.Options
|
||||
err = options.UnmarshalJSON(configContent)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "decode config")
|
||||
return nil, E.Cause(err, "decode config at ", path)
|
||||
}
|
||||
return options, nil
|
||||
return &OptionsEntry{
|
||||
content: configContent,
|
||||
path: path,
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readConfig() ([]*OptionsEntry, error) {
|
||||
var optionsList []*OptionsEntry
|
||||
for _, path := range configPaths {
|
||||
optionsEntry, err := readConfigAt(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optionsList = append(optionsList, optionsEntry)
|
||||
}
|
||||
for _, directory := range configDirectories {
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "read config directory at ", directory)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optionsList = append(optionsList, optionsEntry)
|
||||
}
|
||||
}
|
||||
sort.Slice(optionsList, func(i, j int) bool {
|
||||
return optionsList[i].path < optionsList[j].path
|
||||
})
|
||||
return optionsList, nil
|
||||
}
|
||||
|
||||
func readConfigAndMerge() (option.Options, error) {
|
||||
optionsList, err := readConfig()
|
||||
if err != nil {
|
||||
return option.Options{}, err
|
||||
}
|
||||
if len(optionsList) == 1 {
|
||||
return optionsList[0].options, nil
|
||||
}
|
||||
var mergedOptions option.Options
|
||||
for _, options := range optionsList {
|
||||
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||
}
|
||||
}
|
||||
return mergedOptions, nil
|
||||
}
|
||||
|
||||
func create() (*box.Box, context.CancelFunc, error) {
|
||||
options, err := readConfig()
|
||||
options, err := readConfigAndMerge()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -64,7 +128,10 @@ 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(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, nil, E.Cause(err, "create service")
|
||||
@@ -111,7 +178,10 @@ func run() error {
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
closeCtx, closed := context.WithCancel(context.Background())
|
||||
go closeMonitor(closeCtx)
|
||||
instance.Close()
|
||||
closed()
|
||||
if osSignal != syscall.SIGHUP {
|
||||
return nil
|
||||
}
|
||||
@@ -119,3 +189,13 @@ func run() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func closeMonitor(ctx context.Context) {
|
||||
time.Sleep(3 * time.Second)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
log.Fatal("sing-box did not close!")
|
||||
}
|
||||
|
||||
53
cmd/sing-box/cmd_tools.go
Normal file
53
cmd/sing-box/cmd_tools.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"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(box.Options{Options: options})
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create service")
|
||||
}
|
||||
err = instance.PreStart()
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "start service")
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
|
||||
if outboundTag == "" {
|
||||
outbound := instance.Router().DefaultOutbound(N.NetworkName(network))
|
||||
if outbound == nil {
|
||||
return nil, E.New("missing default outbound")
|
||||
}
|
||||
return outbound, nil
|
||||
} else {
|
||||
outbound, loaded := instance.Router().Outbound(outboundTag)
|
||||
if !loaded {
|
||||
return nil, E.New("outbound not found: ", outboundTag)
|
||||
}
|
||||
return outbound, nil
|
||||
}
|
||||
}
|
||||
73
cmd/sing-box/cmd_tools_connect.go
Normal file
73
cmd/sing-box/cmd_tools_connect.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/task"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var commandConnectFlagNetwork string
|
||||
|
||||
var commandConnect = &cobra.Command{
|
||||
Use: "connect [address]",
|
||||
Short: "Connect to an address",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := connect(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type")
|
||||
commandTools.AddCommand(commandConnect)
|
||||
}
|
||||
|
||||
func connect(address string) error {
|
||||
switch N.NetworkName(commandConnectFlagNetwork) {
|
||||
case N.NetworkTCP, N.NetworkUDP:
|
||||
default:
|
||||
return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork)
|
||||
}
|
||||
instance, err := createPreStartedClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address))
|
||||
if err != nil {
|
||||
return E.Cause(err, "connect to server")
|
||||
}
|
||||
var group task.Group
|
||||
group.Append("upload", func(ctx context.Context) error {
|
||||
return common.Error(bufio.Copy(conn, os.Stdin))
|
||||
})
|
||||
group.Append("download", func(ctx context.Context) error {
|
||||
return common.Error(bufio.Copy(os.Stdout, conn))
|
||||
})
|
||||
group.Cleanup(func() {
|
||||
conn.Close()
|
||||
})
|
||||
err = group.Run(context.Background())
|
||||
if E.IsClosed(err) {
|
||||
log.Info(err)
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
91
cmd/sing-box/cmd_tools_fetch.go
Normal file
91
cmd/sing-box/cmd_tools_fetch.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var commandFetch = &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "Fetch an URL",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := fetch(args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandTools.AddCommand(commandFetch)
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
func fetch(args []string) error {
|
||||
instance, err := createPreStartedClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
httpClient = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
dialer, err := createDialer(instance, network, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||
},
|
||||
ForceAttemptHTTP2: true,
|
||||
},
|
||||
}
|
||||
defer httpClient.CloseIdleConnections()
|
||||
for _, urlString := range args {
|
||||
parsedURL, err := url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch parsedURL.Scheme {
|
||||
case "":
|
||||
parsedURL.Scheme = "http"
|
||||
fallthrough
|
||||
case "http", "https":
|
||||
err = fetchHTTP(parsedURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchHTTP(parsedURL *url.URL) error {
|
||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.Header.Add("User-Agent", "curl/7.88.0")
|
||||
response, err := httpClient.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
_, err = bufio.Copy(os.Stdout, response.Body)
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
69
cmd/sing-box/cmd_tools_synctime.go
Normal file
69
cmd/sing-box/cmd_tools_synctime.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/common/settings"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
commandSyncTimeFlagServer string
|
||||
commandSyncTimeOutputFormat string
|
||||
commandSyncTimeWrite bool
|
||||
)
|
||||
|
||||
var commandSyncTime = &cobra.Command{
|
||||
Use: "synctime",
|
||||
Short: "Sync time using the NTP protocol",
|
||||
Args: cobra.NoArgs,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := syncTime()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, "server", "s", "time.apple.com", "Set NTP server")
|
||||
commandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, "format", "f", C.TimeLayout, "Set output format")
|
||||
commandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, "write", "w", false, "Write time to system")
|
||||
commandTools.AddCommand(commandSyncTime)
|
||||
}
|
||||
|
||||
func syncTime() error {
|
||||
instance, err := createPreStartedClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer instance.Close()
|
||||
serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer)
|
||||
if serverAddress.Port == 0 {
|
||||
serverAddress.Port = 123
|
||||
}
|
||||
response, err := ntp.Exchange(context.Background(), dialer, serverAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if commandSyncTimeWrite {
|
||||
err = settings.SetSystemTime(response.Time)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write time to system")
|
||||
}
|
||||
}
|
||||
os.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat))
|
||||
return nil
|
||||
}
|
||||
@@ -11,9 +11,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
configPath string
|
||||
workingDir string
|
||||
disableColor bool
|
||||
configPaths []string
|
||||
configDirectories []string
|
||||
workingDir string
|
||||
disableColor bool
|
||||
)
|
||||
|
||||
var mainCommand = &cobra.Command{
|
||||
@@ -22,7 +23,8 @@ var mainCommand = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
mainCommand.PersistentFlags().StringVarP(&configPath, "config", "c", "config.json", "set configuration file path")
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path")
|
||||
mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path")
|
||||
mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory")
|
||||
mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output")
|
||||
}
|
||||
@@ -38,8 +40,15 @@ func preRun(cmd *cobra.Command, args []string) {
|
||||
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger())
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
80
common/badjsonmerge/merge.go
Normal file
80
common/badjsonmerge/merge.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package badjsonmerge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/sagernet/sing-box/common/badjson"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func MergeOptions(source option.Options, destination option.Options) (option.Options, error) {
|
||||
rawSource, err := json.Marshal(source)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "marshal source")
|
||||
}
|
||||
rawDestination, err := json.Marshal(destination)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "marshal destination")
|
||||
}
|
||||
rawMerged, err := MergeJSON(rawSource, rawDestination)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "merge options")
|
||||
}
|
||||
var merged option.Options
|
||||
err = json.Unmarshal(rawMerged, &merged)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "unmarshal merged options")
|
||||
}
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
func MergeJSON(rawSource json.RawMessage, rawDestination json.RawMessage) (json.RawMessage, error) {
|
||||
source, err := badjson.Decode(rawSource)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode source")
|
||||
}
|
||||
destination, err := badjson.Decode(rawDestination)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode destination")
|
||||
}
|
||||
merged, err := mergeJSON(source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(merged)
|
||||
}
|
||||
|
||||
func mergeJSON(anySource any, anyDestination any) (any, error) {
|
||||
switch destination := anyDestination.(type) {
|
||||
case badjson.JSONArray:
|
||||
switch source := anySource.(type) {
|
||||
case badjson.JSONArray:
|
||||
destination = append(destination, source...)
|
||||
default:
|
||||
destination = append(destination, source)
|
||||
}
|
||||
return destination, nil
|
||||
case *badjson.JSONObject:
|
||||
switch source := anySource.(type) {
|
||||
case *badjson.JSONObject:
|
||||
for _, entry := range source.Entries() {
|
||||
oldValue, loaded := destination.Get(entry.Key)
|
||||
if loaded {
|
||||
var err error
|
||||
entry.Value, err = mergeJSON(entry.Value, oldValue)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "merge object item ", entry.Key)
|
||||
}
|
||||
}
|
||||
destination.Put(entry.Key, entry.Value)
|
||||
}
|
||||
default:
|
||||
return nil, E.New("cannot merge json object into ", reflect.TypeOf(destination))
|
||||
}
|
||||
return destination, nil
|
||||
default:
|
||||
return destination, nil
|
||||
}
|
||||
}
|
||||
59
common/badjsonmerge/merge_test.go
Normal file
59
common/badjsonmerge/merge_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package badjsonmerge
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMergeJSON(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := option.Options{
|
||||
Log: &option.LogOptions{
|
||||
Level: "info",
|
||||
},
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Network: N.NetworkTCP,
|
||||
Outbound: "direct",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
anotherOptions := option.Options{
|
||||
Outbounds: []option.Outbound{
|
||||
{
|
||||
Type: C.TypeDirect,
|
||||
Tag: "direct",
|
||||
},
|
||||
},
|
||||
}
|
||||
thirdOptions := option.Options{
|
||||
Route: &option.RouteOptions{
|
||||
Rules: []option.Rule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: option.DefaultRule{
|
||||
Network: N.NetworkUDP,
|
||||
Outbound: "direct",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
mergeOptions, err := MergeOptions(options, anotherOptions)
|
||||
require.NoError(t, err)
|
||||
mergeOptions, err = MergeOptions(thirdOptions, mergeOptions)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "info", mergeOptions.Log.Level)
|
||||
require.Equal(t, 2, len(mergeOptions.Route.Rules))
|
||||
require.Equal(t, C.TypeDirect, mergeOptions.Outbounds[0].Type)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build go1.19 && !go1.20
|
||||
//go:build go1.20 && !go1.21
|
||||
|
||||
package badtls
|
||||
|
||||
@@ -14,39 +14,60 @@ import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
aTLS "github.com/sagernet/sing/common/tls"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
*tls.Conn
|
||||
writer N.ExtendedWriter
|
||||
activeCall *int32
|
||||
closeNotifySent *bool
|
||||
version *uint16
|
||||
rand io.Reader
|
||||
halfAccess *sync.Mutex
|
||||
halfError *error
|
||||
cipher cipher.AEAD
|
||||
explicitNonceLen int
|
||||
halfPtr uintptr
|
||||
halfSeq []byte
|
||||
halfScratchBuf []byte
|
||||
writer N.ExtendedWriter
|
||||
isHandshakeComplete *atomic.Bool
|
||||
activeCall *atomic.Int32
|
||||
closeNotifySent *bool
|
||||
version *uint16
|
||||
rand io.Reader
|
||||
halfAccess *sync.Mutex
|
||||
halfError *error
|
||||
cipher cipher.AEAD
|
||||
explicitNonceLen int
|
||||
halfPtr uintptr
|
||||
halfSeq []byte
|
||||
halfScratchBuf []byte
|
||||
}
|
||||
|
||||
func Create(conn *tls.Conn) (TLSConn, error) {
|
||||
if !handshakeComplete(conn) {
|
||||
func TryCreate(conn aTLS.Conn) aTLS.Conn {
|
||||
tlsConn, ok := conn.(*tls.Conn)
|
||||
if !ok {
|
||||
return conn
|
||||
}
|
||||
badConn, err := Create(tlsConn)
|
||||
if err != nil {
|
||||
log.Warn("initialize badtls: ", err)
|
||||
return conn
|
||||
}
|
||||
return badConn
|
||||
}
|
||||
|
||||
func Create(conn *tls.Conn) (aTLS.Conn, error) {
|
||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
|
||||
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
|
||||
return nil, E.New("badtls: invalid isHandshakeComplete")
|
||||
}
|
||||
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
|
||||
if !isHandshakeComplete.Load() {
|
||||
return nil, E.New("handshake not finished")
|
||||
}
|
||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||
rawActiveCall := rawConn.FieldByName("activeCall")
|
||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Int32 {
|
||||
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
|
||||
return nil, E.New("badtls: invalid active call")
|
||||
}
|
||||
activeCall := (*int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
||||
activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
|
||||
rawHalfConn := rawConn.FieldByName("out")
|
||||
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||
return nil, E.New("badtls: invalid half conn")
|
||||
@@ -108,19 +129,20 @@ func Create(conn *tls.Conn) (TLSConn, error) {
|
||||
}
|
||||
halfScratchBuf := rawHalfScratchBuf.Bytes()
|
||||
return &Conn{
|
||||
Conn: conn,
|
||||
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
||||
activeCall: activeCall,
|
||||
closeNotifySent: closeNotifySent,
|
||||
version: version,
|
||||
halfAccess: halfAccess,
|
||||
halfError: halfError,
|
||||
cipher: aeadCipher,
|
||||
explicitNonceLen: explicitNonceLen,
|
||||
rand: randReader,
|
||||
halfPtr: rawHalfConn.UnsafeAddr(),
|
||||
halfSeq: halfSeq,
|
||||
halfScratchBuf: halfScratchBuf,
|
||||
Conn: conn,
|
||||
writer: bufio.NewExtendedWriter(conn.NetConn()),
|
||||
isHandshakeComplete: isHandshakeComplete,
|
||||
activeCall: activeCall,
|
||||
closeNotifySent: closeNotifySent,
|
||||
version: version,
|
||||
halfAccess: halfAccess,
|
||||
halfError: halfError,
|
||||
cipher: aeadCipher,
|
||||
explicitNonceLen: explicitNonceLen,
|
||||
rand: randReader,
|
||||
halfPtr: rawHalfConn.UnsafeAddr(),
|
||||
halfSeq: halfSeq,
|
||||
halfScratchBuf: halfScratchBuf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -130,15 +152,15 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
return common.Error(c.Write(buffer.Bytes()))
|
||||
}
|
||||
for {
|
||||
x := atomic.LoadInt32(c.activeCall)
|
||||
x := c.activeCall.Load()
|
||||
if x&1 != 0 {
|
||||
return net.ErrClosed
|
||||
}
|
||||
if atomic.CompareAndSwapInt32(c.activeCall, x, x+2) {
|
||||
if c.activeCall.CompareAndSwap(x, x+2) {
|
||||
break
|
||||
}
|
||||
}
|
||||
defer atomic.AddInt32(c.activeCall, -2)
|
||||
defer c.activeCall.Add(-2)
|
||||
c.halfAccess.Lock()
|
||||
defer c.halfAccess.Unlock()
|
||||
if err := *c.halfError; err != nil {
|
||||
@@ -186,6 +208,7 @@ func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
|
||||
}
|
||||
incSeq(c.halfPtr)
|
||||
log.Trace("badtls write ", buffer.Len())
|
||||
return c.writer.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !go1.19 || go1.20
|
||||
//go:build !go1.19 || go1.21
|
||||
|
||||
package badtls
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package badtls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
type TLSConn interface {
|
||||
net.Conn
|
||||
HandshakeContext(ctx context.Context) error
|
||||
ConnectionState() tls.ConnectionState
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
//go:build go1.19 && !go.1.20
|
||||
//go:build go1.20 && !go.1.21
|
||||
|
||||
package badtls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"reflect"
|
||||
_ "unsafe"
|
||||
)
|
||||
@@ -16,9 +15,6 @@ const (
|
||||
//go:linkname errShutdown crypto/tls.errShutdown
|
||||
var errShutdown error
|
||||
|
||||
//go:linkname handshakeComplete crypto/tls.(*Conn).handshakeComplete
|
||||
func handshakeComplete(conn *tls.Conn) bool
|
||||
|
||||
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
|
||||
func incSeq(conn uintptr)
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -12,7 +12,7 @@ type Conn struct {
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewConn(conn net.Conn) (*Conn, error) {
|
||||
func NewConn(conn net.Conn) (net.Conn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
|
||||
@@ -12,7 +12,7 @@ type PacketConn struct {
|
||||
element *list.Element[io.Closer]
|
||||
}
|
||||
|
||||
func NewPacketConn(conn net.PacketConn) (*PacketConn, error) {
|
||||
func NewPacketConn(conn net.PacketConn) (net.PacketConn, error) {
|
||||
connAccess.Lock()
|
||||
element := openConnection.PushBack(conn)
|
||||
connAccess.Unlock()
|
||||
|
||||
@@ -14,10 +14,16 @@ var (
|
||||
)
|
||||
|
||||
func Count() int {
|
||||
if !Enabled {
|
||||
return 0
|
||||
}
|
||||
return openConnection.Len()
|
||||
}
|
||||
|
||||
func List() []io.Closer {
|
||||
if !Enabled {
|
||||
return nil
|
||||
}
|
||||
connAccess.RLock()
|
||||
defer connAccess.RUnlock()
|
||||
connList := make([]io.Closer, 0, openConnection.Len())
|
||||
@@ -28,6 +34,9 @@ func List() []io.Closer {
|
||||
}
|
||||
|
||||
func Close() {
|
||||
if !Enabled {
|
||||
return
|
||||
}
|
||||
connAccess.Lock()
|
||||
defer connAccess.Unlock()
|
||||
for element := openConnection.Front(); element != nil; element = element.Next() {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"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"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
@@ -17,41 +16,6 @@ import (
|
||||
"github.com/sagernet/tfo-go"
|
||||
)
|
||||
|
||||
var warnBindInterfaceOnUnsupportedPlatform = warning.New(
|
||||
func() bool {
|
||||
return !(C.IsLinux || C.IsWindows || C.IsDarwin)
|
||||
},
|
||||
"outbound option `bind_interface` is only supported on Linux and Windows",
|
||||
)
|
||||
|
||||
var warnRoutingMarkOnUnsupportedPlatform = warning.New(
|
||||
func() bool {
|
||||
return !C.IsLinux
|
||||
},
|
||||
"outbound option `routing_mark` is only supported on Linux",
|
||||
)
|
||||
|
||||
var warnReuseAdderOnUnsupportedPlatform = warning.New(
|
||||
func() bool {
|
||||
return !(C.IsDarwin || C.IsDragonfly || C.IsFreebsd || C.IsLinux || C.IsNetbsd || C.IsOpenbsd || C.IsSolaris || C.IsWindows)
|
||||
},
|
||||
"outbound option `reuse_addr` is unsupported on current platform",
|
||||
)
|
||||
|
||||
var warnProtectPathOnNonAndroid = warning.New(
|
||||
func() bool {
|
||||
return !C.IsAndroid
|
||||
},
|
||||
"outbound option `protect_path` is only supported on Android",
|
||||
)
|
||||
|
||||
var warnTFOOnUnsupportedPlatform = warning.New(
|
||||
func() bool {
|
||||
return !(C.IsDarwin || C.IsFreebsd || C.IsLinux || C.IsWindows)
|
||||
},
|
||||
"outbound option `tcp_fast_open` is unsupported on current platform",
|
||||
)
|
||||
|
||||
type DefaultDialer struct {
|
||||
dialer4 tfo.Dialer
|
||||
dialer6 tfo.Dialer
|
||||
@@ -66,7 +30,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
var dialer net.Dialer
|
||||
var listener net.ListenConfig
|
||||
if options.BindInterface != "" {
|
||||
warnBindInterfaceOnUnsupportedPlatform.Check()
|
||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
@@ -80,7 +43,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
if options.RoutingMark != 0 {
|
||||
warnRoutingMarkOnUnsupportedPlatform.Check()
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||
} else if router.DefaultMark() != 0 {
|
||||
@@ -88,11 +50,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
warnReuseAdderOnUnsupportedPlatform.Check()
|
||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||
}
|
||||
if options.ProtectPath != "" {
|
||||
warnProtectPathOnNonAndroid.Check()
|
||||
dialer.Control = control.Append(dialer.Control, control.ProtectPath(options.ProtectPath))
|
||||
listener.Control = control.Append(listener.Control, control.ProtectPath(options.ProtectPath))
|
||||
}
|
||||
@@ -101,9 +61,6 @@ func NewDefault(router adapter.Router, options option.DialerOptions) *DefaultDia
|
||||
} else {
|
||||
dialer.Timeout = C.TCPTimeout
|
||||
}
|
||||
if options.TCPFastOpen {
|
||||
warnTFOOnUnsupportedPlatform.Check()
|
||||
}
|
||||
var udpFragment bool
|
||||
if options.UDPFragment != nil {
|
||||
udpFragment = *options.UDPFragment
|
||||
@@ -154,9 +111,9 @@ func (d *DefaultDialer) DialContext(ctx context.Context, network string, address
|
||||
switch N.NetworkName(network) {
|
||||
case N.NetworkUDP:
|
||||
if !address.IsIPv6() {
|
||||
return d.udpDialer4.DialContext(ctx, network, address.String())
|
||||
return trackConn(d.udpDialer4.DialContext(ctx, network, address.String()))
|
||||
} else {
|
||||
return d.udpDialer6.DialContext(ctx, network, address.String())
|
||||
return trackConn(d.udpDialer6.DialContext(ctx, network, address.String()))
|
||||
}
|
||||
}
|
||||
if !address.IsIPv6() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
@@ -68,11 +69,11 @@ func (d *ResolveDialer) ListenPacket(ctx context.Context, destination M.Socksadd
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
||||
conn, destinationAddress, err := N.ListenSerial(ctx, d.dialer, destination, addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewResolvePacketConn(ctx, d.router, d.strategy, conn), nil
|
||||
return bufio.NewNATPacketConn(bufio.NewPacketConn(conn), M.SocksaddrFrom(destinationAddress, destination.Port), destination), nil
|
||||
}
|
||||
|
||||
func (d *ResolveDialer) Upstream() any {
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-dns"
|
||||
"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"
|
||||
)
|
||||
|
||||
func NewResolvePacketConn(ctx context.Context, router adapter.Router, strategy dns.DomainStrategy, conn net.PacketConn) N.NetPacketConn {
|
||||
if udpConn, ok := conn.(*net.UDPConn); ok {
|
||||
return &ResolveUDPConn{udpConn, ctx, router, strategy}
|
||||
} else {
|
||||
return &ResolvePacketConn{conn, ctx, router, strategy}
|
||||
}
|
||||
}
|
||||
|
||||
type ResolveUDPConn struct {
|
||||
*net.UDPConn
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
strategy dns.DomainStrategy
|
||||
}
|
||||
|
||||
func (w *ResolveUDPConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||
n, addr, err := w.ReadFromUDPAddrPort(buffer.FreeBytes())
|
||||
if err != nil {
|
||||
return M.Socksaddr{}, err
|
||||
}
|
||||
buffer.Truncate(n)
|
||||
return M.SocksaddrFromNetIP(addr), nil
|
||||
}
|
||||
|
||||
func (w *ResolveUDPConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
defer buffer.Release()
|
||||
if destination.IsFqdn() {
|
||||
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).AddrPort()))
|
||||
}
|
||||
return common.Error(w.UDPConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort()))
|
||||
}
|
||||
|
||||
func (w *ResolveUDPConn) Upstream() any {
|
||||
return w.UDPConn
|
||||
}
|
||||
|
||||
type ResolvePacketConn struct {
|
||||
net.PacketConn
|
||||
ctx context.Context
|
||||
router adapter.Router
|
||||
strategy dns.DomainStrategy
|
||||
}
|
||||
|
||||
func (w *ResolvePacketConn) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
|
||||
_, addr, err := buffer.ReadPacketFrom(w)
|
||||
if err != nil {
|
||||
return M.Socksaddr{}, err
|
||||
}
|
||||
return M.SocksaddrFromNet(addr), err
|
||||
}
|
||||
|
||||
func (w *ResolvePacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||
defer buffer.Release()
|
||||
if destination.IsFqdn() {
|
||||
addresses, err := w.router.Lookup(w.ctx, destination.Fqdn, w.strategy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return common.Error(w.WriteTo(buffer.Bytes(), M.SocksaddrFrom(addresses[0], destination.Port).UDPAddr()))
|
||||
}
|
||||
return common.Error(w.WriteTo(buffer.Bytes(), destination.UDPAddr()))
|
||||
}
|
||||
|
||||
func (w *ResolvePacketConn) Upstream() any {
|
||||
return w.PacketConn
|
||||
}
|
||||
@@ -119,6 +119,10 @@ 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)
|
||||
|
||||
@@ -249,6 +249,10 @@ func (c *ClientConn) WriterReplaceable() bool {
|
||||
return c.requestWrite
|
||||
}
|
||||
|
||||
func (c *ClientConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ClientConn) Upstream() any {
|
||||
return c.Conn
|
||||
}
|
||||
@@ -377,6 +381,10 @@ func (c *ClientPacketConn) RemoteAddr() net.Addr {
|
||||
return c.destination.UDPAddr()
|
||||
}
|
||||
|
||||
func (c *ClientPacketConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ClientPacketConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
@@ -413,7 +421,11 @@ func (c *ClientPacketAddrConn) ReadFrom(p []byte) (n int, addr net.Addr, err err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
addr = destination.UDPAddr()
|
||||
if destination.IsFqdn() {
|
||||
addr = destination
|
||||
} else {
|
||||
addr = destination.UDPAddr()
|
||||
}
|
||||
var length uint16
|
||||
err = binary.Read(c.ExtendedConn, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
@@ -514,6 +526,10 @@ func (c *ClientPacketAddrConn) FrontHeadroom() int {
|
||||
return 2 + M.MaxSocksaddrLength
|
||||
}
|
||||
|
||||
func (c *ClientPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ClientPacketAddrConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
|
||||
@@ -131,6 +131,10 @@ func (c *ServerConn) FrontHeadroom() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *ServerConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ServerConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
@@ -183,6 +187,10 @@ func (c *ServerPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksad
|
||||
return c.ExtendedConn.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (c *ServerPacketConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ServerPacketConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
@@ -245,6 +253,10 @@ func (c *ServerPacketAddrConn) WritePacket(buffer *buf.Buffer, destination M.Soc
|
||||
return c.ExtendedConn.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (c *ServerPacketAddrConn) NeedAdditionalReadDeadline() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *ServerPacketAddrConn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ package process
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os/user"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-tun"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
type Searcher interface {
|
||||
@@ -28,5 +30,15 @@ type Info struct {
|
||||
}
|
||||
|
||||
func FindProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
return findProcessInfo(searcher, ctx, network, source, destination)
|
||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.UserId != -1 {
|
||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||
if osUser != nil {
|
||||
info.User = osUser.Username
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//go:build linux && !android
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"os/user"
|
||||
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
info, err := searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.UserId != -1 {
|
||||
osUser, _ := user.LookupId(F.ToString(info.UserId))
|
||||
if osUser != nil {
|
||||
info.User = osUser.Username
|
||||
}
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//go:build !linux || android
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func findProcessInfo(searcher Searcher, ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) {
|
||||
return searcher.FindProcessInfo(ctx, network, source, destination)
|
||||
}
|
||||
12
common/settings/time_stub.go
Normal file
12
common/settings/time_stub.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build !(windows || linux || darwin)
|
||||
|
||||
package settings
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetSystemTime(nowTime time.Time) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
14
common/settings/time_unix.go
Normal file
14
common/settings/time_unix.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package settings
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func SetSystemTime(nowTime time.Time) error {
|
||||
timeVal := unix.NsecToTimeval(nowTime.UnixNano())
|
||||
return unix.Settimeofday(&timeVal)
|
||||
}
|
||||
32
common/settings/time_windows.go
Normal file
32
common/settings/time_windows.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func SetSystemTime(nowTime time.Time) error {
|
||||
var systemTime windows.Systemtime
|
||||
systemTime.Year = uint16(nowTime.Year())
|
||||
systemTime.Month = uint16(nowTime.Month())
|
||||
systemTime.Day = uint16(nowTime.Day())
|
||||
systemTime.Hour = uint16(nowTime.Hour())
|
||||
systemTime.Minute = uint16(nowTime.Minute())
|
||||
systemTime.Second = uint16(nowTime.Second())
|
||||
systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000)
|
||||
|
||||
dllKernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||
proc := dllKernel32.NewProc("SetSystemTime")
|
||||
|
||||
_, _, err := proc.Call(
|
||||
uintptr(unsafe.Pointer(&systemTime)),
|
||||
)
|
||||
|
||||
if err != nil && err.Error() != "The operation completed successfully." {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/protocol/http"
|
||||
)
|
||||
|
||||
@@ -15,5 +16,5 @@ func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: request.Host}, nil
|
||||
return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil
|
||||
}
|
||||
|
||||
27
common/sniff/http_test.go
Normal file
27
common/sniff/http_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSniffHTTP1(t *testing.T) {
|
||||
t.Parallel()
|
||||
pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n"
|
||||
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, metadata.Domain, "www.google.com")
|
||||
}
|
||||
|
||||
func TestSniffHTTP1WithPort(t *testing.T) {
|
||||
t.Parallel()
|
||||
pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n"
|
||||
metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, metadata.Domain, "www.gov.cn")
|
||||
}
|
||||
@@ -24,12 +24,12 @@ func PeekStream(ctx context.Context, conn net.Conn, buffer *buf.Buffer, timeout
|
||||
}
|
||||
err := conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, E.Cause(err, "set read deadline")
|
||||
}
|
||||
_, err = buffer.ReadOnceFrom(conn)
|
||||
err = E.Errors(err, conn.SetReadDeadline(time.Time{}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, E.Cause(err, "read payload")
|
||||
}
|
||||
var metadata *adapter.InboundContext
|
||||
var errors []error
|
||||
|
||||
@@ -111,6 +111,16 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(uConfig.NextProtos) > 0 {
|
||||
for _, extension := range uConn.Extensions {
|
||||
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
|
||||
alpnExtension.AlpnProtocols = uConfig.NextProtos
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hello := uConn.HandshakeState.Hello
|
||||
hello.SessionId = make([]byte, 32)
|
||||
copy(hello.Raw[39:], hello.SessionId)
|
||||
@@ -124,8 +134,9 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||
|
||||
hello.SessionId[0] = 1
|
||||
hello.SessionId[1] = 7
|
||||
hello.SessionId[2] = 5
|
||||
hello.SessionId[1] = 8
|
||||
hello.SessionId[2] = 0
|
||||
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
||||
copy(hello.SessionId[8:], e.shortID[:])
|
||||
|
||||
if debug.Enabled {
|
||||
|
||||
@@ -180,7 +180,7 @@ func NewSTDServer(ctx context.Context, router adapter.Router, logger log.Logger,
|
||||
tlsConfig.ServerName = options.ServerName
|
||||
}
|
||||
if len(options.ALPN) > 0 {
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, options.ALPN...)
|
||||
tlsConfig.NextProtos = append(options.ALPN, tlsConfig.NextProtos...)
|
||||
}
|
||||
if options.MinVersion != "" {
|
||||
minVersion, err := ParseTLSVersion(options.MinVersion)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"math/rand"
|
||||
@@ -47,7 +48,7 @@ func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||
return &utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, nil
|
||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
||||
}
|
||||
|
||||
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||
@@ -87,6 +88,31 @@ func (c *utlsConnWrapper) Upstream() any {
|
||||
return c.UConn
|
||||
}
|
||||
|
||||
type utlsALPNWrapper struct {
|
||||
utlsConnWrapper
|
||||
nextProtocols []string
|
||||
}
|
||||
|
||||
func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
||||
if len(c.nextProtocols) > 0 {
|
||||
err := c.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, extension := range c.Extensions {
|
||||
if alpnExtension, isALPN := extension.(*utls.ALPNExtension); isALPN {
|
||||
alpnExtension.AlpnProtocols = c.nextProtocols
|
||||
err = c.BuildHandshakeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return c.UConn.HandshakeContext(ctx)
|
||||
}
|
||||
|
||||
func NewUTLSClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||
var serverName string
|
||||
if options.ServerName != "" {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package warning
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
)
|
||||
|
||||
type Warning struct {
|
||||
logger log.Logger
|
||||
check CheckFunc
|
||||
message string
|
||||
checkOnce sync.Once
|
||||
}
|
||||
|
||||
type CheckFunc = func() bool
|
||||
|
||||
func New(checkFunc CheckFunc, message string) Warning {
|
||||
return Warning{
|
||||
check: checkFunc,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Warning) Check() {
|
||||
w.checkOnce.Do(func() {
|
||||
if w.check() {
|
||||
log.Warn(w.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
3
constant/time.go
Normal file
3
constant/time.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package constant
|
||||
|
||||
const TimeLayout = "2006-01-02 15:04:05 -0700"
|
||||
35
debug.go
Normal file
35
debug.go
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build go1.19
|
||||
|
||||
package box
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func applyDebugOptions(options option.DebugOptions) {
|
||||
if options.GCPercent != nil {
|
||||
debug.SetGCPercent(*options.GCPercent)
|
||||
}
|
||||
if options.MaxStack != nil {
|
||||
debug.SetMaxStack(*options.MaxStack)
|
||||
}
|
||||
if options.MaxThreads != nil {
|
||||
debug.SetMaxThreads(*options.MaxThreads)
|
||||
}
|
||||
if options.PanicOnFault != nil {
|
||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||
}
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
35
debug_go118.go
Normal file
35
debug_go118.go
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build !go1.19
|
||||
|
||||
package box
|
||||
|
||||
import (
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/sagernet/sing-box/common/dialer/conntrack"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
func applyDebugOptions(options option.DebugOptions) {
|
||||
if options.GCPercent != nil {
|
||||
debug.SetGCPercent(*options.GCPercent)
|
||||
}
|
||||
if options.MaxStack != nil {
|
||||
debug.SetMaxStack(*options.MaxStack)
|
||||
}
|
||||
if options.MaxThreads != nil {
|
||||
debug.SetMaxThreads(*options.MaxThreads)
|
||||
}
|
||||
if options.PanicOnFault != nil {
|
||||
debug.SetPanicOnFault(*options.PanicOnFault)
|
||||
}
|
||||
if options.TraceBack != "" {
|
||||
debug.SetTraceback(options.TraceBack)
|
||||
}
|
||||
if options.MemoryLimit != 0 {
|
||||
// debug.SetMemoryLimit(int64(options.MemoryLimit))
|
||||
conntrack.MemoryLimit = int64(options.MemoryLimit)
|
||||
}
|
||||
if options.OOMKiller != nil {
|
||||
conntrack.KillerEnabled = *options.OOMKiller
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,80 @@
|
||||
#### 1.2.7
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
#### 1.2.6
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
#### 1.2.3
|
||||
|
||||
* Introducing our [new Android client application](/installation/clients/sfa)
|
||||
* Improve UDP domain destination NAT
|
||||
* Update reality protocol
|
||||
* Fix TTL calculation for DNS response
|
||||
* Fix v2ray HTTP transport compatibility
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
#### 1.2.2
|
||||
|
||||
* Accept `any` outbound in dns rule **1**
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
*1*:
|
||||
|
||||
Now you can use the `any` outbound rule to match server address queries instead of filling in all server domains
|
||||
to `domain` rule.
|
||||
|
||||
#### 1.2.1
|
||||
|
||||
* Fix missing default host in v2ray http transport`s request
|
||||
* Flush DNS cache for macOS when tun start/close
|
||||
* Fix tun's DNS hijacking compatibility with systemd-resolved
|
||||
|
||||
#### 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)
|
||||
|
||||
@@ -232,6 +232,8 @@ Invert match result.
|
||||
|
||||
Match outbound.
|
||||
|
||||
`any` can be used as a value to match any outbound.
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
@@ -254,18 +256,4 @@ Disable cache and save cache in this query.
|
||||
|
||||
#### rules
|
||||
|
||||
Included default rules.
|
||||
|
||||
#### invert
|
||||
|
||||
Invert match result.
|
||||
|
||||
#### server
|
||||
|
||||
==Required==
|
||||
|
||||
Tag of the target dns server.
|
||||
|
||||
#### disable_cache
|
||||
|
||||
Disable cache and save cache in this query.
|
||||
Included default rules.
|
||||
@@ -231,6 +231,8 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
匹配出站。
|
||||
|
||||
`any` 可作为值用于匹配任意出站。
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
@@ -253,18 +255,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### rules
|
||||
|
||||
包括的默认规则。
|
||||
|
||||
#### invert
|
||||
|
||||
反选匹配结果。
|
||||
|
||||
#### server
|
||||
|
||||
==必填==
|
||||
|
||||
目标 DNS 服务器的标签。
|
||||
|
||||
#### disable_cache
|
||||
|
||||
在此查询中禁用缓存。
|
||||
包括的默认规则。
|
||||
@@ -40,4 +40,8 @@ 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,4 +40,8 @@ HTTP 用户
|
||||
|
||||
仅支持 Linux、Android、Windows 和 macOS。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
||||
|
||||
启动时自动设置系统代理,停止时自动清理。
|
||||
@@ -74,14 +74,10 @@ 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,14 +74,10 @@ Hysteria 用户
|
||||
|
||||
#### users.auth
|
||||
|
||||
==与 auth_str 必填一个==
|
||||
|
||||
base64 编码的认证密码。
|
||||
|
||||
#### users.auth_str
|
||||
|
||||
==与 auth 必填一个==
|
||||
|
||||
认证密码。
|
||||
|
||||
#### recv_window_conn
|
||||
|
||||
@@ -37,4 +37,8 @@ No authentication required if empty.
|
||||
|
||||
Only supported on Linux, Android, Windows, and macOS.
|
||||
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
!!! 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.
|
||||
|
||||
@@ -37,4 +37,8 @@ SOCKS 和 HTTP 用户
|
||||
|
||||
仅支持 Linux、Android、Windows 和 macOS。
|
||||
|
||||
!!! warning ""
|
||||
|
||||
要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。
|
||||
|
||||
启动时自动设置系统代理,停止时自动清理。
|
||||
@@ -107,8 +107,7 @@ Enforce strict routing rules when `auto_route` is enabled:
|
||||
* Let unsupported network unreachable
|
||||
* Route all connections to tun
|
||||
|
||||
It prevents address leaks and makes DNS hijacking work on Android and Linux with systemd-resolved, but your device will
|
||||
not be accessible by others.
|
||||
It prevents address leaks and makes DNS hijacking work on Android, but your device will not be accessible by others.
|
||||
|
||||
*In Windows*:
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ tun 接口的 IPv6 前缀。
|
||||
* 让不支持的网络无法到达
|
||||
* 将所有连接路由到 tun
|
||||
|
||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 和使用 systemd-resolved 的 Linux 上工作,但你的设备将无法其他设备被访问。
|
||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作,但你的设备将无法其他设备被访问。
|
||||
|
||||
*在 Windows 中*:
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ The protocol version, `1` or `2`.
|
||||
|
||||
### Application support
|
||||
|
||||
| Project | UoT v1 | UoT v2 |
|
||||
|--------------|----------------------|------------|
|
||||
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
||||
| Xray-core | v1.5.7 (2022/06/05) | / |
|
||||
| Clash.Meta | v1.12.0 (2022/07/02) | / |
|
||||
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
||||
| Project | UoT v1 | UoT v2 |
|
||||
|--------------|----------------------|-------------------------------------------------------------------------------------------------------------------|
|
||||
| sing-box | v0 (2022/08/11) | v1.2-beta9 |
|
||||
| Xray-core | v1.5.7 (2022/06/05) | [f57ec13](https://github.com/XTLS/Xray-core/commit/f57ec1388084df041a2289bacab14e446bf1b357) (Not released) |
|
||||
| Clash.Meta | v1.12.0 (2022/07/02) | [8cb67b6](https://github.com/MetaCubeX/Clash.Meta/commit/8cb67b6480649edfa45dcc9ac89ce0789651e8b3) (Not released) |
|
||||
| Shadowrocket | v2.2.12 (2022/08/13) | / |
|
||||
|
||||
### Protocol details
|
||||
|
||||
@@ -64,8 +64,6 @@ Protocol version 2 uses a new magic address: `sp.v2.udp-over-tcp.arpa`
|
||||
|-----------|------|----------|-------|
|
||||
| u8 | u8 | variable | u16be |
|
||||
|
||||
**version**: Fixed to 2.
|
||||
|
||||
**isConnect**: Set to 1 to indicates that the stream uses the connect format, 0 to disable.
|
||||
|
||||
**ATYP / address / port**: Request destination, uses the SOCKS address format.
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#### Install
|
||||
|
||||
```shell
|
||||
git clone https://github.com/SagerNet/sing-box
|
||||
git clone -b main https://github.com/SagerNet/sing-box
|
||||
cd sing-box
|
||||
./release/local/install_go.sh # skip if you have go1.19 already installed
|
||||
./release/local/install_go.sh # skip if you have golang already installed
|
||||
./release/local/install.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
#### 安装
|
||||
|
||||
```shell
|
||||
git clone https://github.com/SagerNet/sing-box
|
||||
git clone -b main https://github.com/SagerNet/sing-box
|
||||
cd sing-box
|
||||
./release/local/install_go.sh # 如果已安装 go1.19 则跳过
|
||||
./release/local/install_go.sh # 如果已安装 golang 则跳过
|
||||
./release/local/install.sh
|
||||
```
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
"disable_cache": true
|
||||
},
|
||||
{
|
||||
"domain": "mydomain.com",
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"geosite": "cn",
|
||||
"server": "local"
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@ the public internet.
|
||||
|
||||
`auto-route` cannot automatically hijack DNS requests when Android's `Private DNS` enabled or `strict_route` disabled.
|
||||
|
||||
##### on Linux
|
||||
|
||||
`auto-route` cannot automatically hijack DNS requests with `systemd-resolved` enabled and `strict_route` disabled.
|
||||
|
||||
#### System proxy
|
||||
|
||||
##### on Linux
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
|
||||
`auto-route` 无法自动劫持 DNS 请求如果 `私人 DNS` 开启或 `strict_route` 禁用。
|
||||
|
||||
##### Linux
|
||||
|
||||
`auto-route` 无法自动劫持 DNS 请求如果 `systemd-resolved` 开启且 `strict_route` 禁用。
|
||||
|
||||
#### 系统代理
|
||||
|
||||
##### Linux
|
||||
|
||||
@@ -25,4 +25,7 @@ GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, no derivative work may use the name or imply association
|
||||
with this application without prior consent.
|
||||
```
|
||||
|
||||
@@ -25,4 +25,7 @@ GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, no derivative work may use the name or imply association
|
||||
with this application without prior consent.
|
||||
```
|
||||
|
||||
17
docs/installation/clients/sfa.md
Normal file
17
docs/installation/clients/sfa.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# SFA
|
||||
|
||||
Experimental Android client for sing-box.
|
||||
|
||||
#### Requirements
|
||||
|
||||
* Android 5.0+
|
||||
|
||||
#### Download
|
||||
|
||||
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
|
||||
|
||||
#### Note
|
||||
|
||||
* User Agent in remote profile request is `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||
* The working directory is located at `/sdcard/Android/data/io.nekohasekai.sfa/files` (External files directory)
|
||||
* Crash logs is located in `$working_directory/stderr.log`
|
||||
17
docs/installation/clients/sfa.zh.md
Normal file
17
docs/installation/clients/sfa.zh.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# SFA
|
||||
|
||||
实验性的 Android sing-box 客户端。
|
||||
|
||||
#### 要求
|
||||
|
||||
* Android 5.0+
|
||||
|
||||
#### 下载
|
||||
|
||||
* [AppCenter](https://install.appcenter.ms/users/nekohasekai/apps/sfa/distribution_groups/publictest)
|
||||
|
||||
#### 注意事项
|
||||
|
||||
* 远程配置文件请求中的 User Agent 为 `SFA/$version ($version_code; sing-box $sing_box_version)`
|
||||
* 工作目录位于 `/sdcard/Android/data/io.nekohasekai.sfa/files` (外部文件目录)
|
||||
* 崩溃日志位于 `$working_directory/stderr.log`
|
||||
@@ -1,6 +1,6 @@
|
||||
# SFI
|
||||
|
||||
Experimental official iOS client for sing-box.
|
||||
Experimental iOS client for sing-box.
|
||||
|
||||
#### Requirements
|
||||
|
||||
@@ -11,9 +11,10 @@ Experimental official iOS client for sing-box.
|
||||
|
||||
* [TestFlight](https://testflight.apple.com/join/c6ylui2j)
|
||||
|
||||
#### Limit
|
||||
#### Note
|
||||
|
||||
* `system` tun stack not working
|
||||
* User Agent in remote profile request is `SFI/$version ($version_code; sing-box $sing_box_version)`
|
||||
* Crash logs is located in `Settings` -> `View Service Log`
|
||||
|
||||
#### Privacy policy
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# SFI
|
||||
|
||||
实验性的官方 iOS sing-box 客户端。
|
||||
实验性的 iOS sing-box 客户端。
|
||||
|
||||
#### 要求
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
|
||||
* [TestFlight](https://testflight.apple.com/join/c6ylui2j)
|
||||
|
||||
#### 限制
|
||||
#### 注意事项
|
||||
|
||||
* `system` tun stack 不工作
|
||||
* 远程配置文件请求中的 User Agent 为 `SFI/$version ($version_code; sing-box $sing_box_version)`
|
||||
* 崩溃日志位于 `设置` -> `查看服务日志`
|
||||
|
||||
#### 隐私政策
|
||||
|
||||
@@ -36,4 +36,4 @@ sing-box version
|
||||
```
|
||||
|
||||
It is also recommended to use systemd to manage sing-box service,
|
||||
see [Linux server installation example](./examples/linux-server-installation).
|
||||
see [Linux server installation example](/examples/linux-server-installation).
|
||||
@@ -36,4 +36,4 @@ sing-box version
|
||||
```
|
||||
|
||||
同时推荐使用 systemd 来管理 sing-box 服务器实例。
|
||||
参阅 [Linux 服务器安装示例](./examples/linux-server-installation)。
|
||||
参阅 [Linux 服务器安装示例](/examples/linux-server-installation)。
|
||||
@@ -133,7 +133,7 @@ func getProxies(server *Server, router adapter.Router) func(w http.ResponseWrite
|
||||
defaultTag = allProxies[0]
|
||||
}
|
||||
|
||||
sort.Slice(allProxies, func(i, j int) bool {
|
||||
sort.SliceStable(allProxies, func(i, j int) bool {
|
||||
return allProxies[i] == defaultTag
|
||||
})
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func NewServer(router adapter.Router, logFactory log.ObservableFactory, options
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
func (s *Server) PreStart() error {
|
||||
if s.cacheFilePath != "" {
|
||||
cacheFile, err := cachefile.Open(s.cacheFilePath)
|
||||
if err != nil {
|
||||
@@ -122,6 +122,10 @@ func (s *Server) Start() error {
|
||||
}
|
||||
s.cacheFile = cacheFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||
if err != nil {
|
||||
return E.Cause(err, "external controller listen error")
|
||||
|
||||
@@ -4,32 +4,26 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
connections compatible.Map[string, tracker]
|
||||
uploadTemp *atomic.Int64
|
||||
downloadTemp *atomic.Int64
|
||||
uploadBlip *atomic.Int64
|
||||
downloadBlip *atomic.Int64
|
||||
uploadTotal *atomic.Int64
|
||||
downloadTotal *atomic.Int64
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
uploadTemp atomic.Int64
|
||||
downloadTemp atomic.Int64
|
||||
uploadBlip atomic.Int64
|
||||
downloadBlip atomic.Int64
|
||||
uploadTotal atomic.Int64
|
||||
downloadTotal atomic.Int64
|
||||
|
||||
connections compatible.Map[string, tracker]
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func NewManager() *Manager {
|
||||
manager := &Manager{
|
||||
uploadTemp: atomic.NewInt64(0),
|
||||
downloadTemp: atomic.NewInt64(0),
|
||||
uploadBlip: atomic.NewInt64(0),
|
||||
downloadBlip: atomic.NewInt64(0),
|
||||
uploadTotal: atomic.NewInt64(0),
|
||||
downloadTotal: atomic.NewInt64(0),
|
||||
ticker: time.NewTicker(time.Second),
|
||||
done: make(chan struct{}),
|
||||
ticker: time.NewTicker(time.Second),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
go manager.handle()
|
||||
return manager
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package trafficontrol
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
@@ -8,10 +9,10 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/experimental/trackerconn"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"go.uber.org/atomic"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
@@ -43,6 +44,19 @@ type trackerInfo struct {
|
||||
RulePayload string `json:"rulePayload"`
|
||||
}
|
||||
|
||||
func (t trackerInfo) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"id": t.UUID.String(),
|
||||
"metadata": t.Metadata,
|
||||
"upload": t.UploadTotal.Load(),
|
||||
"download": t.DownloadTotal.Load(),
|
||||
"start": t.Start,
|
||||
"chains": t.Chain,
|
||||
"rule": t.Rule,
|
||||
"rulePayload": t.RulePayload,
|
||||
})
|
||||
}
|
||||
|
||||
type tcpTracker struct {
|
||||
N.ExtendedConn `json:"-"`
|
||||
*trackerInfo
|
||||
@@ -97,8 +111,8 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
||||
next = group.Now()
|
||||
}
|
||||
|
||||
upload := atomic.NewInt64(0)
|
||||
download := atomic.NewInt64(0)
|
||||
upload := new(atomic.Int64)
|
||||
download := new(atomic.Int64)
|
||||
|
||||
t := &tcpTracker{
|
||||
ExtendedConn: trackerconn.NewHook(conn, func(n int64) {
|
||||
@@ -184,8 +198,8 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
||||
next = group.Now()
|
||||
}
|
||||
|
||||
upload := atomic.NewInt64(0)
|
||||
download := atomic.NewInt64(0)
|
||||
upload := new(atomic.Int64)
|
||||
download := new(atomic.Int64)
|
||||
|
||||
ut := &udpTracker{
|
||||
PacketConn: trackerconn.NewHookPacket(conn, func(n int64) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
@@ -46,6 +44,7 @@ func clientConnect(sharedDirectory string) (net.Conn, error) {
|
||||
}
|
||||
|
||||
func (c *CommandClient) Connect() error {
|
||||
common.Close(c.conn)
|
||||
conn, err := clientConnect(c.sharedDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
@@ -10,6 +8,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/observable"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
@@ -57,7 +57,10 @@ func (s *CommandServer) Start() error {
|
||||
}
|
||||
|
||||
func (s *CommandServer) Close() error {
|
||||
return s.listener.Close()
|
||||
return common.Close(
|
||||
s.listener,
|
||||
s.observer,
|
||||
)
|
||||
}
|
||||
|
||||
func (s *CommandServer) loopConnection(listener net.Listener) {
|
||||
@@ -69,7 +72,9 @@ func (s *CommandServer) loopConnection(listener net.Listener) {
|
||||
go func() {
|
||||
hErr := s.handleConnection(conn)
|
||||
if hErr != nil && !E.IsClosed(err) {
|
||||
log.Warn("log-server: process connection: ", hErr)
|
||||
if debug.Enabled {
|
||||
log.Warn("log-server: process connection: ", hErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
@@ -44,7 +42,7 @@ func (s *CommandServer) handleStatusConn(conn net.Conn) error {
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
@@ -15,3 +18,36 @@ func parseConfig(configContent string) (option.Options, error) {
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func CheckConfig(configContent string) error {
|
||||
options, err := parseConfig(configContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
})
|
||||
if err == nil {
|
||||
instance.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func FormatConfig(configContent string) (string, error) {
|
||||
options, err := parseConfig(configContent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
json.NewEncoder(&buffer)
|
||||
encoder := json.NewEncoder(&buffer)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
241
experimental/libbox/http.go
Normal file
241
experimental/libbox/http.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"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"
|
||||
"github.com/sagernet/sing/protocol/socks"
|
||||
"github.com/sagernet/sing/protocol/socks/socks5"
|
||||
)
|
||||
|
||||
type HTTPClient interface {
|
||||
RestrictedTLS()
|
||||
ModernTLS()
|
||||
PinnedTLS12()
|
||||
PinnedSHA256(sumHex string)
|
||||
TrySocks5(port int32)
|
||||
KeepAlive()
|
||||
NewRequest() HTTPRequest
|
||||
Close()
|
||||
}
|
||||
|
||||
type HTTPRequest interface {
|
||||
SetURL(link string) error
|
||||
SetMethod(method string)
|
||||
SetHeader(key string, value string)
|
||||
SetContent(content []byte)
|
||||
SetContentString(content string)
|
||||
RandomUserAgent()
|
||||
SetUserAgent(userAgent string)
|
||||
Execute() (HTTPResponse, error)
|
||||
}
|
||||
|
||||
type HTTPResponse interface {
|
||||
GetContent() ([]byte, error)
|
||||
GetContentString() (string, error)
|
||||
WriteTo(path string) error
|
||||
}
|
||||
|
||||
var (
|
||||
_ HTTPClient = (*httpClient)(nil)
|
||||
_ HTTPRequest = (*httpRequest)(nil)
|
||||
_ HTTPResponse = (*httpResponse)(nil)
|
||||
)
|
||||
|
||||
type httpClient struct {
|
||||
tls tls.Config
|
||||
client http.Client
|
||||
transport http.Transport
|
||||
}
|
||||
|
||||
func NewHTTPClient() HTTPClient {
|
||||
client := new(httpClient)
|
||||
client.client.Timeout = C.TCPTimeout
|
||||
client.client.Transport = &client.transport
|
||||
client.transport.TLSClientConfig = &client.tls
|
||||
client.transport.DisableKeepAlives = true
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *httpClient) ModernTLS() {
|
||||
c.tls.MinVersion = tls.VersionTLS12
|
||||
c.tls.CipherSuites = common.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID })
|
||||
}
|
||||
|
||||
func (c *httpClient) RestrictedTLS() {
|
||||
c.tls.MinVersion = tls.VersionTLS13
|
||||
c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool {
|
||||
return common.Contains(it.SupportedVersions, uint16(tls.VersionTLS13))
|
||||
}), func(it *tls.CipherSuite) uint16 {
|
||||
return it.ID
|
||||
})
|
||||
}
|
||||
|
||||
func (c *httpClient) PinnedTLS12() {
|
||||
c.tls.MinVersion = tls.VersionTLS12
|
||||
c.tls.MaxVersion = tls.VersionTLS12
|
||||
}
|
||||
|
||||
func (c *httpClient) PinnedSHA256(sumHex string) {
|
||||
c.tls.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
for _, rawCert := range rawCerts {
|
||||
certSum := sha256.Sum256(rawCert)
|
||||
if sumHex == hex.EncodeToString(certSum[:]) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return E.New("pinned sha256 sum mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *httpClient) TrySocks5(port int32) {
|
||||
dialer := new(net.Dialer)
|
||||
c.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
for {
|
||||
socksConn, err := dialer.DialContext(ctx, "tcp", "127.0.0.1:"+strconv.Itoa(int(port)))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
_, err = socks.ClientHandshake5(socksConn, socks5.CommandConnect, M.ParseSocksaddr(addr), "", "")
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
//nolint:staticcheck
|
||||
return socksConn, err
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *httpClient) KeepAlive() {
|
||||
c.transport.ForceAttemptHTTP2 = true
|
||||
c.transport.DisableKeepAlives = false
|
||||
}
|
||||
|
||||
func (c *httpClient) NewRequest() HTTPRequest {
|
||||
req := &httpRequest{httpClient: c}
|
||||
req.request = http.Request{
|
||||
Method: "GET",
|
||||
Header: http.Header{},
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func (c *httpClient) Close() {
|
||||
c.transport.CloseIdleConnections()
|
||||
}
|
||||
|
||||
type httpRequest struct {
|
||||
*httpClient
|
||||
request http.Request
|
||||
}
|
||||
|
||||
func (r *httpRequest) SetURL(link string) (err error) {
|
||||
r.request.URL, err = url.Parse(link)
|
||||
if r.request.URL.User != nil {
|
||||
user := r.request.URL.User.Username()
|
||||
password, _ := r.request.URL.User.Password()
|
||||
r.request.SetBasicAuth(user, password)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *httpRequest) SetMethod(method string) {
|
||||
r.request.Method = method
|
||||
}
|
||||
|
||||
func (r *httpRequest) SetHeader(key string, value string) {
|
||||
r.request.Header.Set(key, value)
|
||||
}
|
||||
|
||||
func (r *httpRequest) RandomUserAgent() {
|
||||
r.request.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
|
||||
}
|
||||
|
||||
func (r *httpRequest) SetUserAgent(userAgent string) {
|
||||
r.request.Header.Set("User-Agent", userAgent)
|
||||
}
|
||||
|
||||
func (r *httpRequest) SetContent(content []byte) {
|
||||
buffer := bytes.Buffer{}
|
||||
buffer.Write(content)
|
||||
r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes()))
|
||||
r.request.ContentLength = int64(len(content))
|
||||
}
|
||||
|
||||
func (r *httpRequest) SetContentString(content string) {
|
||||
r.SetContent([]byte(content))
|
||||
}
|
||||
|
||||
func (r *httpRequest) Execute() (HTTPResponse, error) {
|
||||
response, err := r.client.Do(&r.request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpResp := &httpResponse{Response: response}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(httpResp.errorString())
|
||||
}
|
||||
return httpResp, nil
|
||||
}
|
||||
|
||||
type httpResponse struct {
|
||||
*http.Response
|
||||
|
||||
getContentOnce sync.Once
|
||||
content []byte
|
||||
contentError error
|
||||
}
|
||||
|
||||
func (h *httpResponse) errorString() string {
|
||||
content, err := h.GetContentString()
|
||||
if err != nil {
|
||||
return fmt.Sprint("HTTP ", h.Status)
|
||||
}
|
||||
return fmt.Sprint("HTTP ", h.Status, ": ", content)
|
||||
}
|
||||
|
||||
func (h *httpResponse) GetContent() ([]byte, error) {
|
||||
h.getContentOnce.Do(func() {
|
||||
defer h.Body.Close()
|
||||
h.content, h.contentError = io.ReadAll(h.Body)
|
||||
})
|
||||
return h.content, h.contentError
|
||||
}
|
||||
|
||||
func (h *httpResponse) GetContentString() (string, error) {
|
||||
content, err := h.GetContent()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func (h *httpResponse) WriteTo(path string) error {
|
||||
defer h.Body.Close()
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
return common.Error(bufio.Copy(file, h.Body))
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import "github.com/sagernet/sing/common"
|
||||
@@ -31,3 +29,19 @@ func (i *iterator[T]) Next() T {
|
||||
func (i *iterator[T]) HasNext() bool {
|
||||
return len(i.values) > 0
|
||||
}
|
||||
|
||||
type abstractIterator[T any] interface {
|
||||
Next() T
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
func iteratorToArray[T any](iterator abstractIterator[T]) []T {
|
||||
if iterator == nil {
|
||||
return nil
|
||||
}
|
||||
var values []T
|
||||
for iterator.HasNext() {
|
||||
values = append(values, iterator.Next())
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
29
experimental/libbox/log.go
Normal file
29
experimental/libbox/log.go
Normal file
@@ -0,0 +1,29 @@
|
||||
//go:build darwin || linux
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var stderrFile *os.File
|
||||
|
||||
func RedirectStderr(path string) error {
|
||||
if stats, err := os.Stat(path); err == nil && stats.Size() > 0 {
|
||||
_ = os.Rename(path, path+".old")
|
||||
}
|
||||
outputFile, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd()))
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputFile.Name())
|
||||
return err
|
||||
}
|
||||
stderrFile = outputFile
|
||||
return nil
|
||||
}
|
||||
183
experimental/libbox/monitor.go
Normal file
183
experimental/libbox/monitor.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package libbox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
)
|
||||
|
||||
var (
|
||||
_ tun.DefaultInterfaceMonitor = (*platformDefaultInterfaceMonitor)(nil)
|
||||
_ InterfaceUpdateListener = (*platformDefaultInterfaceMonitor)(nil)
|
||||
)
|
||||
|
||||
type platformDefaultInterfaceMonitor struct {
|
||||
*platformInterfaceWrapper
|
||||
errorHandler E.Handler
|
||||
networkAddresses []networkAddress
|
||||
defaultInterfaceName string
|
||||
defaultInterfaceIndex int
|
||||
element *list.Element[tun.NetworkUpdateCallback]
|
||||
access sync.Mutex
|
||||
callbacks list.List[tun.DefaultInterfaceUpdateCallback]
|
||||
}
|
||||
|
||||
type networkAddress struct {
|
||||
interfaceName string
|
||||
interfaceIndex int
|
||||
addresses []netip.Prefix
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) Start() error {
|
||||
return m.iif.StartDefaultInterfaceMonitor(m)
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) Close() error {
|
||||
return m.iif.CloseDefaultInterfaceMonitor(m)
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceName(destination netip.Addr) string {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.interfaceName
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.defaultInterfaceName
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) DefaultInterfaceIndex(destination netip.Addr) int {
|
||||
for _, address := range m.networkAddresses {
|
||||
for _, prefix := range address.addresses {
|
||||
if prefix.Contains(destination) {
|
||||
return address.interfaceIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
return m.defaultInterfaceIndex
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) OverrideAndroidVPN() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) AndroidVPNEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
return m.callbacks.PushBack(callback)
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||
m.access.Lock()
|
||||
defer m.access.Unlock()
|
||||
m.callbacks.Remove(element)
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) UpdateDefaultInterface(interfaceName string, interfaceIndex32 int32) {
|
||||
var err error
|
||||
if m.iif.UsePlatformInterfaceGetter() {
|
||||
err = m.updateInterfacesPlatform()
|
||||
} else {
|
||||
err = m.updateInterfaces()
|
||||
}
|
||||
if err == nil {
|
||||
err = m.router.UpdateInterfaces()
|
||||
}
|
||||
if err != nil {
|
||||
m.errorHandler.NewError(context.Background(), E.Cause(err, "update interfaces"))
|
||||
}
|
||||
interfaceIndex := int(interfaceIndex32)
|
||||
if interfaceName == "" {
|
||||
for _, netIf := range m.networkAddresses {
|
||||
if netIf.interfaceIndex == interfaceIndex {
|
||||
interfaceName = netIf.interfaceName
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if interfaceIndex == -1 {
|
||||
for _, netIf := range m.networkAddresses {
|
||||
if netIf.interfaceName == interfaceName {
|
||||
interfaceIndex = netIf.interfaceIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if interfaceName == "" {
|
||||
m.errorHandler.NewError(context.Background(), E.New("invalid interface name for ", interfaceIndex))
|
||||
return
|
||||
} else if interfaceIndex == -1 {
|
||||
m.errorHandler.NewError(context.Background(), E.New("invalid interface index for ", interfaceName))
|
||||
return
|
||||
}
|
||||
if m.defaultInterfaceName == interfaceName && m.defaultInterfaceIndex == interfaceIndex {
|
||||
return
|
||||
}
|
||||
m.defaultInterfaceName = interfaceName
|
||||
m.defaultInterfaceIndex = interfaceIndex
|
||||
m.access.Lock()
|
||||
callbacks := m.callbacks.Array()
|
||||
m.access.Unlock()
|
||||
for _, callback := range callbacks {
|
||||
err = callback(tun.EventInterfaceUpdate)
|
||||
if err != nil {
|
||||
m.errorHandler.NewError(context.Background(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) updateInterfaces() error {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var addresses []networkAddress
|
||||
for _, iif := range interfaces {
|
||||
var netAddresses []net.Addr
|
||||
netAddresses, err = iif.Addrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var address networkAddress
|
||||
address.interfaceName = iif.Name
|
||||
address.interfaceIndex = iif.Index
|
||||
address.addresses = common.Map(common.FilterIsInstance(netAddresses, func(it net.Addr) (*net.IPNet, bool) {
|
||||
value, loaded := it.(*net.IPNet)
|
||||
return value, loaded
|
||||
}), func(it *net.IPNet) netip.Prefix {
|
||||
bits, _ := it.Mask.Size()
|
||||
return netip.PrefixFrom(M.AddrFromIP(it.IP), bits)
|
||||
})
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
m.networkAddresses = addresses
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *platformDefaultInterfaceMonitor) updateInterfacesPlatform() error {
|
||||
interfaces, err := m.Interfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var addresses []networkAddress
|
||||
for _, iif := range interfaces {
|
||||
var address networkAddress
|
||||
address.interfaceName = iif.Name
|
||||
address.interfaceIndex = iif.Index
|
||||
// address.addresses = common.Map(iif.Addresses, netip.MustParsePrefix)
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
m.networkAddresses = addresses
|
||||
return nil
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import "github.com/sagernet/sing-box/option"
|
||||
import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
)
|
||||
|
||||
type PlatformInterface interface {
|
||||
UsePlatformAutoDetectInterfaceControl() bool
|
||||
AutoDetectInterfaceControl(fd int32) error
|
||||
OpenTun(options TunOptions) (int32, error)
|
||||
WriteLog(message string)
|
||||
@@ -12,6 +13,11 @@ type PlatformInterface interface {
|
||||
FindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)
|
||||
PackageNameByUid(uid int32) (string, error)
|
||||
UIDByPackageName(packageName string) (int32, error)
|
||||
UsePlatformDefaultInterfaceMonitor() bool
|
||||
StartDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
||||
CloseDefaultInterfaceMonitor(listener InterfaceUpdateListener) error
|
||||
UsePlatformInterfaceGetter() bool
|
||||
GetInterfaces() (NetworkInterfaceIterator, error)
|
||||
}
|
||||
|
||||
type TunInterface interface {
|
||||
@@ -19,8 +25,19 @@ type TunInterface interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type OnDemandRuleIterator interface {
|
||||
Next() OnDemandRule
|
||||
type InterfaceUpdateListener interface {
|
||||
UpdateDefaultInterface(interfaceName string, interfaceIndex int32)
|
||||
}
|
||||
|
||||
type NetworkInterface struct {
|
||||
Index int32
|
||||
MTU int32
|
||||
Name string
|
||||
Addresses StringIterator
|
||||
}
|
||||
|
||||
type NetworkInterfaceIterator interface {
|
||||
Next() *NetworkInterface
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
@@ -33,6 +50,11 @@ type OnDemandRule interface {
|
||||
ProbeURL() string
|
||||
}
|
||||
|
||||
type OnDemandRuleIterator interface {
|
||||
Next() OnDemandRule
|
||||
HasNext() bool
|
||||
}
|
||||
|
||||
type onDemandRule struct {
|
||||
option.OnDemandRule
|
||||
}
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/netip"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Initialize(ctx context.Context, router adapter.Router) error
|
||||
UsePlatformAutoDetectInterfaceControl() bool
|
||||
AutoDetectInterfaceControl() control.Func
|
||||
OpenTun(options tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||
UsePlatformDefaultInterfaceMonitor() bool
|
||||
CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor
|
||||
UsePlatformInterfaceGetter() bool
|
||||
Interfaces() ([]NetworkInterface, error)
|
||||
process.Searcher
|
||||
io.Writer
|
||||
}
|
||||
|
||||
type NetworkInterface struct {
|
||||
Index int
|
||||
MTU int
|
||||
Name string
|
||||
Addresses []netip.Prefix
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build linux || darwin
|
||||
|
||||
package libbox
|
||||
|
||||
import (
|
||||
@@ -8,11 +6,13 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/process"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
@@ -30,7 +30,11 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
instance, err := box.New(ctx, options, &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()})
|
||||
instance, err := box.New(box.Options{
|
||||
Context: ctx,
|
||||
Options: options,
|
||||
PlatformInterface: &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()},
|
||||
})
|
||||
if err != nil {
|
||||
cancel()
|
||||
return nil, E.Cause(err, "create service")
|
||||
@@ -56,6 +60,16 @@ var _ platform.Interface = (*platformInterfaceWrapper)(nil)
|
||||
type platformInterfaceWrapper struct {
|
||||
iif PlatformInterface
|
||||
useProcFS bool
|
||||
router adapter.Router
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error {
|
||||
w.router = router
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool {
|
||||
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
||||
@@ -66,7 +80,7 @@ func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
||||
}
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) OpenTun(options tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||
if len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {
|
||||
return nil, E.New("android: unsupported uid options")
|
||||
}
|
||||
@@ -77,12 +91,16 @@ func (w *platformInterfaceWrapper) OpenTun(options tun.Options, platformOptions
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dupFd, err := syscall.Dup(int(tunFd))
|
||||
options.Name, err = getTunnelName(tunFd)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "query tun name")
|
||||
}
|
||||
dupFd, err := dup(int(tunFd))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "dup tun file descriptor")
|
||||
}
|
||||
options.FileDescriptor = dupFd
|
||||
return tun.New(options)
|
||||
return tun.New(*options)
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) Write(p []byte) (n int, err error) {
|
||||
@@ -116,3 +134,36 @@ func (w *platformInterfaceWrapper) FindProcessInfo(ctx context.Context, network
|
||||
packageName, _ := w.iif.PackageNameByUid(uid)
|
||||
return &process.Info{UserId: uid, PackageName: packageName}, nil
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {
|
||||
return w.iif.UsePlatformDefaultInterfaceMonitor()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) CreateDefaultInterfaceMonitor(errorHandler E.Handler) tun.DefaultInterfaceMonitor {
|
||||
return &platformDefaultInterfaceMonitor{
|
||||
platformInterfaceWrapper: w,
|
||||
errorHandler: errorHandler,
|
||||
defaultInterfaceIndex: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) UsePlatformInterfaceGetter() bool {
|
||||
return w.iif.UsePlatformInterfaceGetter()
|
||||
}
|
||||
|
||||
func (w *platformInterfaceWrapper) Interfaces() ([]platform.NetworkInterface, error) {
|
||||
interfaceIterator, err := w.iif.GetInterfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var interfaces []platform.NetworkInterface
|
||||
for _, netInterface := range iteratorToArray[*NetworkInterface](interfaceIterator) {
|
||||
interfaces = append(interfaces, platform.NetworkInterface{
|
||||
Index: int(netInterface.Index),
|
||||
MTU: int(netInterface.MTU),
|
||||
Name: netInterface.Name,
|
||||
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
||||
})
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user