mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-14 00:51:12 +03:00
Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -350,7 +350,7 @@ jobs:
|
|||||||
mkdir clients/android/app/libs
|
mkdir clients/android/app/libs
|
||||||
cp libbox.aar clients/android/app/libs
|
cp libbox.aar clients/android/app/libs
|
||||||
cd clients/android
|
cd clients/android
|
||||||
./gradlew :app:assemblePlayRelease :app:assembleOtherRelease
|
./gradlew :app:assemblePlayRelease
|
||||||
env:
|
env:
|
||||||
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
JAVA_HOME: /usr/lib/jvm/java-17-openjdk-amd64
|
||||||
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
|
||||||
|
|||||||
150
.goreleaser.yaml
150
.goreleaser.yaml
@@ -31,6 +31,54 @@ builds:
|
|||||||
- linux_arm_7
|
- linux_arm_7
|
||||||
- linux_s390x
|
- linux_s390x
|
||||||
- linux_riscv64
|
- linux_riscv64
|
||||||
|
- linux_mips
|
||||||
|
- linux_mips_softfloat
|
||||||
|
- linux_mipsle
|
||||||
|
- linux_mipsle_softfloat
|
||||||
|
- linux_mips64
|
||||||
|
- linux_mips64le
|
||||||
|
- windows_amd64_v1
|
||||||
|
- windows_386
|
||||||
|
- windows_arm64
|
||||||
|
- darwin_amd64_v1
|
||||||
|
- darwin_arm64
|
||||||
|
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||||
|
- id: manager
|
||||||
|
main: ./cmd/sing-box
|
||||||
|
flags:
|
||||||
|
- -v
|
||||||
|
- -trimpath
|
||||||
|
ldflags:
|
||||||
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
|
- -s
|
||||||
|
- -buildid=
|
||||||
|
tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_utls
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
|
- with_manager
|
||||||
|
- with_admin_panel
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
- GOTOOLCHAIN=local
|
||||||
|
targets:
|
||||||
|
- linux_386
|
||||||
|
- linux_amd64_v1
|
||||||
|
- linux_arm64
|
||||||
|
- linux_arm_6
|
||||||
|
- linux_arm_7
|
||||||
|
- linux_s390x
|
||||||
|
- linux_riscv64
|
||||||
|
- linux_mips
|
||||||
|
- linux_mips_softfloat
|
||||||
|
- linux_mipsle
|
||||||
|
- linux_mipsle_softfloat
|
||||||
|
- linux_mips64
|
||||||
- linux_mips64le
|
- linux_mips64le
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
@@ -51,8 +99,6 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOROOT={{ .Env.GOPATH }}/go_legacy
|
|
||||||
tool: "{{ .Env.GOPATH }}/go_legacy/bin/go"
|
|
||||||
targets:
|
targets:
|
||||||
- windows_amd64_v1
|
- windows_amd64_v1
|
||||||
- windows_386
|
- windows_386
|
||||||
@@ -104,91 +150,25 @@ archives:
|
|||||||
wrap_in_directory: true
|
wrap_in_directory: true
|
||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
|
- id: archive_with_manager
|
||||||
|
builds:
|
||||||
|
- manager
|
||||||
|
formats:
|
||||||
|
- tar.gz
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
formats:
|
||||||
|
- zip
|
||||||
|
wrap_in_directory: true
|
||||||
|
files:
|
||||||
|
- LICENSE
|
||||||
|
name_template: '{{ .ProjectName }}-{{ .Version }}-with-manager-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
- id: archive-legacy
|
- id: archive-legacy
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
- legacy
|
- legacy
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||||
nfpms:
|
|
||||||
- id: package
|
|
||||||
package_name: sing-box
|
|
||||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}_{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
|
||||||
builds:
|
|
||||||
- main
|
|
||||||
homepage: https://sing-box.sagernet.org/
|
|
||||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
|
||||||
description: The universal proxy platform.
|
|
||||||
license: GPLv3 or later
|
|
||||||
formats:
|
|
||||||
- deb
|
|
||||||
- rpm
|
|
||||||
- archlinux
|
|
||||||
# - apk
|
|
||||||
# - ipk
|
|
||||||
priority: extra
|
|
||||||
contents:
|
|
||||||
- src: release/config/config.json
|
|
||||||
dst: /etc/sing-box/config.json
|
|
||||||
type: "config|noreplace"
|
|
||||||
|
|
||||||
- src: release/config/sing-box.service
|
|
||||||
dst: /usr/lib/systemd/system/sing-box.service
|
|
||||||
- src: release/config/sing-box@.service
|
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
|
||||||
- src: release/config/sing-box.sysusers
|
|
||||||
dst: /usr/lib/sysusers.d/sing-box.conf
|
|
||||||
- src: release/config/sing-box.rules
|
|
||||||
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
- src: release/config/sing-box-split-dns.xml
|
|
||||||
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
- src: release/completions/sing-box.fish
|
|
||||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
- src: release/completions/sing-box.zsh
|
|
||||||
dst: /usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
- src: LICENSE
|
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
|
||||||
deb:
|
|
||||||
signature:
|
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
|
||||||
fields:
|
|
||||||
Bugs: https://github.com/SagerNet/sing-box/issues
|
|
||||||
rpm:
|
|
||||||
signature:
|
|
||||||
key_file: "{{ .Env.NFPM_KEY_PATH }}"
|
|
||||||
overrides:
|
|
||||||
apk:
|
|
||||||
contents:
|
|
||||||
- src: release/config/config.json
|
|
||||||
dst: /etc/sing-box/config.json
|
|
||||||
type: config
|
|
||||||
|
|
||||||
- src: release/config/sing-box.initd
|
|
||||||
dst: /etc/init.d/sing-box
|
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
|
||||||
- src: release/completions/sing-box.fish
|
|
||||||
dst: /usr/share/fish/vendor_completions.d/sing-box.fish
|
|
||||||
- src: release/completions/sing-box.zsh
|
|
||||||
dst: /usr/share/zsh/site-functions/_sing-box
|
|
||||||
|
|
||||||
- src: LICENSE
|
|
||||||
dst: /usr/share/licenses/sing-box/LICENSE
|
|
||||||
ipk:
|
|
||||||
contents:
|
|
||||||
- src: release/config/config.json
|
|
||||||
dst: /etc/sing-box/config.json
|
|
||||||
type: config
|
|
||||||
|
|
||||||
- src: release/config/openwrt.init
|
|
||||||
dst: /etc/init.d/sing-box
|
|
||||||
- src: release/config/openwrt.conf
|
|
||||||
dst: /etc/config/sing-box
|
|
||||||
source:
|
source:
|
||||||
enabled: false
|
enabled: false
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
name_template: '{{ .ProjectName }}-{{ .Version }}.source'
|
||||||
@@ -200,8 +180,8 @@ signs:
|
|||||||
- artifacts: checksum
|
- artifacts: checksum
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: SagerNet
|
owner: shtorm-7
|
||||||
name: sing-box
|
name: sing-box-extended
|
||||||
draft: true
|
draft: true
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
mode: replace
|
mode: replace
|
||||||
@@ -209,5 +189,3 @@ release:
|
|||||||
- archive
|
- archive
|
||||||
- package
|
- package
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
partial:
|
|
||||||
by: target
|
|
||||||
12
Makefile
12
Makefile
@@ -1,6 +1,6 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_manager,with_admin_panel
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
@@ -64,14 +64,10 @@ update_certificates:
|
|||||||
go run ./cmd/internal/update_certificates
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
release:
|
release:
|
||||||
go run ./cmd/internal/build goreleaser release --clean --skip publish
|
go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz \
|
mv dist/*.tar.gz \
|
||||||
dist/*.zip \
|
dist/*.zip \
|
||||||
dist/*.deb \
|
|
||||||
dist/*.rpm \
|
|
||||||
dist/*_amd64.pkg.tar.zst \
|
|
||||||
dist/*_arm64.pkg.tar.zst \
|
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
rm -r dist/release
|
rm -r dist/release
|
||||||
@@ -86,7 +82,7 @@ update_android_version:
|
|||||||
go run ./cmd/internal/update_android_version
|
go run ./cmd/internal/update_android_version
|
||||||
|
|
||||||
build_android:
|
build_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease :app:assembleOtherRelease && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:clean :app:assemblePlayRelease && ./gradlew --stop
|
||||||
|
|
||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
@@ -95,7 +91,7 @@ upload_android:
|
|||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||||
rm -rf dist/release_android
|
rm -rf dist/release_android
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type UDPInjectableInbound interface {
|
|||||||
type InboundRegistry interface {
|
type InboundRegistry interface {
|
||||||
option.InboundOptionsRegistry
|
option.InboundOptionsRegistry
|
||||||
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
Create(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
||||||
|
UnsafeCreate(ctx context.Context, router Router, logger log.ContextLogger, tag string, inboundType string, options any) (Inbound, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundManager interface {
|
type InboundManager interface {
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
|||||||
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
func (m *Registry) Create(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
defer m.access.Unlock()
|
defer m.access.Unlock()
|
||||||
|
return m.UnsafeCreate(ctx, router, logger, tag, outboundType, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Registry) UnsafeCreate(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
|
||||||
constructor, loaded := m.constructor[outboundType]
|
constructor, loaded := m.constructor[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type Outbound interface {
|
|||||||
type OutboundRegistry interface {
|
type OutboundRegistry interface {
|
||||||
option.OutboundOptionsRegistry
|
option.OutboundOptionsRegistry
|
||||||
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
|
UnsafeCreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundManager interface {
|
type OutboundManager interface {
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ func (r *Registry) CreateOptions(outboundType string) (any, bool) {
|
|||||||
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||||
r.access.Lock()
|
r.access.Lock()
|
||||||
defer r.access.Unlock()
|
defer r.access.Unlock()
|
||||||
|
return r.UnsafeCreateOutbound(ctx, router, logger, tag, outboundType, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Registry) UnsafeCreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
|
||||||
constructor, loaded := r.constructors[outboundType]
|
constructor, loaded := r.constructors[outboundType]
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
return nil, E.New("outbound type not found: " + outboundType)
|
||||||
|
|||||||
1
box.go
1
box.go
@@ -159,6 +159,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
service.MustRegister[log.Factory](ctx, logFactory)
|
||||||
|
|
||||||
var internalServices []adapter.LifecycleService
|
var internalServices []adapter.LifecycleService
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
//go:build with_quic
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
service, err := mux.NewService(mux.ServiceOptions{
|
service, err := mux.NewService(mux.ServiceOptions{
|
||||||
|
NewConnectionContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||||
|
return log.ContextWithNewMuxID(ctx)
|
||||||
|
},
|
||||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||||
return log.ContextWithNewID(ctx)
|
return log.ContextWithNewID(ctx)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,40 +1,50 @@
|
|||||||
package constant
|
package constant
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeTun = "tun"
|
TypeTun = "tun"
|
||||||
TypeRedirect = "redirect"
|
TypeRedirect = "redirect"
|
||||||
TypeTProxy = "tproxy"
|
TypeTProxy = "tproxy"
|
||||||
TypeDirect = "direct"
|
TypeDirect = "direct"
|
||||||
TypeBlock = "block"
|
TypeBlock = "block"
|
||||||
TypeDNS = "dns"
|
TypeDNS = "dns"
|
||||||
TypeSOCKS = "socks"
|
TypeSOCKS = "socks"
|
||||||
TypeHTTP = "http"
|
TypeHTTP = "http"
|
||||||
TypeMixed = "mixed"
|
TypeMixed = "mixed"
|
||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeWARP = "warp"
|
TypeWARP = "warp"
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
TypeTor = "tor"
|
TypeTor = "tor"
|
||||||
TypeSSH = "ssh"
|
TypeSSH = "ssh"
|
||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeMieru = "mieru"
|
TypeMieru = "mieru"
|
||||||
TypeAnyTLS = "anytls"
|
TypeAnyTLS = "anytls"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTunnelClient = "tunnel_client"
|
TypeBond = "bond"
|
||||||
TypeTunnelServer = "tunnel_server"
|
TypeTunnelServer = "tunnel-server"
|
||||||
TypeTailscale = "tailscale"
|
TypeTunnelClient = "tunnel-client"
|
||||||
TypeDERP = "derp"
|
TypeTailscale = "tailscale"
|
||||||
TypeResolved = "resolved"
|
TypeConnectionLimiter = "connection-limiter"
|
||||||
TypeSSMAPI = "ssm-api"
|
TypeBandwidthLimiter = "bandwidth-limiter"
|
||||||
|
TypeTrafficLimiter = "traffic-limiter"
|
||||||
|
TypeAdminPanel = "admin-panel"
|
||||||
|
TypeNodeManagerServer = "node-manager-server"
|
||||||
|
TypeNodeManagerClient = "node-manager-client"
|
||||||
|
TypeDERP = "derp"
|
||||||
|
TypeManager = "manager"
|
||||||
|
TypeNode = "node"
|
||||||
|
TypeResolved = "resolved"
|
||||||
|
TypeSSMAPI = "ssm-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
TypeFailover = "failover"
|
||||||
TypeSelector = "selector"
|
TypeSelector = "selector"
|
||||||
TypeURLTest = "urltest"
|
TypeURLTest = "urltest"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,4 +7,5 @@ const (
|
|||||||
V2RayTransportTypeGRPC = "grpc"
|
V2RayTransportTypeGRPC = "grpc"
|
||||||
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
V2RayTransportTypeHTTPUpgrade = "httpupgrade"
|
||||||
V2RayTransportTypeXHTTP = "xhttp"
|
V2RayTransportTypeXHTTP = "xhttp"
|
||||||
|
V2RayTransportTypeKCP = "mkcp"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"level": "error"
|
"level": "info"
|
||||||
},
|
},
|
||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_client",
|
"type": "tunnel-client",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
{
|
{
|
||||||
"type": "mixed",
|
"type": "mixed",
|
||||||
"tag": "mixed-in",
|
"tag": "mixed-in",
|
||||||
"listen_port": 7897
|
"listen_port": 10000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -41,16 +41,22 @@
|
|||||||
{
|
{
|
||||||
"type": "dns",
|
"type": "dns",
|
||||||
"tag": "dns-out"
|
"tag": "dns-out"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "failover",
|
||||||
|
"tag": "f",
|
||||||
|
"outbounds": ["tunnel", "direct-out"],
|
||||||
|
"interrupt_exist_connections": false,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"outbound": "tunnel",
|
"outbound": "f",
|
||||||
"override_tunnel_destination": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13"
|
"override_tunnel_destination": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"final": "direct-out",
|
"final": "f",
|
||||||
"default_domain_resolver": "default",
|
"default_domain_resolver": "default",
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"level": "error"
|
"level": "info"
|
||||||
},
|
},
|
||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_server",
|
"type": "tunnel-server",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
"users": [
|
"users": [
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_client",
|
"type": "tunnel-client",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_client",
|
"type": "tunnel-client",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "487f6073-3300-4819-a07d-39652e45fb4d",
|
"uuid": "487f6073-3300-4819-a07d-39652e45fb4d",
|
||||||
"key": "3d74d616-2502-4c17-9cc3-92c366550f4f",
|
"key": "3d74d616-2502-4c17-9cc3-92c366550f4f",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_server",
|
"type": "tunnel-server",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
"users": [
|
"users": [
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_server",
|
"type": "tunnel-server",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
"users": [
|
"users": [
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_client",
|
"type": "tunnel-client",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"level": "error"
|
"level": "info"
|
||||||
},
|
},
|
||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_client",
|
"type": "tunnel-client",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
"uuid": "9b65b7e1-04c8-4717-8f45-2aa61fd25937",
|
||||||
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
"key": "1c9b2ccf-b0c0-4c26-868d-a55a4edad3fe",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"log": {
|
"log": {
|
||||||
"level": "error"
|
"level": "info"
|
||||||
},
|
},
|
||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
{
|
{
|
||||||
"type": "tunnel_server",
|
"type": "tunnel-server",
|
||||||
"tag": "tunnel",
|
"tag": "tunnel",
|
||||||
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
"uuid": "f79f7678-55e7-432d-a15f-6e8ab2b7fe13",
|
||||||
"users": [
|
"users": [
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
{
|
{
|
||||||
"type": "mixed",
|
"type": "mixed",
|
||||||
"tag": "mixed-in",
|
"tag": "mixed-in",
|
||||||
"listen_port": 7897
|
"listen_port": 10000
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
|
|||||||
92
go.mod
92
go.mod
@@ -1,20 +1,25 @@
|
|||||||
module github.com/sagernet/sing-box
|
module github.com/sagernet/sing-box
|
||||||
|
|
||||||
go 1.24.4
|
go 1.25
|
||||||
|
|
||||||
toolchain go1.24.6
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/GoAdminGroup/go-admin v1.2.26
|
||||||
|
github.com/GoAdminGroup/themes v0.0.48
|
||||||
github.com/anytls/sing-anytls v0.0.11
|
github.com/anytls/sing-anytls v0.0.11
|
||||||
github.com/caddyserver/certmagic v0.23.0
|
github.com/caddyserver/certmagic v0.23.0
|
||||||
github.com/coder/websocket v1.8.13
|
github.com/coder/websocket v1.8.13
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/enfein/mieru/v3 v3.17.1
|
github.com/enfein/mieru/v3 v3.17.1
|
||||||
|
github.com/go-chi/chi v1.5.5
|
||||||
github.com/go-chi/chi/v5 v5.2.2
|
github.com/go-chi/chi/v5 v5.2.2
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
||||||
github.com/gofrs/uuid/v5 v5.3.2
|
github.com/gofrs/uuid/v5 v5.3.2
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
|
github.com/huandu/go-sqlbuilder v1.38.1
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
|
github.com/libdns/alidns v1.0.5-libdns.v1.beta1
|
||||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
|
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
@@ -30,7 +35,7 @@ require (
|
|||||||
github.com/sagernet/gomobile v0.1.8
|
github.com/sagernet/gomobile v0.1.8
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||||
github.com/sagernet/quic-go v0.52.0-sing-box-mod.3
|
github.com/sagernet/quic-go v0.52.0-sing-box-mod.3
|
||||||
github.com/sagernet/sing v0.7.13
|
github.com/sagernet/sing v0.7.14
|
||||||
github.com/sagernet/sing-mux v0.3.3
|
github.com/sagernet/sing-mux v0.3.3
|
||||||
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb
|
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||||
@@ -38,47 +43,59 @@ require (
|
|||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.7.3
|
github.com/sagernet/sing-tun v0.7.3
|
||||||
github.com/sagernet/sing-vmess v0.2.7
|
github.com/sagernet/sing-vmess v0.2.7
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1
|
||||||
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2
|
github.com/sagernet/tailscale v1.80.3-sing-box-1.12-mod.2
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/tidwall/gjson v1.18.0
|
github.com/tidwall/gjson v1.18.0
|
||||||
github.com/vishvananda/netns v0.0.5
|
github.com/vishvananda/netns v0.0.5
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.41.0
|
golang.org/x/crypto v0.47.0
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
|
||||||
golang.org/x/mod v0.27.0
|
golang.org/x/mod v0.32.0
|
||||||
golang.org/x/net v0.43.0
|
golang.org/x/net v0.49.0
|
||||||
golang.org/x/sys v0.35.0
|
golang.org/x/sys v0.40.0
|
||||||
golang.org/x/time v0.11.0
|
golang.org/x/time v0.12.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||||
google.golang.org/grpc v1.73.0
|
google.golang.org/grpc v1.78.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.11
|
||||||
howett.net/plist v1.0.1
|
howett.net/plist v1.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
|
github.com/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
|
||||||
|
github.com/nxadm/tail v1.4.11 // indirect
|
||||||
|
github.com/zeebo/assert v1.3.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AdguardTeam/golibs v0.32.7 // indirect
|
github.com/AdguardTeam/golibs v0.32.7 // indirect
|
||||||
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
github.com/ameshkov/dnscrypt/v2 v2.4.0
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
github.com/ameshkov/dnsstamps v1.0.3 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
golang.org/x/tools v0.36.0 // indirect
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
//replace github.com/sagernet/sing => ../sing
|
//replace github.com/sagernet/sing => ../sing
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/360EntSecGroup-Skylar/excelize v1.4.1 // indirect
|
||||||
|
github.com/GoAdminGroup/html v0.0.1 // indirect
|
||||||
|
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e // indirect
|
||||||
github.com/ajg/form v1.5.1 // indirect
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
github.com/akutz/memconn v0.1.0 // indirect
|
github.com/akutz/memconn v0.1.0 // indirect
|
||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
@@ -89,12 +106,19 @@ require (
|
|||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
github.com/gaissmai/bart v0.11.1 // indirect
|
github.com/gaissmai/bart v0.11.1 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/gin-gonic/gin v1.3.0 // indirect
|
||||||
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect
|
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/golang/snappy v1.0.0 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
@@ -104,19 +128,31 @@ require (
|
|||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
|
||||||
|
github.com/huandu/go-clone v1.7.3 // indirect
|
||||||
|
github.com/huandu/xstrings v1.4.0 // indirect
|
||||||
github.com/illarion/gonotify/v2 v2.0.3 // indirect
|
github.com/illarion/gonotify/v2 v2.0.3 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.8.0
|
||||||
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
github.com/jsimonetti/rtnetlink v1.4.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
github.com/klauspost/compress v1.18.3 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/libdns/libdns v1.1.0 // indirect
|
github.com/libdns/libdns v1.1.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect
|
||||||
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
||||||
github.com/mdlayher/socket v0.5.1 // indirect
|
github.com/mdlayher/socket v0.5.1 // indirect
|
||||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.25 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
@@ -124,6 +160,7 @@ require (
|
|||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
github.com/spf13/pflag v1.0.6 // indirect
|
github.com/spf13/pflag v1.0.6 // indirect
|
||||||
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect
|
||||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect
|
||||||
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
|
github.com/tailscale/golang-x-crypto v0.0.0-20240604161659-3fde5e568aa4 // indirect
|
||||||
@@ -134,24 +171,33 @@ require (
|
|||||||
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
|
||||||
github.com/tevino/abool v1.2.0 // indirect
|
github.com/tevino/abool v1.2.0 // indirect
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // indirect
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||||
golang.org/x/term v0.34.0 // indirect
|
golang.org/x/term v0.39.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.4.1 // indirect
|
lukechampine.com/blake3 v1.4.1 // indirect
|
||||||
|
xorm.io/builder v0.3.7 // indirect
|
||||||
|
xorm.io/xorm v1.0.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/sagernet/wireguard-go => github.com/shtorm-7/wireguard-go v0.0.1-beta.7-extended-1.1.0
|
replace github.com/sagernet/wireguard-go => github.com/shtorm-7/wireguard-go v0.0.1-beta.7-extended-1.1.0
|
||||||
|
|
||||||
replace github.com/sagernet/tailscale => github.com/shtorm-7/tailscale v1.80.3-sing-box-1.12-mod.2-extended-1.0.0
|
replace github.com/sagernet/tailscale => github.com/shtorm-7/tailscale v1.80.3-sing-box-1.12-mod.2-extended-1.0.0
|
||||||
|
|
||||||
|
replace github.com/sagernet/sing-mux => github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0
|
||||||
|
|
||||||
replace github.com/sagernet/sing-dns => github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0
|
replace github.com/sagernet/sing-dns => github.com/shtorm-7/sing-dns v0.4.6-extended-1.0.0
|
||||||
|
|
||||||
replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0
|
replace github.com/ameshkov/dnscrypt/v2 => github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0
|
||||||
|
|||||||
407
go.sum
407
go.sum
@@ -1,19 +1,45 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
|
||||||
|
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
|
||||||
|
github.com/360EntSecGroup-Skylar/excelize v1.4.1 h1:l55mJb6rkkaUzOpSsgEeKYtS6/0gHwBYyfo5Jcjv/Ks=
|
||||||
|
github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE=
|
||||||
github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4=
|
github.com/AdguardTeam/golibs v0.32.7 h1:3dmGlAVgmvquCCwHsvEl58KKcRAK3z1UnjMnwSIeDH4=
|
||||||
github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA=
|
github.com/AdguardTeam/golibs v0.32.7/go.mod h1:bE8KV1zqTzgZjmjFyBJ9f9O5DEKO717r7e57j1HclJA=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/GoAdminGroup/go-admin v1.2.26 h1:kk18rVrteLcrzH7iMM5p/13jghDC5n3DJG/7zAnbnEU=
|
||||||
|
github.com/GoAdminGroup/go-admin v1.2.26/go.mod h1:QXj94ZrDclKzqwZnAGUWaK3qY1Wfr6/Qy5GnRGeXR+k=
|
||||||
|
github.com/GoAdminGroup/html v0.0.1 h1:SdWNWl4OKPsvDk2GDp5ZKD6ceWoN8n4Pj6cUYxavUd0=
|
||||||
|
github.com/GoAdminGroup/html v0.0.1/go.mod h1:A1laTJaOx8sQ64p2dE8IqtstDeCNBHEazrEp7hR5VvM=
|
||||||
|
github.com/GoAdminGroup/themes v0.0.48 h1:OveEEoFBCBTU5kNicqnvs0e/pL6uZKNQU1RAP9kmNFA=
|
||||||
|
github.com/GoAdminGroup/themes v0.0.48/go.mod h1:w/5P0WCmM8iv7DYE5scIT8AODYMoo6zj/bVlzAbgOaU=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
|
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e h1:n+DcnTNkQnHlwpsrHoQtkrJIO7CBx029fw6oR4vIob4=
|
||||||
|
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
|
||||||
|
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||||
|
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A=
|
||||||
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw=
|
||||||
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1OYVo=
|
||||||
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=
|
||||||
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=
|
||||||
|
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE=
|
||||||
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||||
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
github.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=
|
||||||
@@ -24,11 +50,17 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
|
|||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk=
|
||||||
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
github.com/coder/websocket v1.8.13 h1:f3QZdXy7uGVz+4uCJy2nTZyM0yTBj8yANEHhqlXZ9FE=
|
||||||
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
github.com/coder/websocket v1.8.13/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
|
||||||
|
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
||||||
|
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||||
|
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 h1:8h5+bWd7R6AYUslN6c6iuZWTKsKxUFDlpnmilO6R2n0=
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
|
||||||
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -37,32 +69,74 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk=
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa/go.mod h1:Nx87SkVqTKd8UtT+xu7sM/l+LgXs6c0aHrlKusR+2EQ=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE=
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
|
||||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||||
|
github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4=
|
||||||
|
github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU=
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yezR0y7jJ5pceLHthLaYf4bA5T14B6q39S4q2Q=
|
||||||
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A=
|
||||||
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
|
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||||
|
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
github.com/enfein/mieru/v3 v3.17.1 h1:pIKbspsKRYNyUrORVI33t1/yz2syaaUkIanskAbGBHY=
|
github.com/enfein/mieru/v3 v3.17.1 h1:pIKbspsKRYNyUrORVI33t1/yz2syaaUkIanskAbGBHY=
|
||||||
github.com/enfein/mieru/v3 v3.17.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
github.com/enfein/mieru/v3 v3.17.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||||
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
|
github.com/gaissmai/bart v0.11.1 h1:5Uv5XwsaFBRo4E5VBcb9TzY8B7zxFf+U7isDxqOrRfc=
|
||||||
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
github.com/gaissmai/bart v0.11.1/go.mod h1:KHeYECXQiBjTzQz/om2tqn3sZF1J7hw9m6z41ftj3fg=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
|
||||||
|
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||||
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
|
||||||
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
|
||||||
|
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
|
||||||
|
github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw=
|
||||||
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
|
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288 h1:KbX3Z3CgiYlbaavUq3Cj9/MjpO+88S7/AGXzynVDv84=
|
||||||
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
|
github.com/go-json-experiment/json v0.0.0-20250103232110-6a9a0fde9288/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||||
|
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
@@ -71,46 +145,107 @@ github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 h1:sQspH8M4niEijh
|
|||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466/go.mod h1:ZiQxhyQ+bbbfxUKVvjfO498oPYvtYhZzycal3G/NHmU=
|
||||||
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=
|
||||||
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||||
|
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||||
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||||
|
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806 h1:wG8RYIyctLhdFk6Vl1yPGtSRtwGpVkWyZww1OCil2MI=
|
||||||
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
github.com/google/nftables v0.2.1-0.20240414091927-5e242ec57806/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M=
|
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30 h1:fiJdrgVBkjZ5B1HJ2WQwNOaXB+QyYcNXTA3t1XYLz0M=
|
||||||
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
github.com/gorilla/csrf v1.7.3-0.20250123201450-9dd6af1f6d30/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||||
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||||
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
|
||||||
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
||||||
|
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
||||||
|
github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs=
|
||||||
|
github.com/huandu/go-clone v1.7.3 h1:rtQODA+ABThEn6J5LBTppJfKmZy/FwfpMUWa8d01TTQ=
|
||||||
|
github.com/huandu/go-clone v1.7.3/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE=
|
||||||
|
github.com/huandu/go-sqlbuilder v1.38.1 h1:kajV1CFJQIrJgyTONhQFheJLRFnwDmTnU6e3CfFP5GQ=
|
||||||
|
github.com/huandu/go-sqlbuilder v1.38.1/go.mod h1:zdONH67liL+/TvoUMwnZP/sUYGSSvHh9psLe/HpXn8E=
|
||||||
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A=
|
github.com/illarion/gonotify/v2 v2.0.3 h1:B6+SKPo/0Sw8cRJh1aLzNEeNVFfzE3c6N+o+vyxM+9A=
|
||||||
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
|
github.com/illarion/gonotify/v2 v2.0.3/go.mod h1:38oIJTgFqupkEydkkClkbL6i5lXV/bxdH9do5TALPEE=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU=
|
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM=
|
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||||
|
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
github.com/jsimonetti/rtnetlink v1.4.0 h1:Z1BF0fRgcETPEa0Kt0MRk3yV5+kF1FWTni6KUFKrq2I=
|
||||||
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
github.com/jsimonetti/rtnetlink v1.4.0/go.mod h1:5W1jDvWdnthFJ7fxYX1GMK07BUpI4oskfOqvPteYS6E=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||||
|
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ=
|
||||||
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a/go.mod h1:YTtCCM3ryyfiu4F7t8HQ1mxvp1UBdWM2r6Xa+nGWvDk=
|
||||||
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ=
|
github.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ=
|
||||||
github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
|
github.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=
|
||||||
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8=
|
github.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8=
|
||||||
@@ -120,6 +255,14 @@ github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
|
|||||||
github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||||
|
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||||
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o=
|
||||||
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
|
github.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=
|
||||||
@@ -138,19 +281,65 @@ github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=
|
|||||||
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=
|
||||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
|
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||||
|
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
|
||||||
|
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
|
||||||
|
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyfyrrjEaAchdy3R4=
|
||||||
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||||
@@ -172,11 +361,8 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
|
|||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/quic-go v0.52.0-sing-box-mod.3 h1:ySqffGm82rPqI1TUPqmtHIYd12pfEGScygnOxjTL56w=
|
github.com/sagernet/quic-go v0.52.0-sing-box-mod.3 h1:ySqffGm82rPqI1TUPqmtHIYd12pfEGScygnOxjTL56w=
|
||||||
github.com/sagernet/quic-go v0.52.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
github.com/sagernet/quic-go v0.52.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.7.14 h1:5QQRDCUvYNOMyVp3LuK/hYEBAIv0VsbD3x/l9zH467s=
|
||||||
github.com/sagernet/sing v0.7.13 h1:XNYgd8e3cxMULs/LLJspdn/deHrnPWyrrglNHeCUAYM=
|
github.com/sagernet/sing v0.7.14/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing v0.7.13/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
|
||||||
github.com/sagernet/sing-mux v0.3.3 h1:YFgt9plMWzH994BMZLmyKL37PdIVaIilwP0Jg+EcLfw=
|
|
||||||
github.com/sagernet/sing-mux v0.3.3/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
|
||||||
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM=
|
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb h1:5Wx3XeTiKrrrcrAky7Hc1bO3CGxrvho2Vu5b/adlEIM=
|
||||||
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
|
github.com/sagernet/sing-quic v0.5.2-0.20250909083218-00a55617c0fb/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||||
@@ -189,31 +375,34 @@ github.com/sagernet/sing-tun v0.7.3 h1:MFnAir+l24ElEyxdfwtY8mqvUUL9nPnL9TDYLkOmV
|
|||||||
github.com/sagernet/sing-tun v0.7.3/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM=
|
github.com/sagernet/sing-tun v0.7.3/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM=
|
||||||
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
|
github.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=
|
||||||
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
github.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
github.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||||
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg=
|
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTVtJ5jDTsTk5wtIIapZTRg=
|
||||||
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
|
github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0/go.mod h1:WpEFV2uhebXb8Jhes/5/fSdpmhGV8TL22RDaeWwV6hI=
|
||||||
|
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0 h1:a5OoXr3e2ACbM6vDIaaGL44IdHQ6wPjcSoU13vfC0Sw=
|
||||||
|
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||||
github.com/shtorm-7/tailscale v1.80.3-sing-box-1.12-mod.2-extended-1.0.0 h1:Yp4dIRwiwLda9JXyGMHkfYRr2r01NarkzsNd/oi10dk=
|
github.com/shtorm-7/tailscale v1.80.3-sing-box-1.12-mod.2-extended-1.0.0 h1:Yp4dIRwiwLda9JXyGMHkfYRr2r01NarkzsNd/oi10dk=
|
||||||
github.com/shtorm-7/tailscale v1.80.3-sing-box-1.12-mod.2-extended-1.0.0/go.mod h1:+znUAXWwgcgza5mb5do8j9RC95rpY9lbSc/TyEyCGa4=
|
github.com/shtorm-7/tailscale v1.80.3-sing-box-1.12-mod.2-extended-1.0.0/go.mod h1:+znUAXWwgcgza5mb5do8j9RC95rpY9lbSc/TyEyCGa4=
|
||||||
github.com/shtorm-7/wireguard-go v0.0.1-beta.7-extended-1.1.0 h1:bTmx3NiEeH7mdgsifyNUxIEAA0wokRMSm8iS/hln6n0=
|
github.com/shtorm-7/wireguard-go v0.0.1-beta.7-extended-1.1.0 h1:bTmx3NiEeH7mdgsifyNUxIEAA0wokRMSm8iS/hln6n0=
|
||||||
github.com/shtorm-7/wireguard-go v0.0.1-beta.7-extended-1.1.0/go.mod h1:DHxMTUaBGHP3tf8nJ/N8AkcoJDD0PHECLhTfLsw+ylQ=
|
github.com/shtorm-7/wireguard-go v0.0.1-beta.7-extended-1.1.0/go.mod h1:DHxMTUaBGHP3tf8nJ/N8AkcoJDD0PHECLhTfLsw+ylQ=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/testify v1.2.3-0.20181224173747-660f15d67dbb/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e h1:PtWT87weP5LWHEY//SWsYkSO3RWRZo4OSWagh3YD2vQ=
|
||||||
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e/go.mod h1:XrBNfAFN+pwoWuksbFS9Ccxnopa15zJGgXRFN90l3K4=
|
||||||
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4=
|
||||||
@@ -242,29 +431,37 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||||
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
|
||||||
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
|
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
||||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
@@ -279,70 +476,140 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4
|
|||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||||
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU=
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
|
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||||
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||||
|
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||||
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
|
||||||
|
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
|
||||||
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489 h1:ze1vwAdliUAr68RQ5NtufWaXaOg8WUO2OACzEV+TNdE=
|
||||||
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
gvisor.dev/gvisor v0.0.0-20231202080848-1f7806d17489/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||||
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
|
||||||
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
lukechampine.com/blake3 v1.4.1 h1:I3Smz7gso8w4/TunLKec6K2fn+kyKtDxr/xcQEN84Wg=
|
||||||
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
lukechampine.com/blake3 v1.4.1/go.mod h1:QFosUxmjB8mnrWFSNwKmvxHpfY72bmD2tQ0kBMM3kwo=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||||
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
|
||||||
|
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
||||||
|
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||||
|
xorm.io/xorm v1.0.2 h1:kZlCh9rqd1AzGwWitcrEEqHE1h1eaZE/ujU5/2tWEtg=
|
||||||
|
xorm.io/xorm v1.0.2/go.mod h1:o4vnEsQ5V2F1/WK6w4XTwmiWJeGj82tqjAnHe44wVHY=
|
||||||
|
|||||||
@@ -19,10 +19,13 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/anytls"
|
"github.com/sagernet/sing-box/protocol/anytls"
|
||||||
"github.com/sagernet/sing-box/protocol/block"
|
"github.com/sagernet/sing-box/protocol/block"
|
||||||
|
"github.com/sagernet/sing-box/protocol/bond"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/protocol/direct"
|
||||||
protocolDNS "github.com/sagernet/sing-box/protocol/dns"
|
protocolDNS "github.com/sagernet/sing-box/protocol/dns"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/protocol/group"
|
||||||
"github.com/sagernet/sing-box/protocol/http"
|
"github.com/sagernet/sing-box/protocol/http"
|
||||||
|
"github.com/sagernet/sing-box/protocol/limiter/bandwidth"
|
||||||
|
"github.com/sagernet/sing-box/protocol/limiter/connection"
|
||||||
"github.com/sagernet/sing-box/protocol/mieru"
|
"github.com/sagernet/sing-box/protocol/mieru"
|
||||||
"github.com/sagernet/sing-box/protocol/mixed"
|
"github.com/sagernet/sing-box/protocol/mixed"
|
||||||
"github.com/sagernet/sing-box/protocol/naive"
|
"github.com/sagernet/sing-box/protocol/naive"
|
||||||
@@ -37,6 +40,11 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/tunnel"
|
"github.com/sagernet/sing-box/protocol/tunnel"
|
||||||
"github.com/sagernet/sing-box/protocol/vless"
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
"github.com/sagernet/sing-box/protocol/vmess"
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
|
"github.com/sagernet/sing-box/service/admin_panel"
|
||||||
|
"github.com/sagernet/sing-box/service/manager"
|
||||||
|
"github.com/sagernet/sing-box/service/node"
|
||||||
|
nodeManagerClient "github.com/sagernet/sing-box/service/node_manager/client"
|
||||||
|
nodeManagerServer "github.com/sagernet/sing-box/service/node_manager/server"
|
||||||
"github.com/sagernet/sing-box/service/resolved"
|
"github.com/sagernet/sing-box/service/resolved"
|
||||||
"github.com/sagernet/sing-box/service/ssmapi"
|
"github.com/sagernet/sing-box/service/ssmapi"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -66,6 +74,8 @@ func InboundRegistry() *inbound.Registry {
|
|||||||
vless.RegisterInbound(registry)
|
vless.RegisterInbound(registry)
|
||||||
anytls.RegisterInbound(registry)
|
anytls.RegisterInbound(registry)
|
||||||
|
|
||||||
|
bond.RegisterInbound(registry)
|
||||||
|
|
||||||
registerQUICInbounds(registry)
|
registerQUICInbounds(registry)
|
||||||
registerStubForRemovedInbounds(registry)
|
registerStubForRemovedInbounds(registry)
|
||||||
|
|
||||||
@@ -80,6 +90,7 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
block.RegisterOutbound(registry)
|
block.RegisterOutbound(registry)
|
||||||
protocolDNS.RegisterOutbound(registry)
|
protocolDNS.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
group.RegisterFailover(registry)
|
||||||
group.RegisterSelector(registry)
|
group.RegisterSelector(registry)
|
||||||
group.RegisterURLTest(registry)
|
group.RegisterURLTest(registry)
|
||||||
|
|
||||||
@@ -95,6 +106,11 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
mieru.RegisterOutbound(registry)
|
mieru.RegisterOutbound(registry)
|
||||||
anytls.RegisterOutbound(registry)
|
anytls.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
bond.RegisterOutbound(registry)
|
||||||
|
|
||||||
|
bandwidth.RegisterOutbound(registry)
|
||||||
|
connection.RegisterOutbound(registry)
|
||||||
|
|
||||||
registerQUICOutbounds(registry)
|
registerQUICOutbounds(registry)
|
||||||
registerWireGuardOutbound(registry)
|
registerWireGuardOutbound(registry)
|
||||||
registerStubForRemovedOutbounds(registry)
|
registerStubForRemovedOutbounds(registry)
|
||||||
@@ -137,6 +153,11 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
|||||||
func ServiceRegistry() *service.Registry {
|
func ServiceRegistry() *service.Registry {
|
||||||
registry := service.NewRegistry()
|
registry := service.NewRegistry()
|
||||||
|
|
||||||
|
admin_panel.RegisterService(registry)
|
||||||
|
manager.RegisterService(registry)
|
||||||
|
node.RegisterService(registry)
|
||||||
|
nodeManagerClient.RegisterService(registry)
|
||||||
|
nodeManagerServer.RegisterService(registry)
|
||||||
resolved.RegisterService(registry)
|
resolved.RegisterService(registry)
|
||||||
ssmapi.RegisterService(registry)
|
ssmapi.RegisterService(registry)
|
||||||
|
|
||||||
|
|||||||
27
log/id.go
27
log/id.go
@@ -13,6 +13,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type idKey struct{}
|
type idKey struct{}
|
||||||
|
type muxIdKey struct{}
|
||||||
|
type hwidKey struct{}
|
||||||
|
|
||||||
type ID struct {
|
type ID struct {
|
||||||
ID uint32
|
ID uint32
|
||||||
@@ -34,3 +36,28 @@ func IDFromContext(ctx context.Context) (ID, bool) {
|
|||||||
id, loaded := ctx.Value((*idKey)(nil)).(ID)
|
id, loaded := ctx.Value((*idKey)(nil)).(ID)
|
||||||
return id, loaded
|
return id, loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ContextWithNewMuxID(ctx context.Context) context.Context {
|
||||||
|
return ContextWithMuxID(ctx, ID{
|
||||||
|
ID: rand.Uint32(),
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextWithMuxID(ctx context.Context, id ID) context.Context {
|
||||||
|
return context.WithValue(ctx, (*muxIdKey)(nil), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MuxIDFromContext(ctx context.Context) (ID, bool) {
|
||||||
|
id, loaded := ctx.Value((*muxIdKey)(nil)).(ID)
|
||||||
|
return id, loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextWithHWID(ctx context.Context, id ID) context.Context {
|
||||||
|
return context.WithValue(ctx, (*hwidKey)(nil), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HWIDFromContext(ctx context.Context) (ID, bool) {
|
||||||
|
id, loaded := ctx.Value((*hwidKey)(nil)).(ID)
|
||||||
|
return id, loaded
|
||||||
|
}
|
||||||
|
|||||||
13
option/admin_panel.go
Normal file
13
option/admin_panel.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type AdminPanelServiceOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
Manager string `json:"manager"`
|
||||||
|
Database AdminPanelServiceDatabase `json:"database"`
|
||||||
|
InboundTLSOptionsContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminPanelServiceDatabase struct {
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
}
|
||||||
16
option/bond.go
Normal file
16
option/bond.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type BondInboundOptions struct {
|
||||||
|
Inbounds []Inbound `json:"inbounds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BondOutboundOptions struct {
|
||||||
|
Outbounds []BondOutbound `json:"outbounds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BondOutbound struct {
|
||||||
|
Outbound Outbound `json:"outbound"`
|
||||||
|
DownloadRatio uint8 `json:"download_ratio"`
|
||||||
|
UploadRatio uint8 `json:"upload_ratio"`
|
||||||
|
Count uint8 `json:"count"`
|
||||||
|
}
|
||||||
@@ -16,3 +16,7 @@ type URLTestOutboundOptions struct {
|
|||||||
IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"`
|
IdleTimeout badoption.Duration `json:"idle_timeout,omitempty"`
|
||||||
InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"`
|
InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FailoverOutboundOptions struct {
|
||||||
|
Outbounds []string `json:"outbounds"`
|
||||||
|
}
|
||||||
|
|||||||
37
option/limiter.go
Normal file
37
option/limiter.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/byteformats"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BandwidthLimiterOutboundOptions struct {
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
ConnectionType string `json:"connection_type,omitempty"`
|
||||||
|
Speed *byteformats.NetworkBytesCompat `json:"speed"`
|
||||||
|
Users []BandwidthLimiterUser `json:"users,omitempty"`
|
||||||
|
Route RouteOptions `json:"route"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiterUser struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
ConnectionType string `json:"connection_type,omitempty"`
|
||||||
|
Speed *byteformats.NetworkBytesCompat `json:"speed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiterOutboundOptions struct {
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
ConnectionType string `json:"connection_type,omitempty"`
|
||||||
|
Count uint32 `json:"count"`
|
||||||
|
Users []ConnectionLimiterUser `json:"users,omitempty"`
|
||||||
|
Route RouteOptions `json:"route"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiterUser struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Strategy string `json:"strategy"`
|
||||||
|
ConnectionType string `json:"connection_type,omitempty"`
|
||||||
|
Count uint32 `json:"count"`
|
||||||
|
}
|
||||||
11
option/manager.go
Normal file
11
option/manager.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type ManagerServiceDatabase struct {
|
||||||
|
Driver string `json:"driver"`
|
||||||
|
DSN string `json:"dsn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagerServiceOptions struct {
|
||||||
|
Inbounds []string `json:"inbounds"`
|
||||||
|
Database ManagerServiceDatabase `json:"database"`
|
||||||
|
}
|
||||||
9
option/node.go
Normal file
9
option/node.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type NodeServiceOptions struct {
|
||||||
|
UUID string
|
||||||
|
Inbounds []string `json:"inbounds"`
|
||||||
|
ConnectionLimiters []string `json:"connection_limiters"`
|
||||||
|
BandwidthLimiters []string `json:"bandwidth_limiters"`
|
||||||
|
Manager string `json:"manager"`
|
||||||
|
}
|
||||||
13
option/node_manager.go
Normal file
13
option/node_manager.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type NodeManagerServerServiceOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
InboundTLSOptionsContainer
|
||||||
|
Manager string `json:"manager"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeManagerClientServiceOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
OutboundTLSOptionsContainer
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ type _V2RayTransportOptions struct {
|
|||||||
GRPCOptions V2RayGRPCOptions `json:"-"`
|
GRPCOptions V2RayGRPCOptions `json:"-"`
|
||||||
HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"`
|
HTTPUpgradeOptions V2RayHTTPUpgradeOptions `json:"-"`
|
||||||
XHTTPOptions V2RayXHTTPOptions `json:"-"`
|
XHTTPOptions V2RayXHTTPOptions `json:"-"`
|
||||||
|
KCPOptions V2RayKCPOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayTransportOptions _V2RayTransportOptions
|
type V2RayTransportOptions _V2RayTransportOptions
|
||||||
@@ -40,6 +41,8 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) {
|
|||||||
v = o.HTTPUpgradeOptions
|
v = o.HTTPUpgradeOptions
|
||||||
case C.V2RayTransportTypeXHTTP:
|
case C.V2RayTransportTypeXHTTP:
|
||||||
v = o.XHTTPOptions
|
v = o.XHTTPOptions
|
||||||
|
case C.V2RayTransportTypeKCP:
|
||||||
|
v = o.KCPOptions
|
||||||
case "":
|
case "":
|
||||||
return nil, E.New("missing transport type")
|
return nil, E.New("missing transport type")
|
||||||
default:
|
default:
|
||||||
@@ -67,6 +70,8 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
v = &o.HTTPUpgradeOptions
|
v = &o.HTTPUpgradeOptions
|
||||||
case C.V2RayTransportTypeXHTTP:
|
case C.V2RayTransportTypeXHTTP:
|
||||||
v = &o.XHTTPOptions
|
v = &o.XHTTPOptions
|
||||||
|
case C.V2RayTransportTypeKCP:
|
||||||
|
v = &o.KCPOptions
|
||||||
default:
|
default:
|
||||||
return E.New("unknown transport type: " + o.Type)
|
return E.New("unknown transport type: " + o.Type)
|
||||||
}
|
}
|
||||||
@@ -250,3 +255,64 @@ func (m *V2RayXHTTPXmuxOptions) GetNormalizedHMaxRequestTimes() Xbadoption.Range
|
|||||||
func (m *V2RayXHTTPXmuxOptions) GetNormalizedHMaxReusableSecs() Xbadoption.Range {
|
func (m *V2RayXHTTPXmuxOptions) GetNormalizedHMaxReusableSecs() Xbadoption.Range {
|
||||||
return m.HMaxReusableSecs
|
return m.HMaxReusableSecs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type V2RayKCPOptions struct {
|
||||||
|
MTU uint32 `json:"mtu,omitempty"`
|
||||||
|
TTI uint32 `json:"tti,omitempty"`
|
||||||
|
UplinkCapacity uint32 `json:"uplink_capacity,omitempty"`
|
||||||
|
DownlinkCapacity uint32 `json:"downlink_capacity,omitempty"`
|
||||||
|
Congestion bool `json:"congestion,omitempty"`
|
||||||
|
ReadBufferSize uint32 `json:"read_buffer_size,omitempty"`
|
||||||
|
WriteBufferSize uint32 `json:"write_buffer_size,omitempty"`
|
||||||
|
HeaderType string `json:"header_type,omitempty"`
|
||||||
|
Seed string `json:"seed,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *V2RayKCPOptions) GetMTU() uint32 {
|
||||||
|
if k.MTU == 0 {
|
||||||
|
return 1350
|
||||||
|
}
|
||||||
|
return k.MTU
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *V2RayKCPOptions) GetTTI() uint32 {
|
||||||
|
if k.TTI == 0 {
|
||||||
|
return 50
|
||||||
|
}
|
||||||
|
return k.TTI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *V2RayKCPOptions) GetUplinkCapacity() uint32 {
|
||||||
|
if k.UplinkCapacity == 0 {
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
return k.UplinkCapacity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *V2RayKCPOptions) GetDownlinkCapacity() uint32 {
|
||||||
|
if k.DownlinkCapacity == 0 {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return k.DownlinkCapacity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *V2RayKCPOptions) GetReadBufferSize() uint32 {
|
||||||
|
if k.ReadBufferSize == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return k.ReadBufferSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *V2RayKCPOptions) GetWriteBufferSize() uint32 {
|
||||||
|
if k.WriteBufferSize == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return k.WriteBufferSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *V2RayKCPOptions) GetHeaderType() string {
|
||||||
|
if k.HeaderType == "" {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
return k.HeaderType
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,15 +33,17 @@ type WireGuardPeer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WireGuardWARPEndpointOptions struct {
|
type WireGuardWARPEndpointOptions struct {
|
||||||
System bool `json:"system,omitempty"`
|
System bool `json:"system,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
ListenPort uint16 `json:"listen_port,omitempty"`
|
ListenPort uint16 `json:"listen_port,omitempty"`
|
||||||
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
|
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
|
||||||
Workers int `json:"workers,omitempty"`
|
PersistentKeepaliveInterval uint16 `json:"persistent_keepalive_interval,omitempty"`
|
||||||
PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"`
|
Reserved []uint8 `json:"reserved,omitempty"`
|
||||||
DisablePauses bool `json:"disable_pauses,omitempty"`
|
Workers int `json:"workers,omitempty"`
|
||||||
Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"`
|
PreallocatedBuffersPerPool uint32 `json:"preallocated_buffers_per_pool,omitempty"`
|
||||||
Profile WARPProfile `json:"profile,omitempty"`
|
DisablePauses bool `json:"disable_pauses,omitempty"`
|
||||||
|
Amnezia *WireGuardAmnezia `json:"amnezia,omitempty"`
|
||||||
|
Profile WARPProfile `json:"profile,omitempty"`
|
||||||
DialerOptions
|
DialerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
164
protocol/bond/conn.go
Normal file
164
protocol/bond/conn.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package bond
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bondedConn struct {
|
||||||
|
conns []net.Conn
|
||||||
|
downloadRatios []uint8
|
||||||
|
uploadRatios []uint8
|
||||||
|
|
||||||
|
readBuffer []byte
|
||||||
|
readOffset int
|
||||||
|
readSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBondedConn(conns []net.Conn, downloadRatios, uploadRatios []uint8) *bondedConn {
|
||||||
|
return &bondedConn{
|
||||||
|
conns: conns,
|
||||||
|
downloadRatios: downloadRatios,
|
||||||
|
uploadRatios: uploadRatios,
|
||||||
|
readBuffer: make([]byte, 65535),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) Read(b []byte) (n int, err error) {
|
||||||
|
if c.readOffset == c.readSize {
|
||||||
|
var header [2]byte
|
||||||
|
_, err := io.ReadFull(c.conns[0], header[:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
size := int(binary.BigEndian.Uint16(header[:]))
|
||||||
|
chunkLens := splitByRatios(size, c.downloadRatios)
|
||||||
|
total := 0
|
||||||
|
for i, chunkLen := range chunkLens {
|
||||||
|
if chunkLen == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chunk := c.readBuffer[total : total+chunkLen]
|
||||||
|
n, err := io.ReadFull(c.conns[i], chunk)
|
||||||
|
total += n
|
||||||
|
if err != nil {
|
||||||
|
return total, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.readOffset = 0
|
||||||
|
c.readSize = size
|
||||||
|
}
|
||||||
|
n = copy(b, c.readBuffer[c.readOffset:c.readSize])
|
||||||
|
c.readOffset += n
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) Write(b []byte) (n int, err error) {
|
||||||
|
chunkLens := splitByRatios(len(b), c.uploadRatios)
|
||||||
|
var header [2]byte
|
||||||
|
binary.BigEndian.PutUint16(header[:], uint16(len(b)))
|
||||||
|
_, err = c.conns[0].Write(header[:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
total := 0
|
||||||
|
for i, chunkLen := range chunkLens {
|
||||||
|
if chunkLen == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
chunk := b[total : total+chunkLen]
|
||||||
|
conn := c.conns[i]
|
||||||
|
subTotal := 0
|
||||||
|
for subTotal < len(chunk) {
|
||||||
|
n, err := conn.Write(chunk[subTotal:])
|
||||||
|
subTotal += n
|
||||||
|
total += n
|
||||||
|
if err != nil {
|
||||||
|
return total, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return total, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) Close() error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for _, conn := range c.conns {
|
||||||
|
err := conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) LocalAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) RemoteAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) SetDeadline(t time.Time) error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for _, conn := range c.conns {
|
||||||
|
err := conn.SetDeadline(t)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) SetReadDeadline(t time.Time) error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for _, conn := range c.conns {
|
||||||
|
err := conn.SetReadDeadline(t)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bondedConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for _, conn := range c.conns {
|
||||||
|
err := conn.SetWriteDeadline(t)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitByRatios(number int, ratios []uint8) []int {
|
||||||
|
result := make([]int, len(ratios))
|
||||||
|
remaining := number
|
||||||
|
for i := 0; i < len(ratios)-1; i++ {
|
||||||
|
part := number * int(ratios[i]) / 100
|
||||||
|
result[i] = part
|
||||||
|
remaining -= part
|
||||||
|
}
|
||||||
|
result[len(ratios)-1] = remaining
|
||||||
|
return result
|
||||||
|
}
|
||||||
146
protocol/bond/inbound.go
Normal file
146
protocol/bond/inbound.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package bond
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/common/uot"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterInbound(registry *inbound.Registry) {
|
||||||
|
inbound.Register[option.BondInboundOptions](registry, C.TypeBond, NewInbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inbound struct {
|
||||||
|
inbound.Adapter
|
||||||
|
logger logger.ContextLogger
|
||||||
|
router adapter.ConnectionRouterEx
|
||||||
|
inbounds []adapter.Inbound
|
||||||
|
conns *cache.Cache
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BondInboundOptions) (adapter.Inbound, error) {
|
||||||
|
if len(options.Inbounds) == 0 {
|
||||||
|
return nil, E.New("missing tags")
|
||||||
|
}
|
||||||
|
inbound := &Inbound{
|
||||||
|
Adapter: inbound.NewAdapter(C.TypeTunnelServer, tag),
|
||||||
|
logger: logger,
|
||||||
|
router: uot.NewRouter(router, logger),
|
||||||
|
conns: cache.New(C.TCPConnectTimeout, time.Second),
|
||||||
|
}
|
||||||
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
|
inbounds := make([]adapter.Inbound, len(options.Inbounds))
|
||||||
|
for i, inboundOptions := range options.Inbounds {
|
||||||
|
inbound, err := inboundRegistry.UnsafeCreate(ctx, NewRouter(router, logger, inbound.connHandler), logger, inboundOptions.Tag, inboundOptions.Type, inboundOptions.Options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbounds[i] = inbound
|
||||||
|
}
|
||||||
|
inbound.inbounds = inbounds
|
||||||
|
inbound.conns.OnEvicted(func(s string, i interface{}) {
|
||||||
|
inbound.mtx.Lock()
|
||||||
|
defer inbound.mtx.Unlock()
|
||||||
|
ratioConns := i.(map[uint8]*ratioConn)
|
||||||
|
for _, ratioConn := range ratioConns {
|
||||||
|
if ratioConn != nil {
|
||||||
|
ratioConn.conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||||
|
for _, inbound := range h.inbounds {
|
||||||
|
err := inbound.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) Close() error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for _, inbound := range h.inbounds {
|
||||||
|
err := inbound.Close()
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) connHandler(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error {
|
||||||
|
request, err := ReadRequest(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.mtx.Lock()
|
||||||
|
defer h.mtx.Unlock()
|
||||||
|
var ratioConns map[uint8]*ratioConn
|
||||||
|
rawRatioConns, ok := h.conns.Get(request.UUID.String())
|
||||||
|
if ok {
|
||||||
|
ratioConns = rawRatioConns.(map[uint8]*ratioConn)
|
||||||
|
} else {
|
||||||
|
ratioConns = make(map[uint8]*ratioConn, request.Count)
|
||||||
|
h.conns.SetDefault(request.UUID.String(), ratioConns)
|
||||||
|
}
|
||||||
|
ratioConns[request.Index] = &ratioConn{
|
||||||
|
conn: conn,
|
||||||
|
downloadRatio: request.DownloadRatio,
|
||||||
|
uploadRatio: request.UploadRatio,
|
||||||
|
}
|
||||||
|
if len(ratioConns) == int(request.Count) {
|
||||||
|
conns := make([]net.Conn, len(ratioConns))
|
||||||
|
downloadRatios := make([]uint8, len(ratioConns))
|
||||||
|
uploadRatios := make([]uint8, len(ratioConns))
|
||||||
|
var totalDownloadRatio, totalUploadRatio uint8
|
||||||
|
for index, ratioConn := range ratioConns {
|
||||||
|
conns[index] = ratioConn.conn
|
||||||
|
downloadRatios[index] = ratioConn.downloadRatio
|
||||||
|
uploadRatios[index] = ratioConn.uploadRatio
|
||||||
|
totalDownloadRatio += ratioConn.downloadRatio
|
||||||
|
totalUploadRatio += ratioConn.uploadRatio
|
||||||
|
delete(ratioConns, index)
|
||||||
|
}
|
||||||
|
if totalDownloadRatio != 100 || totalUploadRatio != 100 {
|
||||||
|
for _, conn := range conns {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
return E.New("invalid ratios")
|
||||||
|
}
|
||||||
|
conn = NewBondedConn(conns, downloadRatios, uploadRatios)
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = C.TypeBond
|
||||||
|
metadata.Destination = request.Destination
|
||||||
|
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ratioConn struct {
|
||||||
|
conn net.Conn
|
||||||
|
downloadRatio uint8
|
||||||
|
uploadRatio uint8
|
||||||
|
}
|
||||||
152
protocol/bond/outbound.go
Normal file
152
protocol/bond/outbound.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package bond
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/uot"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.BondOutboundOptions](registry, C.TypeBond, NewOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
outbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
logger logger.ContextLogger
|
||||||
|
outbounds []adapter.Outbound
|
||||||
|
downloadRatios []uint8
|
||||||
|
uploadRatios []uint8
|
||||||
|
uotClient *uot.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BondOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
|
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||||
|
downloadRatios := make([]uint8, 0, len(options.Outbounds))
|
||||||
|
uploadRatios := make([]uint8, 0, len(options.Outbounds))
|
||||||
|
var totalDownloadRatio, totalUploadRatio uint8
|
||||||
|
for _, outboundOptions := range options.Outbounds {
|
||||||
|
count := outboundOptions.Count
|
||||||
|
if count == 0 {
|
||||||
|
count = 1
|
||||||
|
}
|
||||||
|
for range count {
|
||||||
|
outbound, err := outboundRegistry.UnsafeCreateOutbound(ctx, router, logger, outboundOptions.Outbound.Tag, outboundOptions.Outbound.Type, outboundOptions.Outbound.Options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outbounds = append(outbounds, outbound)
|
||||||
|
downloadRatios = append(downloadRatios, outboundOptions.DownloadRatio)
|
||||||
|
uploadRatios = append(uploadRatios, outboundOptions.UploadRatio)
|
||||||
|
totalDownloadRatio += outboundOptions.DownloadRatio
|
||||||
|
totalUploadRatio += outboundOptions.UploadRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if totalDownloadRatio != 100 || totalUploadRatio != 100 {
|
||||||
|
return nil, E.New("invalid ratios")
|
||||||
|
}
|
||||||
|
outbound := &Outbound{
|
||||||
|
Adapter: outbound.NewAdapter(C.TypeTunnelClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
|
||||||
|
ctx: ctx,
|
||||||
|
outbounds: outbounds,
|
||||||
|
downloadRatios: downloadRatios,
|
||||||
|
uploadRatios: uploadRatios,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
outbound.uotClient = &uot.Client{
|
||||||
|
Dialer: outbound,
|
||||||
|
Version: uot.Version,
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
if N.NetworkName(network) == N.NetworkUDP {
|
||||||
|
return h.uotClient.DialContext(ctx, network, destination)
|
||||||
|
}
|
||||||
|
conns := make([]net.Conn, len(h.outbounds))
|
||||||
|
connUUID, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
errs := make([]error, 0, len(conns))
|
||||||
|
var mtx sync.Mutex
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i, outbound := range h.outbounds {
|
||||||
|
wg.Go(
|
||||||
|
func() {
|
||||||
|
conn, err := outbound.DialContext(ctx, network, Destination)
|
||||||
|
if err != nil {
|
||||||
|
mtx.Lock()
|
||||||
|
errs = append(errs, err)
|
||||||
|
mtx.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = WriteRequest(
|
||||||
|
conn,
|
||||||
|
&Request{
|
||||||
|
UUID: connUUID,
|
||||||
|
Index: byte(i),
|
||||||
|
Count: byte(len(h.outbounds)),
|
||||||
|
DownloadRatio: h.uploadRatios[i],
|
||||||
|
UploadRatio: h.downloadRatios[i],
|
||||||
|
Destination: destination,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
mtx.Lock()
|
||||||
|
errs = append(errs, err)
|
||||||
|
mtx.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conns[i] = conn
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if len(errs) != 0 {
|
||||||
|
for _, conn := range conns {
|
||||||
|
if conn != nil {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.Join(errs...)
|
||||||
|
}
|
||||||
|
conn := NewBondedConn(conns, h.downloadRatios, h.uploadRatios)
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
return h.uotClient.ListenPacket(ctx, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) Close() error {
|
||||||
|
errs := make([]error, 0)
|
||||||
|
for _, outbound := range h.outbounds {
|
||||||
|
err := common.Close(outbound)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
100
protocol/bond/protocol.go
Normal file
100
protocol/bond/protocol.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package bond
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
var Destination = M.Socksaddr{
|
||||||
|
Fqdn: "sp.bond.sing-box.arpa",
|
||||||
|
Port: 444,
|
||||||
|
}
|
||||||
|
|
||||||
|
var AddressSerializer = M.NewSerializer(
|
||||||
|
M.AddressFamilyByte(0x01, M.AddressFamilyIPv4),
|
||||||
|
M.AddressFamilyByte(0x03, M.AddressFamilyIPv6),
|
||||||
|
M.AddressFamilyByte(0x02, M.AddressFamilyFqdn),
|
||||||
|
M.PortThenAddress(),
|
||||||
|
)
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
UUID uuid.UUID
|
||||||
|
Index byte
|
||||||
|
Count byte
|
||||||
|
DownloadRatio byte
|
||||||
|
UploadRatio byte
|
||||||
|
Destination M.Socksaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadRequest(reader io.Reader) (*Request, error) {
|
||||||
|
var request Request
|
||||||
|
var version uint8
|
||||||
|
err := binary.Read(reader, binary.BigEndian, &version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if version != Version {
|
||||||
|
return nil, E.New("unknown version: ", version)
|
||||||
|
}
|
||||||
|
_, err = io.ReadFull(reader, request.UUID[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &request.Index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &request.Count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &request.DownloadRatio)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = binary.Read(reader, binary.BigEndian, &request.UploadRatio)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
request.Destination, err = AddressSerializer.ReadAddrPort(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &request, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WriteRequest(writer io.Writer, request *Request) error {
|
||||||
|
var requestLen int
|
||||||
|
requestLen += 1 // version
|
||||||
|
requestLen += 16 // UUID
|
||||||
|
requestLen += 1 // index
|
||||||
|
requestLen += 1 // count
|
||||||
|
requestLen += 1 // download ratio
|
||||||
|
requestLen += 1 // upload ratio
|
||||||
|
requestLen += AddressSerializer.AddrPortLen(request.Destination)
|
||||||
|
buffer := buf.NewSize(requestLen)
|
||||||
|
defer buffer.Release()
|
||||||
|
common.Must(
|
||||||
|
buffer.WriteByte(Version),
|
||||||
|
common.Error(buffer.Write(request.UUID[:])),
|
||||||
|
buffer.WriteByte(request.Index),
|
||||||
|
buffer.WriteByte(request.Count),
|
||||||
|
buffer.WriteByte(request.DownloadRatio),
|
||||||
|
buffer.WriteByte(request.UploadRatio),
|
||||||
|
)
|
||||||
|
err := AddressSerializer.WriteAddrPort(buffer, request.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return common.Error(writer.Write(buffer.Bytes()))
|
||||||
|
}
|
||||||
41
protocol/bond/router.go
Normal file
41
protocol/bond/router.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package bond
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
adapter.Router
|
||||||
|
logger logger.ContextLogger
|
||||||
|
handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(router adapter.Router, logger logger.ContextLogger, handler func(context.Context, net.Conn, adapter.InboundContext, N.CloseHandlerFunc) error) *Router {
|
||||||
|
return &Router{Router: router, logger: logger, handler: handler}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
|
return r.handler(ctx, conn, metadata, func(error) {})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
if err := r.handler(ctx, conn, metadata, onClose); err != nil {
|
||||||
|
r.logger.ErrorContext(ctx, err)
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
r.logger.ErrorContext(ctx, os.ErrInvalid)
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
}
|
||||||
96
protocol/group/failover.go
Normal file
96
protocol/group/failover.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package group
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterFailover(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.FailoverOutboundOptions](registry, C.TypeFailover, NewFailover)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ adapter.OutboundGroup = (*Failover)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Failover struct {
|
||||||
|
outbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
outbound adapter.OutboundManager
|
||||||
|
logger logger.ContextLogger
|
||||||
|
tags []string
|
||||||
|
outbounds map[string]adapter.Outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFailover(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.FailoverOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
if len(options.Outbounds) == 0 {
|
||||||
|
return nil, E.New("missing tags")
|
||||||
|
}
|
||||||
|
outbound := &Failover{
|
||||||
|
Adapter: outbound.NewAdapter(C.TypeFailover, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.Outbounds),
|
||||||
|
ctx: ctx,
|
||||||
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
|
logger: logger,
|
||||||
|
tags: options.Outbounds,
|
||||||
|
outbounds: make(map[string]adapter.Outbound, len(options.Outbounds)),
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Failover) Start() error {
|
||||||
|
for i, tag := range s.tags {
|
||||||
|
outbound, loaded := s.outbound.Outbound(tag)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("outbound ", i, " not found: ", tag)
|
||||||
|
}
|
||||||
|
s.outbounds[tag] = outbound
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Failover) Now() string {
|
||||||
|
return s.tags[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Failover) All() []string {
|
||||||
|
return s.tags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Failover) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
var conn net.Conn
|
||||||
|
var err error
|
||||||
|
for _, outbound := range s.outbounds {
|
||||||
|
conn, err = outbound.DialContext(ctx, network, destination)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.ErrorContext(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Failover) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
var conn net.PacketConn
|
||||||
|
var err error
|
||||||
|
for _, outbound := range s.outbounds {
|
||||||
|
conn, err = outbound.ListenPacket(ctx, destination)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.ErrorContext(ctx, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
@@ -180,3 +180,11 @@ func (h *Inbound) Close() error {
|
|||||||
common.PtrOrNil(h.service),
|
common.PtrOrNil(h.service),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []option.HysteriaUser) {
|
||||||
|
h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.HysteriaUser) int {
|
||||||
|
return index
|
||||||
|
}), common.Map(users, func(it option.HysteriaUser) string {
|
||||||
|
return it.AuthString
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -213,3 +213,11 @@ func (h *Inbound) Close() error {
|
|||||||
common.PtrOrNil(h.service),
|
common.PtrOrNil(h.service),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []option.Hysteria2User) {
|
||||||
|
h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.Hysteria2User) int {
|
||||||
|
return index
|
||||||
|
}), common.Map(users, func(it option.Hysteria2User) string {
|
||||||
|
return it.Password
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
158
protocol/limiter/bandwidth/limiter.go
Normal file
158
protocol/limiter/bandwidth/limiter.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package bandwidth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type connWithDownloadBandwidthLimiter struct {
|
||||||
|
net.Conn
|
||||||
|
ctx context.Context
|
||||||
|
limiter *rate.Limiter
|
||||||
|
burst int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter *rate.Limiter) *connWithDownloadBandwidthLimiter {
|
||||||
|
return &connWithDownloadBandwidthLimiter{conn, ctx, limiter, limiter.Burst()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *connWithDownloadBandwidthLimiter) Write(p []byte) (n int, err error) {
|
||||||
|
var nn int
|
||||||
|
for {
|
||||||
|
end := len(p)
|
||||||
|
if end == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if conn.burst < len(p) {
|
||||||
|
end = conn.burst
|
||||||
|
}
|
||||||
|
err = conn.limiter.WaitN(conn.ctx, end)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nn, err = conn.Conn.Write(p[:end])
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = p[end:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type connWithUploadBandwidthLimiter struct {
|
||||||
|
net.Conn
|
||||||
|
ctx context.Context
|
||||||
|
limiter *rate.Limiter
|
||||||
|
burst int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnWithUploadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter *rate.Limiter) *connWithUploadBandwidthLimiter {
|
||||||
|
return &connWithUploadBandwidthLimiter{conn, ctx, limiter, limiter.Burst()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *connWithUploadBandwidthLimiter) Read(p []byte) (n int, err error) {
|
||||||
|
if conn.burst < len(p) {
|
||||||
|
p = p[:conn.burst]
|
||||||
|
}
|
||||||
|
n, err = conn.Conn.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.limiter.WaitN(conn.ctx, n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type connWithCloseHandler struct {
|
||||||
|
net.Conn
|
||||||
|
onClose CloseHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnWithCloseHandler(conn net.Conn, onClose CloseHandlerFunc) *connWithCloseHandler {
|
||||||
|
return &connWithCloseHandler{conn, onClose}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *connWithCloseHandler) Close() error {
|
||||||
|
conn.onClose()
|
||||||
|
return conn.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConnWithDownloadBandwidthLimiter struct {
|
||||||
|
net.PacketConn
|
||||||
|
ctx context.Context
|
||||||
|
limiter *rate.Limiter
|
||||||
|
burst int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter) *packetConnWithDownloadBandwidthLimiter {
|
||||||
|
return &packetConnWithDownloadBandwidthLimiter{conn, ctx, limiter, limiter.Burst()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *packetConnWithDownloadBandwidthLimiter) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||||
|
var nn int
|
||||||
|
for {
|
||||||
|
end := len(p)
|
||||||
|
if end == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if conn.burst < len(p) {
|
||||||
|
end = conn.burst
|
||||||
|
}
|
||||||
|
err = conn.limiter.WaitN(conn.ctx, end)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nn, err = conn.PacketConn.WriteTo(p[:end], addr)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p = p[end:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConnWithUploadBandwidthLimiter struct {
|
||||||
|
net.PacketConn
|
||||||
|
ctx context.Context
|
||||||
|
limiter *rate.Limiter
|
||||||
|
burst int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConnWithUploadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter) *packetConnWithUploadBandwidthLimiter {
|
||||||
|
return &packetConnWithUploadBandwidthLimiter{conn, ctx, limiter, limiter.Burst()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *packetConnWithUploadBandwidthLimiter) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
|
||||||
|
if conn.burst < len(p) {
|
||||||
|
p = p[:conn.burst]
|
||||||
|
}
|
||||||
|
n, addr, err = conn.PacketConn.ReadFrom(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = conn.limiter.WaitN(conn.ctx, n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConnWithCloseHandler struct {
|
||||||
|
net.PacketConn
|
||||||
|
onClose CloseHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConnWithCloseHandler(conn net.PacketConn, onClose CloseHandlerFunc) *packetConnWithCloseHandler {
|
||||||
|
return &packetConnWithCloseHandler{conn, onClose}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *packetConnWithCloseHandler) Close() error {
|
||||||
|
conn.onClose()
|
||||||
|
return conn.PacketConn.Close()
|
||||||
|
}
|
||||||
146
protocol/limiter/bandwidth/outbound.go
Normal file
146
protocol/limiter/bandwidth/outbound.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package bandwidth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/route"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.BandwidthLimiterOutboundOptions](registry, C.TypeBandwidthLimiter, NewOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
outbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
outbound adapter.OutboundManager
|
||||||
|
connection adapter.ConnectionManager
|
||||||
|
logger logger.ContextLogger
|
||||||
|
strategy BandwidthStrategy
|
||||||
|
outboundTag string
|
||||||
|
detour adapter.Outbound
|
||||||
|
router *route.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.BandwidthLimiterOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
if options.Strategy == "" {
|
||||||
|
return nil, E.New("missing strategy")
|
||||||
|
}
|
||||||
|
if options.Route.Final == "" {
|
||||||
|
return nil, E.New("missing final outbound")
|
||||||
|
}
|
||||||
|
var strategy BandwidthStrategy
|
||||||
|
var err error
|
||||||
|
switch options.Strategy {
|
||||||
|
case "users":
|
||||||
|
usersStrategies := make(map[string]BandwidthStrategy, len(options.Users))
|
||||||
|
for _, user := range options.Users {
|
||||||
|
userStrategy, err := CreateStrategy(user.Strategy, user.Mode, user.ConnectionType, options.Speed.Value())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
usersStrategies[user.Name] = userStrategy
|
||||||
|
}
|
||||||
|
strategy = NewUsersBandwidthStrategy(usersStrategies)
|
||||||
|
case "manager":
|
||||||
|
strategy = NewManagerBandwidthStrategy()
|
||||||
|
default:
|
||||||
|
strategy, err = CreateStrategy(options.Strategy, options.Mode, options.ConnectionType, options.Speed.Value())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logFactory := service.FromContext[log.Factory](ctx)
|
||||||
|
r := route.NewRouter(ctx, logFactory, options.Route, option.DNSOptions{})
|
||||||
|
err = r.Initialize(options.Route.Rules, options.Route.RuleSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outbound := &Outbound{
|
||||||
|
Adapter: outbound.NewAdapter(C.TypeBandwidthLimiter, tag, nil, []string{}),
|
||||||
|
ctx: ctx,
|
||||||
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
|
connection: service.FromContext[adapter.ConnectionManager](ctx),
|
||||||
|
logger: logger,
|
||||||
|
strategy: strategy,
|
||||||
|
outboundTag: options.Route.Final,
|
||||||
|
router: r,
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) Network() []string {
|
||||||
|
return []string{N.NetworkTCP, N.NetworkUDP}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) Start() error {
|
||||||
|
detour, loaded := h.outbound.Outbound(h.outboundTag)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("outbound not found: ", h.outboundTag)
|
||||||
|
}
|
||||||
|
h.detour = detour
|
||||||
|
for _, stage := range []adapter.StartStage{adapter.StartStateStart, adapter.StartStatePostStart, adapter.StartStateStarted} {
|
||||||
|
err := h.router.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
conn, err := h.detour.DialContext(ctx, network, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h.strategy.wrapConn(ctx, conn, adapter.ContextFrom(ctx), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
conn, err := h.detour.ListenPacket(ctx, destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return h.strategy.wrapPacketConn(ctx, conn, adapter.ContextFrom(ctx), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
conn, err := h.strategy.wrapConn(ctx, conn, &metadata, false)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.ErrorContext(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = h.Type()
|
||||||
|
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
packetConn, err := h.strategy.wrapPacketConn(ctx, bufio.NewNetPacketConn(conn), &metadata, false)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.ErrorContext(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = h.Type()
|
||||||
|
h.router.RoutePacketConnectionEx(ctx, bufio.NewPacketConn(packetConn), metadata, onClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) GetStrategy() BandwidthStrategy {
|
||||||
|
return h.strategy
|
||||||
|
}
|
||||||
266
protocol/limiter/bandwidth/strategy.go
Normal file
266
protocol/limiter/bandwidth/strategy.go
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
package bandwidth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CloseHandlerFunc = func()
|
||||||
|
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
||||||
|
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn
|
||||||
|
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn
|
||||||
|
)
|
||||||
|
|
||||||
|
type BandwidthStrategy interface {
|
||||||
|
wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error)
|
||||||
|
wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiterStrategy interface {
|
||||||
|
getLimiter(ctx context.Context, metadata *adapter.InboundContext) (*rate.Limiter, CloseHandlerFunc, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultWrapStrategy struct {
|
||||||
|
limiterStrategy BandwidthLimiterStrategy
|
||||||
|
connWrapper ConnWrapper
|
||||||
|
packetConnWrapper PacketConnWrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultWrapStrategy(limiterStrategy BandwidthLimiterStrategy, connWrapper ConnWrapper, packetConnWrapper PacketConnWrapper) *DefaultWrapStrategy {
|
||||||
|
return &DefaultWrapStrategy{limiterStrategy, connWrapper, packetConnWrapper}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultWrapStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
|
||||||
|
limiter, onClose, err := s.limiterStrategy.getLimiter(ctx, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewConnWithCloseHandler(s.connWrapper(ctx, conn, limiter, reverse), onClose), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultWrapStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
|
||||||
|
limiter, onClose, err := s.limiterStrategy.getLimiter(ctx, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewPacketConnWithCloseHandler(s.packetConnWrapper(ctx, conn, limiter, reverse), onClose), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GlobalBandwidthStrategy struct {
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGlobalBandwidthStrategy(speed uint64) *GlobalBandwidthStrategy {
|
||||||
|
return &GlobalBandwidthStrategy{
|
||||||
|
limiter: createSpeedLimiter(speed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GlobalBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (*rate.Limiter, CloseHandlerFunc, error) {
|
||||||
|
return s.limiter, func() {}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type idBandwidthLimiter struct {
|
||||||
|
limiter *rate.Limiter
|
||||||
|
handles uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionBandwidthStrategy struct {
|
||||||
|
limiters map[string]*idBandwidthLimiter
|
||||||
|
connIDGetter ConnIDGetter
|
||||||
|
speed uint64
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnectionBandwidthStrategy(connIDGetter ConnIDGetter, speed uint64) *ConnectionBandwidthStrategy {
|
||||||
|
return &ConnectionBandwidthStrategy{
|
||||||
|
limiters: make(map[string]*idBandwidthLimiter),
|
||||||
|
connIDGetter: connIDGetter,
|
||||||
|
speed: speed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConnectionBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (*rate.Limiter, CloseHandlerFunc, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
id, ok := s.connIDGetter(ctx, metadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, E.New("id not found")
|
||||||
|
}
|
||||||
|
limiter, ok := s.limiters[id]
|
||||||
|
if !ok {
|
||||||
|
limiter = &idBandwidthLimiter{
|
||||||
|
limiter: createSpeedLimiter(s.speed),
|
||||||
|
}
|
||||||
|
s.limiters[id] = limiter
|
||||||
|
}
|
||||||
|
limiter.handles++
|
||||||
|
var once sync.Once
|
||||||
|
return limiter.limiter, func() {
|
||||||
|
once.Do(func() {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
limiter.handles--
|
||||||
|
if limiter.handles == 0 {
|
||||||
|
delete(s.limiters, id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UsersBandwidthStrategy struct {
|
||||||
|
strategies map[string]BandwidthStrategy
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUsersBandwidthStrategy(strategies map[string]BandwidthStrategy) *UsersBandwidthStrategy {
|
||||||
|
return &UsersBandwidthStrategy{
|
||||||
|
strategies: strategies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UsersBandwidthStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
|
||||||
|
strategy, err := s.getStrategy(ctx, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return strategy.wrapConn(ctx, conn, metadata, reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UsersBandwidthStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
|
||||||
|
strategy, err := s.getStrategy(ctx, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return strategy.wrapPacketConn(ctx, conn, metadata, reverse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UsersBandwidthStrategy) getStrategy(ctx context.Context, metadata *adapter.InboundContext) (BandwidthStrategy, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
var user string
|
||||||
|
if metadata != nil {
|
||||||
|
user = metadata.User
|
||||||
|
}
|
||||||
|
strategy, ok := s.strategies[user]
|
||||||
|
if ok {
|
||||||
|
return strategy, nil
|
||||||
|
}
|
||||||
|
return nil, E.New("user strategy not found: ", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagerBandwidthStrategy struct {
|
||||||
|
*UsersBandwidthStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManagerBandwidthStrategy() *ManagerBandwidthStrategy {
|
||||||
|
return &ManagerBandwidthStrategy{
|
||||||
|
UsersBandwidthStrategy: NewUsersBandwidthStrategy(map[string]BandwidthStrategy{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ManagerBandwidthStrategy) UpdateStrategies(strategies map[string]BandwidthStrategy) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.strategies = strategies
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateStrategy(strategy string, mode string, connectionType string, speed uint64) (BandwidthStrategy, error) {
|
||||||
|
var limiterStrategy BandwidthLimiterStrategy
|
||||||
|
switch strategy {
|
||||||
|
case "global":
|
||||||
|
limiterStrategy = NewGlobalBandwidthStrategy(speed)
|
||||||
|
case "connection":
|
||||||
|
var connIDGetter ConnIDGetter
|
||||||
|
switch connectionType {
|
||||||
|
case "mux":
|
||||||
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
id, ok := log.MuxIDFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return "", ok
|
||||||
|
}
|
||||||
|
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||||
|
}
|
||||||
|
case "hwid":
|
||||||
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
id, ok := ctx.Value("hwid").(string)
|
||||||
|
return id, ok
|
||||||
|
}
|
||||||
|
case "ip":
|
||||||
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
return metadata.Source.IPAddr().String(), true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("connection type not found: ", connectionType)
|
||||||
|
}
|
||||||
|
limiterStrategy = NewConnectionBandwidthStrategy(connIDGetter, speed)
|
||||||
|
default:
|
||||||
|
return nil, E.New("strategy not found: ", strategy)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
connWrapper ConnWrapper
|
||||||
|
packetConnWrapper PacketConnWrapper
|
||||||
|
)
|
||||||
|
switch mode {
|
||||||
|
case "download":
|
||||||
|
connWrapper = connWithDownloadBandwidthWrapper
|
||||||
|
packetConnWrapper = packetConnWithDownloadBandwidthWrapper
|
||||||
|
case "upload":
|
||||||
|
connWrapper = connWithUploadBandwidthWrapper
|
||||||
|
packetConnWrapper = packetConnWithUploadBandwidthWrapper
|
||||||
|
case "duplex":
|
||||||
|
connWrapper = connWithDuplexBandwidthWrapper
|
||||||
|
packetConnWrapper = packetConnWithDuplexBandwidthWrapper
|
||||||
|
default:
|
||||||
|
return nil, E.New("mode not found: ", mode)
|
||||||
|
}
|
||||||
|
return NewDefaultWrapStrategy(limiterStrategy, connWrapper, packetConnWrapper), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSpeedLimiter(speed uint64) *rate.Limiter {
|
||||||
|
return rate.NewLimiter(rate.Limit(float64(speed)), 10000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func connWithDownloadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn {
|
||||||
|
if reverse {
|
||||||
|
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func connWithUploadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn {
|
||||||
|
if reverse {
|
||||||
|
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func connWithDuplexBandwidthWrapper(ctx context.Context, conn net.Conn, limiter *rate.Limiter, reverse bool) net.Conn {
|
||||||
|
return NewConnWithUploadBandwidthLimiter(ctx, NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func packetConnWithDownloadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn {
|
||||||
|
if reverse {
|
||||||
|
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func packetConnWithUploadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn {
|
||||||
|
if reverse {
|
||||||
|
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func packetConnWithDuplexBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter *rate.Limiter, reverse bool) net.PacketConn {
|
||||||
|
return NewPacketConnWithUploadBandwidthLimiter(ctx, NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||||
|
}
|
||||||
37
protocol/limiter/connection/lock.go
Normal file
37
protocol/limiter/connection/lock.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDefaultLock(max uint32) LockIDGetter {
|
||||||
|
locks := make(map[string]*uint32)
|
||||||
|
mtx := sync.Mutex{}
|
||||||
|
return func(id string) (CloseHandlerFunc, context.Context, error) {
|
||||||
|
mtx.Lock()
|
||||||
|
defer mtx.Unlock()
|
||||||
|
handles, ok := locks[id]
|
||||||
|
if !ok {
|
||||||
|
if len(locks) == int(max) {
|
||||||
|
return nil, nil, E.New("not enough free locks")
|
||||||
|
}
|
||||||
|
handles = new(uint32)
|
||||||
|
locks[id] = handles
|
||||||
|
}
|
||||||
|
*handles++
|
||||||
|
var once sync.Once
|
||||||
|
return func() {
|
||||||
|
once.Do(func() {
|
||||||
|
mtx.Lock()
|
||||||
|
defer mtx.Unlock()
|
||||||
|
*handles--
|
||||||
|
if *handles == 0 {
|
||||||
|
delete(locks, id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
204
protocol/limiter/connection/outbound.go
Normal file
204
protocol/limiter/connection/outbound.go
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/route"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.ConnectionLimiterOutboundOptions](registry, C.TypeConnectionLimiter, NewOutbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Outbound struct {
|
||||||
|
outbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
outbound adapter.OutboundManager
|
||||||
|
connection adapter.ConnectionManager
|
||||||
|
logger logger.ContextLogger
|
||||||
|
strategy ConnectionStrategy
|
||||||
|
outboundTag string
|
||||||
|
detour adapter.Outbound
|
||||||
|
router *route.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ConnectionLimiterOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
if options.Strategy == "" {
|
||||||
|
return nil, E.New("missing strategy")
|
||||||
|
}
|
||||||
|
if options.Route.Final == "" {
|
||||||
|
return nil, E.New("missing final outbound")
|
||||||
|
}
|
||||||
|
var strategy ConnectionStrategy
|
||||||
|
var err error
|
||||||
|
switch options.Strategy {
|
||||||
|
case "users":
|
||||||
|
usersStrategies := make(map[string]ConnectionStrategy, len(options.Users))
|
||||||
|
for _, user := range options.Users {
|
||||||
|
userStrategy, err := CreateStrategy(user.Strategy, user.ConnectionType, NewDefaultLock(user.Count))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
usersStrategies[user.Name] = userStrategy
|
||||||
|
}
|
||||||
|
strategy = NewUsersConnectionStrategy(usersStrategies)
|
||||||
|
case "manager":
|
||||||
|
strategy = NewManagerConnectionStrategy()
|
||||||
|
default:
|
||||||
|
strategy, err = CreateStrategy(options.Strategy, options.ConnectionType, NewDefaultLock(options.Count))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logFactory := service.FromContext[log.Factory](ctx)
|
||||||
|
r := route.NewRouter(ctx, logFactory, options.Route, option.DNSOptions{})
|
||||||
|
err = r.Initialize(options.Route.Rules, options.Route.RuleSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outbound := &Outbound{
|
||||||
|
Adapter: outbound.NewAdapter(C.TypeConnectionLimiter, tag, nil, []string{}),
|
||||||
|
ctx: ctx,
|
||||||
|
outbound: service.FromContext[adapter.OutboundManager](ctx),
|
||||||
|
connection: service.FromContext[adapter.ConnectionManager](ctx),
|
||||||
|
logger: logger,
|
||||||
|
outboundTag: options.Route.Final,
|
||||||
|
strategy: strategy,
|
||||||
|
router: r,
|
||||||
|
}
|
||||||
|
return outbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) Network() []string {
|
||||||
|
return []string{N.NetworkTCP, N.NetworkUDP}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) Start() error {
|
||||||
|
detour, loaded := h.outbound.Outbound(h.outboundTag)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("outbound not found: ", h.outboundTag)
|
||||||
|
}
|
||||||
|
h.detour = detour
|
||||||
|
for _, stage := range []adapter.StartStage{adapter.StartStateStart, adapter.StartStatePostStart, adapter.StartStateStarted} {
|
||||||
|
err := h.router.Start(stage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
|
onClose, lockCtx, err := h.strategy.request(ctx, adapter.ContextFrom(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := h.detour.DialContext(ctx, network, destination)
|
||||||
|
if err != nil {
|
||||||
|
onClose()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn = newConnWithCloseHandlerFunc(conn, onClose)
|
||||||
|
if lockCtx != nil {
|
||||||
|
go connChecker(lockCtx, conn.Close)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
|
onClose, lockCtx, err := h.strategy.request(ctx, adapter.ContextFrom(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn, err := h.detour.ListenPacket(ctx, destination)
|
||||||
|
if err != nil {
|
||||||
|
onClose()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn = newPacketConnWithCloseHandlerFunc(conn, onClose)
|
||||||
|
if lockCtx != nil {
|
||||||
|
go connChecker(lockCtx, conn.Close)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
limiterOnClose, lockCtx, err := h.strategy.request(ctx, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.ErrorContext(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn = newConnWithCloseHandlerFunc(conn, limiterOnClose)
|
||||||
|
if lockCtx != nil {
|
||||||
|
go connChecker(lockCtx, conn.Close)
|
||||||
|
}
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = h.Type()
|
||||||
|
h.router.RouteConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
|
limiterOnClose, lockCtx, err := h.strategy.request(ctx, &metadata)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.ErrorContext(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn = bufio.NewPacketConn(newPacketConnWithCloseHandlerFunc(bufio.NewNetPacketConn(conn), limiterOnClose))
|
||||||
|
if lockCtx != nil {
|
||||||
|
go connChecker(lockCtx, conn.Close)
|
||||||
|
}
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = h.Type()
|
||||||
|
h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Outbound) GetStrategy() ConnectionStrategy {
|
||||||
|
return h.strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
type connWithCloseHandlerFunc struct {
|
||||||
|
net.Conn
|
||||||
|
onClose CloseHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConnWithCloseHandlerFunc(conn net.Conn, onClose CloseHandlerFunc) *connWithCloseHandlerFunc {
|
||||||
|
return &connWithCloseHandlerFunc{conn, onClose}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *connWithCloseHandlerFunc) Close() error {
|
||||||
|
conn.onClose()
|
||||||
|
return conn.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type packetConnWithCloseHandlerFunc struct {
|
||||||
|
net.PacketConn
|
||||||
|
onClose CloseHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPacketConnWithCloseHandlerFunc(conn net.PacketConn, onClose CloseHandlerFunc) *packetConnWithCloseHandlerFunc {
|
||||||
|
return &packetConnWithCloseHandlerFunc{conn, onClose}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *packetConnWithCloseHandlerFunc) Close() error {
|
||||||
|
conn.onClose()
|
||||||
|
return conn.PacketConn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func connChecker(ctx context.Context, closeFunc func() error) {
|
||||||
|
<-ctx.Done()
|
||||||
|
closeFunc()
|
||||||
|
}
|
||||||
119
protocol/limiter/connection/strategy.go
Normal file
119
protocol/limiter/connection/strategy.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package connection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
CloseHandlerFunc = func()
|
||||||
|
|
||||||
|
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
||||||
|
LockIDGetter = func(string) (CloseHandlerFunc, context.Context, error)
|
||||||
|
|
||||||
|
ConnectionStrategy interface {
|
||||||
|
request(ctx context.Context, metadata *adapter.InboundContext) (onClose CloseHandlerFunc, lockCtx context.Context, err error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type DefaultConnectionStrategy struct {
|
||||||
|
connIDGetter ConnIDGetter
|
||||||
|
lockIDGetter LockIDGetter
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultConnectionStrategy(connIDGetter ConnIDGetter, lockIDGetter LockIDGetter) *DefaultConnectionStrategy {
|
||||||
|
outbound := &DefaultConnectionStrategy{
|
||||||
|
connIDGetter: connIDGetter,
|
||||||
|
lockIDGetter: lockIDGetter,
|
||||||
|
}
|
||||||
|
return outbound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (CloseHandlerFunc, context.Context, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
id, ok := s.connIDGetter(ctx, metadata)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, E.New("id not found")
|
||||||
|
}
|
||||||
|
return s.lockIDGetter(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UsersConnectionStrategy struct {
|
||||||
|
strategies map[string]ConnectionStrategy
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUsersConnectionStrategy(strategies map[string]ConnectionStrategy) *UsersConnectionStrategy {
|
||||||
|
return &UsersConnectionStrategy{
|
||||||
|
strategies: strategies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UsersConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (CloseHandlerFunc, context.Context, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
var user string
|
||||||
|
if metadata != nil {
|
||||||
|
user = metadata.User
|
||||||
|
}
|
||||||
|
strategy, ok := s.strategies[user]
|
||||||
|
if ok {
|
||||||
|
return strategy.request(ctx, metadata)
|
||||||
|
}
|
||||||
|
return nil, nil, E.New("user strategy not found: ", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagerConnectionStrategy struct {
|
||||||
|
*UsersConnectionStrategy
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManagerConnectionStrategy() *ManagerConnectionStrategy {
|
||||||
|
return &ManagerConnectionStrategy{
|
||||||
|
UsersConnectionStrategy: NewUsersConnectionStrategy(map[string]ConnectionStrategy{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ManagerConnectionStrategy) UpdateStrategies(strategies map[string]ConnectionStrategy) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.strategies = strategies
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateStrategy(strategy string, connectionType string, lockIDGetter LockIDGetter) (ConnectionStrategy, error) {
|
||||||
|
switch strategy {
|
||||||
|
case "connection":
|
||||||
|
var connIDGetter ConnIDGetter
|
||||||
|
switch connectionType {
|
||||||
|
case "mux":
|
||||||
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
id, ok := log.MuxIDFromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return "", ok
|
||||||
|
}
|
||||||
|
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||||
|
}
|
||||||
|
case "hwid":
|
||||||
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
id, ok := ctx.Value("hwid").(string)
|
||||||
|
return id, ok
|
||||||
|
}
|
||||||
|
case "ip":
|
||||||
|
connIDGetter = func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
return metadata.Source.IPAddr().String(), true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("connection type not found: ", connectionType)
|
||||||
|
}
|
||||||
|
return NewDefaultConnectionStrategy(connIDGetter, lockIDGetter), nil
|
||||||
|
default:
|
||||||
|
return nil, E.New("strategy not found: ", strategy)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -158,6 +158,14 @@ func (h *Inbound) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []option.TrojanUser) {
|
||||||
|
h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.TrojanUser) int {
|
||||||
|
return index
|
||||||
|
}), common.Map(users, func(it option.TrojanUser) string {
|
||||||
|
return it.Password
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
if h.tlsConfig != nil && h.transport == nil {
|
if h.tlsConfig != nil && h.transport == nil {
|
||||||
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
||||||
|
|||||||
@@ -170,3 +170,13 @@ func (h *Inbound) Close() error {
|
|||||||
common.PtrOrNil(h.server),
|
common.PtrOrNil(h.server),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []option.TUICUser) {
|
||||||
|
h.server.UpdateUsers(common.MapIndexed(users, func(index int, _ option.TUICUser) int {
|
||||||
|
return index
|
||||||
|
}), common.Map(users, func(it option.TUICUser) [16]byte {
|
||||||
|
return [16]byte(uuid.Must(uuid.FromString(it.UUID)).Bytes())
|
||||||
|
}), common.Map(users, func(it option.TUICUser) string {
|
||||||
|
return it.Password
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ package tunnel
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
sbUot "github.com/sagernet/sing-box/common/uot"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/uot"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,12 +28,13 @@ func RegisterClientEndpoint(registry *endpoint.Registry) {
|
|||||||
|
|
||||||
type ClientEndpoint struct {
|
type ClientEndpoint struct {
|
||||||
outbound.Adapter
|
outbound.Adapter
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
outbound adapter.Outbound
|
outbound adapter.Outbound
|
||||||
router adapter.ConnectionRouterEx
|
router adapter.ConnectionRouterEx
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
key uuid.UUID
|
key uuid.UUID
|
||||||
|
uotClient *uot.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelClientEndpointOptions) (adapter.Endpoint, error) {
|
func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelClientEndpointOptions) (adapter.Endpoint, error) {
|
||||||
@@ -45,9 +47,9 @@ func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.Co
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
client := &ClientEndpoint{
|
client := &ClientEndpoint{
|
||||||
Adapter: outbound.NewAdapter(C.TypeTunnelClient, tag, []string{N.NetworkTCP}, []string{}),
|
Adapter: outbound.NewAdapter(C.TypeTunnelClient, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
router: router,
|
router: sbUot.NewRouter(router, logger),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
uuid: clientUUID,
|
uuid: clientUUID,
|
||||||
key: clientKey,
|
key: clientKey,
|
||||||
@@ -58,6 +60,10 @@ func NewClientEndpoint(ctx context.Context, router adapter.Router, logger log.Co
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
client.outbound = outbound
|
client.outbound = outbound
|
||||||
|
client.uotClient = &uot.Client{
|
||||||
|
Dialer: outbound,
|
||||||
|
Version: uot.Version,
|
||||||
|
}
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +91,8 @@ func (c *ClientEndpoint) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if network != N.NetworkTCP {
|
if N.NetworkName(network) == N.NetworkUDP {
|
||||||
return nil, os.ErrInvalid
|
return c.uotClient.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
var destinationUUID *uuid.UUID
|
var destinationUUID *uuid.UUID
|
||||||
if metadata := adapter.ContextFrom(ctx); metadata != nil {
|
if metadata := adapter.ContextFrom(ctx); metadata != nil {
|
||||||
@@ -109,11 +115,14 @@ func (c *ClientEndpoint) DialContext(ctx context.Context, network string, destin
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandTCP, DestinationUUID: *destinationUUID, Destination: destination})
|
err = WriteRequest(conn, &Request{UUID: c.key, Command: CommandTCP, DestinationUUID: *destinationUUID, Destination: destination})
|
||||||
return conn, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (c *ClientEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return nil, os.ErrInvalid
|
return c.uotClient.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClientEndpoint) Close() error {
|
func (c *ClientEndpoint) Close() error {
|
||||||
@@ -139,6 +148,7 @@ func (c *ClientEndpoint) startInboundConn() error {
|
|||||||
|
|
||||||
func (c *ClientEndpoint) connHandler(conn net.Conn, request *Request) {
|
func (c *ClientEndpoint) connHandler(conn net.Conn, request *Request) {
|
||||||
metadata := adapter.InboundContext{
|
metadata := adapter.InboundContext{
|
||||||
|
Inbound: c.Tag(),
|
||||||
Source: M.ParseSocksaddr(conn.RemoteAddr().String()),
|
Source: M.ParseSocksaddr(conn.RemoteAddr().String()),
|
||||||
Destination: request.Destination,
|
Destination: request.Destination,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ package tunnel
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofrs/uuid/v5"
|
"github.com/gofrs/uuid/v5"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
sbUot "github.com/sagernet/sing-box/common/uot"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
@@ -19,6 +18,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/uot"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,16 +28,15 @@ func RegisterServerEndpoint(registry *endpoint.Registry) {
|
|||||||
|
|
||||||
type ServerEndpoint struct {
|
type ServerEndpoint struct {
|
||||||
outbound.Adapter
|
outbound.Adapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
inbound adapter.Inbound
|
inbound adapter.Inbound
|
||||||
router adapter.Router
|
router adapter.ConnectionRouterEx
|
||||||
uuid uuid.UUID
|
uuid uuid.UUID
|
||||||
users map[uuid.UUID]uuid.UUID
|
users map[uuid.UUID]uuid.UUID
|
||||||
keys map[uuid.UUID]uuid.UUID
|
keys map[uuid.UUID]uuid.UUID
|
||||||
conns map[uuid.UUID]chan net.Conn
|
conns map[uuid.UUID]chan net.Conn
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
uotClient *uot.Client
|
||||||
mtx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelServerEndpointOptions) (adapter.Endpoint, error) {
|
func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunnelServerEndpointOptions) (adapter.Endpoint, error) {
|
||||||
@@ -46,9 +45,9 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
server := &ServerEndpoint{
|
server := &ServerEndpoint{
|
||||||
Adapter: outbound.NewAdapter(C.TypeTunnelServer, tag, []string{N.NetworkTCP}, []string{}),
|
Adapter: outbound.NewAdapter(C.TypeTunnelServer, tag, []string{N.NetworkTCP, N.NetworkUDP}, []string{}),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
router: router,
|
router: sbUot.NewRouter(router, logger),
|
||||||
uuid: serverUUID,
|
uuid: serverUUID,
|
||||||
}
|
}
|
||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
@@ -78,6 +77,10 @@ func NewServerEndpoint(ctx context.Context, router adapter.Router, logger log.Co
|
|||||||
} else {
|
} else {
|
||||||
server.timeout = C.TCPConnectTimeout
|
server.timeout = C.TCPConnectTimeout
|
||||||
}
|
}
|
||||||
|
server.uotClient = &uot.Client{
|
||||||
|
Dialer: server,
|
||||||
|
Version: uot.Version,
|
||||||
|
}
|
||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +89,8 @@ func (s *ServerEndpoint) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
|
||||||
if network != N.NetworkTCP {
|
if N.NetworkName(network) == N.NetworkUDP {
|
||||||
return nil, os.ErrInvalid
|
return s.uotClient.DialContext(ctx, network, destination)
|
||||||
}
|
}
|
||||||
var sourceUUID *uuid.UUID
|
var sourceUUID *uuid.UUID
|
||||||
var ch chan net.Conn
|
var ch chan net.Conn
|
||||||
@@ -97,13 +100,11 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.mtx.Lock()
|
|
||||||
var ok bool
|
var ok bool
|
||||||
ch, ok = s.conns[tunnelDestination]
|
ch, ok = s.conns[tunnelDestination]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, E.New("user ", metadata.TunnelDestination, " not found")
|
return nil, E.New("user ", metadata.TunnelDestination, " not found")
|
||||||
}
|
}
|
||||||
s.mtx.Unlock()
|
|
||||||
}
|
}
|
||||||
if metadata.TunnelSource != "" {
|
if metadata.TunnelSource != "" {
|
||||||
tunnelSource, err := uuid.FromString(metadata.TunnelSource)
|
tunnelSource, err := uuid.FromString(metadata.TunnelSource)
|
||||||
@@ -131,6 +132,7 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin
|
|||||||
case conn := <-ch:
|
case conn := <-ch:
|
||||||
err := WriteRequest(conn, &Request{UUID: *sourceUUID, Command: CommandTCP, Destination: destination})
|
err := WriteRequest(conn, &Request{UUID: *sourceUUID, Command: CommandTCP, Destination: destination})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
s.logger.ErrorContext(ctx, err)
|
s.logger.ErrorContext(ctx, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -142,7 +144,7 @@ func (s *ServerEndpoint) DialContext(ctx context.Context, network string, destin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
func (s *ServerEndpoint) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
return nil, os.ErrInvalid
|
return s.uotClient.ListenPacket(ctx, destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ServerEndpoint) Close() error {
|
func (s *ServerEndpoint) Close() error {
|
||||||
@@ -159,8 +161,6 @@ func (s *ServerEndpoint) connHandler(ctx context.Context, conn net.Conn, metadat
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if request.Command == CommandInbound {
|
if request.Command == CommandInbound {
|
||||||
s.mtx.Lock()
|
|
||||||
defer s.mtx.Unlock()
|
|
||||||
uuid, ok := s.users[request.UUID]
|
uuid, ok := s.users[request.UUID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return E.New("key ", request.UUID.String(), " not found")
|
return E.New("key ", request.UUID.String(), " not found")
|
||||||
@@ -183,14 +183,12 @@ func (s *ServerEndpoint) connHandler(ctx context.Context, conn net.Conn, metadat
|
|||||||
if sourceUUID == request.DestinationUUID {
|
if sourceUUID == request.DestinationUUID {
|
||||||
return E.New("routing loop on ", sourceUUID)
|
return E.New("routing loop on ", sourceUUID)
|
||||||
}
|
}
|
||||||
s.mtx.Lock()
|
|
||||||
if request.DestinationUUID != s.uuid {
|
if request.DestinationUUID != s.uuid {
|
||||||
_, ok = s.keys[request.DestinationUUID]
|
_, ok = s.keys[request.DestinationUUID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return E.New("user ", sourceUUID, " not found")
|
return E.New("user ", request.DestinationUUID, " not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.mtx.Unlock()
|
|
||||||
metadata.Inbound = s.Tag()
|
metadata.Inbound = s.Tag()
|
||||||
metadata.InboundType = C.TypeTunnelServer
|
metadata.InboundType = C.TypeTunnelServer
|
||||||
metadata.Destination = request.Destination
|
metadata.Destination = request.Destination
|
||||||
|
|||||||
@@ -138,6 +138,17 @@ func (h *Inbound) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []option.VLESSUser) {
|
||||||
|
h.users = users
|
||||||
|
h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.VLESSUser) int {
|
||||||
|
return index
|
||||||
|
}), common.Map(users, func(it option.VLESSUser) string {
|
||||||
|
return it.UUID
|
||||||
|
}), common.Map(users, func(it option.VLESSUser) string {
|
||||||
|
return it.Flow
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
if h.tlsConfig != nil && h.transport == nil {
|
if h.tlsConfig != nil && h.transport == nil {
|
||||||
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
||||||
|
|||||||
@@ -153,6 +153,16 @@ func (h *Inbound) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []option.VMessUser) {
|
||||||
|
h.service.UpdateUsers(common.MapIndexed(users, func(index int, _ option.VMessUser) int {
|
||||||
|
return index
|
||||||
|
}), common.Map(users, func(it option.VMessUser) string {
|
||||||
|
return it.UUID
|
||||||
|
}), common.Map(users, func(it option.VMessUser) int {
|
||||||
|
return it.AlterId
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
|
||||||
if h.tlsConfig != nil && h.transport == nil {
|
if h.tlsConfig != nil && h.transport == nil {
|
||||||
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ func NewWARPEndpoint(ctx context.Context, router adapter.Router, logger log.Cont
|
|||||||
netip.MustParsePrefix("0.0.0.0/0"),
|
netip.MustParsePrefix("0.0.0.0/0"),
|
||||||
netip.MustParsePrefix("::/0"),
|
netip.MustParsePrefix("::/0"),
|
||||||
},
|
},
|
||||||
|
PersistentKeepaliveInterval: options.PersistentKeepaliveInterval,
|
||||||
|
Reserved: options.Reserved,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MTU: 1280,
|
MTU: 1280,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
tf "github.com/sagernet/sing-box/common/tlsfragment"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
@@ -303,7 +303,7 @@ func (m *ConnectionManager) connectionCopy(ctx context.Context, source net.Conn,
|
|||||||
} else {
|
} else {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
m.logger.DebugContext(ctx, "connection download finished")
|
m.logger.DebugContext(ctx, "connection download finished")
|
||||||
} else if !E.IsClosedOrCanceled(err) && !strings.Contains(err.Error(), "NO_ERROR") {
|
} else if !E.IsClosedOrCanceled(err) && !strings.Contains(err.Error(), "NO_ERROR") && !strings.Contains(err.Error(), "response body closed") {
|
||||||
m.logger.ErrorContext(ctx, "connection download closed: ", err)
|
m.logger.ErrorContext(ctx, "connection download closed: ", err)
|
||||||
} else {
|
} else {
|
||||||
m.logger.TraceContext(ctx, "connection download closed")
|
m.logger.TraceContext(ctx, "connection download closed")
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
R "github.com/sagernet/sing-box/route/rule"
|
R "github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing-mux"
|
mux "github.com/sagernet/sing-mux"
|
||||||
"github.com/sagernet/sing-vmess"
|
vmess "github.com/sagernet/sing-vmess"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
@@ -123,12 +123,11 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if selectedRule == nil {
|
if selectedRule == nil {
|
||||||
defaultOutbound := r.outbound.Default()
|
if !common.Contains(r.defaultOutbound.Network(), N.NetworkTCP) {
|
||||||
if !common.Contains(defaultOutbound.Network(), N.NetworkTCP) {
|
|
||||||
buf.ReleaseMulti(buffers)
|
buf.ReleaseMulti(buffers)
|
||||||
return E.New("TCP is not supported by default outbound: ", defaultOutbound.Tag())
|
return E.New("TCP is not supported by default outbound: ", r.defaultOutbound.Tag())
|
||||||
}
|
}
|
||||||
selectedOutbound = defaultOutbound
|
selectedOutbound = r.defaultOutbound
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, buffer := range buffers {
|
for _, buffer := range buffers {
|
||||||
@@ -234,12 +233,11 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if selectedRule == nil || selectReturn {
|
if selectedRule == nil || selectReturn {
|
||||||
defaultOutbound := r.outbound.Default()
|
if !common.Contains(r.defaultOutbound.Network(), N.NetworkUDP) {
|
||||||
if !common.Contains(defaultOutbound.Network(), N.NetworkUDP) {
|
|
||||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||||
return E.New("UDP is not supported by outbound: ", defaultOutbound.Tag())
|
return E.New("UDP is not supported by outbound: ", r.defaultOutbound.Tag())
|
||||||
}
|
}
|
||||||
selectedOutbound = defaultOutbound
|
selectedOutbound = r.defaultOutbound
|
||||||
}
|
}
|
||||||
for _, buffer := range packetBuffers {
|
for _, buffer := range packetBuffers {
|
||||||
conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination)
|
conn = bufio.NewCachedPacketConn(conn, buffer.Buffer, buffer.Destination)
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ type Router struct {
|
|||||||
dnsTransport adapter.DNSTransportManager
|
dnsTransport adapter.DNSTransportManager
|
||||||
connection adapter.ConnectionManager
|
connection adapter.ConnectionManager
|
||||||
network adapter.NetworkManager
|
network adapter.NetworkManager
|
||||||
|
defaultOutbound adapter.Outbound
|
||||||
rules []adapter.Rule
|
rules []adapter.Rule
|
||||||
|
final string
|
||||||
needFindProcess bool
|
needFindProcess bool
|
||||||
ruleSets []adapter.RuleSet
|
ruleSets []adapter.RuleSet
|
||||||
ruleSetMap map[string]adapter.RuleSet
|
ruleSetMap map[string]adapter.RuleSet
|
||||||
@@ -53,6 +55,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.Route
|
|||||||
connection: service.FromContext[adapter.ConnectionManager](ctx),
|
connection: service.FromContext[adapter.ConnectionManager](ctx),
|
||||||
network: service.FromContext[adapter.NetworkManager](ctx),
|
network: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
rules: make([]adapter.Rule, 0, len(options.Rules)),
|
||||||
|
final: options.Final,
|
||||||
ruleSetMap: make(map[string]adapter.RuleSet),
|
ruleSetMap: make(map[string]adapter.RuleSet),
|
||||||
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
needFindProcess: hasRule(options.Rules, isProcessRule) || hasDNSRule(dnsOptions.Rules, isProcessDNSRule) || options.FindProcess,
|
||||||
pauseManager: service.FromContext[pause.Manager](ctx),
|
pauseManager: service.FromContext[pause.Manager](ctx),
|
||||||
@@ -159,6 +162,15 @@ func (r *Router) Start(stage adapter.StartStage) error {
|
|||||||
return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]")
|
return E.Cause(err, "post start rule_set[", ruleSet.Name(), "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if r.final != "" {
|
||||||
|
defaultOutbound, loaded := r.outbound.Outbound(r.final)
|
||||||
|
if !loaded {
|
||||||
|
return E.New("outbound not found: ", r.final)
|
||||||
|
}
|
||||||
|
r.defaultOutbound = defaultOutbound
|
||||||
|
} else {
|
||||||
|
r.defaultOutbound = r.outbound.Default()
|
||||||
|
}
|
||||||
r.started = true
|
r.started = true
|
||||||
return nil
|
return nil
|
||||||
case adapter.StartStateStarted:
|
case adapter.StartStateStarted:
|
||||||
|
|||||||
400
service/admin_panel/migration/postgresql.go
Normal file
400
service/admin_panel/migration/postgresql.go
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
|
"github.com/sagernet/sing-box/common/migrate/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
var migrations = map[string]string{
|
||||||
|
"1_initialize_schema.up.sql": `
|
||||||
|
SET statement_timeout = 0;
|
||||||
|
SET lock_timeout = 0;
|
||||||
|
SET idle_in_transaction_session_timeout = 0;
|
||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
SET standard_conforming_strings = on;
|
||||||
|
SELECT pg_catalog.set_config('search_path', '', false);
|
||||||
|
SET check_function_bodies = false;
|
||||||
|
SET xmloption = content;
|
||||||
|
SET client_min_messages = warning;
|
||||||
|
SET row_security = off;
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.goadmin_menu_myid_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
MAXVALUE 99999999
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
SET default_tablespace = '';
|
||||||
|
|
||||||
|
SET default_table_access_method = heap;
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_menu (
|
||||||
|
id integer DEFAULT nextval('public.goadmin_menu_myid_seq'::regclass) NOT NULL,
|
||||||
|
parent_id integer DEFAULT 0 NOT NULL,
|
||||||
|
type integer DEFAULT 0,
|
||||||
|
"order" integer DEFAULT 0 NOT NULL,
|
||||||
|
title character varying(50) NOT NULL,
|
||||||
|
header character varying(100),
|
||||||
|
plugin_name character varying(100) NOT NULL,
|
||||||
|
icon character varying(50) NOT NULL,
|
||||||
|
uri character varying(3000) NOT NULL,
|
||||||
|
uuid character varying(100),
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.goadmin_operation_log_myid_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
MAXVALUE 99999999
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_operation_log (
|
||||||
|
id integer DEFAULT nextval('public.goadmin_operation_log_myid_seq'::regclass) NOT NULL,
|
||||||
|
user_id integer NOT NULL,
|
||||||
|
path character varying(255) NOT NULL,
|
||||||
|
method character varying(10) NOT NULL,
|
||||||
|
ip character varying(15) NOT NULL,
|
||||||
|
input text NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.goadmin_permissions_myid_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
MAXVALUE 99999999
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_permissions (
|
||||||
|
id integer DEFAULT nextval('public.goadmin_permissions_myid_seq'::regclass) NOT NULL,
|
||||||
|
name character varying(50) NOT NULL,
|
||||||
|
slug character varying(50) NOT NULL,
|
||||||
|
http_method character varying(255),
|
||||||
|
http_path text NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_role_menu (
|
||||||
|
role_id integer NOT NULL,
|
||||||
|
menu_id integer NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_role_permissions (
|
||||||
|
role_id integer NOT NULL,
|
||||||
|
permission_id integer NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_role_users (
|
||||||
|
role_id integer NOT NULL,
|
||||||
|
user_id integer NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.goadmin_roles_myid_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
MAXVALUE 99999999
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_roles (
|
||||||
|
id integer DEFAULT nextval('public.goadmin_roles_myid_seq'::regclass) NOT NULL,
|
||||||
|
name character varying NOT NULL,
|
||||||
|
slug character varying NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.goadmin_session_myid_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
MAXVALUE 99999999
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_session (
|
||||||
|
id integer DEFAULT nextval('public.goadmin_session_myid_seq'::regclass) NOT NULL,
|
||||||
|
sid character varying(50) NOT NULL,
|
||||||
|
"values" character varying(3000) NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.goadmin_site_myid_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
MAXVALUE 99999999
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_site (
|
||||||
|
id integer DEFAULT nextval('public.goadmin_site_myid_seq'::regclass) NOT NULL,
|
||||||
|
key character varying(100) NOT NULL,
|
||||||
|
value text NOT NULL,
|
||||||
|
type integer DEFAULT 0,
|
||||||
|
description character varying(3000),
|
||||||
|
state integer DEFAULT 0,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_user_permissions (
|
||||||
|
user_id integer NOT NULL,
|
||||||
|
permission_id integer NOT NULL,
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE public.goadmin_users_myid_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
MAXVALUE 99999999
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
CREATE TABLE public.goadmin_users (
|
||||||
|
id integer DEFAULT nextval('public.goadmin_users_myid_seq'::regclass) NOT NULL,
|
||||||
|
username character varying(100) NOT NULL,
|
||||||
|
password character varying(100) NOT NULL,
|
||||||
|
name character varying(100) NOT NULL,
|
||||||
|
avatar character varying(255),
|
||||||
|
remember_token character varying(100),
|
||||||
|
created_at timestamp without time zone DEFAULT now(),
|
||||||
|
updated_at timestamp without time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (1, 0, 1, 1, 'Dashboard', NULL, '', 'fa-bar-chart', '/', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (2, 0, 1, 2, 'Admin', NULL, '', 'fa-tasks', '', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (3, 2, 1, 2, 'Users', NULL, '', 'fa-users', '/info/manager', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (4, 2, 1, 3, 'Roles', NULL, '', 'fa-user', '/info/roles', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (5, 2, 1, 4, 'Permission', NULL, '', 'fa-ban', '/info/permission', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (7, 2, 1, 6, 'Operation log', NULL, '', 'fa-history', '/info/op', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (9, 0, 0, 9, 'Users', '', '', 'fa-users', '/info/users', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (14, 0, 0, 12, 'Github', 'Miscellaneous', '', 'fa-github', 'https://github.com/shtorm-7/sing-box-extended', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (15, 0, 0, 13, 'Donate', '', '', 'fa-heart', 'https://github.com/shtorm-7/sing-box-extended#support-the-project', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (13, 0, 0, 7, 'Squads', 'General', '', 'fa-gg', '/info/squads', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (11, 0, 0, 8, 'Nodes', '', '', 'fa-sitemap', '/info/nodes', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (10, 0, 0, 10, 'Connection limiters', 'Limiters', '', 'fa-plug', '/info/connection_limiters', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_menu (id, parent_id, type, "order", title, header, plugin_name, icon, uri, uuid, created_at, updated_at) VALUES (8, 0, 0, 11, 'Bandwidth limiters', '', '', 'fa-dashboard', '/info/bandwidth_limiters', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_permissions (id, name, slug, http_method, http_path, created_at, updated_at) VALUES (1, 'All permission', '*', '', '*', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_permissions (id, name, slug, http_method, http_path, created_at, updated_at) VALUES (2, 'Dashboard', 'dashboard', 'GET,PUT,POST,DELETE', '/', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (1, 7, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_role_menu (role_id, menu_id, created_at, updated_at) VALUES (2, 7, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (1, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (2, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_role_permissions (role_id, permission_id, created_at, updated_at) VALUES (0, 3, NULL, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_role_users (role_id, user_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_role_users (role_id, user_id, created_at, updated_at) VALUES (2, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_roles (id, name, slug, created_at, updated_at) VALUES (1, 'Administrator', 'administrator', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_roles (id, name, slug, created_at, updated_at) VALUES (2, 'Operator', 'operator', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (6, 'site_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.436501', '2026-02-15 09:57:02.436501');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (7, 'prohibit_config_modification', 'false', 0, NULL, 1, '2026-02-15 09:57:02.441183', '2026-02-15 09:57:02.441183');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (11, 'login_url', '/login', 0, NULL, 1, '2026-02-15 09:57:02.459525', '2026-02-15 09:57:02.459525');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (16, 'open_admin_api', 'false', 0, NULL, 1, '2026-02-15 09:57:02.483908', '2026-02-15 09:57:02.483908');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (18, 'domain', '', 0, NULL, 1, '2026-02-15 09:57:02.493151', '2026-02-15 09:57:02.493151');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (23, 'asset_root_path', './public/', 0, NULL, 1, '2026-02-15 09:57:02.517213', '2026-02-15 09:57:02.517213');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (24, 'url_prefix', 'admin', 0, NULL, 1, '2026-02-15 09:57:02.521815', '2026-02-15 09:57:02.521815');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (33, 'exclude_theme_components', 'null', 0, NULL, 1, '2026-02-15 09:57:02.565725', '2026-02-15 09:57:02.565725');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (39, 'app_id', 'Qn0eh7HQsrt9', 0, NULL, 1, '2026-02-15 09:57:02.592551', '2026-02-15 09:57:02.592551');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (41, 'auth_user_table', 'goadmin_users', 0, NULL, 1, '2026-02-15 09:57:02.601496', '2026-02-15 09:57:02.601496');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (53, 'bootstrap_file_path', '', 0, NULL, 1, '2026-02-15 09:57:02.658984', '2026-02-15 09:57:02.658984');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (55, 'index_url', '/', 0, NULL, 1, '2026-02-15 09:57:02.668457', '2026-02-15 09:57:02.668457');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (66, 'login_logo', '', 0, NULL, 1, '2026-02-15 09:57:02.719608', '2026-02-15 09:57:02.719608');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (67, 'hide_visitor_user_center_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.724307', '2026-02-15 09:57:02.724307');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (68, 'go_mod_file_path', '', 0, NULL, 1, '2026-02-15 09:57:02.728694', '2026-02-15 09:57:02.728694');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (3, 'logger_encoder_caller', 'full', 0, NULL, 1, '2026-02-15 09:57:02.420312', '2026-02-15 09:57:02.420312');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (60, 'logger_encoder_caller_key', 'caller', 0, NULL, 1, '2026-02-15 09:57:02.692189', '2026-02-15 09:57:02.692189');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (34, 'logo', 'Sing-box Extended', 0, NULL, 1, '2026-02-15 09:57:02.570594', '2026-02-15 09:57:02.570594');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (69, 'env', 'prod', 0, NULL, 1, '2026-02-15 09:57:02.733059', '2026-02-15 09:57:02.733059');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (29, 'color_scheme', 'skin-black', 0, NULL, 1, '2026-02-15 09:57:02.545599', '2026-02-15 09:57:02.545599');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (17, 'allow_del_operation_log', 'false', 0, NULL, 1, '2026-02-15 09:57:02.488458', '2026-02-15 09:57:02.488458');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (35, 'info_log_path', '', 0, NULL, 1, '2026-02-15 09:57:02.574649', '2026-02-15 09:57:02.574649');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (22, 'operation_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.512394', '2026-02-15 09:57:02.512394');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (42, 'hide_app_info_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.606071', '2026-02-15 09:57:02.606071');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (12, 'access_log_path', '', 0, NULL, 1, '2026-02-15 09:57:02.464612', '2026-02-15 09:57:02.464612');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (32, 'logger_rotate_max_age', '30', 0, NULL, 1, '2026-02-15 09:57:02.560801', '2026-02-15 09:57:02.560801');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (40, 'custom_foot_html', '', 0, NULL, 1, '2026-02-15 09:57:02.597285', '2026-02-15 09:57:02.597285');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (62, 'logger_encoder_duration', 'string', 0, NULL, 1, '2026-02-15 09:57:02.701522', '2026-02-15 09:57:02.701522');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (65, 'logger_encoder_level_key', 'level', 0, NULL, 1, '2026-02-15 09:57:02.715108', '2026-02-15 09:57:02.715108');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (64, 'debug', 'false', 0, NULL, 1, '2026-02-15 09:57:02.710705', '2026-02-15 09:57:02.710705');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (43, 'hide_plugin_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.610825', '2026-02-15 09:57:02.610825');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (54, 'animation_type', '', 0, NULL, 1, '2026-02-15 09:57:02.663713', '2026-02-15 09:57:02.663713');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (48, 'theme', 'sword', 0, NULL, 1, '2026-02-15 09:57:02.634039', '2026-02-15 09:57:02.634039');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (45, 'info_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.620165', '2026-02-15 09:57:02.620165');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (31, 'error_log_path', '', 0, NULL, 1, '2026-02-15 09:57:02.555798', '2026-02-15 09:57:02.555798');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (5, 'asset_url', '', 0, NULL, 1, '2026-02-15 09:57:02.431855', '2026-02-15 09:57:02.431855');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (36, 'logger_encoder_encoding', 'console', 0, NULL, 1, '2026-02-15 09:57:02.579052', '2026-02-15 09:57:02.579052');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (27, 'login_title', 'Sing-box Extended', 0, NULL, 1, '2026-02-15 09:57:02.536102', '2026-02-15 09:57:02.536102');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (51, 'animation_duration', '0.00', 0, NULL, 1, '2026-02-15 09:57:02.64867', '2026-02-15 09:57:02.64867');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (19, 'file_upload_engine', '{"name":"local"}', 0, NULL, 1, '2026-02-15 09:57:02.49794', '2026-02-15 09:57:02.49794');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (26, 'logger_encoder_time', 'iso8601', 0, NULL, 1, '2026-02-15 09:57:02.531365', '2026-02-15 09:57:02.531365');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (10, 'custom_404_html', '', 0, NULL, 1, '2026-02-15 09:57:02.454777', '2026-02-15 09:57:02.454777');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (58, 'sql_log', 'false', 0, NULL, 1, '2026-02-15 09:57:02.682567', '2026-02-15 09:57:02.682567');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (2, 'logger_encoder_message_key', 'msg', 0, NULL, 1, '2026-02-15 09:57:02.415189', '2026-02-15 09:57:02.415189');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (46, 'logger_encoder_stacktrace_key', 'stacktrace', 0, NULL, 1, '2026-02-15 09:57:02.624977', '2026-02-15 09:57:02.624977');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (63, 'mini_logo', 'SBE', 0, NULL, 1, '2026-02-15 09:57:02.706145', '2026-02-15 09:57:02.706145');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (38, 'custom_403_html', '', 0, NULL, 1, '2026-02-15 09:57:02.588062', '2026-02-15 09:57:02.588062');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (30, 'language', 'en', 0, NULL, 1, '2026-02-15 09:57:02.550466', '2026-02-15 09:57:02.550466');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (15, 'hide_config_center_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.479097', '2026-02-15 09:57:02.479097');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (59, 'logger_rotate_max_backups', '5', 0, NULL, 1, '2026-02-15 09:57:02.687429', '2026-02-15 09:57:02.687429');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (57, 'custom_head_html', '', 0, NULL, 1, '2026-02-15 09:57:02.677723', '2026-02-15 09:57:02.677723');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (52, 'custom_500_html', '', 0, NULL, 1, '2026-02-15 09:57:02.654236', '2026-02-15 09:57:02.654236');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (44, 'title', 'Sing-box Extended', 0, NULL, 1, '2026-02-15 09:57:02.615471', '2026-02-15 09:57:02.615471');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (47, 'session_life_time', '7200', 0, NULL, 1, '2026-02-15 09:57:02.629619', '2026-02-15 09:57:02.629619');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (8, 'access_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.445593', '2026-02-15 09:57:02.445593');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (49, 'error_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.6385', '2026-02-15 09:57:02.6385');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (50, 'logger_rotate_max_size', '10', 0, NULL, 1, '2026-02-15 09:57:02.643733', '2026-02-15 09:57:02.643733');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (14, 'logger_rotate_compress', 'false', 0, NULL, 1, '2026-02-15 09:57:02.474296', '2026-02-15 09:57:02.474296');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (13, 'logger_encoder_time_key', 'ts', 0, NULL, 1, '2026-02-15 09:57:02.469396', '2026-02-15 09:57:02.469396');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (37, 'animation_delay', '0.00', 0, NULL, 1, '2026-02-15 09:57:02.583815', '2026-02-15 09:57:02.583815');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (20, 'extra', '', 0, NULL, 1, '2026-02-15 09:57:02.50276', '2026-02-15 09:57:02.50276');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (25, 'access_assets_log_off', 'false', 0, NULL, 1, '2026-02-15 09:57:02.526618', '2026-02-15 09:57:02.526618');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (4, 'logger_level', '0', 0, NULL, 1, '2026-02-15 09:57:02.426736', '2026-02-15 09:57:02.426736');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (9, 'footer_info', '', 0, NULL, 1, '2026-02-15 09:57:02.450409', '2026-02-15 09:57:02.450409');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (21, 'no_limit_login_ip', 'false', 0, NULL, 1, '2026-02-15 09:57:02.507609', '2026-02-15 09:57:02.507609');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (28, 'hide_tool_entrance', 'false', 0, NULL, 1, '2026-02-15 09:57:02.540813', '2026-02-15 09:57:02.540813');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (61, 'logger_encoder_level', 'capitalColor', 0, NULL, 1, '2026-02-15 09:57:02.696859', '2026-02-15 09:57:02.696859');
|
||||||
|
INSERT INTO public.goadmin_site (id, key, value, type, description, state, created_at, updated_at) VALUES (56, 'logger_encoder_name_key', 'logger', 0, NULL, 1, '2026-02-15 09:57:02.672962', '2026-02-15 09:57:02.672962');
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (1, 1, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (2, 2, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
INSERT INTO public.goadmin_user_permissions (user_id, permission_id, created_at, updated_at) VALUES (0, 1, NULL, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO public.goadmin_users (id, username, password, name, avatar, remember_token, created_at, updated_at) VALUES (2, 'operator', '$2a$10$rVqkOzHjN2MdlEprRflb1eGP0oZXuSrbJLOmJagFsCd81YZm0bsh.', 'Operator', '', NULL, '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
INSERT INTO public.goadmin_users (id, username, password, name, avatar, remember_token, created_at, updated_at) VALUES (1, 'admin', '$2a$10$ilNHHnX5S6EMw.Ffc1Y1JezYCyquFIO.7Z0vLr1eHJUXnGy4cdrtq', 'admin', '', 'tlNcBVK9AvfYH7WEnwB1RKvocJu8FfRy4um3DJtwdHuJy0dwFsLOgAc0xUfh', '2019-09-10 00:00:00', '2019-09-10 00:00:00');
|
||||||
|
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.goadmin_menu_myid_seq', 12, true);
|
||||||
|
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.goadmin_operation_log_myid_seq', 11, true);
|
||||||
|
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.goadmin_permissions_myid_seq', 2, true);
|
||||||
|
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.goadmin_roles_myid_seq', 2, true);
|
||||||
|
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.goadmin_session_myid_seq', 7, true);
|
||||||
|
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.goadmin_site_myid_seq', 69, true);
|
||||||
|
|
||||||
|
|
||||||
|
SELECT pg_catalog.setval('public.goadmin_users_myid_seq', 2, true);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.goadmin_menu
|
||||||
|
ADD CONSTRAINT goadmin_menu_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.goadmin_operation_log
|
||||||
|
ADD CONSTRAINT goadmin_operation_log_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.goadmin_permissions
|
||||||
|
ADD CONSTRAINT goadmin_permissions_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.goadmin_roles
|
||||||
|
ADD CONSTRAINT goadmin_roles_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.goadmin_session
|
||||||
|
ADD CONSTRAINT goadmin_session_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.goadmin_site
|
||||||
|
ADD CONSTRAINT goadmin_site_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.goadmin_users
|
||||||
|
ADD CONSTRAINT goadmin_users_pkey PRIMARY KEY (id);
|
||||||
|
`,
|
||||||
|
"1_initialize_schema.down.sql": ``,
|
||||||
|
}
|
||||||
|
|
||||||
|
func MigratePostgreSQL(db *sql.DB) error {
|
||||||
|
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceDriver := source.NewRawDriver(migrations)
|
||||||
|
if err := sourceDriver.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := migrate.NewWithInstance(
|
||||||
|
"raw",
|
||||||
|
sourceDriver,
|
||||||
|
"postgres",
|
||||||
|
driver,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Up()
|
||||||
|
}
|
||||||
13
service/admin_panel/pages/dashboard.go
Normal file
13
service/admin_panel/pages/dashboard.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/GoAdminGroup/go-admin/context"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DashboardPage(ctx *context.Context) (types.Panel, error) {
|
||||||
|
|
||||||
|
return types.Panel{
|
||||||
|
Title: "Dashboard",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
188
service/admin_panel/service.go
Normal file
188
service/admin_panel/service.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
//go:build with_admin_panel
|
||||||
|
|
||||||
|
package admin_panel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
|
||||||
|
_ "github.com/GoAdminGroup/go-admin/adapter/chi"
|
||||||
|
"github.com/GoAdminGroup/go-admin/engine"
|
||||||
|
"github.com/GoAdminGroup/go-admin/modules/config"
|
||||||
|
_ "github.com/GoAdminGroup/go-admin/modules/db/drivers/sqlite"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/chartjs"
|
||||||
|
_ "github.com/GoAdminGroup/themes/adminlte"
|
||||||
|
_ "github.com/GoAdminGroup/themes/sword"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/service/admin_panel/migration"
|
||||||
|
"github.com/sagernet/sing-box/service/admin_panel/pages"
|
||||||
|
"github.com/sagernet/sing-box/service/admin_panel/tables"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *boxService.Registry) {
|
||||||
|
boxService.Register[option.AdminPanelServiceOptions](registry, C.TypeAdminPanel, NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
boxService.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
logger log.ContextLogger
|
||||||
|
listener *listener.Listener
|
||||||
|
tlsConfig tls.ServerConfig
|
||||||
|
httpServer *http.Server
|
||||||
|
options option.AdminPanelServiceOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.AdminPanelServiceOptions) (adapter.Service, error) {
|
||||||
|
s := &Service{
|
||||||
|
Adapter: boxService.NewAdapter(C.TypeAdminPanel, tag),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
listener: listener.New(listener.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Logger: logger,
|
||||||
|
Network: []string{N.NetworkTCP},
|
||||||
|
Listen: options.ListenOptions,
|
||||||
|
}),
|
||||||
|
options: options,
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
boxManager := service.FromContext[adapter.ServiceManager](s.ctx)
|
||||||
|
service, ok := boxManager.Get(s.options.Manager)
|
||||||
|
if !ok {
|
||||||
|
return E.New("manager ", s.options.Manager, " not found")
|
||||||
|
}
|
||||||
|
manager, ok := service.(CM.Manager)
|
||||||
|
if !ok {
|
||||||
|
return E.New("invalid ", s.options.Manager, " manager")
|
||||||
|
}
|
||||||
|
switch s.options.Database.Driver {
|
||||||
|
case "postgresql":
|
||||||
|
db, err := sql.Open("postgres", s.options.Database.DSN)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
if err := migration.MigratePostgreSQL(db); err != nil && err != migrate.ErrNoChange {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return E.New("unknown driver \"", s.options.Database.Driver, "\"")
|
||||||
|
}
|
||||||
|
var generators = map[string]table.Generator{
|
||||||
|
"squads": tables.SquadTableFactory(
|
||||||
|
manager,
|
||||||
|
s.logger,
|
||||||
|
),
|
||||||
|
"nodes": tables.NodeTableFactory(
|
||||||
|
manager,
|
||||||
|
s.logger,
|
||||||
|
),
|
||||||
|
"users": tables.UserTableFactory(
|
||||||
|
manager,
|
||||||
|
s.logger,
|
||||||
|
),
|
||||||
|
"connection_limiters": tables.ConnectionLimiterTableFactory(
|
||||||
|
manager,
|
||||||
|
s.logger,
|
||||||
|
),
|
||||||
|
"bandwidth_limiters": tables.BandwidthLimiterTableFactory(
|
||||||
|
manager,
|
||||||
|
s.logger,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
eng := engine.Default()
|
||||||
|
chiRouter := chi.NewRouter()
|
||||||
|
template.AddComp(chartjs.NewChart())
|
||||||
|
if err := eng.AddConfig(&config.Config{
|
||||||
|
UrlPrefix: "admin",
|
||||||
|
IndexUrl: "/",
|
||||||
|
LoginUrl: "/login",
|
||||||
|
Databases: config.DatabaseList{
|
||||||
|
"default": config.Database{
|
||||||
|
Driver: s.options.Database.Driver,
|
||||||
|
Dsn: s.options.Database.DSN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
AddGenerators(generators).
|
||||||
|
Use(chiRouter); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
eng.HTML("GET", "/admin", pages.DashboardPage)
|
||||||
|
chiRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/admin", http.StatusMovedPermanently)
|
||||||
|
})
|
||||||
|
chiRouter.Get("/admin/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, "/admin", http.StatusMovedPermanently)
|
||||||
|
})
|
||||||
|
if s.options.TLS != nil {
|
||||||
|
tlsConfig, err := tls.NewServer(s.ctx, s.logger, common.PtrValueOrDefault(s.options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.tlsConfig = tlsConfig
|
||||||
|
}
|
||||||
|
if s.tlsConfig != nil {
|
||||||
|
err := s.tlsConfig.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create TLS config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcpListener, err := s.listener.ListenTCP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.tlsConfig != nil {
|
||||||
|
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||||
|
s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
|
||||||
|
}
|
||||||
|
tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)
|
||||||
|
}
|
||||||
|
s.httpServer = &http.Server{
|
||||||
|
Handler: chiRouter,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
err = s.httpServer.Serve(tcpListener)
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
s.logger.Error("serve error: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
common.PtrOrNil(s.httpServer),
|
||||||
|
common.PtrOrNil(s.listener),
|
||||||
|
s.tlsConfig,
|
||||||
|
)
|
||||||
|
}
|
||||||
20
service/admin_panel/service_stub.go
Normal file
20
service/admin_panel/service_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build !with_admin_panel
|
||||||
|
|
||||||
|
package admin_panel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *service.Registry) {
|
||||||
|
service.Register[option.AdminPanelServiceOptions](registry, C.TypeAdminPanel, func(ctx context.Context, logger log.ContextLogger, tag string, options option.AdminPanelServiceOptions) (adapter.Service, error) {
|
||||||
|
return nil, E.New(`Admin panel is not included in this build, rebuild with -tags with_admin_panel`)
|
||||||
|
})
|
||||||
|
}
|
||||||
259
service/admin_panel/tables/bandwidth_limiter.go
Normal file
259
service/admin_panel/tables/bandwidth_limiter.go
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoAdminGroup/go-admin/context"
|
||||||
|
"github.com/GoAdminGroup/go-admin/modules/db"
|
||||||
|
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types/form"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BandwidthLimiterTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) table.Table {
|
||||||
|
return func(ctx *context.Context) table.Table {
|
||||||
|
t := table.NewDefaultTable(ctx, table.Config{
|
||||||
|
CanAdd: true,
|
||||||
|
Editable: true,
|
||||||
|
Deletable: true,
|
||||||
|
Exportable: true,
|
||||||
|
PrimaryKey: table.PrimaryKey{
|
||||||
|
Type: db.Int,
|
||||||
|
Name: table.DefaultPrimaryKeyName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
squads, err := manager.GetSquads(map[string][]string{})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
squadsByID := make(map[int]string, len(squads))
|
||||||
|
squadOptions := make(types.FieldOptions, len(squads))
|
||||||
|
for i, squad := range squads {
|
||||||
|
squadsByID[squad.ID] = squad.Name
|
||||||
|
squadOptions[i] = types.FieldOption{
|
||||||
|
Text: squad.Name,
|
||||||
|
Value: strconv.Itoa(squad.ID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info := t.GetInfo().SetFilterFormLayout(form.LayoutFilter)
|
||||||
|
info.AddField("ID", "id", db.Int).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Squads", "squad_ids", db.Varchar).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
values := model.Row["squad_ids"].([]interface{})
|
||||||
|
labels := template.HTML("")
|
||||||
|
labelTpl := label(ctx).SetType("success")
|
||||||
|
labelValues := make([]string, len(values))
|
||||||
|
for i, squadID := range values {
|
||||||
|
labelValues[i] = squadsByID[int(squadID.(float64))]
|
||||||
|
}
|
||||||
|
for key, label := range labelValues {
|
||||||
|
if key == len(labelValues)-1 {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
} else {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
})
|
||||||
|
info.AddField("Username", "username", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Outbound", "outbound", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Strategy", "strategy", db.Varchar).
|
||||||
|
FieldFilterable(types.FilterType{
|
||||||
|
FormType: form.SelectSingle,
|
||||||
|
Options: types.FieldOptions{
|
||||||
|
{Text: "Connection", Value: "connection"},
|
||||||
|
{Text: "Global", Value: "global"},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Mode", "mode", db.Varchar).
|
||||||
|
FieldFilterable(types.FilterType{
|
||||||
|
FormType: form.SelectSingle,
|
||||||
|
Options: types.FieldOptions{
|
||||||
|
{Text: "Download", Value: "download"},
|
||||||
|
{Text: "Upload", Value: "upload"},
|
||||||
|
{Text: "Duplex", Value: "duplex"},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Connection type", "connection_type", db.Varchar).
|
||||||
|
FieldFilterable(types.FilterType{
|
||||||
|
FormType: form.SelectSingle,
|
||||||
|
Options: types.FieldOptions{
|
||||||
|
{Text: "HWID", Value: "hwid"},
|
||||||
|
{Text: "Mux", Value: "mux"},
|
||||||
|
{Text: "IP", Value: "ip"},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Speed", "speed", db.Varchar).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Created at", "created_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Updated at", "updated_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
|
||||||
|
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
|
||||||
|
filters := make(map[string][]string)
|
||||||
|
listFilters := map[string][]string{
|
||||||
|
"offset": {strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)},
|
||||||
|
"limit": {param.PageSize},
|
||||||
|
}
|
||||||
|
for k, v := range param.Fields {
|
||||||
|
if strings.HasPrefix(k, "__") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.TrimSuffix(k, "__goadmin")
|
||||||
|
filters[key] = v
|
||||||
|
listFilters[key] = v
|
||||||
|
}
|
||||||
|
if param.SortField != "" {
|
||||||
|
if param.SortType == "asc" {
|
||||||
|
listFilters["sort_asc"] = []string{param.SortField}
|
||||||
|
} else {
|
||||||
|
listFilters["sort_desc"] = []string{param.SortField}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items, err := manager.GetBandwidthLimiters(listFilters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
count, err := manager.GetBandwidthLimitersCount(filters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
result := make([]map[string]interface{}, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
var data map[string]interface{}
|
||||||
|
raw, _ := json.Marshal(item)
|
||||||
|
json.Unmarshal(raw, &data)
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
return result, count
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetDeleteFn(func(ids []string) error {
|
||||||
|
for _, id := range ids {
|
||||||
|
i, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := manager.DeleteBandwidthLimiter(i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetTable("bandwidth_limiters").SetTitle("Bandwidth Limiters").SetDescription("Bandwidth Limiters")
|
||||||
|
|
||||||
|
formList := t.GetForm()
|
||||||
|
formList.AddField("ID", "id", db.Int, form.Default).
|
||||||
|
FieldNotAllowAdd().
|
||||||
|
FieldNotAllowEdit()
|
||||||
|
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
|
||||||
|
FieldMust().
|
||||||
|
FieldOptions(squadOptions).
|
||||||
|
FieldDisableWhenUpdate()
|
||||||
|
formList.AddField("Username", "username", db.Varchar, form.Text).
|
||||||
|
FieldMust().
|
||||||
|
FieldDisplayButCanNotEditWhenUpdate()
|
||||||
|
formList.AddField("Outbound", "outbound", db.Varchar, form.Text).
|
||||||
|
FieldMust().
|
||||||
|
FieldDisplayButCanNotEditWhenUpdate()
|
||||||
|
formList.AddField("Strategy", "strategy", db.Varchar, form.SelectSingle).
|
||||||
|
FieldMust().
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "Connection", Value: "connection"},
|
||||||
|
{Text: "Global", Value: "global"},
|
||||||
|
}).
|
||||||
|
FieldOnChooseOptionsHide([]string{"", "global"}, "connection_type")
|
||||||
|
formList.AddField("Mode", "mode", db.Varchar, form.SelectSingle).
|
||||||
|
FieldMust().
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "Download", Value: "download"},
|
||||||
|
{Text: "Upload", Value: "upload"},
|
||||||
|
{Text: "Duplex", Value: "duplex"},
|
||||||
|
})
|
||||||
|
formList.AddField("Connection type", "connection_type", db.Varchar, form.SelectSingle).
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "HWID", Value: "hwid"},
|
||||||
|
{Text: "Mux", Value: "mux"},
|
||||||
|
{Text: "IP", Value: "ip"},
|
||||||
|
})
|
||||||
|
formList.AddField("Speed", "speed", db.Varchar, form.Text).
|
||||||
|
FieldMust()
|
||||||
|
|
||||||
|
formList.SetInsertFn(func(values mForm.Values) error {
|
||||||
|
squadIDs := make([]int, len(values["squad_ids[]"]))
|
||||||
|
for i, rawSquadID := range values["squad_ids[]"] {
|
||||||
|
squadID, err := strconv.Atoi(rawSquadID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
squadIDs[i] = squadID
|
||||||
|
}
|
||||||
|
_, err := manager.CreateBandwidthLimiter(CM.BandwidthLimiterCreate{
|
||||||
|
SquadIDs: squadIDs,
|
||||||
|
Username: values.Get("username"),
|
||||||
|
Outbound: values.Get("outbound"),
|
||||||
|
Strategy: values.Get("strategy"),
|
||||||
|
Mode: values.Get("mode"),
|
||||||
|
ConnectionType: values.Get("connection_type"),
|
||||||
|
Speed: values.Get("speed"),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetUpdateFn(func(values mForm.Values) error {
|
||||||
|
id, err := strconv.Atoi(values.Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = manager.UpdateBandwidthLimiter(id, CM.BandwidthLimiterUpdate{
|
||||||
|
Username: values.Get("username"),
|
||||||
|
Outbound: values.Get("outbound"),
|
||||||
|
Strategy: values.Get("strategy"),
|
||||||
|
Mode: values.Get("mode"),
|
||||||
|
ConnectionType: values.Get("connection_type"),
|
||||||
|
Speed: values.Get("speed"),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetTable("bandwidth_limiters").SetTitle("Bandwidth Limiters").SetDescription("Bandwidth Limiters")
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
261
service/admin_panel/tables/connection_limiter.go
Normal file
261
service/admin_panel/tables/connection_limiter.go
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoAdminGroup/go-admin/context"
|
||||||
|
"github.com/GoAdminGroup/go-admin/modules/db"
|
||||||
|
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types/form"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConnectionLimiterTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) table.Table {
|
||||||
|
return func(ctx *context.Context) table.Table {
|
||||||
|
connectionLimiterTable := table.NewDefaultTable(ctx, table.Config{
|
||||||
|
CanAdd: true,
|
||||||
|
Editable: true,
|
||||||
|
Deletable: true,
|
||||||
|
Exportable: true,
|
||||||
|
PrimaryKey: table.PrimaryKey{
|
||||||
|
Type: db.Int,
|
||||||
|
Name: table.DefaultPrimaryKeyName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
squads, err := manager.GetSquads(map[string][]string{})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
squadsByID := make(map[int]string, len(squads))
|
||||||
|
squadOptions := make(types.FieldOptions, len(squads))
|
||||||
|
for i, squad := range squads {
|
||||||
|
squadsByID[squad.ID] = squad.Name
|
||||||
|
squadOptions[i] = types.FieldOption{
|
||||||
|
Text: squad.Name,
|
||||||
|
Value: strconv.Itoa(squad.ID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info := connectionLimiterTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
|
||||||
|
info.AddField("ID", "id", db.Int).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Squads", "squad_ids", db.Varchar).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
values := model.Row["squad_ids"].([]interface{})
|
||||||
|
labels := template.HTML("")
|
||||||
|
labelTpl := label(ctx).SetType("success")
|
||||||
|
labelValues := make([]string, len(values))
|
||||||
|
for i, squadID := range values {
|
||||||
|
labelValues[i] = squadsByID[int(squadID.(float64))]
|
||||||
|
}
|
||||||
|
for key, label := range labelValues {
|
||||||
|
if key == len(labelValues)-1 {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
} else {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
})
|
||||||
|
info.AddField("Username", "username", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Outbound", "outbound", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Strategy", "strategy", db.Varchar).
|
||||||
|
FieldFilterable(types.FilterType{
|
||||||
|
FormType: form.SelectSingle,
|
||||||
|
Options: types.FieldOptions{
|
||||||
|
{Text: "Connection", Value: "connection"},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Connection type", "connection_type", db.Varchar).
|
||||||
|
FieldFilterable(types.FilterType{
|
||||||
|
FormType: form.SelectSingle,
|
||||||
|
Options: types.FieldOptions{
|
||||||
|
{Text: "Mux", Value: "mux"},
|
||||||
|
{Text: "HWID", Value: "hwid"},
|
||||||
|
{Text: "IP", Value: "ip"},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Lock type", "lock_type", db.Varchar).
|
||||||
|
FieldFilterable(types.FilterType{
|
||||||
|
FormType: form.SelectSingle,
|
||||||
|
Options: types.FieldOptions{
|
||||||
|
{Text: "Manager", Value: "manager"},
|
||||||
|
},
|
||||||
|
}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Count", "count", db.Int).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Created at", "created_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Updated at", "updated_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
|
||||||
|
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
|
||||||
|
filters := make(map[string][]string)
|
||||||
|
listFilters := map[string][]string{
|
||||||
|
"offset": {strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)},
|
||||||
|
"limit": {param.PageSize},
|
||||||
|
}
|
||||||
|
for k, v := range param.Fields {
|
||||||
|
if strings.HasPrefix(k, "__") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.TrimSuffix(k, "__goadmin")
|
||||||
|
filters[key] = v
|
||||||
|
listFilters[key] = v
|
||||||
|
}
|
||||||
|
if param.SortField != "" {
|
||||||
|
if param.SortType == "asc" {
|
||||||
|
listFilters["sort_asc"] = []string{param.SortField}
|
||||||
|
} else {
|
||||||
|
listFilters["sort_desc"] = []string{param.SortField}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items, err := manager.GetConnectionLimiters(listFilters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
count, err := manager.GetConnectionLimitersCount(filters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
result := make([]map[string]interface{}, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
var data map[string]interface{}
|
||||||
|
raw, _ := json.Marshal(item)
|
||||||
|
json.Unmarshal(raw, &data)
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
return result, count
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetDeleteFn(func(ids []string) error {
|
||||||
|
for _, id := range ids {
|
||||||
|
i, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := manager.DeleteConnectionLimiter(i); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetTable("connection_limiters").SetTitle("Connection Limiters").SetDescription("Connection Limiters")
|
||||||
|
|
||||||
|
formList := connectionLimiterTable.GetForm()
|
||||||
|
formList.AddField("ID", "id", db.Int, form.Default).
|
||||||
|
FieldNotAllowAdd().
|
||||||
|
FieldNotAllowEdit()
|
||||||
|
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
|
||||||
|
FieldMust().
|
||||||
|
FieldOptions(squadOptions).
|
||||||
|
FieldDisableWhenUpdate()
|
||||||
|
formList.AddField("Username", "username", db.Varchar, form.Text).
|
||||||
|
FieldMust().
|
||||||
|
FieldDisplayButCanNotEditWhenUpdate()
|
||||||
|
formList.AddField("Outbound", "outbound", db.Varchar, form.Text).
|
||||||
|
FieldMust().
|
||||||
|
FieldDisplayButCanNotEditWhenUpdate()
|
||||||
|
formList.AddField("Strategy", "strategy", db.Varchar, form.SelectSingle).
|
||||||
|
FieldMust().
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "Connection", Value: "connection"},
|
||||||
|
}).
|
||||||
|
FieldDefault("connection")
|
||||||
|
formList.AddField("Connection type", "connection_type", db.Varchar, form.SelectSingle).
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "Mux", Value: "mux"},
|
||||||
|
{Text: "HWID", Value: "hwid"},
|
||||||
|
{Text: "IP", Value: "ip"},
|
||||||
|
})
|
||||||
|
formList.AddField("Lock type", "lock_type", db.Varchar, form.SelectSingle).
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "Manager", Value: "manager"},
|
||||||
|
})
|
||||||
|
formList.AddField("Count", "count", db.Int, form.Number).
|
||||||
|
FieldMust().
|
||||||
|
FieldDefault("0")
|
||||||
|
|
||||||
|
formList.SetInsertFn(func(values mForm.Values) error {
|
||||||
|
squadIDs := make([]int, len(values["squad_ids[]"]))
|
||||||
|
for i, rawSquadID := range values["squad_ids[]"] {
|
||||||
|
squadID, err := strconv.Atoi(rawSquadID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
squadIDs[i] = squadID
|
||||||
|
}
|
||||||
|
count, err := strconv.ParseUint(values.Get("count"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = manager.CreateConnectionLimiter(CM.ConnectionLimiterCreate{
|
||||||
|
SquadIDs: squadIDs,
|
||||||
|
Username: values.Get("username"),
|
||||||
|
Outbound: values.Get("outbound"),
|
||||||
|
Strategy: values.Get("strategy"),
|
||||||
|
ConnectionType: values.Get("connection_type"),
|
||||||
|
LockType: values.Get("lock_type"),
|
||||||
|
Count: uint32(count),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetUpdateFn(func(values mForm.Values) error {
|
||||||
|
id, err := strconv.Atoi(values.Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
count, err := strconv.ParseUint(values.Get("count"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = manager.UpdateConnectionLimiter(id, CM.ConnectionLimiterUpdate{
|
||||||
|
Username: values.Get("username"),
|
||||||
|
Outbound: values.Get("outbound"),
|
||||||
|
Strategy: values.Get("strategy"),
|
||||||
|
ConnectionType: values.Get("connection_type"),
|
||||||
|
LockType: values.Get("lock_type"),
|
||||||
|
Count: uint32(count),
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetTable("connection_limiters").SetTitle("Connection Limiters").SetDescription("Connection Limiters")
|
||||||
|
return connectionLimiterTable
|
||||||
|
}
|
||||||
|
}
|
||||||
201
service/admin_panel/tables/node.go
Normal file
201
service/admin_panel/tables/node.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoAdminGroup/go-admin/context"
|
||||||
|
"github.com/GoAdminGroup/go-admin/modules/config"
|
||||||
|
"github.com/GoAdminGroup/go-admin/modules/db"
|
||||||
|
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types/form"
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func label(ctx *context.Context) types.LabelAttribute {
|
||||||
|
return template.Get(ctx, config.GetTheme()).Label().SetType("success")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NodeTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (nodeTable table.Table) {
|
||||||
|
return func(ctx *context.Context) (nodeTable table.Table) {
|
||||||
|
nodeTable = table.NewDefaultTable(ctx, table.Config{
|
||||||
|
CanAdd: true,
|
||||||
|
Editable: true,
|
||||||
|
Deletable: true,
|
||||||
|
Exportable: true,
|
||||||
|
PrimaryKey: table.PrimaryKey{
|
||||||
|
Type: db.Varchar,
|
||||||
|
Name: "uuid",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
squads, err := manager.GetSquads(map[string][]string{})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
squadsByID := make(map[int]string, len(squads))
|
||||||
|
squadOptions := make(types.FieldOptions, len(squads))
|
||||||
|
for i, squad := range squads {
|
||||||
|
squadsByID[squad.ID] = squad.Name
|
||||||
|
squadOptions[i] = types.FieldOption{
|
||||||
|
Text: squad.Name,
|
||||||
|
Value: strconv.Itoa(squad.ID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info := nodeTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
|
||||||
|
info.AddField("UUID", "uuid", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Name", "name", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Squads", "squad_ids", db.Varchar).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
values := model.Row["squad_ids"].([]interface{})
|
||||||
|
labels := template.HTML("")
|
||||||
|
labelTpl := label(ctx).SetType("success")
|
||||||
|
labelValues := make([]string, len(values))
|
||||||
|
for i, squadID := range values {
|
||||||
|
labelValues[i] = squadsByID[int(squadID.(float64))]
|
||||||
|
}
|
||||||
|
for key, label := range labelValues {
|
||||||
|
if key == len(labelValues)-1 {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
} else {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
})
|
||||||
|
info.AddField("Status", "status", db.Varchar).
|
||||||
|
FieldDisplay(func(value types.FieldModel) interface{} {
|
||||||
|
uuid := value.Row["uuid"].(string)
|
||||||
|
return manager.GetNodeStatus(uuid)
|
||||||
|
})
|
||||||
|
info.AddField("Created at", "created_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Updated at", "updated_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
|
||||||
|
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
|
||||||
|
filters := make(map[string][]string, len(param.Fields))
|
||||||
|
listFilters := make(map[string][]string, len(param.Fields)+2)
|
||||||
|
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
|
||||||
|
listFilters["limit"] = []string{param.PageSize}
|
||||||
|
for key, values := range param.Fields {
|
||||||
|
if key == "__pk" {
|
||||||
|
key = "uuid"
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(key, "__") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key = strings.TrimSuffix(key, "__goadmin")
|
||||||
|
}
|
||||||
|
filters[key] = values
|
||||||
|
listFilters[key] = values
|
||||||
|
}
|
||||||
|
if param.SortField != "" {
|
||||||
|
if param.SortType == "asc" {
|
||||||
|
listFilters["sort_asc"] = []string{param.SortField}
|
||||||
|
} else {
|
||||||
|
listFilters["sort_desc"] = []string{param.SortField}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes, err := manager.GetNodes(listFilters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
count, err := manager.GetNodesCount(filters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
result := make([]map[string]interface{}, 0, len(nodes))
|
||||||
|
for _, node := range nodes {
|
||||||
|
var data map[string]interface{}
|
||||||
|
rawData, _ := json.Marshal(node)
|
||||||
|
json.Unmarshal(rawData, &data)
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
return result, count
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetDeleteFn(func(ids []string) error {
|
||||||
|
for _, uuid := range ids {
|
||||||
|
if _, err := manager.DeleteNode(uuid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetTable("nodes").SetTitle("Nodes").SetDescription("Nodes")
|
||||||
|
|
||||||
|
defaultUUID, _ := uuid.NewV4()
|
||||||
|
formList := nodeTable.GetForm()
|
||||||
|
formList.AddField("UUID", "uuid", db.Varchar, form.Text).
|
||||||
|
FieldMust().
|
||||||
|
FieldNotAllowEdit().
|
||||||
|
FieldDefault(defaultUUID.String())
|
||||||
|
formList.AddField("Name", "name", db.Varchar, form.Text).
|
||||||
|
FieldMust()
|
||||||
|
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
|
||||||
|
FieldMust().
|
||||||
|
FieldOptions(squadOptions).
|
||||||
|
FieldDisableWhenUpdate()
|
||||||
|
|
||||||
|
formList.SetInsertFn(func(values mForm.Values) (err error) {
|
||||||
|
squadIDs := make([]int, len(values["squad_ids[]"]))
|
||||||
|
for i, rawSquadID := range values["squad_ids[]"] {
|
||||||
|
squadID, err := strconv.Atoi(rawSquadID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
squadIDs[i] = squadID
|
||||||
|
}
|
||||||
|
_, err = manager.CreateNode(CM.NodeCreate{
|
||||||
|
UUID: values.Get("uuid"),
|
||||||
|
Name: values.Get("name"),
|
||||||
|
SquadIDs: squadIDs,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetUpdateFn(func(values mForm.Values) (err error) {
|
||||||
|
uuid := values.Get("uuid")
|
||||||
|
_, err = manager.UpdateNode(uuid, CM.NodeUpdate{
|
||||||
|
Name: values.Get("name"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetTable("nodes").SetTitle("Nodes").SetDescription("Nodes")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
164
service/admin_panel/tables/squad.go
Normal file
164
service/admin_panel/tables/squad.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoAdminGroup/go-admin/context"
|
||||||
|
"github.com/GoAdminGroup/go-admin/modules/db"
|
||||||
|
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types/form"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SquadTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (squadTable table.Table) {
|
||||||
|
return func(ctx *context.Context) (squadTable table.Table) {
|
||||||
|
squadTable = table.NewDefaultTable(ctx, table.Config{
|
||||||
|
CanAdd: true,
|
||||||
|
Editable: true,
|
||||||
|
Deletable: true,
|
||||||
|
Exportable: true,
|
||||||
|
PrimaryKey: table.PrimaryKey{
|
||||||
|
Type: db.Int,
|
||||||
|
Name: table.DefaultPrimaryKeyName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
info := squadTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
|
||||||
|
info.AddField("ID", "id", db.Int).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Name", "name", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Created At", "created_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldSortable().
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange})
|
||||||
|
info.AddField("Updated At", "updated_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldSortable().
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange})
|
||||||
|
|
||||||
|
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
|
||||||
|
filters := make(map[string][]string, len(param.Fields))
|
||||||
|
listFilters := make(map[string][]string, len(param.Fields)+2)
|
||||||
|
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
|
||||||
|
listFilters["limit"] = []string{param.PageSize}
|
||||||
|
for key, values := range param.Fields {
|
||||||
|
if key == "__pk" {
|
||||||
|
key = "pk"
|
||||||
|
} else if strings.HasPrefix(key, "__") {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
key = strings.TrimSuffix(key, "__goadmin")
|
||||||
|
}
|
||||||
|
filters[key] = values
|
||||||
|
listFilters[key] = values
|
||||||
|
}
|
||||||
|
if param.SortField != "" {
|
||||||
|
if param.SortType == "asc" {
|
||||||
|
listFilters["sort_asc"] = []string{param.SortField}
|
||||||
|
} else {
|
||||||
|
listFilters["sort_desc"] = []string{param.SortField}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
squads, err := manager.GetSquads(listFilters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
count, err := manager.GetSquadsCount(filters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
result := make([]map[string]interface{}, 0, len(squads))
|
||||||
|
for _, squad := range squads {
|
||||||
|
var data map[string]interface{}
|
||||||
|
rawData, _ := json.Marshal(squad)
|
||||||
|
json.Unmarshal(rawData, &data)
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
return result, count
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetDeleteFn(func(ids []string) error {
|
||||||
|
for _, id := range ids {
|
||||||
|
intID, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := manager.DeleteSquad(intID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetTable("squads").SetTitle("Squads").SetDescription("Squads")
|
||||||
|
|
||||||
|
formList := squadTable.GetForm()
|
||||||
|
formList.AddField("ID", "id", db.Int, form.Default).
|
||||||
|
FieldNotAllowAdd().
|
||||||
|
FieldNotAllowEdit()
|
||||||
|
formList.AddField("Name", "name", db.Varchar, form.Text).
|
||||||
|
FieldMust()
|
||||||
|
|
||||||
|
formList.SetInsertFn(func(values mForm.Values) (err error) {
|
||||||
|
_, err = manager.CreateSquad(CM.SquadCreate{
|
||||||
|
Name: values.Get("name"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if ve, ok := err.(validator.ValidationErrors); ok {
|
||||||
|
var errors []string
|
||||||
|
for _, e := range ve {
|
||||||
|
switch e.Tag() {
|
||||||
|
case "required":
|
||||||
|
errors = append(errors, e.StructField()+": required field missing")
|
||||||
|
default:
|
||||||
|
errors = append(errors, e.StructField()+": invalid request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%s", strings.Join(errors, "<br>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetUpdateFn(func(values mForm.Values) (err error) {
|
||||||
|
id, err := strconv.Atoi(values.Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = manager.UpdateSquad(id, CM.SquadUpdate{
|
||||||
|
Name: values.Get("name"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetTable("squads").SetTitle("Squads").SetDescription("Squads")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
282
service/admin_panel/tables/user.go
Normal file
282
service/admin_panel/tables/user.go
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
package tables
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GoAdminGroup/go-admin/context"
|
||||||
|
"github.com/GoAdminGroup/go-admin/modules/db"
|
||||||
|
mForm "github.com/GoAdminGroup/go-admin/plugins/admin/modules/form"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/parameter"
|
||||||
|
"github.com/GoAdminGroup/go-admin/plugins/admin/modules/table"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types"
|
||||||
|
"github.com/GoAdminGroup/go-admin/template/types/form"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UserTableFactory(manager CM.Manager, logger log.Logger) func(ctx *context.Context) (userTable table.Table) {
|
||||||
|
return func(ctx *context.Context) (userTable table.Table) {
|
||||||
|
userTable = table.NewDefaultTable(ctx, table.Config{
|
||||||
|
CanAdd: true,
|
||||||
|
Editable: true,
|
||||||
|
Deletable: true,
|
||||||
|
Exportable: true,
|
||||||
|
PrimaryKey: table.PrimaryKey{
|
||||||
|
Type: db.Int,
|
||||||
|
Name: table.DefaultPrimaryKeyName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
squads, err := manager.GetSquads(map[string][]string{})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
squadsByID := make(map[int]string, len(squads))
|
||||||
|
squadOptions := make(types.FieldOptions, len(squads))
|
||||||
|
for i, squad := range squads {
|
||||||
|
squadsByID[squad.ID] = squad.Name
|
||||||
|
squadOptions[i] = types.FieldOption{
|
||||||
|
Text: squad.Name,
|
||||||
|
Value: strconv.Itoa(squad.ID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info := userTable.GetInfo().SetFilterFormLayout(form.LayoutFilter)
|
||||||
|
info.AddField("ID", "id", db.Int).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Squads", "squad_ids", db.Varchar).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
values := model.Row["squad_ids"].([]interface{})
|
||||||
|
labels := template.HTML("")
|
||||||
|
labelTpl := label(ctx).SetType("success")
|
||||||
|
labelValues := make([]string, len(values))
|
||||||
|
for i, squadID := range values {
|
||||||
|
labelValues[i] = squadsByID[int(squadID.(float64))]
|
||||||
|
}
|
||||||
|
for key, label := range labelValues {
|
||||||
|
if key == len(labelValues)-1 {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
} else {
|
||||||
|
labels += labelTpl.SetContent(template.HTML(label)).GetContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
})
|
||||||
|
info.AddField("Username", "username", db.Varchar).
|
||||||
|
FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Type", "type", db.Varchar).
|
||||||
|
FieldFilterable(
|
||||||
|
types.FilterType{
|
||||||
|
FormType: form.SelectSingle,
|
||||||
|
Options: types.FieldOptions{
|
||||||
|
{Text: "Hysteria", Value: "hysteria"},
|
||||||
|
{Text: "Hysteria2", Value: "hysteria2"},
|
||||||
|
{Text: "Trojan", Value: "trojan"},
|
||||||
|
{Text: "TUIC", Value: "tuic"},
|
||||||
|
{Text: "VLESS", Value: "vless"},
|
||||||
|
{Text: "VMess", Value: "vmess"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Inbound", "inbound", db.Varchar).FieldFilterable().
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Created at", "created_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
info.AddField("Updated at", "updated_at", db.Datetime).
|
||||||
|
FieldDisplay(func(model types.FieldModel) interface{} {
|
||||||
|
t, err := time.Parse(time.RFC3339, model.Value)
|
||||||
|
if err != nil {
|
||||||
|
return model.Value
|
||||||
|
}
|
||||||
|
return t.Format("2006-01-02 15:04:05")
|
||||||
|
}).
|
||||||
|
FieldFilterable(types.FilterType{FormType: form.DatetimeRange}).
|
||||||
|
FieldSortable()
|
||||||
|
|
||||||
|
info.SetGetDataFn(func(param parameter.Parameters) ([]map[string]interface{}, int) {
|
||||||
|
filters := make(map[string][]string, len(param.Fields))
|
||||||
|
listFilters := make(map[string][]string, len(param.Fields)+2)
|
||||||
|
listFilters["offset"] = []string{strconv.Itoa((param.PageInt - 1) * param.PageSizeInt)}
|
||||||
|
listFilters["limit"] = []string{param.PageSize}
|
||||||
|
for key, values := range param.Fields {
|
||||||
|
if key == "__pk" {
|
||||||
|
key = "pk"
|
||||||
|
} else {
|
||||||
|
if strings.HasPrefix(key, "__") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key = strings.TrimSuffix(key, "__goadmin")
|
||||||
|
}
|
||||||
|
filters[key] = values
|
||||||
|
listFilters[key] = values
|
||||||
|
}
|
||||||
|
if param.SortField != "" {
|
||||||
|
if param.SortType == "asc" {
|
||||||
|
listFilters["sort_asc"] = []string{param.SortField}
|
||||||
|
} else {
|
||||||
|
listFilters["sort_desc"] = []string{param.SortField}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
users, err := manager.GetUsers(listFilters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
count, err := manager.GetUsersCount(filters)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
result := make([]map[string]interface{}, 0, len(users))
|
||||||
|
for _, user := range users {
|
||||||
|
var data map[string]interface{}
|
||||||
|
rawData, _ := json.Marshal(user)
|
||||||
|
json.Unmarshal(rawData, &data)
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
return result, count
|
||||||
|
})
|
||||||
|
info.SetDeleteFn(func(ids []string) error {
|
||||||
|
for _, id := range ids {
|
||||||
|
value, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := manager.DeleteUser(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
info.SetTable("users").SetTitle("Users").SetDescription("Users")
|
||||||
|
|
||||||
|
formList := userTable.GetForm()
|
||||||
|
formList.AddField("ID", "id", db.Int, form.Default).
|
||||||
|
FieldNotAllowEdit().
|
||||||
|
FieldNotAllowAdd()
|
||||||
|
formList.AddField("Squads", "squad_ids", db.Varchar, form.Select).
|
||||||
|
FieldMust().
|
||||||
|
FieldOptions(squadOptions).
|
||||||
|
FieldDisableWhenUpdate()
|
||||||
|
formList.AddField("Username", "username", db.Varchar, form.Text).
|
||||||
|
FieldMust().
|
||||||
|
FieldDisplayButCanNotEditWhenUpdate()
|
||||||
|
formList.AddField("Type", "type", db.Varchar, form.SelectSingle).
|
||||||
|
FieldMust().
|
||||||
|
FieldDisplayButCanNotEditWhenUpdate().
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "Hysteria", Value: "hysteria"},
|
||||||
|
{Text: "Hysteria2", Value: "hysteria2"},
|
||||||
|
{Text: "Trojan", Value: "trojan"},
|
||||||
|
{Text: "TUIC", Value: "tuic"},
|
||||||
|
{Text: "VLESS", Value: "vless"},
|
||||||
|
{Text: "VMess", Value: "vmess"},
|
||||||
|
}).
|
||||||
|
FieldOnChooseOptionsHide([]string{""}, "inbound").
|
||||||
|
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic"}, "uuid").
|
||||||
|
FieldOnChooseOptionsHide([]string{"", "vless", "vmess"}, "password").
|
||||||
|
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic", "vmess"}, "flow").
|
||||||
|
FieldOnChooseOptionsHide([]string{"", "hysteria", "hysteria2", "shadowsocks", "trojan", "tuic", "vless"}, "alter_id")
|
||||||
|
formList.AddField("Inbound", "inbound", db.Varchar, form.Text).
|
||||||
|
FieldMust().
|
||||||
|
FieldDisplayButCanNotEditWhenUpdate().
|
||||||
|
FieldOptionInitFn(func(val types.FieldModel) types.FieldOptions {
|
||||||
|
return types.FieldOptions{
|
||||||
|
{Value: val.Value, Text: val.Value, Selected: true},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
formList.AddField("UUID", "uuid", db.Varchar, form.Text)
|
||||||
|
formList.AddField("Password", "password", db.Varchar, form.Text)
|
||||||
|
formList.AddField("Flow", "flow", db.Varchar, form.SelectSingle).
|
||||||
|
FieldOptions(types.FieldOptions{
|
||||||
|
{Text: "xtls-rprx-vision", Value: "xtls-rprx-vision"},
|
||||||
|
})
|
||||||
|
formList.AddField("Alter ID", "alter_id", db.Varchar, form.Number).
|
||||||
|
FieldDefault("0")
|
||||||
|
|
||||||
|
formList.SetInsertFn(func(values mForm.Values) (err error) {
|
||||||
|
squadIDs := make([]int, len(values["squad_ids[]"]))
|
||||||
|
for i, rawSquadID := range values["squad_ids[]"] {
|
||||||
|
squadID, err := strconv.Atoi(rawSquadID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
squadIDs[i] = squadID
|
||||||
|
}
|
||||||
|
var alterId int
|
||||||
|
if value := values.Get("alter_id"); value != "" {
|
||||||
|
alterId, err = strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = manager.CreateUser(CM.UserCreate{
|
||||||
|
SquadIDs: squadIDs,
|
||||||
|
Username: values.Get("username"),
|
||||||
|
Type: values.Get("type"),
|
||||||
|
Inbound: values.Get("inbound"),
|
||||||
|
UUID: values.Get("uuid"),
|
||||||
|
Password: values.Get("password"),
|
||||||
|
Flow: values.Get("flow"),
|
||||||
|
AlterID: alterId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if ve, ok := err.(validator.ValidationErrors); ok {
|
||||||
|
var errors []string
|
||||||
|
for _, e := range ve {
|
||||||
|
switch e.Tag() {
|
||||||
|
case "required":
|
||||||
|
errors = append(errors, e.StructField()+": required field missing")
|
||||||
|
case "uuid4":
|
||||||
|
errors = append(errors, e.StructField()+": invalid UUID")
|
||||||
|
default:
|
||||||
|
errors = append(errors, e.StructField()+": invalid request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = fmt.Errorf("%s", strings.Join(errors, "<br>"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
formList.SetUpdateFn(func(values mForm.Values) (err error) {
|
||||||
|
id, err := strconv.Atoi(values.Get("id"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var alterId int
|
||||||
|
if value := values.Get("alter_id"); value != "" {
|
||||||
|
alterId, err = strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = manager.UpdateUser(id, CM.UserUpdate{
|
||||||
|
UUID: values.Get("uuid"),
|
||||||
|
Password: values.Get("password"),
|
||||||
|
Flow: values.Get("flow"),
|
||||||
|
AlterID: alterId,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
formList.SetTable("users").SetTitle("Users").SetDescription("Users")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
164
service/manager/constant/dto.go
Normal file
164
service/manager/constant/dto.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Squad struct {
|
||||||
|
ID int `json:"id" validate:"required"`
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
CreatedAt time.Time `json:"created_at" validate:"required"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SquadCreate struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SquadUpdate struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
UUID string `json:"uuid" validate:"required,uuid4"`
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
CreatedAt time.Time `json:"created_at" validate:"required"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeCreate struct {
|
||||||
|
UUID string `json:"uuid" validate:"required,uuid4"`
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeUpdate struct {
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseNode struct {
|
||||||
|
UUID string `json:"uuid" validate:"required,uuid4"`
|
||||||
|
Name string `json:"name" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int `json:"id" validate:"required"`
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Type string `json:"type" validate:"required"`
|
||||||
|
Inbound string `json:"inbound" validate:"required"`
|
||||||
|
UUID string `json:"uuid" validate:"required"`
|
||||||
|
Password string `json:"password" validate:"required"`
|
||||||
|
Flow string `json:"flow" validate:"required"`
|
||||||
|
AlterID int `json:"alter_id" validate:"required"`
|
||||||
|
CreatedAt time.Time `json:"created_at" validate:"required"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserCreate struct {
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Type string `json:"type" validate:"required,oneof=hysteria hysteria2 trojan tuic vless vmess"`
|
||||||
|
Inbound string `json:"inbound" validate:"required"`
|
||||||
|
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||||
|
Password string `json:"password" validate:"omitempty"`
|
||||||
|
Flow string `json:"flow" validate:"omitempty"`
|
||||||
|
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserUpdate struct {
|
||||||
|
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||||
|
Password string `json:"password" validate:"omitempty"`
|
||||||
|
Flow string `json:"flow" validate:"omitempty"`
|
||||||
|
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseUser struct {
|
||||||
|
UUID string `json:"uuid" validate:"omitempty,uuid4"`
|
||||||
|
Password string `json:"password" validate:"omitempty"`
|
||||||
|
Flow string `json:"flow" validate:"omitempty"`
|
||||||
|
AlterID int `json:"alter_id" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiter struct {
|
||||||
|
ID int `json:"id" validate:"required"`
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=connection"`
|
||||||
|
ConnectionType string `json:"connection_type" validate:"omitempty,oneof=hwid mux ip"`
|
||||||
|
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
|
||||||
|
Count uint32 `json:"count" validate:"required"`
|
||||||
|
CreatedAt time.Time `json:"created_at" validate:"required"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiterCreate struct {
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=connection"`
|
||||||
|
ConnectionType string `json:"type" validate:"omitempty,oneof=hwid mux ip"`
|
||||||
|
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
|
||||||
|
Count uint32 `json:"count" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiterUpdate struct {
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=connection"`
|
||||||
|
ConnectionType string `json:"type" validate:"omitempty,oneof=hwid mux ip"`
|
||||||
|
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
|
||||||
|
Count uint32 `json:"count" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseConnectionLimiter struct {
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=connection"`
|
||||||
|
ConnectionType string `json:"type" validate:"omitempty,oneof=hwid mux ip"`
|
||||||
|
LockType string `json:"lock_type" validate:"omitempty,oneof=manager"`
|
||||||
|
Count uint32 `json:"count" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiter struct {
|
||||||
|
ID int `json:"id" validate:"required"`
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required"`
|
||||||
|
Mode string `json:"mode" validate:"required"`
|
||||||
|
ConnectionType string `json:"connection_type" validate:"omitempty"`
|
||||||
|
Speed string `json:"speed" validate:"required"`
|
||||||
|
RawSpeed uint64 `json:"raw_speed" validate:"required"`
|
||||||
|
CreatedAt time.Time `json:"created_at" validate:"required"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiterCreate struct {
|
||||||
|
SquadIDs []int `json:"squad_ids" validate:"required"`
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=global connection"`
|
||||||
|
Mode string `json:"mode" validate:"required"`
|
||||||
|
ConnectionType string `json:"connection_type" validate:"omitempty"`
|
||||||
|
Speed string `json:"speed" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiterUpdate struct {
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=global connection"`
|
||||||
|
Mode string `json:"mode" validate:"required"`
|
||||||
|
ConnectionType string `json:"connection_type" validate:"omitempty"`
|
||||||
|
Speed string `json:"speed" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseBandwidthLimiter struct {
|
||||||
|
Username string `json:"username" validate:"required"`
|
||||||
|
Outbound string `json:"outbound" validate:"required"`
|
||||||
|
Strategy string `json:"strategy" validate:"required,oneof=global connection"`
|
||||||
|
Mode string `json:"mode" validate:"required"`
|
||||||
|
ConnectionType string `json:"connection_type" validate:"omitempty"`
|
||||||
|
Speed string `json:"speed" validate:"required"`
|
||||||
|
RawSpeed uint64 `json:"raw_speed" validate:"required"`
|
||||||
|
}
|
||||||
5
service/manager/constant/error.go
Normal file
5
service/manager/constant/error.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
var ErrNotFound = E.New("not found")
|
||||||
48
service/manager/constant/manager.go
Normal file
48
service/manager/constant/manager.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
type NodeManager interface {
|
||||||
|
AddNode(id string, node ConnectedNode) error
|
||||||
|
AcquireLock(limiterId int, id string) (string, error)
|
||||||
|
RefreshLock(limiterId int, id string, handleId string) error
|
||||||
|
ReleaseLock(limiterId int, id string, handleId string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
NodeManager
|
||||||
|
|
||||||
|
CreateSquad(user SquadCreate) (Squad, error)
|
||||||
|
GetSquads(filters map[string][]string) ([]Squad, error)
|
||||||
|
GetSquadsCount(filters map[string][]string) (int, error)
|
||||||
|
GetSquad(id int) (Squad, error)
|
||||||
|
UpdateSquad(id int, user SquadUpdate) (Squad, error)
|
||||||
|
DeleteSquad(id int) (Squad, error)
|
||||||
|
|
||||||
|
CreateNode(node NodeCreate) (Node, error)
|
||||||
|
GetNodes(filters map[string][]string) ([]Node, error)
|
||||||
|
GetNodesCount(filters map[string][]string) (int, error)
|
||||||
|
GetNode(uuid string) (Node, error)
|
||||||
|
GetNodeStatus(uuid string) string
|
||||||
|
UpdateNode(uuid string, node NodeUpdate) (Node, error)
|
||||||
|
DeleteNode(uuid string) (Node, error)
|
||||||
|
|
||||||
|
CreateUser(user UserCreate) (User, error)
|
||||||
|
GetUsers(filters map[string][]string) ([]User, error)
|
||||||
|
GetUsersCount(filters map[string][]string) (int, error)
|
||||||
|
GetUser(id int) (User, error)
|
||||||
|
UpdateUser(id int, user UserUpdate) (User, error)
|
||||||
|
DeleteUser(id int) (User, error)
|
||||||
|
|
||||||
|
CreateBandwidthLimiter(limiter BandwidthLimiterCreate) (BandwidthLimiter, error)
|
||||||
|
GetBandwidthLimiters(filters map[string][]string) ([]BandwidthLimiter, error)
|
||||||
|
GetBandwidthLimitersCount(filters map[string][]string) (int, error)
|
||||||
|
GetBandwidthLimiter(id int) (BandwidthLimiter, error)
|
||||||
|
UpdateBandwidthLimiter(id int, limiter BandwidthLimiterUpdate) (BandwidthLimiter, error)
|
||||||
|
DeleteBandwidthLimiter(id int) (BandwidthLimiter, error)
|
||||||
|
|
||||||
|
CreateConnectionLimiter(limiter ConnectionLimiterCreate) (ConnectionLimiter, error)
|
||||||
|
GetConnectionLimiters(filters map[string][]string) ([]ConnectionLimiter, error)
|
||||||
|
GetConnectionLimitersCount(filters map[string][]string) (int, error)
|
||||||
|
GetConnectionLimiter(id int) (ConnectionLimiter, error)
|
||||||
|
UpdateConnectionLimiter(id int, limiter ConnectionLimiterUpdate) (ConnectionLimiter, error)
|
||||||
|
DeleteConnectionLimiter(id int) (ConnectionLimiter, error)
|
||||||
|
}
|
||||||
20
service/manager/constant/node.go
Normal file
20
service/manager/constant/node.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
type ConnectedNode interface {
|
||||||
|
UpdateUser(user User)
|
||||||
|
UpdateUsers(users []User)
|
||||||
|
DeleteUser(user User)
|
||||||
|
|
||||||
|
UpdateConnectionLimiter(limiter ConnectionLimiter)
|
||||||
|
UpdateConnectionLimiters(limiter []ConnectionLimiter)
|
||||||
|
DeleteConnectionLimiter(limiter ConnectionLimiter)
|
||||||
|
|
||||||
|
UpdateBandwidthLimiter(limiter BandwidthLimiter)
|
||||||
|
UpdateBandwidthLimiters(limiter []BandwidthLimiter)
|
||||||
|
DeleteBandwidthLimiter(limiter BandwidthLimiter)
|
||||||
|
|
||||||
|
IsLocal() bool
|
||||||
|
IsOnline() bool
|
||||||
|
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
38
service/manager/constant/repository.go
Normal file
38
service/manager/constant/repository.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
type Repository interface {
|
||||||
|
CreateSquad(user SquadCreate) (Squad, error)
|
||||||
|
GetSquads(filters map[string][]string) ([]Squad, error)
|
||||||
|
GetSquadsCount(filters map[string][]string) (int, error)
|
||||||
|
GetSquad(id int) (Squad, error)
|
||||||
|
UpdateSquad(id int, user SquadUpdate) (Squad, error)
|
||||||
|
DeleteSquad(id int) (Squad, error)
|
||||||
|
|
||||||
|
CreateNode(node NodeCreate) (Node, error)
|
||||||
|
GetNodes(filters map[string][]string) ([]Node, error)
|
||||||
|
GetNodesCount(filters map[string][]string) (int, error)
|
||||||
|
GetNode(uuid string) (Node, error)
|
||||||
|
UpdateNode(uuid string, node NodeUpdate) (Node, error)
|
||||||
|
DeleteNode(uuid string) (Node, error)
|
||||||
|
|
||||||
|
CreateUser(user UserCreate) (User, error)
|
||||||
|
GetUsers(filters map[string][]string) ([]User, error)
|
||||||
|
GetUsersCount(filters map[string][]string) (int, error)
|
||||||
|
GetUser(id int) (User, error)
|
||||||
|
UpdateUser(id int, user UserUpdate) (User, error)
|
||||||
|
DeleteUser(id int) (User, error)
|
||||||
|
|
||||||
|
CreateConnectionLimiter(limiter ConnectionLimiterCreate) (ConnectionLimiter, error)
|
||||||
|
GetConnectionLimiters(filters map[string][]string) ([]ConnectionLimiter, error)
|
||||||
|
GetConnectionLimitersCount(filters map[string][]string) (int, error)
|
||||||
|
GetConnectionLimiter(id int) (ConnectionLimiter, error)
|
||||||
|
UpdateConnectionLimiter(id int, limiter ConnectionLimiterUpdate) (ConnectionLimiter, error)
|
||||||
|
DeleteConnectionLimiter(id int) (ConnectionLimiter, error)
|
||||||
|
|
||||||
|
CreateBandwidthLimiter(limiter BandwidthLimiterCreate) (BandwidthLimiter, error)
|
||||||
|
GetBandwidthLimiters(filters map[string][]string) ([]BandwidthLimiter, error)
|
||||||
|
GetBandwidthLimitersCount(filters map[string][]string) (int, error)
|
||||||
|
GetBandwidthLimiter(id int) (BandwidthLimiter, error)
|
||||||
|
UpdateBandwidthLimiter(id int, limiter BandwidthLimiterUpdate) (BandwidthLimiter, error)
|
||||||
|
DeleteBandwidthLimiter(id int) (BandwidthLimiter, error)
|
||||||
|
}
|
||||||
155
service/manager/repository/postgresql/filter.go
Normal file
155
service/manager/repository/postgresql/filter.go
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/huandu/go-sqlbuilder"
|
||||||
|
"github.com/sagernet/sing/common/byteformats"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Filter func(sb *sqlbuilder.SelectBuilder, value []string) error
|
||||||
|
|
||||||
|
func EqualFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.Where(sb.Equal(field, value[0]))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EqualOrNullFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.Where(sb.Or(sb.Equal(field, value[0]), sb.IsNull(field)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GreaterThanFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.Where(sb.GreaterThan(field, value[0]))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LessThanFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.Where(sb.LessThan(field, value[0]))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GreaterEqualThanFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.Where(sb.GreaterEqualThan(field, value[0]))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LessEqualThanFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.Where(sb.LessEqualThan(field, value[0]))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpeedGreaterEqualThanFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
bytesSpeed, err := json.Marshal(value[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
speed := &byteformats.NetworkBytesCompat{}
|
||||||
|
err = speed.UnmarshalJSON(bytesSpeed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sb.Where(sb.GreaterEqualThan(field, speed.Value()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpeedLessEqualThanFilter(field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
bytesSpeed, err := json.Marshal(value[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
speed := &byteformats.NetworkBytesCompat{}
|
||||||
|
err = speed.UnmarshalJSON(bytesSpeed)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sb.Where(sb.LessEqualThan(field, speed.Value()))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExistsAndWhereInFilter(subquery *sqlbuilder.SelectBuilder, field string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
values := make([]interface{}, len(value))
|
||||||
|
for i, v := range value {
|
||||||
|
values[i] = v
|
||||||
|
}
|
||||||
|
subquery.Where(subquery.In(field, values...))
|
||||||
|
sb.Where(sb.Exists(subquery))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortAscFilter() Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.OrderByAsc(value[0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SortDescFilter() Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
sb.OrderByDesc(value[0])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplacedSortAscFilter(replace map[string]string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
if replacedValue, ok := replace[value[0]]; ok {
|
||||||
|
sb.OrderByAsc(replacedValue)
|
||||||
|
} else {
|
||||||
|
sb.OrderByAsc(value[0])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReplacedSortDescFilter(replace map[string]string) Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
if replacedValue, ok := replace[value[0]]; ok {
|
||||||
|
sb.OrderByDesc(replacedValue)
|
||||||
|
} else {
|
||||||
|
sb.OrderByDesc(value[0])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LimitFilter() Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
limit, err := strconv.Atoi(value[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sb.Limit(limit)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OffsetFilter() Filter {
|
||||||
|
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||||
|
offset, err := strconv.Atoi(value[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sb.Offset(offset)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
132
service/manager/repository/postgresql/migration.go
Normal file
132
service/manager/repository/postgresql/migration.go
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
|
"github.com/sagernet/sing-box/common/migrate/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
var migrations = map[string]string{
|
||||||
|
"1_initialize_schema.up.sql": `
|
||||||
|
CREATE TABLE squads (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE nodes (
|
||||||
|
uuid VARCHAR(36) PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE node_to_squad (
|
||||||
|
node_uuid VARCHAR(36) NOT NULL,
|
||||||
|
squad_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (node_uuid, squad_id),
|
||||||
|
FOREIGN KEY (node_uuid) REFERENCES nodes(uuid) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
node_uuid VARCHAR(36),
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
inbound TEXT NOT NULL,
|
||||||
|
uuid TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
flow TEXT NOT NULL,
|
||||||
|
alter_id INTEGER NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL,
|
||||||
|
UNIQUE (username, inbound)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE user_to_squad (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
squad_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, squad_id),
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE connection_limiters (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
outbound TEXT NOT NULL,
|
||||||
|
strategy TEXT NOT NULL,
|
||||||
|
connection_type TEXT NOT NULL,
|
||||||
|
lock_type TEXT NOT NULL,
|
||||||
|
count INTEGER NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL,
|
||||||
|
UNIQUE (username, outbound)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE connection_limiter_to_squad (
|
||||||
|
connection_limiter_id INTEGER NOT NULL,
|
||||||
|
squad_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (connection_limiter_id, squad_id),
|
||||||
|
FOREIGN KEY (connection_limiter_id) REFERENCES connection_limiters(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE bandwidth_limiters (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
outbound TEXT NOT NULL,
|
||||||
|
strategy TEXT NOT NULL,
|
||||||
|
mode TEXT NOT NULL,
|
||||||
|
connection_type TEXT NOT NULL,
|
||||||
|
speed TEXT NOT NULL,
|
||||||
|
raw_speed BIGINT NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP NOT NULL,
|
||||||
|
UNIQUE (username, outbound)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE bandwidth_limiter_to_squad (
|
||||||
|
bandwidth_limiter_id INTEGER NOT NULL,
|
||||||
|
squad_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (bandwidth_limiter_id, squad_id),
|
||||||
|
FOREIGN KEY (bandwidth_limiter_id) REFERENCES bandwidth_limiters(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (squad_id) REFERENCES squads(id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
`,
|
||||||
|
"1_initialize_schema.down.sql": `
|
||||||
|
DROP TABLE IF EXISTS squas;
|
||||||
|
DROP TABLE IF EXISTS nodes;
|
||||||
|
DROP TABLE IF EXISTS users;
|
||||||
|
DROP TABLE IF EXISTS bandwidth_limiters;
|
||||||
|
DROP TABLE IF EXISTS connection_limiters;
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
func Migrate(db *sql.DB) error {
|
||||||
|
driver, err := postgres.WithInstance(db, &postgres.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceDriver := source.NewRawDriver(migrations)
|
||||||
|
if err := sourceDriver.Init(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := migrate.NewWithInstance(
|
||||||
|
"raw",
|
||||||
|
sourceDriver,
|
||||||
|
"postgres",
|
||||||
|
driver,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.Up()
|
||||||
|
}
|
||||||
1347
service/manager/repository/postgresql/repository.go
Normal file
1347
service/manager/repository/postgresql/repository.go
Normal file
File diff suppressed because it is too large
Load Diff
598
service/manager/service.go
Normal file
598
service/manager/service.go
Normal file
@@ -0,0 +1,598 @@
|
|||||||
|
//go:build with_manager
|
||||||
|
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
"github.com/patrickmn/go-cache"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/manager/repository/postgresql"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *boxService.Registry) {
|
||||||
|
boxService.Register[option.ManagerServiceOptions](registry, C.TypeManager, NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
boxService.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
logger log.ContextLogger
|
||||||
|
repository constant.Repository
|
||||||
|
nodes map[string]constant.ConnectedNode
|
||||||
|
|
||||||
|
limiterLocks map[int]map[string]*cache.Cache
|
||||||
|
|
||||||
|
userValidator *validator.Validate
|
||||||
|
defaultValidator *validator.Validate
|
||||||
|
|
||||||
|
mtx sync.RWMutex
|
||||||
|
connLockMtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.ManagerServiceOptions) (adapter.Service, error) {
|
||||||
|
var repository constant.Repository
|
||||||
|
var err error
|
||||||
|
switch options.Database.Driver {
|
||||||
|
case "postgresql":
|
||||||
|
repository, err = postgresql.NewPostgreSQLRepository(ctx, options.Database.DSN)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown driver \"", options.Database.Driver, "\"")
|
||||||
|
}
|
||||||
|
userValidator := validator.New()
|
||||||
|
userValidator.RegisterStructValidation(func(sl validator.StructLevel) {
|
||||||
|
user := sl.Current().Interface().(constant.UserCreate)
|
||||||
|
switch user.Type {
|
||||||
|
case "vless":
|
||||||
|
if user.UUID == "" {
|
||||||
|
sl.ReportError(user.UUID, "uuid", "UUID", "required", "")
|
||||||
|
}
|
||||||
|
case "vmess":
|
||||||
|
if user.UUID == "" {
|
||||||
|
sl.ReportError(user.UUID, "uuid", "UUID", "required", "")
|
||||||
|
}
|
||||||
|
if user.AlterID == 0 {
|
||||||
|
sl.ReportError(user.AlterID, "alter_id", "AlterID", "required", "")
|
||||||
|
}
|
||||||
|
case "trojan", "shadowsocks", "hysteria", "hysteria2":
|
||||||
|
if user.Password == "" {
|
||||||
|
sl.ReportError(user.Password, "password", "Password", "required", "")
|
||||||
|
}
|
||||||
|
case "tuic":
|
||||||
|
if user.UUID == "" {
|
||||||
|
sl.ReportError(user.UUID, "uuid", "UUID", "required", "")
|
||||||
|
}
|
||||||
|
if user.Password == "" {
|
||||||
|
sl.ReportError(user.Password, "password", "Password", "required", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, constant.UserCreate{})
|
||||||
|
return &Service{
|
||||||
|
Adapter: boxService.NewAdapter(C.TypeManager, tag),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
repository: repository,
|
||||||
|
nodes: make(map[string]constant.ConnectedNode, 0),
|
||||||
|
limiterLocks: make(map[int]map[string]*cache.Cache),
|
||||||
|
userValidator: userValidator,
|
||||||
|
defaultValidator: validator.New(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateSquad(node constant.SquadCreate) (constant.Squad, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(node)
|
||||||
|
if err != nil {
|
||||||
|
return constant.Squad{}, err
|
||||||
|
}
|
||||||
|
createdSquad, err := s.repository.CreateSquad(node)
|
||||||
|
if err != nil {
|
||||||
|
return createdSquad, err
|
||||||
|
}
|
||||||
|
return createdSquad, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSquads(filters map[string][]string) ([]constant.Squad, error) {
|
||||||
|
return s.repository.GetSquads(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSquadsCount(filters map[string][]string) (int, error) {
|
||||||
|
return s.repository.GetSquadsCount(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetSquad(id int) (constant.Squad, error) {
|
||||||
|
return s.repository.GetSquad(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateSquad(id int, squad constant.SquadUpdate) (constant.Squad, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(squad)
|
||||||
|
if err != nil {
|
||||||
|
return constant.Squad{}, err
|
||||||
|
}
|
||||||
|
updatedSquad, err := s.repository.UpdateSquad(id, squad)
|
||||||
|
if err != nil {
|
||||||
|
return updatedSquad, err
|
||||||
|
}
|
||||||
|
return updatedSquad, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteSquad(id int) (constant.Squad, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
deletedSquad, err := s.repository.DeleteSquad(id)
|
||||||
|
if err != nil {
|
||||||
|
return deletedSquad, err
|
||||||
|
}
|
||||||
|
return deletedSquad, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateNode(node constant.NodeCreate) (constant.Node, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(node)
|
||||||
|
if err != nil {
|
||||||
|
return constant.Node{}, err
|
||||||
|
}
|
||||||
|
createdNode, err := s.repository.CreateNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return createdNode, err
|
||||||
|
}
|
||||||
|
return createdNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetNodes(filters map[string][]string) ([]constant.Node, error) {
|
||||||
|
return s.repository.GetNodes(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetNodesCount(filters map[string][]string) (int, error) {
|
||||||
|
return s.repository.GetNodesCount(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetNode(uuid string) (constant.Node, error) {
|
||||||
|
return s.repository.GetNode(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetNodeStatus(uuid string) string {
|
||||||
|
s.mtx.RLock()
|
||||||
|
defer s.mtx.RUnlock()
|
||||||
|
node, ok := s.nodes[uuid]
|
||||||
|
if !ok || !node.IsOnline() {
|
||||||
|
return "offline"
|
||||||
|
}
|
||||||
|
return "online"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateNode(uuid string, node constant.NodeUpdate) (constant.Node, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(node)
|
||||||
|
if err != nil {
|
||||||
|
return constant.Node{}, err
|
||||||
|
}
|
||||||
|
updatedNode, err := s.repository.UpdateNode(uuid, node)
|
||||||
|
if err != nil {
|
||||||
|
return updatedNode, err
|
||||||
|
}
|
||||||
|
return updatedNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteNode(uuid string) (constant.Node, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
deletedNode, err := s.repository.DeleteNode(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return deletedNode, err
|
||||||
|
}
|
||||||
|
node, ok := s.nodes[uuid]
|
||||||
|
if ok {
|
||||||
|
node.Close()
|
||||||
|
delete(s.nodes, uuid)
|
||||||
|
}
|
||||||
|
return deletedNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateUser(user constant.UserCreate) (constant.User, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.userValidator.Struct(user)
|
||||||
|
if err != nil {
|
||||||
|
return constant.User{}, err
|
||||||
|
}
|
||||||
|
createdUser, err := s.repository.CreateUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return createdUser, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(createdUser.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return createdUser, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.UpdateUser(createdUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createdUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetUsers(filters map[string][]string) ([]constant.User, error) {
|
||||||
|
return s.repository.GetUsers(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetUsersCount(filters map[string][]string) (int, error) {
|
||||||
|
return s.repository.GetUsersCount(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetUser(id int) (constant.User, error) {
|
||||||
|
return s.repository.GetUser(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateUser(id int, user constant.UserUpdate) (constant.User, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
updatedUser, err := s.repository.UpdateUser(id, user)
|
||||||
|
if err != nil {
|
||||||
|
return updatedUser, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(updatedUser.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return updatedUser, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.UpdateUser(updatedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteUser(id int) (constant.User, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
deletedUser, err := s.repository.DeleteUser(id)
|
||||||
|
if err != nil {
|
||||||
|
return deletedUser, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(deletedUser.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return deletedUser, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.DeleteUser(deletedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deletedUser, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateConnectionLimiter(limiter constant.ConnectionLimiterCreate) (constant.ConnectionLimiter, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return constant.ConnectionLimiter{}, err
|
||||||
|
}
|
||||||
|
createdLimiter, err := s.repository.CreateConnectionLimiter(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return createdLimiter, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(createdLimiter.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return createdLimiter, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.UpdateConnectionLimiter(createdLimiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createdLimiter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetConnectionLimiters(filters map[string][]string) ([]constant.ConnectionLimiter, error) {
|
||||||
|
return s.repository.GetConnectionLimiters(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetConnectionLimitersCount(filters map[string][]string) (int, error) {
|
||||||
|
return s.repository.GetConnectionLimitersCount(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetConnectionLimiter(id int) (constant.ConnectionLimiter, error) {
|
||||||
|
return s.repository.GetConnectionLimiter(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateConnectionLimiter(id int, limiter constant.ConnectionLimiterUpdate) (constant.ConnectionLimiter, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return constant.ConnectionLimiter{}, err
|
||||||
|
}
|
||||||
|
updatedLimiter, err := s.repository.UpdateConnectionLimiter(id, limiter)
|
||||||
|
if err != nil {
|
||||||
|
return updatedLimiter, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(updatedLimiter.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return updatedLimiter, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.UpdateConnectionLimiter(updatedLimiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if limiter.LockType != "manager" {
|
||||||
|
s.connLockMtx.Lock()
|
||||||
|
defer s.connLockMtx.Unlock()
|
||||||
|
delete(s.limiterLocks, id)
|
||||||
|
}
|
||||||
|
return updatedLimiter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteConnectionLimiter(id int) (constant.ConnectionLimiter, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
deletedLimiter, err := s.repository.DeleteConnectionLimiter(id)
|
||||||
|
if err != nil {
|
||||||
|
return deletedLimiter, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(deletedLimiter.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return deletedLimiter, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.DeleteConnectionLimiter(deletedLimiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if deletedLimiter.LockType == "manager" {
|
||||||
|
s.connLockMtx.Lock()
|
||||||
|
defer s.connLockMtx.Unlock()
|
||||||
|
delete(s.limiterLocks, id)
|
||||||
|
}
|
||||||
|
return deletedLimiter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CreateBandwidthLimiter(limiter constant.BandwidthLimiterCreate) (constant.BandwidthLimiter, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return constant.BandwidthLimiter{}, err
|
||||||
|
}
|
||||||
|
createdLimiter, err := s.repository.CreateBandwidthLimiter(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return createdLimiter, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(createdLimiter.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return createdLimiter, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.UpdateBandwidthLimiter(createdLimiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return createdLimiter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetBandwidthLimiters(filters map[string][]string) ([]constant.BandwidthLimiter, error) {
|
||||||
|
return s.repository.GetBandwidthLimiters(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetBandwidthLimitersCount(filters map[string][]string) (int, error) {
|
||||||
|
return s.repository.GetBandwidthLimitersCount(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
|
||||||
|
return s.repository.GetBandwidthLimiter(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateBandwidthLimiter(id int, limiter constant.BandwidthLimiterUpdate) (constant.BandwidthLimiter, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
err := s.defaultValidator.Struct(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return constant.BandwidthLimiter{}, err
|
||||||
|
}
|
||||||
|
updatedLimiter, err := s.repository.UpdateBandwidthLimiter(id, limiter)
|
||||||
|
if err != nil {
|
||||||
|
return updatedLimiter, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(updatedLimiter.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return updatedLimiter, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.UpdateBandwidthLimiter(updatedLimiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updatedLimiter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteBandwidthLimiter(id int) (constant.BandwidthLimiter, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
deletedLimiter, err := s.repository.DeleteBandwidthLimiter(id)
|
||||||
|
if err != nil {
|
||||||
|
return deletedLimiter, err
|
||||||
|
}
|
||||||
|
nodes, err := s.repository.GetNodes(map[string][]string{
|
||||||
|
"squad_id_in": convertIntSliceToStringSlice(deletedLimiter.SquadIDs),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
s.closeAllNodes()
|
||||||
|
return deletedLimiter, err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node, ok := s.nodes[node.UUID]; ok {
|
||||||
|
node.DeleteBandwidthLimiter(deletedLimiter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deletedLimiter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AddNode(uuid string, node constant.ConnectedNode) error {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
var node_ constant.Node
|
||||||
|
var err error
|
||||||
|
node_, err = s.repository.GetNode(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
squadIDs := convertIntSliceToStringSlice(node_.SquadIDs)
|
||||||
|
users, err := s.repository.GetUsers(map[string][]string{
|
||||||
|
"squad_id_in": squadIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
node.UpdateUsers(users)
|
||||||
|
bandwidthLimiters, err := s.repository.GetBandwidthLimiters(map[string][]string{
|
||||||
|
"squad_id_in": squadIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
node.UpdateBandwidthLimiters(bandwidthLimiters)
|
||||||
|
connectionLimiters, err := s.repository.GetConnectionLimiters(map[string][]string{
|
||||||
|
"squad_id_in": squadIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
node.UpdateConnectionLimiters(connectionLimiters)
|
||||||
|
s.nodes[uuid] = node
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AcquireLock(limiterId int, id string) (string, error) {
|
||||||
|
s.connLockMtx.Lock()
|
||||||
|
defer s.connLockMtx.Unlock()
|
||||||
|
limiter, err := s.repository.GetConnectionLimiter(limiterId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if limiter.LockType != "manager" {
|
||||||
|
return "", E.New("invalid lock type")
|
||||||
|
}
|
||||||
|
locks, ok := s.limiterLocks[limiterId]
|
||||||
|
if !ok {
|
||||||
|
locks = make(map[string]*cache.Cache)
|
||||||
|
s.limiterLocks[limiter.ID] = locks
|
||||||
|
}
|
||||||
|
lock, ok := locks[id]
|
||||||
|
if !ok {
|
||||||
|
if len(locks) == int(limiter.Count) {
|
||||||
|
return "", E.New("not enough free locks")
|
||||||
|
}
|
||||||
|
lock = cache.New(time.Second*30, time.Second)
|
||||||
|
lock.OnEvicted(func(_ string, _ interface{}) {
|
||||||
|
s.connLockMtx.Lock()
|
||||||
|
defer s.connLockMtx.Unlock()
|
||||||
|
if lock.ItemCount() == 0 {
|
||||||
|
delete(locks, id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
locks[id] = lock
|
||||||
|
}
|
||||||
|
handleID, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
lock.SetDefault(handleID.String(), new(struct{}))
|
||||||
|
return handleID.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) RefreshLock(limiterId int, id string, handleId string) error {
|
||||||
|
s.connLockMtx.Lock()
|
||||||
|
defer s.connLockMtx.Unlock()
|
||||||
|
locks, ok := s.limiterLocks[limiterId]
|
||||||
|
if !ok {
|
||||||
|
return E.New("limiter not found")
|
||||||
|
}
|
||||||
|
lock, ok := locks[id]
|
||||||
|
if !ok {
|
||||||
|
return E.New("lock not found")
|
||||||
|
}
|
||||||
|
err := lock.Replace(handleId, new(struct{}), time.Second*30)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ReleaseLock(limiterId int, id string, handleId string) error {
|
||||||
|
s.connLockMtx.Lock()
|
||||||
|
defer s.connLockMtx.Unlock()
|
||||||
|
locks, ok := s.limiterLocks[limiterId]
|
||||||
|
if !ok {
|
||||||
|
return E.New("limiter not found")
|
||||||
|
}
|
||||||
|
lock, ok := locks[id]
|
||||||
|
if !ok {
|
||||||
|
return E.New("lock not found")
|
||||||
|
}
|
||||||
|
go lock.Delete(handleId)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) closeAllNodes() {
|
||||||
|
for _, node := range s.nodes {
|
||||||
|
node.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertIntSliceToStringSlice(values []int) []string {
|
||||||
|
result := make([]string, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
result[i] = strconv.Itoa(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
20
service/manager/service_stub.go
Normal file
20
service/manager/service_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build !with_manager
|
||||||
|
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/service"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *service.Registry) {
|
||||||
|
service.Register[option.ManagerServiceOptions](registry, C.TypeManager, func(ctx context.Context, logger log.ContextLogger, tag string, options option.ManagerServiceOptions) (adapter.Service, error) {
|
||||||
|
return nil, E.New(`Manager is not included in this build, rebuild with -tags with_manager`)
|
||||||
|
})
|
||||||
|
}
|
||||||
18
service/node/constant/bandwidth.go
Normal file
18
service/node/constant/bandwidth.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BandwidthLimiterManager interface {
|
||||||
|
AddBandwidthLimiterStrategyManager(outbound adapter.Outbound) error
|
||||||
|
GetBandwidthLimiterStrategyManager(tag string) (BandwidthLimiterStrategyManager, bool)
|
||||||
|
GetBandwidthLimiterStrategyManagerTags() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiterStrategyManager interface {
|
||||||
|
UpdateBandwidthLimiter(limiter C.BandwidthLimiter)
|
||||||
|
UpdateBandwidthLimiters(limiter []C.BandwidthLimiter)
|
||||||
|
DeleteBandwidthLimiter(username string)
|
||||||
|
}
|
||||||
18
service/node/constant/connection.go
Normal file
18
service/node/constant/connection.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConnectionLimiterManager interface {
|
||||||
|
AddConnectionLimiterStrategyManager(outbound adapter.Outbound) error
|
||||||
|
GetConnectionLimiterStrategyManager(tag string) (ConnectionLimiterStrategyManager, bool)
|
||||||
|
GetConnectionLimiterStrategyManagerTags() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiterStrategyManager interface {
|
||||||
|
UpdateConnectionLimiter(limiter C.ConnectionLimiter)
|
||||||
|
UpdateConnectionLimiters(limiter []C.ConnectionLimiter)
|
||||||
|
DeleteConnectionLimiter(username string)
|
||||||
|
}
|
||||||
18
service/node/constant/inbound.go
Normal file
18
service/node/constant/inbound.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package constant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InboundManager interface {
|
||||||
|
AddUserManager(inbound adapter.Inbound) error
|
||||||
|
GetUserManager(tag string) (UserManager, bool)
|
||||||
|
GetUserManagerTags() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserManager interface {
|
||||||
|
UpdateUser(user C.User)
|
||||||
|
UpdateUsers(users []C.User)
|
||||||
|
DeleteUser(username string)
|
||||||
|
}
|
||||||
88
service/node/inbound/hysteria.go
Normal file
88
service/node/inbound/hysteria.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/hysteria"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HysteriaManager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
inbounds map[string]*HysteriaUserManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHysteriaManager() *HysteriaManager {
|
||||||
|
return &HysteriaManager{
|
||||||
|
inbounds: make(map[string]*HysteriaUserManager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *HysteriaManager) AddUserManager(inbound adapter.Inbound) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.inbounds[inbound.Tag()] = &HysteriaUserManager{
|
||||||
|
inbound: inbound.(*hysteria.Inbound),
|
||||||
|
usersMap: make(map[string]option.HysteriaUser),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *HysteriaManager) GetUserManager(tag string) (constant.UserManager, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
inbound, ok := m.inbounds[tag]
|
||||||
|
return inbound, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *HysteriaManager) GetUserManagerTags() []string {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.inbounds))
|
||||||
|
for tag, _ := range m.inbounds {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaUserManager struct {
|
||||||
|
inbound *hysteria.Inbound
|
||||||
|
usersMap map[string]option.HysteriaUser
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *HysteriaUserManager) postUpdate() {
|
||||||
|
users := make([]option.HysteriaUser, 0, len(i.usersMap))
|
||||||
|
for _, user := range i.usersMap {
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
i.inbound.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *HysteriaUserManager) UpdateUser(user CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
i.usersMap[user.Username] = option.HysteriaUser{Name: user.Username, AuthString: user.Password}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *HysteriaUserManager) UpdateUsers(users []CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.usersMap)
|
||||||
|
for _, user := range users {
|
||||||
|
i.usersMap[user.Username] = option.HysteriaUser{Name: user.Username, AuthString: user.Password}
|
||||||
|
}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *HysteriaUserManager) DeleteUser(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.usersMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
88
service/node/inbound/hysteria2.go
Normal file
88
service/node/inbound/hysteria2.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/hysteria2"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Hysteria2Manager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
inbounds map[string]*Hysteria2UserManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHysteria2Manager() *Hysteria2Manager {
|
||||||
|
return &Hysteria2Manager{
|
||||||
|
inbounds: make(map[string]*Hysteria2UserManager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Hysteria2Manager) AddUserManager(inbound adapter.Inbound) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.inbounds[inbound.Tag()] = &Hysteria2UserManager{
|
||||||
|
inbound: inbound.(*hysteria2.Inbound),
|
||||||
|
usersMap: make(map[string]option.Hysteria2User),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Hysteria2Manager) GetUserManager(tag string) (constant.UserManager, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
inbound, ok := m.inbounds[tag]
|
||||||
|
return inbound, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Hysteria2Manager) GetUserManagerTags() []string {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.inbounds))
|
||||||
|
for tag, _ := range m.inbounds {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hysteria2UserManager struct {
|
||||||
|
inbound *hysteria2.Inbound
|
||||||
|
usersMap map[string]option.Hysteria2User
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Hysteria2UserManager) postUpdate() {
|
||||||
|
users := make([]option.Hysteria2User, 0, len(i.usersMap))
|
||||||
|
for _, user := range i.usersMap {
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
i.inbound.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Hysteria2UserManager) UpdateUser(user CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
i.usersMap[user.Username] = option.Hysteria2User{Name: user.Username, Password: user.Password}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Hysteria2UserManager) UpdateUsers(users []CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.usersMap)
|
||||||
|
for _, user := range users {
|
||||||
|
i.usersMap[user.Username] = option.Hysteria2User{Name: user.Username, Password: user.Password}
|
||||||
|
}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Hysteria2UserManager) DeleteUser(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.usersMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
88
service/node/inbound/trojan.go
Normal file
88
service/node/inbound/trojan.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/trojan"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrojanManager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
inbounds map[string]*TrojanUserManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrojanManager() *TrojanManager {
|
||||||
|
return &TrojanManager{
|
||||||
|
inbounds: make(map[string]*TrojanUserManager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TrojanManager) AddUserManager(inbound adapter.Inbound) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.inbounds[inbound.Tag()] = &TrojanUserManager{
|
||||||
|
inbound: inbound.(*trojan.Inbound),
|
||||||
|
usersMap: make(map[string]option.TrojanUser),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TrojanManager) GetUserManager(tag string) (constant.UserManager, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
inbound, ok := m.inbounds[tag]
|
||||||
|
return inbound, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TrojanManager) GetUserManagerTags() []string {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.inbounds))
|
||||||
|
for tag, _ := range m.inbounds {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrojanUserManager struct {
|
||||||
|
inbound *trojan.Inbound
|
||||||
|
usersMap map[string]option.TrojanUser
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TrojanUserManager) postUpdate() {
|
||||||
|
users := make([]option.TrojanUser, 0, len(i.usersMap))
|
||||||
|
for _, user := range i.usersMap {
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
i.inbound.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TrojanUserManager) UpdateUser(user CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
i.usersMap[user.Username] = option.TrojanUser{Name: user.Username, Password: user.Password}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TrojanUserManager) UpdateUsers(users []CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.usersMap)
|
||||||
|
for _, user := range users {
|
||||||
|
i.usersMap[user.Username] = option.TrojanUser{Name: user.Username, Password: user.Password}
|
||||||
|
}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TrojanUserManager) DeleteUser(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.usersMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
88
service/node/inbound/tuic.go
Normal file
88
service/node/inbound/tuic.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/tuic"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TUICManager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
inbounds map[string]*TUICUserManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTUICManager() *TUICManager {
|
||||||
|
return &TUICManager{
|
||||||
|
inbounds: make(map[string]*TUICUserManager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TUICManager) AddUserManager(inbound adapter.Inbound) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.inbounds[inbound.Tag()] = &TUICUserManager{
|
||||||
|
inbound: inbound.(*tuic.Inbound),
|
||||||
|
usersMap: make(map[string]option.TUICUser),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TUICManager) GetUserManager(tag string) (constant.UserManager, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
inbound, ok := m.inbounds[tag]
|
||||||
|
return inbound, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *TUICManager) GetUserManagerTags() []string {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.inbounds))
|
||||||
|
for tag, _ := range m.inbounds {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type TUICUserManager struct {
|
||||||
|
inbound *tuic.Inbound
|
||||||
|
usersMap map[string]option.TUICUser
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TUICUserManager) postUpdate() {
|
||||||
|
users := make([]option.TUICUser, 0, len(i.usersMap))
|
||||||
|
for _, user := range i.usersMap {
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
i.inbound.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TUICUserManager) UpdateUser(user CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
i.usersMap[user.Username] = option.TUICUser{Name: user.Username, UUID: user.UUID, Password: user.Password}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TUICUserManager) UpdateUsers(users []CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.usersMap)
|
||||||
|
for _, user := range users {
|
||||||
|
i.usersMap[user.Username] = option.TUICUser{Name: user.Username, UUID: user.UUID, Password: user.Password}
|
||||||
|
}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *TUICUserManager) DeleteUser(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.usersMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
88
service/node/inbound/vless.go
Normal file
88
service/node/inbound/vless.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VLESSManager struct {
|
||||||
|
access sync.Mutex
|
||||||
|
inbounds map[string]*VLESSUserManager
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVLESSManager() *VLESSManager {
|
||||||
|
return &VLESSManager{
|
||||||
|
inbounds: make(map[string]*VLESSUserManager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *VLESSManager) AddUserManager(inbound adapter.Inbound) error {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
m.inbounds[inbound.Tag()] = &VLESSUserManager{
|
||||||
|
inbound: inbound.(*vless.Inbound),
|
||||||
|
usersMap: make(map[string]option.VLESSUser),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *VLESSManager) GetUserManager(tag string) (constant.UserManager, bool) {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
inbound, ok := m.inbounds[tag]
|
||||||
|
return inbound, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *VLESSManager) GetUserManagerTags() []string {
|
||||||
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.inbounds))
|
||||||
|
for tag, _ := range m.inbounds {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type VLESSUserManager struct {
|
||||||
|
inbound *vless.Inbound
|
||||||
|
usersMap map[string]option.VLESSUser
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VLESSUserManager) postUpdate() {
|
||||||
|
users := make([]option.VLESSUser, 0, len(i.usersMap))
|
||||||
|
for _, user := range i.usersMap {
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
i.inbound.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VLESSUserManager) UpdateUser(user CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
i.usersMap[user.Username] = option.VLESSUser{Name: user.Username, UUID: user.UUID, Flow: user.Flow}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VLESSUserManager) UpdateUsers(users []CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.usersMap)
|
||||||
|
for _, user := range users {
|
||||||
|
i.usersMap[user.Username] = option.VLESSUser{Name: user.Username, UUID: user.UUID, Flow: user.Flow}
|
||||||
|
}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VLESSUserManager) DeleteUser(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.usersMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
88
service/node/inbound/vmess.go
Normal file
88
service/node/inbound/vmess.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VMessManager struct {
|
||||||
|
inbounds map[string]*VMessUserManager
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVMessManager() *VMessManager {
|
||||||
|
return &VMessManager{
|
||||||
|
inbounds: make(map[string]*VMessUserManager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *VMessManager) AddUserManager(inbound adapter.Inbound) error {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
m.inbounds[inbound.Tag()] = &VMessUserManager{
|
||||||
|
inbound: inbound.(*vmess.Inbound),
|
||||||
|
usersMap: make(map[string]option.VMessUser),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *VMessManager) GetUserManager(tag string) (constant.UserManager, bool) {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
inbound, ok := m.inbounds[tag]
|
||||||
|
return inbound, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *VMessManager) GetUserManagerTags() []string {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.inbounds))
|
||||||
|
for tag, _ := range m.inbounds {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type VMessUserManager struct {
|
||||||
|
inbound *vmess.Inbound
|
||||||
|
usersMap map[string]option.VMessUser
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VMessUserManager) postUpdate() {
|
||||||
|
users := make([]option.VMessUser, 0, len(i.usersMap))
|
||||||
|
for _, user := range i.usersMap {
|
||||||
|
users = append(users, user)
|
||||||
|
}
|
||||||
|
i.inbound.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VMessUserManager) UpdateUser(user CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
i.usersMap[user.Username] = option.VMessUser{Name: user.Username, UUID: user.UUID, AlterId: user.AlterID}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VMessUserManager) UpdateUsers(users []CM.User) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.usersMap)
|
||||||
|
for _, user := range users {
|
||||||
|
i.usersMap[user.Username] = option.VMessUser{Name: user.Username, UUID: user.UUID, AlterId: user.AlterID}
|
||||||
|
}
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *VMessUserManager) DeleteUser(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.usersMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
107
service/node/limiter/bandwidth.go
Normal file
107
service/node/limiter/bandwidth.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/protocol/limiter/bandwidth"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ManagedBandwidthStrategy interface {
|
||||||
|
UpdateStrategies(strategies map[string]bandwidth.BandwidthStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiterManager struct {
|
||||||
|
managers map[string]*BandwidthLimiterStrategyManager
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBandwidthLimiterManager() *BandwidthLimiterManager {
|
||||||
|
return &BandwidthLimiterManager{
|
||||||
|
managers: make(map[string]*BandwidthLimiterStrategyManager),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BandwidthLimiterManager) AddBandwidthLimiterStrategyManager(outbound adapter.Outbound) error {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
limiter, ok := outbound.(*bandwidth.Outbound)
|
||||||
|
if !ok {
|
||||||
|
return E.New("invalid bandwidth limiter: ", outbound.Tag())
|
||||||
|
}
|
||||||
|
strategy, ok := limiter.GetStrategy().(ManagedBandwidthStrategy)
|
||||||
|
if !ok {
|
||||||
|
return E.New("strategy for outbound ", outbound.Tag(), " is not manager")
|
||||||
|
}
|
||||||
|
m.managers[outbound.Tag()] = &BandwidthLimiterStrategyManager{
|
||||||
|
strategy: strategy,
|
||||||
|
strategiesMap: make(map[string]bandwidth.BandwidthStrategy),
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BandwidthLimiterManager) GetBandwidthLimiterStrategyManager(tag string) (constant.BandwidthLimiterStrategyManager, bool) {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
manager, ok := m.managers[tag]
|
||||||
|
return manager, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BandwidthLimiterManager) GetBandwidthLimiterStrategyManagerTags() []string {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.managers))
|
||||||
|
for tag, _ := range m.managers {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type BandwidthLimiterStrategyManager struct {
|
||||||
|
strategy ManagedBandwidthStrategy
|
||||||
|
strategiesMap map[string]bandwidth.BandwidthStrategy
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BandwidthLimiterStrategyManager) postUpdate() {
|
||||||
|
i.strategy.UpdateStrategies(i.strategiesMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BandwidthLimiterStrategyManager) UpdateBandwidthLimiter(limiter CM.BandwidthLimiter) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
strategy, err := bandwidth.CreateStrategy(limiter.Strategy, limiter.Mode, limiter.ConnectionType, limiter.RawSpeed)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.strategiesMap[limiter.Username] = strategy
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BandwidthLimiterStrategyManager) UpdateBandwidthLimiters(limiters []CM.BandwidthLimiter) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.strategiesMap)
|
||||||
|
newStrategiesMap := make(map[string]bandwidth.BandwidthStrategy)
|
||||||
|
for _, limiter := range limiters {
|
||||||
|
strategy, err := bandwidth.CreateStrategy(limiter.Strategy, limiter.Mode, limiter.ConnectionType, limiter.RawSpeed)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newStrategiesMap[limiter.Username] = strategy
|
||||||
|
}
|
||||||
|
i.strategiesMap = newStrategiesMap
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BandwidthLimiterStrategyManager) DeleteBandwidthLimiter(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.strategiesMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
195
service/node/limiter/connection.go
Normal file
195
service/node/limiter/connection.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package limiter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/protocol/limiter/connection"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ManagedConnectionStrategy interface {
|
||||||
|
UpdateStrategies(strategies map[string]connection.ConnectionStrategy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiterManager struct {
|
||||||
|
nodeManager CM.NodeManager
|
||||||
|
managers map[string]*ConnectionLimiterStrategyManager
|
||||||
|
logger log.Logger
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConnectionLimiterManager(nodeManager CM.NodeManager, logger log.Logger) *ConnectionLimiterManager {
|
||||||
|
return &ConnectionLimiterManager{
|
||||||
|
nodeManager: nodeManager,
|
||||||
|
managers: make(map[string]*ConnectionLimiterStrategyManager),
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ConnectionLimiterManager) AddConnectionLimiterStrategyManager(outbound adapter.Outbound) error {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
limiter, ok := outbound.(*connection.Outbound)
|
||||||
|
if !ok {
|
||||||
|
return E.New("invalid connection limiter: ", outbound.Tag())
|
||||||
|
}
|
||||||
|
strategy, ok := limiter.GetStrategy().(ManagedConnectionStrategy)
|
||||||
|
if !ok {
|
||||||
|
return E.New("strategy ", strategy, " is not manager")
|
||||||
|
}
|
||||||
|
m.managers[outbound.Tag()] = &ConnectionLimiterStrategyManager{
|
||||||
|
strategy: strategy,
|
||||||
|
strategiesMap: make(map[string]connection.ConnectionStrategy),
|
||||||
|
manager: m,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ConnectionLimiterManager) GetConnectionLimiterStrategyManager(tag string) (constant.ConnectionLimiterStrategyManager, bool) {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
manager, ok := m.managers[tag]
|
||||||
|
return manager, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *ConnectionLimiterManager) GetConnectionLimiterStrategyManagerTags() []string {
|
||||||
|
m.mtx.Lock()
|
||||||
|
defer m.mtx.Unlock()
|
||||||
|
tags := make([]string, 0, len(m.managers))
|
||||||
|
for tag, _ := range m.managers {
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionLimiterStrategyManager struct {
|
||||||
|
strategy ManagedConnectionStrategy
|
||||||
|
strategiesMap map[string]connection.ConnectionStrategy
|
||||||
|
tag string
|
||||||
|
manager *ConnectionLimiterManager
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ConnectionLimiterStrategyManager) postUpdate() {
|
||||||
|
i.strategy.UpdateStrategies(i.strategiesMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ConnectionLimiterStrategyManager) UpdateConnectionLimiter(limiter CM.ConnectionLimiter) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
lock, err := i.createLock(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strategy, err := connection.CreateStrategy(limiter.Strategy, limiter.ConnectionType, lock)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.strategiesMap[limiter.Username] = strategy
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ConnectionLimiterStrategyManager) UpdateConnectionLimiters(limiters []CM.ConnectionLimiter) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
clear(i.strategiesMap)
|
||||||
|
newStrategiesMap := make(map[string]connection.ConnectionStrategy)
|
||||||
|
for _, limiter := range limiters {
|
||||||
|
lock, err := i.createLock(limiter)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strategy, err := connection.CreateStrategy(limiter.Strategy, limiter.ConnectionType, lock)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newStrategiesMap[limiter.Username] = strategy
|
||||||
|
}
|
||||||
|
i.strategiesMap = newStrategiesMap
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ConnectionLimiterStrategyManager) DeleteConnectionLimiter(username string) {
|
||||||
|
i.mtx.Lock()
|
||||||
|
defer i.mtx.Unlock()
|
||||||
|
delete(i.strategiesMap, username)
|
||||||
|
i.postUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ConnectionLimiterStrategyManager) createLock(limiter CM.ConnectionLimiter) (connection.LockIDGetter, error) {
|
||||||
|
switch limiter.LockType {
|
||||||
|
case "manager":
|
||||||
|
return i.newManagerLock(limiter.ID), nil
|
||||||
|
case "":
|
||||||
|
return connection.NewDefaultLock(limiter.Count), nil
|
||||||
|
default:
|
||||||
|
return nil, E.New("unknown lock type \"", limiter.LockType, "\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManagerLock struct {
|
||||||
|
handleId string
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
handles uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ConnectionLimiterStrategyManager) newManagerLock(limiterId int) connection.LockIDGetter {
|
||||||
|
conns := make(map[string]*ManagerLock)
|
||||||
|
mtx := sync.Mutex{}
|
||||||
|
return func(id string) (connection.CloseHandlerFunc, context.Context, error) {
|
||||||
|
mtx.Lock()
|
||||||
|
defer mtx.Unlock()
|
||||||
|
conn, ok := conns[id]
|
||||||
|
if !ok {
|
||||||
|
nodeManager := i.manager.nodeManager
|
||||||
|
handleId, err := nodeManager.AcquireLock(limiterId, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(time.Second * 5):
|
||||||
|
err := nodeManager.RefreshLock(limiterId, id, handleId)
|
||||||
|
if err != nil {
|
||||||
|
cancel()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
conn = &ManagerLock{
|
||||||
|
handleId: handleId,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
conns[id] = conn
|
||||||
|
}
|
||||||
|
conn.handles++
|
||||||
|
var once sync.Once
|
||||||
|
return func() {
|
||||||
|
once.Do(func() {
|
||||||
|
mtx.Lock()
|
||||||
|
defer mtx.Unlock()
|
||||||
|
conn.handles--
|
||||||
|
if conn.handles == 0 {
|
||||||
|
conn.cancel()
|
||||||
|
i.manager.nodeManager.ReleaseLock(limiterId, id, conn.handleId)
|
||||||
|
delete(conns, id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, conn.ctx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
235
service/node/service.go
Normal file
235
service/node/service.go
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/constant"
|
||||||
|
"github.com/sagernet/sing-box/service/node/inbound"
|
||||||
|
"github.com/sagernet/sing-box/service/node/limiter"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *boxService.Registry) {
|
||||||
|
boxService.Register[option.NodeServiceOptions](registry, C.TypeNode, NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
boxService.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
logger log.ContextLogger
|
||||||
|
inboundManagers map[string]constant.InboundManager
|
||||||
|
bandwidthManager constant.BandwidthLimiterManager
|
||||||
|
connectionManager constant.ConnectionLimiterManager
|
||||||
|
options option.NodeServiceOptions
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeServiceOptions) (adapter.Service, error) {
|
||||||
|
return &Service{
|
||||||
|
Adapter: boxService.NewAdapter(C.TypeManager, tag),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
boxManager := service.FromContext[adapter.ServiceManager](s.ctx)
|
||||||
|
serviceManager, ok := boxManager.Get(s.options.Manager)
|
||||||
|
if !ok {
|
||||||
|
return E.New("manager ", s.options.Manager, " not found")
|
||||||
|
}
|
||||||
|
nodeManager, ok := serviceManager.(CM.NodeManager)
|
||||||
|
if !ok {
|
||||||
|
return E.New("invalid ", s.options.Manager, " manager")
|
||||||
|
}
|
||||||
|
inboundManager := service.FromContext[adapter.InboundManager](s.ctx)
|
||||||
|
outboundManager := service.FromContext[adapter.OutboundManager](s.ctx)
|
||||||
|
s.inboundManagers = map[string]constant.InboundManager{
|
||||||
|
"hysteria": inbound.NewHysteriaManager(),
|
||||||
|
"hysteria2": inbound.NewHysteria2Manager(),
|
||||||
|
"trojan": inbound.NewTrojanManager(),
|
||||||
|
"tuic": inbound.NewTUICManager(),
|
||||||
|
"vless": inbound.NewVLESSManager(),
|
||||||
|
"vmess": inbound.NewVMessManager(),
|
||||||
|
}
|
||||||
|
s.connectionManager = limiter.NewConnectionLimiterManager(nodeManager, s.logger)
|
||||||
|
s.bandwidthManager = limiter.NewBandwidthLimiterManager()
|
||||||
|
for _, tag := range s.options.Inbounds {
|
||||||
|
inbound, ok := inboundManager.Get(tag)
|
||||||
|
if !ok {
|
||||||
|
return E.New("inbound ", tag, " not found")
|
||||||
|
}
|
||||||
|
inboundManager, ok := s.inboundManagers[inbound.Type()]
|
||||||
|
if !ok {
|
||||||
|
return E.New("inbound manager for ", tag, " not found")
|
||||||
|
}
|
||||||
|
err := inboundManager.AddUserManager(inbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, limiter := range s.options.ConnectionLimiters {
|
||||||
|
outbound, ok := outboundManager.Outbound(limiter)
|
||||||
|
if !ok {
|
||||||
|
return E.New("outbound ", limiter, " not found")
|
||||||
|
}
|
||||||
|
err := s.connectionManager.AddConnectionLimiterStrategyManager(outbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, limiter := range s.options.BandwidthLimiters {
|
||||||
|
outbound, ok := outboundManager.Outbound(limiter)
|
||||||
|
if !ok {
|
||||||
|
return E.New("outbound ", limiter, " not found")
|
||||||
|
}
|
||||||
|
err := s.bandwidthManager.AddBandwidthLimiterStrategyManager(outbound)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodeManager.AddNode(s.options.UUID, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateUser(user CM.User) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
manager, ok := s.inboundManagers[user.Type]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userManager, ok := manager.GetUserManager(user.Inbound)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userManager.UpdateUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateUsers(users []CM.User) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
typedUsers := make(map[string][]CM.User)
|
||||||
|
for _, user := range users {
|
||||||
|
u, ok := typedUsers[user.Type]
|
||||||
|
if !ok {
|
||||||
|
typedUsers[user.Type] = make([]CM.User, 0)
|
||||||
|
}
|
||||||
|
typedUsers[user.Type] = append(u, user)
|
||||||
|
}
|
||||||
|
for type_, users := range typedUsers {
|
||||||
|
manager, ok := s.inboundManagers[type_]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
userManager, ok := manager.GetUserManager(user.Inbound)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
userManager.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteUser(user CM.User) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
manager, ok := s.inboundManagers[user.Type]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userManager, ok := manager.GetUserManager(user.Inbound)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userManager.DeleteUser(user.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateConnectionLimiter(limiter CM.ConnectionLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
manager, ok := s.connectionManager.GetConnectionLimiterStrategyManager(limiter.Outbound)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.UpdateConnectionLimiter(limiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateConnectionLimiters(limiters []CM.ConnectionLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
for _, limiter := range limiters {
|
||||||
|
manager, ok := s.connectionManager.GetConnectionLimiterStrategyManager(limiter.Outbound)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
manager.UpdateConnectionLimiters(limiters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteConnectionLimiter(limiter CM.ConnectionLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
manager, ok := s.connectionManager.GetConnectionLimiterStrategyManager(limiter.Outbound)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.DeleteConnectionLimiter(limiter.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateBandwidthLimiter(limiter CM.BandwidthLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
manager, ok := s.bandwidthManager.GetBandwidthLimiterStrategyManager(limiter.Outbound)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.UpdateBandwidthLimiter(limiter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateBandwidthLimiters(limiters []CM.BandwidthLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
for _, limiter := range limiters {
|
||||||
|
manager, ok := s.bandwidthManager.GetBandwidthLimiterStrategyManager(limiter.Outbound)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
manager.UpdateBandwidthLimiters(limiters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteBandwidthLimiter(limiter CM.BandwidthLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
manager, ok := s.bandwidthManager.GetBandwidthLimiterStrategyManager(limiter.Outbound)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
manager.DeleteBandwidthLimiter(limiter.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) IsLocal() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) IsOnline() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
274
service/node_manager/client/service.go
Normal file
274
service/node_manager/client/service.go
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
pb "github.com/sagernet/sing-box/service/node_manager/manager"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/connectivity"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *boxService.Registry) {
|
||||||
|
boxService.Register[option.NodeManagerClientServiceOptions](registry, C.TypeNodeManagerClient, NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
boxService.Adapter
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
logger log.ContextLogger
|
||||||
|
dialer N.Dialer
|
||||||
|
creds credentials.TransportCredentials
|
||||||
|
options option.NodeManagerClientServiceOptions
|
||||||
|
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeManagerClientServiceOptions) (adapter.Service, error) {
|
||||||
|
outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
creds := insecure.NewCredentials()
|
||||||
|
if options.TLS != nil {
|
||||||
|
tlsConfig, err := tls.NewClient(ctx, options.Server, common.PtrValueOrDefault(options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
creds = &tlsCreds{tlsConfig}
|
||||||
|
}
|
||||||
|
return &Service{
|
||||||
|
Adapter: boxService.NewAdapter(C.TypeManager, tag),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
dialer: outboundDialer,
|
||||||
|
creds: creds,
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AddNode(uuid string, node CM.ConnectedNode) error {
|
||||||
|
go func() {
|
||||||
|
isRetry := false
|
||||||
|
for {
|
||||||
|
if !isRetry {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
isRetry = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
select {
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
break
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn, err := s.getConn()
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
client := pb.NewManagerClient(conn)
|
||||||
|
stream, err := client.AddNode(s.ctx, &pb.Node{Uuid: uuid})
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = s.handler(node, stream)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AcquireLock(limiterId int, id string) (string, error) {
|
||||||
|
conn, err := s.getConn()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
client := pb.NewManagerClient(conn)
|
||||||
|
lockReply, err := client.AcquireLock(s.ctx, &pb.AcquireLockRequest{LimiterId: int32(limiterId), Id: id})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return lockReply.HandleId, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) RefreshLock(limiterId int, id string, handleId string) error {
|
||||||
|
conn, err := s.getConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := pb.NewManagerClient(conn)
|
||||||
|
_, err = client.RefreshLock(s.ctx, &pb.LockData{LimiterId: int32(limiterId), Id: id, HandleId: handleId})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ReleaseLock(limiterId int, id string, handleId string) error {
|
||||||
|
conn, err := s.getConn()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client := pb.NewManagerClient(conn)
|
||||||
|
_, err = client.ReleaseLock(s.ctx, &pb.LockData{LimiterId: int32(limiterId), Id: id, HandleId: handleId})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start(stage adapter.StartStage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getConn() (*grpc.ClientConn, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
if s.conn != nil {
|
||||||
|
state := s.conn.GetState()
|
||||||
|
if state != connectivity.Shutdown && state != connectivity.TransientFailure {
|
||||||
|
return s.conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
conn, err := s.createConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.conn = conn
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) createConn() (*grpc.ClientConn, error) {
|
||||||
|
conn, err := grpc.NewClient(
|
||||||
|
s.options.ServerOptions.Build().String(),
|
||||||
|
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
|
||||||
|
return s.dialer.DialContext(ctx, N.NetworkTCP, M.ParseSocksaddr(addr))
|
||||||
|
}),
|
||||||
|
grpc.WithTransportCredentials(s.creds),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) handler(node CM.ConnectedNode, stream grpc.ServerStreamingClient[pb.NodeData]) error {
|
||||||
|
for {
|
||||||
|
data, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch data.Op {
|
||||||
|
case pb.OpType_updateUser:
|
||||||
|
s.logger.DebugContext(s.ctx, "update user")
|
||||||
|
node.UpdateUser(s.convertUser(data.Data.(*pb.NodeData_User).User))
|
||||||
|
case pb.OpType_updateUsers:
|
||||||
|
s.logger.DebugContext(s.ctx, "update users")
|
||||||
|
users := data.Data.(*pb.NodeData_Users).Users.Values
|
||||||
|
convertedUsers := make([]CM.User, len(users))
|
||||||
|
for i, user := range users {
|
||||||
|
convertedUsers[i] = s.convertUser(user)
|
||||||
|
}
|
||||||
|
node.UpdateUsers(convertedUsers)
|
||||||
|
case pb.OpType_deleteUser:
|
||||||
|
s.logger.DebugContext(s.ctx, "delete user")
|
||||||
|
node.DeleteUser(s.convertUser(data.Data.(*pb.NodeData_User).User))
|
||||||
|
|
||||||
|
case pb.OpType_updateConnectionLimiter:
|
||||||
|
s.logger.DebugContext(s.ctx, "update connection limiter")
|
||||||
|
node.UpdateConnectionLimiter(s.convertConnectionLimiter(data.Data.(*pb.NodeData_ConnectionLimiter).ConnectionLimiter))
|
||||||
|
case pb.OpType_updateConnectionLimiters:
|
||||||
|
s.logger.DebugContext(s.ctx, "update connection limiters")
|
||||||
|
limiters := data.Data.(*pb.NodeData_ConnectionLimiters).ConnectionLimiters.Values
|
||||||
|
convertedLimiters := make([]CM.ConnectionLimiter, len(limiters))
|
||||||
|
for i, limiter := range limiters {
|
||||||
|
convertedLimiters[i] = s.convertConnectionLimiter(limiter)
|
||||||
|
}
|
||||||
|
node.UpdateConnectionLimiters(convertedLimiters)
|
||||||
|
case pb.OpType_deleteConnectionLimiter:
|
||||||
|
s.logger.DebugContext(s.ctx, "delete connection limiter")
|
||||||
|
node.DeleteConnectionLimiter(s.convertConnectionLimiter(data.Data.(*pb.NodeData_ConnectionLimiter).ConnectionLimiter))
|
||||||
|
|
||||||
|
case pb.OpType_updateBandwidthLimiter:
|
||||||
|
s.logger.DebugContext(s.ctx, "update bandwidth limiter")
|
||||||
|
node.UpdateBandwidthLimiter(s.convertBandwidthLimiter(data.Data.(*pb.NodeData_BandwidthLimiter).BandwidthLimiter))
|
||||||
|
case pb.OpType_updateBandwidthLimiters:
|
||||||
|
s.logger.DebugContext(s.ctx, "update bandwidth limiters")
|
||||||
|
limiters := data.Data.(*pb.NodeData_BandwidthLimiters).BandwidthLimiters.Values
|
||||||
|
convertedLimiters := make([]CM.BandwidthLimiter, len(limiters))
|
||||||
|
for i, limiter := range limiters {
|
||||||
|
convertedLimiters[i] = s.convertBandwidthLimiter(limiter)
|
||||||
|
}
|
||||||
|
node.UpdateBandwidthLimiters(convertedLimiters)
|
||||||
|
case pb.OpType_deleteBandwidthLimiter:
|
||||||
|
s.logger.DebugContext(s.ctx, "delete bandwidth limiter")
|
||||||
|
node.DeleteBandwidthLimiter(s.convertBandwidthLimiter(data.Data.(*pb.NodeData_BandwidthLimiter).BandwidthLimiter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) convertUser(user *pb.User) CM.User {
|
||||||
|
return CM.User{
|
||||||
|
ID: int(user.Id),
|
||||||
|
Username: user.Username,
|
||||||
|
Type: user.Type,
|
||||||
|
Inbound: user.Inbound,
|
||||||
|
UUID: user.Uuid,
|
||||||
|
Password: user.Password,
|
||||||
|
Flow: user.Flow,
|
||||||
|
AlterID: int(user.AlterId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) convertBandwidthLimiter(limiter *pb.BandwidthLimiter) CM.BandwidthLimiter {
|
||||||
|
return CM.BandwidthLimiter{
|
||||||
|
ID: int(limiter.Id),
|
||||||
|
Username: limiter.Username,
|
||||||
|
Outbound: limiter.Outbound,
|
||||||
|
Strategy: limiter.Strategy,
|
||||||
|
Mode: limiter.Mode,
|
||||||
|
ConnectionType: limiter.ConnectionType,
|
||||||
|
Speed: limiter.Speed,
|
||||||
|
RawSpeed: limiter.RawSpeed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) convertConnectionLimiter(limiter *pb.ConnectionLimiter) CM.ConnectionLimiter {
|
||||||
|
return CM.ConnectionLimiter{
|
||||||
|
ID: int(limiter.Id),
|
||||||
|
Username: limiter.Username,
|
||||||
|
Outbound: limiter.Outbound,
|
||||||
|
Strategy: limiter.Strategy,
|
||||||
|
ConnectionType: limiter.ConnectionType,
|
||||||
|
LockType: limiter.LockType,
|
||||||
|
Count: limiter.Count,
|
||||||
|
}
|
||||||
|
}
|
||||||
44
service/node_manager/client/tls.go
Normal file
44
service/node_manager/client/tls.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tlsCreds struct {
|
||||||
|
// TLS configuration
|
||||||
|
config tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c tlsCreds) Info() credentials.ProtocolInfo {
|
||||||
|
return credentials.ProtocolInfo{
|
||||||
|
SecurityProtocol: "tls",
|
||||||
|
SecurityVersion: "1.2",
|
||||||
|
ServerName: c.config.ServerName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||||
|
conn, err := tls.ClientHandshake(ctx, rawConn, c.config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return conn, credentials.TLSInfo{State: conn.ConnectionState()}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
||||||
|
return nil, nil, E.New("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) Clone() credentials.TransportCredentials {
|
||||||
|
return &tlsCreds{config: c.config.Clone()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tlsCreds) OverrideServerName(serverNameOverride string) error {
|
||||||
|
c.config.SetServerName(serverNameOverride)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
1023
service/node_manager/manager/manager.pb.go
Normal file
1023
service/node_manager/manager/manager.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
104
service/node_manager/manager/manager.proto
Normal file
104
service/node_manager/manager/manager.proto
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option go_package = "github.com/sagernet/sing-box/service/remotemanager/manager";
|
||||||
|
|
||||||
|
package manager.v1;
|
||||||
|
|
||||||
|
service Manager {
|
||||||
|
rpc AddNode (Node) returns (stream NodeData);
|
||||||
|
rpc AcquireLock(AcquireLockRequest) returns (LockData);
|
||||||
|
rpc RefreshLock(LockData) returns (Empty);
|
||||||
|
rpc ReleaseLock(LockData) returns (Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
message Node {
|
||||||
|
string uuid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OpType {
|
||||||
|
updateUsers = 0;
|
||||||
|
updateUser = 1;
|
||||||
|
deleteUser = 2;
|
||||||
|
|
||||||
|
updateBandwidthLimiters = 3;
|
||||||
|
updateBandwidthLimiter = 4;
|
||||||
|
deleteBandwidthLimiter = 5;
|
||||||
|
|
||||||
|
updateConnectionLimiters = 6;
|
||||||
|
updateConnectionLimiter = 7;
|
||||||
|
deleteConnectionLimiter = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message User {
|
||||||
|
int32 id = 1;
|
||||||
|
string username = 3;
|
||||||
|
string type = 4;
|
||||||
|
string inbound = 5;
|
||||||
|
string uuid = 6;
|
||||||
|
string password = 7;
|
||||||
|
string flow = 8;
|
||||||
|
int32 alter_id = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserList {
|
||||||
|
repeated User values = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BandwidthLimiter {
|
||||||
|
int32 id = 1;
|
||||||
|
string username = 3;
|
||||||
|
string outbound = 4;
|
||||||
|
string strategy = 5;
|
||||||
|
string mode = 6;
|
||||||
|
string connection_type = 7;
|
||||||
|
string speed = 8;
|
||||||
|
uint64 raw_speed = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BandwidthLimiterList {
|
||||||
|
repeated BandwidthLimiter values = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionLimiter {
|
||||||
|
int32 id = 1;
|
||||||
|
string username = 3;
|
||||||
|
string outbound = 4;
|
||||||
|
string strategy = 5;
|
||||||
|
string connection_type = 6;
|
||||||
|
string lock_type = 7;
|
||||||
|
uint32 count = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionLimiterList {
|
||||||
|
repeated ConnectionLimiter values = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NodeData {
|
||||||
|
|
||||||
|
OpType op = 1;
|
||||||
|
|
||||||
|
oneof data {
|
||||||
|
UserList users = 2;
|
||||||
|
User user = 3;
|
||||||
|
BandwidthLimiterList bandwidth_limiters = 4;
|
||||||
|
BandwidthLimiter bandwidth_limiter = 5;
|
||||||
|
ConnectionLimiterList connection_limiters = 6;
|
||||||
|
ConnectionLimiter connection_limiter = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
message AcquireLockRequest {
|
||||||
|
int32 limiter_id = 1;
|
||||||
|
string id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message LockData {
|
||||||
|
int32 limiter_id = 1;
|
||||||
|
string id = 2;
|
||||||
|
string handleId = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Empty {
|
||||||
|
|
||||||
|
}
|
||||||
239
service/node_manager/manager/manager_grpc.pb.go
Normal file
239
service/node_manager/manager/manager_grpc.pb.go
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.6.0
|
||||||
|
// - protoc v6.33.1
|
||||||
|
// source: manager/manager.proto
|
||||||
|
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
grpc "google.golang.org/grpc"
|
||||||
|
codes "google.golang.org/grpc/codes"
|
||||||
|
status "google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the grpc package it is being compiled against.
|
||||||
|
// Requires gRPC-Go v1.64.0 or later.
|
||||||
|
const _ = grpc.SupportPackageIsVersion9
|
||||||
|
|
||||||
|
const (
|
||||||
|
Manager_AddNode_FullMethodName = "/manager.v1.Manager/AddNode"
|
||||||
|
Manager_AcquireLock_FullMethodName = "/manager.v1.Manager/AcquireLock"
|
||||||
|
Manager_RefreshLock_FullMethodName = "/manager.v1.Manager/RefreshLock"
|
||||||
|
Manager_ReleaseLock_FullMethodName = "/manager.v1.Manager/ReleaseLock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ManagerClient is the client API for Manager service.
|
||||||
|
//
|
||||||
|
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||||
|
type ManagerClient interface {
|
||||||
|
AddNode(ctx context.Context, in *Node, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NodeData], error)
|
||||||
|
AcquireLock(ctx context.Context, in *AcquireLockRequest, opts ...grpc.CallOption) (*LockData, error)
|
||||||
|
RefreshLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error)
|
||||||
|
ReleaseLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type managerClient struct {
|
||||||
|
cc grpc.ClientConnInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewManagerClient(cc grpc.ClientConnInterface) ManagerClient {
|
||||||
|
return &managerClient{cc}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *managerClient) AddNode(ctx context.Context, in *Node, opts ...grpc.CallOption) (grpc.ServerStreamingClient[NodeData], error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
stream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[0], Manager_AddNode_FullMethodName, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x := &grpc.GenericClientStream[Node, NodeData]{ClientStream: stream}
|
||||||
|
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := x.ClientStream.CloseSend(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type Manager_AddNodeClient = grpc.ServerStreamingClient[NodeData]
|
||||||
|
|
||||||
|
func (c *managerClient) AcquireLock(ctx context.Context, in *AcquireLockRequest, opts ...grpc.CallOption) (*LockData, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(LockData)
|
||||||
|
err := c.cc.Invoke(ctx, Manager_AcquireLock_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *managerClient) RefreshLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(Empty)
|
||||||
|
err := c.cc.Invoke(ctx, Manager_RefreshLock_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *managerClient) ReleaseLock(ctx context.Context, in *LockData, opts ...grpc.CallOption) (*Empty, error) {
|
||||||
|
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||||
|
out := new(Empty)
|
||||||
|
err := c.cc.Invoke(ctx, Manager_ReleaseLock_FullMethodName, in, out, cOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ManagerServer is the server API for Manager service.
|
||||||
|
// All implementations must embed UnimplementedManagerServer
|
||||||
|
// for forward compatibility.
|
||||||
|
type ManagerServer interface {
|
||||||
|
AddNode(*Node, grpc.ServerStreamingServer[NodeData]) error
|
||||||
|
AcquireLock(context.Context, *AcquireLockRequest) (*LockData, error)
|
||||||
|
RefreshLock(context.Context, *LockData) (*Empty, error)
|
||||||
|
ReleaseLock(context.Context, *LockData) (*Empty, error)
|
||||||
|
mustEmbedUnimplementedManagerServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnimplementedManagerServer must be embedded to have
|
||||||
|
// forward compatible implementations.
|
||||||
|
//
|
||||||
|
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||||
|
// pointer dereference when methods are called.
|
||||||
|
type UnimplementedManagerServer struct{}
|
||||||
|
|
||||||
|
func (UnimplementedManagerServer) AddNode(*Node, grpc.ServerStreamingServer[NodeData]) error {
|
||||||
|
return status.Error(codes.Unimplemented, "method AddNode not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedManagerServer) AcquireLock(context.Context, *AcquireLockRequest) (*LockData, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method AcquireLock not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedManagerServer) RefreshLock(context.Context, *LockData) (*Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method RefreshLock not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedManagerServer) ReleaseLock(context.Context, *LockData) (*Empty, error) {
|
||||||
|
return nil, status.Error(codes.Unimplemented, "method ReleaseLock not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedManagerServer) mustEmbedUnimplementedManagerServer() {}
|
||||||
|
func (UnimplementedManagerServer) testEmbeddedByValue() {}
|
||||||
|
|
||||||
|
// UnsafeManagerServer may be embedded to opt out of forward compatibility for this service.
|
||||||
|
// Use of this interface is not recommended, as added methods to ManagerServer will
|
||||||
|
// result in compilation errors.
|
||||||
|
type UnsafeManagerServer interface {
|
||||||
|
mustEmbedUnimplementedManagerServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterManagerServer(s grpc.ServiceRegistrar, srv ManagerServer) {
|
||||||
|
// If the following call panics, it indicates UnimplementedManagerServer was
|
||||||
|
// embedded by pointer and is nil. This will cause panics if an
|
||||||
|
// unimplemented method is ever invoked, so we test this at initialization
|
||||||
|
// time to prevent it from happening at runtime later due to I/O.
|
||||||
|
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||||
|
t.testEmbeddedByValue()
|
||||||
|
}
|
||||||
|
s.RegisterService(&Manager_ServiceDesc, srv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Manager_AddNode_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||||
|
m := new(Node)
|
||||||
|
if err := stream.RecvMsg(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return srv.(ManagerServer).AddNode(m, &grpc.GenericServerStream[Node, NodeData]{ServerStream: stream})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||||
|
type Manager_AddNodeServer = grpc.ServerStreamingServer[NodeData]
|
||||||
|
|
||||||
|
func _Manager_AcquireLock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(AcquireLockRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ManagerServer).AcquireLock(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Manager_AcquireLock_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ManagerServer).AcquireLock(ctx, req.(*AcquireLockRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Manager_RefreshLock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LockData)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ManagerServer).RefreshLock(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Manager_RefreshLock_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ManagerServer).RefreshLock(ctx, req.(*LockData))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _Manager_ReleaseLock_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(LockData)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(ManagerServer).ReleaseLock(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: Manager_ReleaseLock_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(ManagerServer).ReleaseLock(ctx, req.(*LockData))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager_ServiceDesc is the grpc.ServiceDesc for Manager service.
|
||||||
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
|
// and not to be introspected or modified (even as a copy)
|
||||||
|
var Manager_ServiceDesc = grpc.ServiceDesc{
|
||||||
|
ServiceName: "manager.v1.Manager",
|
||||||
|
HandlerType: (*ManagerServer)(nil),
|
||||||
|
Methods: []grpc.MethodDesc{
|
||||||
|
{
|
||||||
|
MethodName: "AcquireLock",
|
||||||
|
Handler: _Manager_AcquireLock_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "RefreshLock",
|
||||||
|
Handler: _Manager_RefreshLock_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "ReleaseLock",
|
||||||
|
Handler: _Manager_ReleaseLock_Handler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Streams: []grpc.StreamDesc{
|
||||||
|
{
|
||||||
|
StreamName: "AddNode",
|
||||||
|
Handler: _Manager_AddNode_Handler,
|
||||||
|
ServerStreams: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Metadata: "manager/manager.proto",
|
||||||
|
}
|
||||||
203
service/node_manager/server/node.go
Normal file
203
service/node_manager/server/node.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
CS "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
pb "github.com/sagernet/sing-box/service/node_manager/manager"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RemoteNode struct {
|
||||||
|
ctx context.Context
|
||||||
|
logger log.ContextLogger
|
||||||
|
stream grpc.ServerStreamingServer[pb.NodeData]
|
||||||
|
errChan chan error
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoteNode(ctx context.Context, logger log.ContextLogger, stream grpc.ServerStreamingServer[pb.NodeData]) (*RemoteNode, chan error) {
|
||||||
|
errChan := make(chan error)
|
||||||
|
return &RemoteNode{
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
stream: stream,
|
||||||
|
errChan: errChan,
|
||||||
|
}, errChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) UpdateUser(user CS.User) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_updateUser,
|
||||||
|
Data: &pb.NodeData_User{User: s.convertUser(user)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) UpdateUsers(users []CS.User) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
pbUsers := make([]*pb.User, len(users))
|
||||||
|
for i, user := range users {
|
||||||
|
pbUsers[i] = s.convertUser(user)
|
||||||
|
}
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_updateUsers,
|
||||||
|
Data: &pb.NodeData_Users{Users: &pb.UserList{Values: pbUsers}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) DeleteUser(user CS.User) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_deleteUser,
|
||||||
|
Data: &pb.NodeData_User{User: s.convertUser(user)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) UpdateConnectionLimiter(limiter CS.ConnectionLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_updateConnectionLimiter,
|
||||||
|
Data: &pb.NodeData_ConnectionLimiter{ConnectionLimiter: s.convertConnectionLimiter(limiter)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) UpdateConnectionLimiters(limiters []CS.ConnectionLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
pbLimiters := make([]*pb.ConnectionLimiter, len(limiters))
|
||||||
|
for i, limiters := range limiters {
|
||||||
|
pbLimiters[i] = s.convertConnectionLimiter(limiters)
|
||||||
|
}
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_updateConnectionLimiters,
|
||||||
|
Data: &pb.NodeData_ConnectionLimiters{ConnectionLimiters: &pb.ConnectionLimiterList{Values: pbLimiters}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) DeleteConnectionLimiter(limiter CS.ConnectionLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_deleteConnectionLimiter,
|
||||||
|
Data: &pb.NodeData_ConnectionLimiter{ConnectionLimiter: s.convertConnectionLimiter(limiter)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) UpdateBandwidthLimiter(limiter CS.BandwidthLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_updateBandwidthLimiter,
|
||||||
|
Data: &pb.NodeData_BandwidthLimiter{BandwidthLimiter: s.convertBandwidthLimiter(limiter)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) UpdateBandwidthLimiters(limiters []CS.BandwidthLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
pbLimiters := make([]*pb.BandwidthLimiter, len(limiters))
|
||||||
|
for i, limiters := range limiters {
|
||||||
|
pbLimiters[i] = s.convertBandwidthLimiter(limiters)
|
||||||
|
}
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_updateBandwidthLimiters,
|
||||||
|
Data: &pb.NodeData_BandwidthLimiters{BandwidthLimiters: &pb.BandwidthLimiterList{Values: pbLimiters}},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) DeleteBandwidthLimiter(limiter CS.BandwidthLimiter) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.send(&pb.NodeData{
|
||||||
|
Op: pb.OpType_deleteBandwidthLimiter,
|
||||||
|
Data: &pb.NodeData_BandwidthLimiter{BandwidthLimiter: s.convertBandwidthLimiter(limiter)},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) IsLocal() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) IsOnline() bool {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
select {
|
||||||
|
case <-s.stream.Context().Done():
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) Close() error {
|
||||||
|
s.close(E.New("server connection is closed"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) send(data *pb.NodeData) {
|
||||||
|
select {
|
||||||
|
case <-s.ctx.Done():
|
||||||
|
s.close(E.New("server connection is closed"))
|
||||||
|
return
|
||||||
|
case <-s.stream.Context().Done():
|
||||||
|
s.close(E.New("client connection is closed"))
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
err := s.stream.Send(data)
|
||||||
|
if err != nil {
|
||||||
|
s.close(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) close(err error) {
|
||||||
|
s.errChan <- err
|
||||||
|
close(s.errChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) convertUser(user CS.User) *pb.User {
|
||||||
|
return &pb.User{
|
||||||
|
Id: int32(user.ID),
|
||||||
|
Username: user.Username,
|
||||||
|
Type: user.Type,
|
||||||
|
Inbound: user.Inbound,
|
||||||
|
Uuid: user.UUID,
|
||||||
|
Password: user.Password,
|
||||||
|
Flow: user.Flow,
|
||||||
|
AlterId: int32(user.AlterID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) convertConnectionLimiter(limiter CS.ConnectionLimiter) *pb.ConnectionLimiter {
|
||||||
|
return &pb.ConnectionLimiter{
|
||||||
|
Id: int32(limiter.ID),
|
||||||
|
Username: limiter.Username,
|
||||||
|
Outbound: limiter.Outbound,
|
||||||
|
Strategy: limiter.Strategy,
|
||||||
|
ConnectionType: limiter.ConnectionType,
|
||||||
|
LockType: limiter.LockType,
|
||||||
|
Count: limiter.Count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RemoteNode) convertBandwidthLimiter(limiter CS.BandwidthLimiter) *pb.BandwidthLimiter {
|
||||||
|
return &pb.BandwidthLimiter{
|
||||||
|
Id: int32(limiter.ID),
|
||||||
|
Username: limiter.Username,
|
||||||
|
Outbound: limiter.Outbound,
|
||||||
|
Strategy: limiter.Strategy,
|
||||||
|
Mode: limiter.Mode,
|
||||||
|
ConnectionType: limiter.ConnectionType,
|
||||||
|
Speed: limiter.Speed,
|
||||||
|
RawSpeed: limiter.RawSpeed,
|
||||||
|
}
|
||||||
|
}
|
||||||
139
service/node_manager/server/service.go
Normal file
139
service/node_manager/server/service.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
boxService "github.com/sagernet/sing-box/adapter/service"
|
||||||
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
CM "github.com/sagernet/sing-box/service/manager/constant"
|
||||||
|
pb "github.com/sagernet/sing-box/service/node_manager/manager"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
|
"github.com/sagernet/sing/service"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterService(registry *boxService.Registry) {
|
||||||
|
boxService.Register[option.NodeManagerServerServiceOptions](registry, C.TypeNodeManagerServer, NewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
pb.UnimplementedManagerServer
|
||||||
|
boxService.Adapter
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
logger log.ContextLogger
|
||||||
|
listener *listener.Listener
|
||||||
|
tlsConfig tls.ServerConfig
|
||||||
|
grpcServer *grpc.Server
|
||||||
|
manager CM.Manager
|
||||||
|
options option.NodeManagerServerServiceOptions
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeManagerServerServiceOptions) (adapter.Service, error) {
|
||||||
|
return &Service{
|
||||||
|
Adapter: boxService.NewAdapter(C.TypeManager, tag),
|
||||||
|
ctx: ctx,
|
||||||
|
logger: logger,
|
||||||
|
listener: listener.New(listener.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Logger: logger,
|
||||||
|
Network: []string{N.NetworkTCP},
|
||||||
|
Listen: options.ListenOptions,
|
||||||
|
}),
|
||||||
|
options: options,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AddNode(node *pb.Node, stream grpc.ServerStreamingServer[pb.NodeData]) error {
|
||||||
|
remoteNode, errChan := NewRemoteNode(s.ctx, s.logger, stream)
|
||||||
|
err := s.manager.AddNode(node.Uuid, remoteNode)
|
||||||
|
if err != nil {
|
||||||
|
if err == CM.ErrNotFound {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
s.logger.Error(err)
|
||||||
|
return E.New("internal error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <-errChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AcquireLock(ctx context.Context, request *pb.AcquireLockRequest) (*pb.LockData, error) {
|
||||||
|
handleId, err := s.manager.AcquireLock(int(request.LimiterId), request.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &pb.LockData{HandleId: handleId}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) RefreshLock(ctx context.Context, data *pb.LockData) (*pb.Empty, error) {
|
||||||
|
return nil, s.manager.RefreshLock(int(data.LimiterId), data.Id, data.HandleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ReleaseLock(ctx context.Context, data *pb.LockData) (*pb.Empty, error) {
|
||||||
|
return nil, s.manager.ReleaseLock(int(data.LimiterId), data.Id, data.HandleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
boxManager := service.FromContext[adapter.ServiceManager](s.ctx)
|
||||||
|
service, ok := boxManager.Get(s.options.Manager)
|
||||||
|
if !ok {
|
||||||
|
return E.New("manager ", s.options.Manager, " not found")
|
||||||
|
}
|
||||||
|
s.manager, ok = service.(CM.Manager)
|
||||||
|
if !ok {
|
||||||
|
return E.New("invalid", s.options.Manager, " manager")
|
||||||
|
}
|
||||||
|
if s.options.TLS != nil {
|
||||||
|
tlsConfig, err := tls.NewServer(s.ctx, s.logger, common.PtrValueOrDefault(s.options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.tlsConfig = tlsConfig
|
||||||
|
}
|
||||||
|
if s.tlsConfig != nil {
|
||||||
|
err := s.tlsConfig.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create TLS config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tcpListener, err := s.listener.ListenTCP()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.tlsConfig != nil {
|
||||||
|
if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||||
|
s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...))
|
||||||
|
}
|
||||||
|
tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig)
|
||||||
|
}
|
||||||
|
s.grpcServer = grpc.NewServer()
|
||||||
|
pb.RegisterManagerServer(s.grpcServer, s)
|
||||||
|
go func() {
|
||||||
|
err = s.grpcServer.Serve(tcpListener)
|
||||||
|
if err != nil && !errors.Is(err, grpc.ErrServerStopped) {
|
||||||
|
s.logger.Error("serve error: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
"github.com/sagernet/sing-box/transport/v2rayhttp"
|
||||||
"github.com/sagernet/sing-box/transport/v2rayhttpupgrade"
|
"github.com/sagernet/sing-box/transport/v2rayhttpupgrade"
|
||||||
|
"github.com/sagernet/sing-box/transport/v2raykcp"
|
||||||
"github.com/sagernet/sing-box/transport/v2raywebsocket"
|
"github.com/sagernet/sing-box/transport/v2raywebsocket"
|
||||||
xhttp "github.com/sagernet/sing-box/transport/v2rayxhttp"
|
xhttp "github.com/sagernet/sing-box/transport/v2rayxhttp"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -42,6 +43,8 @@ func NewServerTransport(ctx context.Context, logger logger.ContextLogger, option
|
|||||||
return v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler)
|
return v2rayhttpupgrade.NewServer(ctx, logger, options.HTTPUpgradeOptions, tlsConfig, handler)
|
||||||
case C.V2RayTransportTypeXHTTP:
|
case C.V2RayTransportTypeXHTTP:
|
||||||
return xhttp.NewServer(ctx, logger, options.XHTTPOptions, tlsConfig, handler)
|
return xhttp.NewServer(ctx, logger, options.XHTTPOptions, tlsConfig, handler)
|
||||||
|
case C.V2RayTransportTypeKCP:
|
||||||
|
return v2raykcp.NewServer(ctx, logger, options.KCPOptions, tlsConfig, handler)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown transport type: " + options.Type)
|
return nil, E.New("unknown transport type: " + options.Type)
|
||||||
}
|
}
|
||||||
@@ -67,6 +70,8 @@ func NewClientTransport(ctx context.Context, dialer N.Dialer, serverAddr M.Socks
|
|||||||
return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig)
|
return v2rayhttpupgrade.NewClient(ctx, dialer, serverAddr, options.HTTPUpgradeOptions, tlsConfig)
|
||||||
case C.V2RayTransportTypeXHTTP:
|
case C.V2RayTransportTypeXHTTP:
|
||||||
return xhttp.NewClient(ctx, dialer, serverAddr, options.XHTTPOptions, tlsConfig)
|
return xhttp.NewClient(ctx, dialer, serverAddr, options.XHTTPOptions, tlsConfig)
|
||||||
|
case C.V2RayTransportTypeKCP:
|
||||||
|
return v2raykcp.NewClient(ctx, dialer, serverAddr, options.KCPOptions, tlsConfig)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown transport type: " + options.Type)
|
return nil, E.New("unknown transport type: " + options.Type)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -265,3 +265,14 @@ func DupContext(ctx context.Context) context.Context {
|
|||||||
}
|
}
|
||||||
return log.ContextWithID(context.Background(), id)
|
return log.ContextWithID(context.Background(), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HWIDContext(ctx context.Context, headers http.Header) context.Context {
|
||||||
|
for key, values := range headers {
|
||||||
|
if strings.ToLower(key) == "x-hwid" {
|
||||||
|
if len(values) != 0 {
|
||||||
|
return context.WithValue(ctx, "hwid", values[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
if requestBody != nil {
|
if requestBody != nil {
|
||||||
conn = bufio.NewCachedConn(conn, requestBody)
|
conn = bufio.NewCachedConn(conn, requestBody)
|
||||||
}
|
}
|
||||||
s.handler.NewConnectionEx(DupContext(request.Context()), conn, source, M.Socksaddr{}, nil)
|
s.handler.NewConnectionEx(HWIDContext(DupContext(request.Context()), request.Header), conn, source, M.Socksaddr{}, nil)
|
||||||
} else {
|
} else {
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
@@ -141,7 +141,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
NewHTTPConn(request.Body, writer),
|
NewHTTPConn(request.Body, writer),
|
||||||
writer.(http.Flusher),
|
writer.(http.Flusher),
|
||||||
})
|
})
|
||||||
s.handler.NewConnectionEx(request.Context(), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) {
|
s.handler.NewConnectionEx(HWIDContext(request.Context(), request.Header), conn, source, M.Socksaddr{}, N.OnceClose(func(it error) {
|
||||||
close(done)
|
close(done)
|
||||||
}))
|
}))
|
||||||
<-done
|
<-done
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ func (s *Server) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
|
s.invalidRequest(writer, request, http.StatusInternalServerError, E.Cause(err, "hijack failed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.handler.NewConnectionEx(v2rayhttp.DupContext(request.Context()), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
|
s.handler.NewConnectionEx(v2rayhttp.HWIDContext(v2rayhttp.DupContext(request.Context()), request.Header), conn, sHttp.SourceAddress(request), M.Socksaddr{}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
|
func (s *Server) invalidRequest(writer http.ResponseWriter, request *http.Request, statusCode int, err error) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user