mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-06-21 02:12:11 +03:00
Compare commits
14 Commits
v1.13.12-e
...
extended
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cbc7691f0 | ||
|
|
d174962a04 | ||
|
|
9c80cf371c | ||
|
|
9f5ccf43d4 | ||
|
|
6f6af8e902 | ||
|
|
6cddffd546 | ||
|
|
a59e9ec9e3 | ||
|
|
9ebec50a72 | ||
|
|
9ff7a84afe | ||
|
|
7a0354699a | ||
|
|
978b951169 | ||
|
|
e363c2ff78 | ||
|
|
0577c28120 | ||
|
|
195a33379d |
@@ -1,12 +1,16 @@
|
|||||||
-s dir
|
-s dir
|
||||||
--name sing-box
|
--name sing-box-extended
|
||||||
--category net
|
--category net
|
||||||
--license GPL-3.0-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform."
|
--description "The universal proxy platform (extended)."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
|
|
||||||
|
--provides sing-box
|
||||||
|
--conflicts sing-box
|
||||||
|
--replaces sing-box
|
||||||
|
|
||||||
--config-files /etc/config/sing-box
|
--config-files /etc/config/sing-box
|
||||||
--config-files /etc/sing-box/config.json
|
--config-files /etc/sing-box/config.json
|
||||||
|
|
||||||
|
|||||||
12
.github/build_openwrt_apk.sh
vendored
12
.github/build_openwrt_apk.sh
vendored
@@ -27,10 +27,7 @@ fi
|
|||||||
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
||||||
|
|
||||||
# Convert version to APK format:
|
# Convert version to APK format:
|
||||||
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
|
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/' | sed -E 's/-[a-z]+-/./g')
|
||||||
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
|
|
||||||
# 1.13.0 -> 1.13.0-r0
|
|
||||||
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
|
|
||||||
APK_VERSION="${APK_VERSION}-r0"
|
APK_VERSION="${APK_VERSION}-r0"
|
||||||
|
|
||||||
ROOT_DIR=$(mktemp -d)
|
ROOT_DIR=$(mktemp -d)
|
||||||
@@ -78,15 +75,16 @@ done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
|||||||
|
|
||||||
# Build APK
|
# Build APK
|
||||||
apk --root "$APK_ROOT_DIR" mkpkg \
|
apk --root "$APK_ROOT_DIR" mkpkg \
|
||||||
--info "name:sing-box" \
|
--info "name:sing-box-extended" \
|
||||||
--info "version:${APK_VERSION}" \
|
--info "version:${APK_VERSION}" \
|
||||||
--info "description:The universal proxy platform." \
|
--info "description:The universal proxy platform (extended)." \
|
||||||
--info "arch:${ARCHITECTURE}" \
|
--info "arch:${ARCHITECTURE}" \
|
||||||
--info "license:GPL-3.0-or-later" \
|
--info "license:GPL-3.0-or-later" \
|
||||||
--info "origin:sing-box" \
|
--info "origin:sing-box-extended" \
|
||||||
--info "url:https://sing-box.sagernet.org/" \
|
--info "url:https://sing-box.sagernet.org/" \
|
||||||
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
||||||
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
|
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
|
||||||
|
--info "provides:sing-box" \
|
||||||
--info "provider-priority:100" \
|
--info "provider-priority:100" \
|
||||||
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
||||||
--files "$ROOT_DIR" \
|
--files "$ROOT_DIR" \
|
||||||
|
|||||||
57
.github/build_openwrt_packages.sh
vendored
Executable file
57
.github/build_openwrt_packages.sh
vendored
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
TARGET="$2"
|
||||||
|
BINARY_PATH="$3"
|
||||||
|
|
||||||
|
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
||||||
|
DIST="$PROJECT/dist"
|
||||||
|
|
||||||
|
case "$TARGET" in
|
||||||
|
linux_amd64*) ARCHITECTURES="x86_64" ;;
|
||||||
|
linux_arm64*) ARCHITECTURES="aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" ;;
|
||||||
|
linux_386*softfloat) ARCHITECTURES="i386_pentium-mmx" ;;
|
||||||
|
linux_386*) ARCHITECTURES="i386_pentium4" ;;
|
||||||
|
linux_arm_7*) ARCHITECTURES="arm_cortex-a5_vfpv4 arm_cortex-a7_neon-vfpv4 arm_cortex-a7_vfpv4 arm_cortex-a8_vfpv3 arm_cortex-a9_neon arm_cortex-a9_vfpv3-d16 arm_cortex-a15_neon-vfpv4" ;;
|
||||||
|
linux_arm_6*) ARCHITECTURES="arm_arm1176jzf-s_vfp" ;;
|
||||||
|
linux_arm_5*) ARCHITECTURES="arm_arm926ej-s arm_cortex-a7 arm_cortex-a9 arm_fa526 arm_xscale" ;;
|
||||||
|
linux_mips64_*) ARCHITECTURES="mips64_mips64r2 mips64_octeonplus" ;;
|
||||||
|
linux_mips64le*) ARCHITECTURES="mips64el_mips64r2" ;;
|
||||||
|
linux_mipsle*hardfloat) ARCHITECTURES="mipsel_24kc_24kf" ;;
|
||||||
|
linux_mipsle*) ARCHITECTURES="mipsel_24kc mipsel_74kc mipsel_mips32" ;;
|
||||||
|
linux_mips_*) ARCHITECTURES="mips_24kc mips_4kec mips_mips32" ;;
|
||||||
|
linux_riscv64*) ARCHITECTURES="riscv64_generic" ;;
|
||||||
|
linux_loong64*) ARCHITECTURES="loongarch64_generic" ;;
|
||||||
|
*) echo "Unknown target: $TARGET"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
PKG_VERSION="${VERSION//-/\~}"
|
||||||
|
|
||||||
|
FPM_DIR=$(mktemp -d)
|
||||||
|
sed "s|release/|$PROJECT/release/|g;s|^LICENSE|$PROJECT/LICENSE|" "$PROJECT/.fpm_openwrt" > "$FPM_DIR/.fpm"
|
||||||
|
trap 'rm -rf "$FPM_DIR"' EXIT
|
||||||
|
|
||||||
|
for ARCH in $ARCHITECTURES; do
|
||||||
|
TMP_DEB=$(mktemp -p "$DIST" _openwrt_XXXXXX.deb)
|
||||||
|
rm -f "$TMP_DEB"
|
||||||
|
(cd "$FPM_DIR" && fpm -t deb \
|
||||||
|
-v "$PKG_VERSION" \
|
||||||
|
-p "$TMP_DEB" \
|
||||||
|
--architecture all \
|
||||||
|
"$BINARY_PATH=/usr/bin/sing-box")
|
||||||
|
|
||||||
|
bash "$PROJECT/.github/deb2ipk.sh" \
|
||||||
|
"$ARCH" \
|
||||||
|
"$TMP_DEB" \
|
||||||
|
"$DIST/sing-box-extended_${VERSION}_openwrt_${ARCH}.ipk"
|
||||||
|
rm -f "$TMP_DEB"
|
||||||
|
|
||||||
|
if command -v apk &>/dev/null; then
|
||||||
|
bash "$PROJECT/.github/build_openwrt_apk.sh" \
|
||||||
|
"$ARCH" "$VERSION" "$BINARY_PATH" \
|
||||||
|
"$DIST/sing-box-extended_${VERSION}_openwrt_${ARCH}.apk"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Built: sing-box-extended_${VERSION}_openwrt_${ARCH} (.ipk/.apk)"
|
||||||
|
done
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -22,4 +22,11 @@ CLAUDE.md
|
|||||||
AGENTS.md
|
AGENTS.md
|
||||||
/.claude/
|
/.claude/
|
||||||
dist
|
dist
|
||||||
logs
|
logs
|
||||||
|
/*.so
|
||||||
|
/*.log
|
||||||
|
/*.db-shm
|
||||||
|
/*.db-wal
|
||||||
|
/*.db.backup.*
|
||||||
|
/test_download.bin
|
||||||
|
/wget-log
|
||||||
130
.goreleaser.yaml
130
.goreleaser.yaml
@@ -11,6 +11,7 @@ builds:
|
|||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
- -s
|
- -s
|
||||||
- -buildid=
|
- -buildid=
|
||||||
|
- -checklinkname=0
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
@@ -22,8 +23,14 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOTOOLCHAIN=local
|
- GOTOOLCHAIN=local
|
||||||
@@ -54,6 +61,12 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
targets:
|
targets:
|
||||||
- linux_mips
|
- linux_mips
|
||||||
- linux_mips_softfloat
|
- linux_mips_softfloat
|
||||||
@@ -107,9 +120,15 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
- with_naive_outbound
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
|
- with_naive_outbound
|
||||||
- with_purego
|
- with_purego
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
@@ -134,9 +153,15 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
- with_naive_outbound
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
|
- with_naive_outbound
|
||||||
- with_purego
|
- with_purego
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
@@ -161,9 +186,15 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
- with_naive_outbound
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
|
- with_naive_outbound
|
||||||
- with_purego
|
- with_purego
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
@@ -188,9 +219,15 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
- with_naive_outbound
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
|
- with_naive_outbound
|
||||||
- with_purego
|
- with_purego
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
@@ -215,8 +252,14 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
- with_naive_outbound
|
- with_naive_outbound
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=1
|
- CGO_ENABLED=1
|
||||||
@@ -258,8 +301,14 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
- with_naive_outbound
|
- with_naive_outbound
|
||||||
- with_musl
|
- with_musl
|
||||||
env:
|
env:
|
||||||
@@ -309,6 +358,12 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
targets:
|
targets:
|
||||||
- linux_mips
|
- linux_mips
|
||||||
- linux_mips_softfloat
|
- linux_mips_softfloat
|
||||||
@@ -347,6 +402,50 @@ builds:
|
|||||||
- android_arm64
|
- android_arm64
|
||||||
- android_386
|
- android_386
|
||||||
- android_amd64
|
- android_amd64
|
||||||
|
- id: openwrt
|
||||||
|
<<: *template
|
||||||
|
hooks:
|
||||||
|
post:
|
||||||
|
- cmd: bash .github/build_openwrt_packages.sh "{{ .Version }}" "{{ .Target }}" "{{ .Path }}"
|
||||||
|
targets:
|
||||||
|
- linux_amd64_v1
|
||||||
|
- linux_arm64
|
||||||
|
- linux_386
|
||||||
|
- linux_arm_7
|
||||||
|
- linux_arm_6
|
||||||
|
- linux_riscv64
|
||||||
|
- linux_loong64
|
||||||
|
- id: openwrt-mips
|
||||||
|
<<: *template
|
||||||
|
tags:
|
||||||
|
- with_gvisor
|
||||||
|
- with_quic
|
||||||
|
- with_dhcp
|
||||||
|
- with_wireguard
|
||||||
|
- with_utls
|
||||||
|
- with_acme
|
||||||
|
- with_clash_api
|
||||||
|
- with_tailscale
|
||||||
|
- with_masque
|
||||||
|
- with_mtproxy
|
||||||
|
- with_ccm
|
||||||
|
- with_ocm
|
||||||
|
- with_openvpn
|
||||||
|
- with_trusttunnel
|
||||||
|
- with_sudoku
|
||||||
|
- with_profiler
|
||||||
|
- badlinkname
|
||||||
|
- tfogo_checklinkname0
|
||||||
|
hooks:
|
||||||
|
post:
|
||||||
|
- cmd: bash .github/build_openwrt_packages.sh "{{ .Version }}" "{{ .Target }}" "{{ .Path }}"
|
||||||
|
targets:
|
||||||
|
- linux_arm_5
|
||||||
|
- linux_mips_softfloat
|
||||||
|
- linux_mips64_softfloat
|
||||||
|
- linux_mipsle_softfloat
|
||||||
|
- linux_mipsle_hardfloat
|
||||||
|
- linux_mips64le
|
||||||
upx:
|
upx:
|
||||||
- enabled: true
|
- enabled: true
|
||||||
ids:
|
ids:
|
||||||
@@ -372,16 +471,6 @@ archives:
|
|||||||
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-naive-glibc
|
|
||||||
<<: *template
|
|
||||||
builds:
|
|
||||||
- naive-glibc
|
|
||||||
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 }}-glibc'
|
|
||||||
- id: archive-naive-musl
|
|
||||||
<<: *template
|
|
||||||
builds:
|
|
||||||
- naive-musl
|
|
||||||
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 }}-musl'
|
|
||||||
- id: archive-naive-purego-linux-amd64
|
- id: archive-naive-purego-linux-amd64
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
@@ -418,11 +507,16 @@ archives:
|
|||||||
- src: dist/naive-purego-windows-arm64_*/libcronet*
|
- src: dist/naive-purego-windows-arm64_*/libcronet*
|
||||||
strip_parent: true
|
strip_parent: true
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
|
||||||
- id: archive-legacy
|
- id: archive-naive-glibc
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
- legacy
|
- naive-glibc
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
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 }}-glibc'
|
||||||
|
- id: archive-naive-musl
|
||||||
|
<<: *template
|
||||||
|
builds:
|
||||||
|
- naive-musl
|
||||||
|
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 }}-musl'
|
||||||
- id: archive-compressed
|
- id: archive-compressed
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
@@ -455,5 +549,5 @@ release:
|
|||||||
- archive-naive-purego-windows-amd64
|
- archive-naive-purego-windows-amd64
|
||||||
- archive-naive-purego-windows-arm64
|
- archive-naive-purego-windows-arm64
|
||||||
- archive-compressed
|
- archive-compressed
|
||||||
- package
|
- archive-openwrt
|
||||||
skip_upload: true
|
skip_upload: true
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -27,7 +27,6 @@ CRONET_GO_PATH ?= $(shell pwd)/cronet-go
|
|||||||
.PHONY: test release docs build
|
.PHONY: test release docs build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
export GOTOOLCHAIN=local && \
|
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
build_admin_panel:
|
build_admin_panel:
|
||||||
@@ -94,6 +93,8 @@ release: build_admin_panel build_naive
|
|||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz \
|
mv dist/*.tar.gz \
|
||||||
dist/*.zip \
|
dist/*.zip \
|
||||||
|
dist/*.ipk \
|
||||||
|
dist/*.apk \
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
@@ -123,12 +124,11 @@ build_android:
|
|||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android
|
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||||
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
./codeberg-release.sh --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
|
release_android: lib_android update_android_version build_android upload_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
|
||||||
|
|||||||
@@ -3,16 +3,21 @@
|
|||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](go.mod)
|
[](go.mod)
|
||||||
[](https://codeberg.org/shtorm-7/sing-box-extended)
|
[](https://codeberg.org/shtorm-7/sing-box-extended)
|
||||||
|
[](https://t.me/sing_box_extended)
|
||||||
|
|
||||||
Sing-box with extended features.
|
Sing-box with extended features.
|
||||||
|
|
||||||
## 🔥 Features
|
## 🔥 Features
|
||||||
|
|
||||||
### Outbounds
|
### Protocols
|
||||||
- **WARP** — Cloudflare WARP integration through WireGuard
|
- **WARP** — Cloudflare WARP integration through WireGuard
|
||||||
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
|
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
|
||||||
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
|
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
|
||||||
- **Mieru** — Secure, hard to classify, hard to probe network protocol
|
- **Mieru** — Secure, hard to classify, hard to probe network protocol
|
||||||
|
- **OpenVPN** — OpenVPN client with tls-auth, tls-crypt and tls-crypt-v2 support
|
||||||
|
- **TrustTunnel** — AdGuard's obfuscated VPN protocol, indistinguishable from HTTPS traffic
|
||||||
|
- **Sudoku** — Traffic obfuscation protocol based on 4×4 Sudoku puzzles with low-entropy fingerprints
|
||||||
|
- **SSH** — SSH client and server with certificate authentication and upstream fallback
|
||||||
- **VPN** — Routed tunnel over any TCP sing-box protocol
|
- **VPN** — Routed tunnel over any TCP sing-box protocol
|
||||||
- **Bond** — Link aggregation for increasing throughput
|
- **Bond** — Link aggregation for increasing throughput
|
||||||
- **Fallback** — Outbound group with priority-based switching
|
- **Fallback** — Outbound group with priority-based switching
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ func (m *Manager) Remove(tag string) error {
|
|||||||
if m.defaultOutbound == outbound {
|
if m.defaultOutbound == outbound {
|
||||||
if len(m.outbounds) > 0 {
|
if len(m.outbounds) > 0 {
|
||||||
m.defaultOutbound = m.outbounds[0]
|
m.defaultOutbound = m.outbounds[0]
|
||||||
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
|
m.logger.Notice("updated default outbound to ", m.defaultOutbound.Tag())
|
||||||
} else {
|
} else {
|
||||||
m.defaultOutbound = nil
|
m.defaultOutbound = nil
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
||||||
m.defaultOutbound = outbound
|
m.defaultOutbound = outbound
|
||||||
if m.started {
|
if m.started {
|
||||||
m.logger.Info("updated default outbound to ", outbound.Tag())
|
m.logger.Notice("updated default outbound to ", outbound.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -34,10 +36,11 @@ type Adapter struct {
|
|||||||
callbackAccess sync.Mutex
|
callbackAccess sync.Mutex
|
||||||
callbacks list.List[adapter.ProviderUpdateCallback]
|
callbacks list.List[adapter.ProviderUpdateCallback]
|
||||||
|
|
||||||
link string
|
link string
|
||||||
enabled bool
|
enabled bool
|
||||||
timeout time.Duration
|
removeEmojis bool
|
||||||
interval time.Duration
|
timeout time.Duration
|
||||||
|
interval time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
|
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
|
||||||
@@ -68,6 +71,10 @@ func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.Out
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Adapter) SetRemoveEmojis(remove bool) {
|
||||||
|
a.removeEmojis = remove
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Adapter) Start() error {
|
func (a *Adapter) Start() error {
|
||||||
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
|
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
|
||||||
if a.history == nil {
|
if a.history == nil {
|
||||||
@@ -102,6 +109,10 @@ func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
|
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
|
||||||
|
if a.removeEmojis {
|
||||||
|
removeEmojisFromTags(newOpts)
|
||||||
|
}
|
||||||
|
uniquifyTags(newOpts)
|
||||||
a.removeUseless(newOpts)
|
a.removeUseless(newOpts)
|
||||||
var (
|
var (
|
||||||
oldOptByTag = make(map[string]option.Outbound)
|
oldOptByTag = make(map[string]option.Outbound)
|
||||||
@@ -265,3 +276,34 @@ func (a *Adapter) removeUseless(newOpts []option.Outbound) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uniquifyTags(opts []option.Outbound) {
|
||||||
|
count := make(map[string]int)
|
||||||
|
for i, opt := range opts {
|
||||||
|
count[opt.Tag]++
|
||||||
|
if count[opt.Tag] > 1 {
|
||||||
|
opts[i].Tag = F.ToString(opt.Tag, " #", count[opt.Tag])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeEmojisFromTags(opts []option.Outbound) {
|
||||||
|
for i, opt := range opts {
|
||||||
|
cleaned := flagRegex.ReplaceAllStringFunc(opt.Tag, flagToCountryCode)
|
||||||
|
cleaned = emojiRegex.ReplaceAllString(cleaned, "")
|
||||||
|
cleaned = multiSpaceRegex.ReplaceAllString(cleaned, " ")
|
||||||
|
opts[i].Tag = strings.TrimSpace(cleaned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func flagToCountryCode(flag string) string {
|
||||||
|
runes := []rune(flag)
|
||||||
|
if len(runes) == 2 {
|
||||||
|
return string(rune(runes[0]-0x1F1E6+'A')) + string(rune(runes[1]-0x1F1E6+'A')) + " "
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var flagRegex = regexp.MustCompile(`[\x{1F1E6}-\x{1F1FF}]{2}`)
|
||||||
|
var emojiRegex = regexp.MustCompile(`[\x{1F1E0}-\x{1F1FF}\x{1F300}-\x{1F9FF}\x{2600}-\x{27BF}\x{FE00}-\x{FE0F}\x{200D}]+`)
|
||||||
|
var multiSpaceRegex = regexp.MustCompile(`\s{2,}`)
|
||||||
|
|||||||
45
adapter/provider/adapter_test.go
Normal file
45
adapter/provider/adapter_test.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package provider
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFlagToCountryCodeAllFlags(t *testing.T) {
|
||||||
|
for first := 'A'; first <= 'Z'; first++ {
|
||||||
|
for second := 'A'; second <= 'Z'; second++ {
|
||||||
|
flag := string(rune(0x1F1E6+(first-'A'))) + string(rune(0x1F1E6+(second-'A')))
|
||||||
|
expected := string(first) + string(second)
|
||||||
|
result := flagToCountryCode(flag)
|
||||||
|
// flagToCountryCode appends a space
|
||||||
|
if result != expected+" " {
|
||||||
|
t.Errorf("flagToCountryCode(%q) = %q, want %q", expected, result, expected+" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRemoveEmojisFromTags(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"🇺🇸 United States", "US United States"},
|
||||||
|
{"🇷🇺 Россия", "RU Россия"},
|
||||||
|
{"🇩🇪 Germany 🚀", "DE Germany"},
|
||||||
|
{"🇫🇷🇬🇧 France-UK", "FR GB France-UK"},
|
||||||
|
{"No emojis here", "No emojis here"},
|
||||||
|
{"🌍 World", "World"},
|
||||||
|
{"🇯🇵 Tokyo ⚡ Fast", "JP Tokyo Fast"},
|
||||||
|
{"Germany 🇩🇪", "Germany DE"},
|
||||||
|
{"Server 🇺🇸 Node", "Server US Node"},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
opts := []option.Outbound{{Tag: tt.input}}
|
||||||
|
removeEmojisFromTags(opts)
|
||||||
|
if opts[0].Tag != tt.expected {
|
||||||
|
t.Errorf("removeEmojisFromTags(%q) = %q, want %q", tt.input, opts[0].Tag, tt.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
box.go
4
box.go
@@ -458,7 +458,7 @@ func (s *Box) PreStart() error {
|
|||||||
s.Close()
|
s.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
s.logger.Notice("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,7 +477,7 @@ func (s *Box) Start() error {
|
|||||||
s.Close()
|
s.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
s.logger.Notice("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_trusttunnel", "with_openvpn", "with_sudoku", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
||||||
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
||||||
// memcTags = append(memcTags, "with_tailscale")
|
// memcTags = append(memcTags, "with_tailscale")
|
||||||
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ func run() error {
|
|||||||
for {
|
for {
|
||||||
osSignal := <-osSignals
|
osSignal := <-osSignals
|
||||||
if osSignal == syscall.SIGHUP {
|
if osSignal == syscall.SIGHUP {
|
||||||
|
log.Notice("received SIGHUP, reloading...")
|
||||||
err = check()
|
err = check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(E.Cause(err, "reload service"))
|
log.Error(E.Cause(err, "reload service"))
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package byteformats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
unitNames = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
|
||||||
iUnitNames = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
||||||
kUnitNames = []string{"kB", "MB", "GB", "TB", "PB", "EB"}
|
|
||||||
kiUnitNames = []string{"KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func formatBytes(s uint64, base float64, sizes []string) string {
|
|
||||||
if s < 10 {
|
|
||||||
return fmt.Sprintf("%d B", s)
|
|
||||||
}
|
|
||||||
e := math.Floor(logn(float64(s), base))
|
|
||||||
suffix := sizes[int(e)]
|
|
||||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatKBytes(s uint64, base float64, sizes []string) string {
|
|
||||||
if s == 0 {
|
|
||||||
return fmt.Sprintf("0 %s", sizes[0])
|
|
||||||
}
|
|
||||||
e := math.Floor(logn(float64(s), base))
|
|
||||||
if e < 1 {
|
|
||||||
e = 1
|
|
||||||
}
|
|
||||||
suffix := sizes[int(e)-1]
|
|
||||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func logn(n, b float64) float64 {
|
|
||||||
return math.Log(n) / math.Log(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatBytes(s uint64) string {
|
|
||||||
return formatBytes(s, 1000, unitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatMemoryBytes(s uint64) string {
|
|
||||||
return formatBytes(s, 1024, unitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatIBytes(s uint64) string {
|
|
||||||
return formatBytes(s, 1024, iUnitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatKBytes(s uint64) string {
|
|
||||||
return formatKBytes(s, 1000, kUnitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatMemoryKBytes(s uint64) string {
|
|
||||||
return formatKBytes(s, 1024, kUnitNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatKIBytes(s uint64) string {
|
|
||||||
return formatKBytes(s, 1024, kiUnitNames)
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
package byteformats
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Byte = 1 << (iota * 10)
|
|
||||||
KiByte
|
|
||||||
MiByte
|
|
||||||
GiByte
|
|
||||||
TiByte
|
|
||||||
PiByte
|
|
||||||
EiByte
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
KByte = Byte * 1000
|
|
||||||
MByte = KByte * 1000
|
|
||||||
GByte = MByte * 1000
|
|
||||||
TByte = GByte * 1000
|
|
||||||
PByte = TByte * 1000
|
|
||||||
EByte = PByte * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
var unitValueTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"k": KByte,
|
|
||||||
"kb": KByte,
|
|
||||||
"ki": KiByte,
|
|
||||||
"kib": KiByte,
|
|
||||||
"m": MByte,
|
|
||||||
"mb": MByte,
|
|
||||||
"mi": MiByte,
|
|
||||||
"mib": MiByte,
|
|
||||||
"g": GByte,
|
|
||||||
"gb": GByte,
|
|
||||||
"gi": GiByte,
|
|
||||||
"gib": GiByte,
|
|
||||||
"t": TByte,
|
|
||||||
"tb": TByte,
|
|
||||||
"ti": TiByte,
|
|
||||||
"tib": TiByte,
|
|
||||||
"p": PByte,
|
|
||||||
"pb": PByte,
|
|
||||||
"pi": PiByte,
|
|
||||||
"pib": PiByte,
|
|
||||||
"e": EByte,
|
|
||||||
"eb": EByte,
|
|
||||||
"ei": EiByte,
|
|
||||||
"eib": EiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var memoryUnitValueTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"k": KiByte,
|
|
||||||
"kb": KiByte,
|
|
||||||
"m": MiByte,
|
|
||||||
"mb": MiByte,
|
|
||||||
"g": GiByte,
|
|
||||||
"gb": GiByte,
|
|
||||||
"t": TiByte,
|
|
||||||
"tb": TiByte,
|
|
||||||
"p": PiByte,
|
|
||||||
"pb": PiByte,
|
|
||||||
"e": EiByte,
|
|
||||||
"eb": EiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var networkUnitValueTable = map[string]uint64{
|
|
||||||
"Bps": Byte,
|
|
||||||
"Kbps": KByte / 8,
|
|
||||||
"KBps": KByte,
|
|
||||||
"Mbps": MByte / 8,
|
|
||||||
"MBps": MByte,
|
|
||||||
"Gbps": GByte / 8,
|
|
||||||
"GBps": GByte,
|
|
||||||
"Tbps": TByte / 8,
|
|
||||||
"TBps": TByte,
|
|
||||||
"Pbps": PByte / 8,
|
|
||||||
"PBps": PByte,
|
|
||||||
"Ebps": EByte / 8,
|
|
||||||
"EBps": EByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawBytes struct {
|
|
||||||
value uint64
|
|
||||||
unit string
|
|
||||||
unitValue uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b rawBytes) MarshalJSON() ([]byte, error) {
|
|
||||||
if b.unit == "" {
|
|
||||||
return json.Marshal(b.value)
|
|
||||||
}
|
|
||||||
return json.Marshal(strconv.FormatUint(b.value/b.unitValue, 10) + b.unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUnit(b *rawBytes, unitTable map[string]uint64, caseSensitive bool, bytes []byte) error {
|
|
||||||
var intValue int64
|
|
||||||
err := json.Unmarshal(bytes, &intValue)
|
|
||||||
if err == nil {
|
|
||||||
b.value = uint64(intValue)
|
|
||||||
b.unit = ""
|
|
||||||
b.unitValue = 1
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var stringValue string
|
|
||||||
err = json.Unmarshal(bytes, &stringValue)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.TrimSpace(stringValue) == "" {
|
|
||||||
b.value = 0
|
|
||||||
b.unit = ""
|
|
||||||
b.unitValue = 1
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
unitIndex := 0
|
|
||||||
for i, c := range stringValue {
|
|
||||||
if c < '0' || c > '9' {
|
|
||||||
unitIndex = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if unitIndex == 0 {
|
|
||||||
return fmt.Errorf("invalid format: %s", stringValue)
|
|
||||||
}
|
|
||||||
value, err := strconv.ParseUint(stringValue[:unitIndex], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parse %s: %w", stringValue[:unitIndex], err)
|
|
||||||
}
|
|
||||||
rawUnit := stringValue[unitIndex:]
|
|
||||||
var unit string
|
|
||||||
if caseSensitive {
|
|
||||||
unit = strings.TrimSpace(rawUnit)
|
|
||||||
} else {
|
|
||||||
unit = strings.TrimSpace(strings.ToLower(rawUnit))
|
|
||||||
}
|
|
||||||
unitValue, loaded := unitTable[unit]
|
|
||||||
if !loaded {
|
|
||||||
return fmt.Errorf("unsupported unit: %s", rawUnit)
|
|
||||||
}
|
|
||||||
b.value = value * unitValue
|
|
||||||
b.unit = rawUnit
|
|
||||||
b.unitValue = unitValue
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bytes struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bytes) Value() uint64 {
|
|
||||||
if b == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return b.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Bytes) UnmarshalJSON(bytes []byte) error {
|
|
||||||
return parseUnit(&b.rawBytes, unitValueTable, false, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MemoryBytes struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemoryBytes) Value() uint64 {
|
|
||||||
if m == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return m.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MemoryBytes) UnmarshalJSON(bytes []byte) error {
|
|
||||||
return parseUnit(&m.rawBytes, memoryUnitValueTable, false, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkBytes struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytes) Value() uint64 {
|
|
||||||
if n == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytes) UnmarshalJSON(bytes []byte) error {
|
|
||||||
return parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NetworkBytesCompat struct {
|
|
||||||
rawBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytesCompat) Value() uint64 {
|
|
||||||
if n == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *NetworkBytesCompat) UnmarshalJSON(bytes []byte) error {
|
|
||||||
err := parseUnit(&n.rawBytes, networkUnitValueTable, true, bytes)
|
|
||||||
if err != nil {
|
|
||||||
newErr := parseUnit(&n.rawBytes, unitValueTable, false, bytes)
|
|
||||||
if newErr == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
package byteformats_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/byteformats"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNetworkBytes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testMap := map[string]uint64{
|
|
||||||
"1 Bps": byteformats.Byte,
|
|
||||||
"1 Kbps": byteformats.KByte / 8,
|
|
||||||
"1 KBps": byteformats.KByte,
|
|
||||||
"1 Mbps": byteformats.MByte / 8,
|
|
||||||
"1 MBps": byteformats.MByte,
|
|
||||||
"1 Gbps": byteformats.GByte / 8,
|
|
||||||
"1 GBps": byteformats.GByte,
|
|
||||||
"1 Tbps": byteformats.TByte / 8,
|
|
||||||
"1 TBps": byteformats.TByte,
|
|
||||||
"1 Pbps": byteformats.PByte / 8,
|
|
||||||
"1 PBps": byteformats.PByte,
|
|
||||||
"1k": byteformats.KByte,
|
|
||||||
"1m": byteformats.MByte,
|
|
||||||
}
|
|
||||||
for k, v := range testMap {
|
|
||||||
var nb byteformats.NetworkBytesCompat
|
|
||||||
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &nb))
|
|
||||||
require.Equal(t, v, nb.Value())
|
|
||||||
b, err := json.Marshal(nb)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "\""+k+"\"", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMemoryBytes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testMap := map[string]uint64{
|
|
||||||
"1 B": byteformats.Byte,
|
|
||||||
"1 KB": byteformats.KiByte,
|
|
||||||
"1 MB": byteformats.MiByte,
|
|
||||||
"1 GB": byteformats.GiByte,
|
|
||||||
"1 TB": byteformats.TiByte,
|
|
||||||
"1 PB": byteformats.PiByte,
|
|
||||||
}
|
|
||||||
for k, v := range testMap {
|
|
||||||
var mb byteformats.MemoryBytes
|
|
||||||
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
|
|
||||||
require.Equal(t, v, mb.Value())
|
|
||||||
b, err := json.Marshal(mb)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "\""+k+"\"", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDefaultBytes(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
testMap := map[string]uint64{
|
|
||||||
"1 B": byteformats.Byte,
|
|
||||||
"1 KB": byteformats.KByte,
|
|
||||||
"1 KiB": byteformats.KiByte,
|
|
||||||
"1 MB": byteformats.MByte,
|
|
||||||
"1 MiB": byteformats.MiByte,
|
|
||||||
"1 GB": byteformats.GByte,
|
|
||||||
"1 GiB": byteformats.GiByte,
|
|
||||||
"1 TB": byteformats.TByte,
|
|
||||||
"1 TiB": byteformats.TiByte,
|
|
||||||
"1 PB": byteformats.PByte,
|
|
||||||
"1 PiB": byteformats.PiByte,
|
|
||||||
"1 EB": byteformats.EByte,
|
|
||||||
"1 EiB": byteformats.EiByte,
|
|
||||||
"1k": byteformats.KByte,
|
|
||||||
"1m": byteformats.MByte,
|
|
||||||
"1g": byteformats.GByte,
|
|
||||||
"1t": byteformats.TByte,
|
|
||||||
"1p": byteformats.PByte,
|
|
||||||
"1e": byteformats.EByte,
|
|
||||||
"1K": byteformats.KByte,
|
|
||||||
"1M": byteformats.MByte,
|
|
||||||
"1G": byteformats.GByte,
|
|
||||||
"1T": byteformats.TByte,
|
|
||||||
"1P": byteformats.PByte,
|
|
||||||
"1E": byteformats.EByte,
|
|
||||||
"1Ki": byteformats.KiByte,
|
|
||||||
"1Mi": byteformats.MiByte,
|
|
||||||
"1Gi": byteformats.GiByte,
|
|
||||||
"1Ti": byteformats.TiByte,
|
|
||||||
"1Pi": byteformats.PiByte,
|
|
||||||
"1Ei": byteformats.EiByte,
|
|
||||||
"1KiB": byteformats.KiByte,
|
|
||||||
"1MiB": byteformats.MiByte,
|
|
||||||
"1GiB": byteformats.GiByte,
|
|
||||||
"1TiB": byteformats.TiByte,
|
|
||||||
"1PiB": byteformats.PiByte,
|
|
||||||
"1EiB": byteformats.EiByte,
|
|
||||||
"1kB": byteformats.KByte,
|
|
||||||
"1mB": byteformats.MByte,
|
|
||||||
"1gB": byteformats.GByte,
|
|
||||||
"1tB": byteformats.TByte,
|
|
||||||
"1pB": byteformats.PByte,
|
|
||||||
"1eB": byteformats.EByte,
|
|
||||||
}
|
|
||||||
for k, v := range testMap {
|
|
||||||
var mb byteformats.Bytes
|
|
||||||
require.NoError(t, json.Unmarshal([]byte("\""+k+"\""), &mb))
|
|
||||||
require.Equal(t, v, mb.Value())
|
|
||||||
b, err := json.Marshal(mb)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "\""+k+"\"", string(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -77,7 +77,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
l.logger.Notice("tcp server started at ", tcpListener.Addr())
|
||||||
l.tcpListener = tcpListener
|
l.tcpListener = tcpListener
|
||||||
return tcpListener, err
|
return tcpListener, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
l.udpConn = udpConn.(*net.UDPConn)
|
l.udpConn = udpConn.(*net.UDPConn)
|
||||||
l.udpAddr = bindAddr
|
l.udpAddr = bindAddr
|
||||||
l.logger.Info("udp server started at ", udpConn.LocalAddr())
|
l.logger.Notice("udp server started at ", udpConn.LocalAddr())
|
||||||
return udpConn, err
|
return udpConn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
38
common/onclose/conn.go
Normal file
38
common/onclose/conn.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package onclose
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CloseHandlerFunc = func()
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
net.Conn
|
||||||
|
onClose func()
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConn(conn net.Conn, onClose func()) *Conn {
|
||||||
|
return &Conn{Conn: conn, onClose: onClose}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
c.once.Do(c.onClose)
|
||||||
|
return c.Conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
type PacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
onClose func()
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPacketConn(conn net.PacketConn, onClose func()) *PacketConn {
|
||||||
|
return &PacketConn{PacketConn: conn, onClose: onClose}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
c.once.Do(c.onClose)
|
||||||
|
return c.PacketConn.Close()
|
||||||
|
}
|
||||||
135
common/tls/openvpn_client.go
Normal file
135
common/tls/openvpn_client.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewOpenVPNClient(ctx context.Context, logger logger.ContextLogger, options option.OpenVPNTLSOptions) (Config, error) {
|
||||||
|
ca := options.CA
|
||||||
|
if ca == "" && options.CAPath != "" {
|
||||||
|
data, err := os.ReadFile(options.CAPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read ca_path")
|
||||||
|
}
|
||||||
|
ca = string(data)
|
||||||
|
}
|
||||||
|
certificate := options.Certificate
|
||||||
|
if certificate == "" && options.CertificatePath != "" {
|
||||||
|
data, err := os.ReadFile(options.CertificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read certificate_path")
|
||||||
|
}
|
||||||
|
certificate = string(data)
|
||||||
|
}
|
||||||
|
key := options.Key
|
||||||
|
if key == "" && options.KeyPath != "" {
|
||||||
|
data, err := os.ReadFile(options.KeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read key_path")
|
||||||
|
}
|
||||||
|
key = string(data)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(ca) == "" {
|
||||||
|
return nil, E.New("openvpn: missing ca certificate")
|
||||||
|
}
|
||||||
|
if block, _ := pem.Decode([]byte(ca)); block == nil {
|
||||||
|
return nil, E.New("openvpn: ca is not valid PEM")
|
||||||
|
}
|
||||||
|
hasCert := strings.TrimSpace(certificate) != "" || strings.TrimSpace(key) != ""
|
||||||
|
if hasCert {
|
||||||
|
if strings.TrimSpace(certificate) == "" || strings.TrimSpace(key) == "" {
|
||||||
|
return nil, E.New("openvpn: certificate and key must both be set")
|
||||||
|
}
|
||||||
|
if block, _ := pem.Decode([]byte(certificate)); block == nil {
|
||||||
|
return nil, E.New("openvpn: certificate is not valid PEM")
|
||||||
|
}
|
||||||
|
if block, _ := pem.Decode([]byte(key)); block == nil {
|
||||||
|
return nil, E.New("openvpn: key is not valid PEM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roots := x509.NewCertPool()
|
||||||
|
if !roots.AppendCertsFromPEM([]byte(ca)) {
|
||||||
|
return nil, E.New("openvpn: failed to parse ca certificate")
|
||||||
|
}
|
||||||
|
var tlsConfig tls.Config
|
||||||
|
tlsConfig.RootCAs = roots
|
||||||
|
tlsConfig.InsecureSkipVerify = true
|
||||||
|
if options.CipherSuites != nil {
|
||||||
|
find:
|
||||||
|
for _, cipherSuite := range options.CipherSuites {
|
||||||
|
for _, tlsCipherSuite := range tls.CipherSuites() {
|
||||||
|
if cipherSuite == tlsCipherSuite.Name {
|
||||||
|
tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
|
||||||
|
continue find
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, E.New("unknown cipher_suite: ", cipherSuite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tlsConfig.VerifyConnection = func(cs tls.ConnectionState) error {
|
||||||
|
if len(cs.PeerCertificates) == 0 {
|
||||||
|
return E.New("openvpn: server did not provide certificate")
|
||||||
|
}
|
||||||
|
cert := cs.PeerCertificates[0]
|
||||||
|
intermediates := x509.NewCertPool()
|
||||||
|
for _, intermediate := range cs.PeerCertificates[1:] {
|
||||||
|
intermediates.AddCert(intermediate)
|
||||||
|
}
|
||||||
|
_, err := cert.Verify(x509.VerifyOptions{
|
||||||
|
Roots: roots,
|
||||||
|
Intermediates: intermediates,
|
||||||
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if options.VerifyX509Name != "" {
|
||||||
|
cn := cert.Subject.CommonName
|
||||||
|
switch options.VerifyX509NameMode {
|
||||||
|
case "name-prefix":
|
||||||
|
if !strings.HasPrefix(cn, options.VerifyX509Name) {
|
||||||
|
return E.New("openvpn: server CN ", cn, " does not match prefix ", options.VerifyX509Name)
|
||||||
|
}
|
||||||
|
case "name-suffix":
|
||||||
|
if !strings.HasSuffix(cn, options.VerifyX509Name) {
|
||||||
|
return E.New("openvpn: server CN ", cn, " does not match suffix ", options.VerifyX509Name)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if cn != options.VerifyX509Name {
|
||||||
|
return E.New("openvpn: server CN ", cn, " does not match ", options.VerifyX509Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if hasCert {
|
||||||
|
cert, err := tls.X509KeyPair([]byte(certificate), []byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "openvpn: parse client certificate/key")
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
var config Config = &STDClientConfig{ctx, &tlsConfig, false, 0, false}
|
||||||
|
if options.KernelRx || options.KernelTx {
|
||||||
|
if !C.IsLinux {
|
||||||
|
return nil, E.New("kTLS is only supported on Linux")
|
||||||
|
}
|
||||||
|
config = &KTLSClientConfig{
|
||||||
|
Config: config,
|
||||||
|
logger: logger,
|
||||||
|
kernelTx: options.KernelTx,
|
||||||
|
kernelRx: options.KernelRx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
@@ -164,7 +164,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
config.Certificates = []tls.Certificate{keyPair}
|
config.Certificates = []tls.Certificate{keyPair}
|
||||||
c.config = config
|
c.config = config
|
||||||
c.access.Unlock()
|
c.access.Unlock()
|
||||||
c.logger.Info("reloaded TLS certificate")
|
c.logger.Notice("reloaded TLS certificate")
|
||||||
} else if common.Contains(c.clientCertificatePath, path) {
|
} else if common.Contains(c.clientCertificatePath, path) {
|
||||||
clientCertificateCA := x509.NewCertPool()
|
clientCertificateCA := x509.NewCertPool()
|
||||||
var reloaded bool
|
var reloaded bool
|
||||||
@@ -188,7 +188,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
config.ClientCAs = clientCertificateCA
|
config.ClientCAs = clientCertificateCA
|
||||||
c.config = config
|
c.config = config
|
||||||
c.access.Unlock()
|
c.access.Unlock()
|
||||||
c.logger.Info("reloaded client certificates")
|
c.logger.Notice("reloaded client certificates")
|
||||||
} else if path == c.echKeyPath {
|
} else if path == c.echKeyPath {
|
||||||
echKey, err := os.ReadFile(c.echKeyPath)
|
echKey, err := os.ReadFile(c.echKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -198,7 +198,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.logger.Info("reloaded ECH keys")
|
c.logger.Notice("reloaded ECH keys")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ const (
|
|||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
|
TypeTrustTunnel = "trusttunnel"
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeWARP = "warp"
|
TypeWARP = "warp"
|
||||||
TypeMASQUE = "masque"
|
TypeMASQUE = "masque"
|
||||||
|
TypeOpenVPN = "openvpn"
|
||||||
TypeMTProxy = "mtproxy"
|
TypeMTProxy = "mtproxy"
|
||||||
TypeParser = "parser"
|
TypeParser = "parser"
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
@@ -25,6 +27,7 @@ const (
|
|||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeMieru = "mieru"
|
TypeMieru = "mieru"
|
||||||
TypeAnyTLS = "anytls"
|
TypeAnyTLS = "anytls"
|
||||||
|
TypeSudoku = "sudoku"
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
@@ -84,6 +87,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "VMess"
|
return "VMess"
|
||||||
case TypeTrojan:
|
case TypeTrojan:
|
||||||
return "Trojan"
|
return "Trojan"
|
||||||
|
case TypeTrustTunnel:
|
||||||
|
return "TrustTunnel"
|
||||||
case TypeNaive:
|
case TypeNaive:
|
||||||
return "Naive"
|
return "Naive"
|
||||||
case TypeWireGuard:
|
case TypeWireGuard:
|
||||||
@@ -92,6 +97,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "WARP"
|
return "WARP"
|
||||||
case TypeMASQUE:
|
case TypeMASQUE:
|
||||||
return "MASQUE"
|
return "MASQUE"
|
||||||
|
case TypeOpenVPN:
|
||||||
|
return "OpenVPN"
|
||||||
case TypeMTProxy:
|
case TypeMTProxy:
|
||||||
return "MTProxy"
|
return "MTProxy"
|
||||||
case TypeParser:
|
case TypeParser:
|
||||||
@@ -120,6 +127,8 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Mieru"
|
return "Mieru"
|
||||||
case TypeAnyTLS:
|
case TypeAnyTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
|
case TypeSudoku:
|
||||||
|
return "Sudoku"
|
||||||
case TypeFallback:
|
case TypeFallback:
|
||||||
return "Fallback"
|
return "Fallback"
|
||||||
case TypeTailscale:
|
case TypeTailscale:
|
||||||
|
|||||||
@@ -20,13 +20,14 @@ const (
|
|||||||
type LogLevel int32
|
type LogLevel int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LogLevel_PANIC LogLevel = 0
|
LogLevel_PANIC LogLevel = 0
|
||||||
LogLevel_FATAL LogLevel = 1
|
LogLevel_FATAL LogLevel = 1
|
||||||
LogLevel_ERROR LogLevel = 2
|
LogLevel_ERROR LogLevel = 2
|
||||||
LogLevel_WARN LogLevel = 3
|
LogLevel_WARN LogLevel = 3
|
||||||
LogLevel_INFO LogLevel = 4
|
LogLevel_NOTICE LogLevel = 4
|
||||||
LogLevel_DEBUG LogLevel = 5
|
LogLevel_INFO LogLevel = 5
|
||||||
LogLevel_TRACE LogLevel = 6
|
LogLevel_DEBUG LogLevel = 6
|
||||||
|
LogLevel_TRACE LogLevel = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for LogLevel.
|
// Enum value maps for LogLevel.
|
||||||
@@ -36,18 +37,20 @@ var (
|
|||||||
1: "FATAL",
|
1: "FATAL",
|
||||||
2: "ERROR",
|
2: "ERROR",
|
||||||
3: "WARN",
|
3: "WARN",
|
||||||
4: "INFO",
|
4: "NOTICE",
|
||||||
5: "DEBUG",
|
5: "INFO",
|
||||||
6: "TRACE",
|
6: "DEBUG",
|
||||||
|
7: "TRACE",
|
||||||
}
|
}
|
||||||
LogLevel_value = map[string]int32{
|
LogLevel_value = map[string]int32{
|
||||||
"PANIC": 0,
|
"PANIC": 0,
|
||||||
"FATAL": 1,
|
"FATAL": 1,
|
||||||
"ERROR": 2,
|
"ERROR": 2,
|
||||||
"WARN": 3,
|
"WARN": 3,
|
||||||
"INFO": 4,
|
"NOTICE": 4,
|
||||||
"DEBUG": 5,
|
"INFO": 5,
|
||||||
"TRACE": 6,
|
"DEBUG": 6,
|
||||||
|
"TRACE": 7,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -59,9 +59,10 @@ enum LogLevel {
|
|||||||
FATAL = 1;
|
FATAL = 1;
|
||||||
ERROR = 2;
|
ERROR = 2;
|
||||||
WARN = 3;
|
WARN = 3;
|
||||||
INFO = 4;
|
NOTICE = 4;
|
||||||
DEBUG = 5;
|
INFO = 5;
|
||||||
TRACE = 6;
|
DEBUG = 6;
|
||||||
|
TRACE = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Log {
|
message Log {
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ func (t *Transport) updateServers() error {
|
|||||||
return E.Cause(err, "dhcp: prepare interface")
|
return E.Cause(err, "dhcp: prepare interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
t.logger.Info("dhcp: query DNS servers on ", iface.Name)
|
t.logger.Notice("dhcp: query DNS servers on ", iface.Name)
|
||||||
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
||||||
err = t.fetchServers0(fetchCtx, iface)
|
err = t.fetchServers0(fetchCtx, iface)
|
||||||
cancel()
|
cancel()
|
||||||
@@ -303,7 +303,7 @@ func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4
|
|||||||
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||||
})
|
})
|
||||||
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
||||||
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
|
t.logger.Notice("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
|
||||||
}
|
}
|
||||||
t.servers = serverAddrs
|
t.servers = serverAddrs
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ func (m *TransportManager) Remove(tag string) error {
|
|||||||
return E.New("default server cannot be fakeip")
|
return E.New("default server cannot be fakeip")
|
||||||
}
|
}
|
||||||
m.defaultTransport = nextTransport
|
m.defaultTransport = nextTransport
|
||||||
m.logger.Info("updated default server to ", m.defaultTransport.Tag())
|
m.logger.Notice("updated default server to ", m.defaultTransport.Tag())
|
||||||
} else {
|
} else {
|
||||||
m.defaultTransport = nil
|
m.defaultTransport = nil
|
||||||
}
|
}
|
||||||
@@ -287,7 +287,7 @@ func (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger,
|
|||||||
}
|
}
|
||||||
m.defaultTransport = transport
|
m.defaultTransport = transport
|
||||||
if m.started {
|
if m.started {
|
||||||
m.logger.Info("updated default server to ", transport.Tag())
|
m.logger.Notice("updated default server to ", transport.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if transport.Type() == C.DNSTypeFakeIP {
|
if transport.Type() == C.DNSTypeFakeIP {
|
||||||
|
|||||||
@@ -31,10 +31,11 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||||
| `vless` | [VLESS](./vless/) | TCP |
|
| `vless` | [VLESS](./vless/) | TCP |
|
||||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||||
|
| `mieru` | [Mieru](./mieru/) | :material-close: |
|
||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
The tag of the inbound.
|
The tag of the inbound.
|
||||||
|
|||||||
@@ -31,10 +31,11 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||||
| `vless` | [VLESS](./vless/) | TCP |
|
| `vless` | [VLESS](./vless/) | TCP |
|
||||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||||
|
| `mieru` | [Mieru](./mieru/) | :material-close: |
|
||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
入站的标签。
|
入站的标签。
|
||||||
|
|||||||
49
docs/configuration/inbound/mieru.md
Normal file
49
docs/configuration/inbound/mieru.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "mieru",
|
||||||
|
"tag": "mieru-in",
|
||||||
|
|
||||||
|
... // Listen Fields
|
||||||
|
|
||||||
|
"transport": "TCP",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "asdf",
|
||||||
|
"password": "hjkl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"traffic_pattern": "GgQIARAK",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Listen Fields
|
||||||
|
|
||||||
|
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
#### transport
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
Transmission protocol. Allowed values are `TCP` and `UDP`.
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
==Required==
|
||||||
|
|
||||||
|
A list of mieru user name and password.
|
||||||
|
|
||||||
|
#### traffic_pattern
|
||||||
|
|
||||||
|
A base64 string to fine tune network behavior.
|
||||||
|
|
||||||
|
#### user_hint_is_mandatory
|
||||||
|
|
||||||
|
If proxy client doesn't sent user hint, proxy server will refuse the connection.
|
||||||
49
docs/configuration/inbound/mieru.zh.md
Normal file
49
docs/configuration/inbound/mieru.zh.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
|
### 结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "mieru",
|
||||||
|
"tag": "mieru-in",
|
||||||
|
|
||||||
|
... // 监听字段
|
||||||
|
|
||||||
|
"transport": "TCP",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "asdf",
|
||||||
|
"password": "hjkl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"traffic_pattern": "GgQIARAK",
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听字段
|
||||||
|
|
||||||
|
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||||
|
|
||||||
|
### 字段
|
||||||
|
|
||||||
|
#### transport
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
通信协议。可设为 `TCP` 或 `UDP`。
|
||||||
|
|
||||||
|
#### users
|
||||||
|
|
||||||
|
==必填==
|
||||||
|
|
||||||
|
一组 mieru 用户名和密码。
|
||||||
|
|
||||||
|
#### traffic_pattern
|
||||||
|
|
||||||
|
一个 base64 字符串用于微调网络行为。
|
||||||
|
|
||||||
|
#### user_hint_is_mandatory
|
||||||
|
|
||||||
|
客户端若不发送用户提示,代理服务器将拒绝连接。
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
| `mieru` | [Mieru](./mieru/) |
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `anytls` | [AnyTLS](./anytls/) |
|
| `anytls` | [AnyTLS](./anytls/) |
|
||||||
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
| `mieru` | [Mieru](./mieru/) |
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `anytls` | [AnyTLS](./anytls/) |
|
| `anytls` | [AnyTLS](./anytls/) |
|
||||||
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ icon: material/new-box
|
|||||||
"username": "asdf",
|
"username": "asdf",
|
||||||
"password": "hjkl",
|
"password": "hjkl",
|
||||||
"multiplexing": "MULTIPLEXING_LOW",
|
"multiplexing": "MULTIPLEXING_LOW",
|
||||||
|
"traffic_pattern": "GgQIARAK",
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ Must set at least one field between `server_port` and `server_ports`.
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Transmission protocol. The only allowed value is `TCP`.
|
Transmission protocol. Allowed values are `TCP` and `UDP`.
|
||||||
|
|
||||||
#### username
|
#### username
|
||||||
|
|
||||||
@@ -66,6 +67,10 @@ mieru password.
|
|||||||
|
|
||||||
Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing.
|
Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing.
|
||||||
|
|
||||||
|
#### traffic_pattern
|
||||||
|
|
||||||
|
A base64 string to fine tune network behavior.
|
||||||
|
|
||||||
### Dial Fields
|
### Dial Fields
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ icon: material/new-box
|
|||||||
"username": "asdf",
|
"username": "asdf",
|
||||||
"password": "hjkl",
|
"password": "hjkl",
|
||||||
"multiplexing": "MULTIPLEXING_LOW",
|
"multiplexing": "MULTIPLEXING_LOW",
|
||||||
|
"traffic_pattern": "GgQIARAK",
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
}
|
}
|
||||||
@@ -48,7 +49,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
通信协议。仅可设为 `TCP`。
|
通信协议。可设为 `TCP` 或 `UDP`。
|
||||||
|
|
||||||
#### username
|
#### username
|
||||||
|
|
||||||
@@ -66,6 +67,10 @@ mieru 密码。
|
|||||||
|
|
||||||
多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
|
多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
|
||||||
|
|
||||||
|
#### traffic_pattern
|
||||||
|
|
||||||
|
一个 base64 字符串用于微调网络行为。
|
||||||
|
|
||||||
### 拨号字段
|
### 拨号字段
|
||||||
|
|
||||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
"tag": "my-manager",
|
"tag": "my-manager",
|
||||||
"database": {
|
"database": {
|
||||||
"driver": "sqlite",
|
"driver": "sqlite",
|
||||||
"dsn": "file:manager.db?_pragma=foreign_keys(on)&_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)" // also supported Postgresql
|
"dsn": "file:manager.db?_pragma=foreign_keys(on)&_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)&_time_format=sqlite" // also supported Postgresql
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -64,6 +64,8 @@
|
|||||||
"listen_port": 7000,
|
"listen_port": 7000,
|
||||||
"manager": "my-manager",
|
"manager": "my-manager",
|
||||||
"api_key": "change-me-secret",
|
"api_key": "change-me-secret",
|
||||||
|
"keep_alive": "10s",
|
||||||
|
"keep_alive_timeout": "5s",
|
||||||
// Enable TLS for production deployments (the node connects via gRPC over h2):
|
// Enable TLS for production deployments (the node connects via gRPC over h2):
|
||||||
// "tls": { // https://sing-box.sagernet.org/configuration/shared/tls/#inbound
|
// "tls": { // https://sing-box.sagernet.org/configuration/shared/tls/#inbound
|
||||||
// "enabled": true,
|
// "enabled": true,
|
||||||
|
|||||||
@@ -23,8 +23,7 @@
|
|||||||
"address": "example.com",
|
"address": "example.com",
|
||||||
"port": 10001,
|
"port": 10001,
|
||||||
"public_key": "3nk7jdnkcL95Fc/z+GCiH7jOovEKhFkLIGPT+U/uLEQ=",
|
"public_key": "3nk7jdnkcL95Fc/z+GCiH7jOovEKhFkLIGPT+U/uLEQ=",
|
||||||
"allowed_ips": ["0.0.0.0/0"],
|
"allowed_ips": ["0.0.0.0/0"]
|
||||||
"reserved": "AAAA"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"udp_timeout": "5m0s",
|
"udp_timeout": "5m0s",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
{
|
{
|
||||||
"type": "masque",
|
"type": "masque",
|
||||||
"tag": "masque-out",
|
"tag": "masque-out",
|
||||||
|
"system": false,
|
||||||
|
"name": "masque0",
|
||||||
"use_http2": false,
|
"use_http2": false,
|
||||||
"use_ipv6": false,
|
"use_ipv6": false,
|
||||||
"profile": {
|
"profile": {
|
||||||
@@ -37,14 +39,15 @@
|
|||||||
"udp_keepalive_period": "30s",
|
"udp_keepalive_period": "30s",
|
||||||
"udp_initial_packet_size": 0,
|
"udp_initial_packet_size": 0,
|
||||||
"reconnect_delay": "5s",
|
"reconnect_delay": "5s",
|
||||||
// TLS fields for HTTP2
|
"tls": { // TLS fields for HTTP2
|
||||||
"insecure": false,
|
"insecure": false,
|
||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"curve_preferences": [],
|
"curve_preferences": [],
|
||||||
"fragment": false,
|
"fragment": false,
|
||||||
"record_fragment": false,
|
"record_fragment": false,
|
||||||
"kernel_tx": false,
|
"kernel_tx": false,
|
||||||
"kernel_rx": false
|
"kernel_rx": false
|
||||||
|
}
|
||||||
// Dial Fields
|
// Dial Fields
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -53,4 +56,4 @@
|
|||||||
"default_domain_resolver": "default",
|
"default_domain_resolver": "default",
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,14 +27,16 @@
|
|||||||
"tag": "mieru-out",
|
"tag": "mieru-out",
|
||||||
"server": "example.com",
|
"server": "example.com",
|
||||||
"server_port": 27017,
|
"server_port": 27017,
|
||||||
"server_ports": "27017-27019",
|
"server_ports": [
|
||||||
|
"27017-27019"
|
||||||
|
],
|
||||||
"transport": "TCP",
|
"transport": "TCP",
|
||||||
"username": "username",
|
"username": "username",
|
||||||
"password": "password",
|
"password": "password",
|
||||||
// valid: MULTIPLEXING_DEFAULT / MULTIPLEXING_OFF / MULTIPLEXING_LOW
|
// valid: MULTIPLEXING_DEFAULT / MULTIPLEXING_OFF / MULTIPLEXING_LOW
|
||||||
// MULTIPLEXING_MIDDLE / MULTIPLEXING_HIGH
|
// MULTIPLEXING_MIDDLE / MULTIPLEXING_HIGH
|
||||||
"multiplexing": "MULTIPLEXING_LOW"
|
"multiplexing": "MULTIPLEXING_LOW",
|
||||||
// Dial Fields
|
"traffic_pattern": "GgQIARAK"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
|
|||||||
32
examples/mieru/server.json
Normal file
32
examples/mieru/server.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mieru",
|
||||||
|
"tag": "mieru-in",
|
||||||
|
"listen_port": 27017,
|
||||||
|
"listen_ports": [
|
||||||
|
"27017-27019"
|
||||||
|
],
|
||||||
|
"transport": "TCP",
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"password": "password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"traffic_pattern": "GgQIARAK"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "direct"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,15 @@
|
|||||||
"packet_encoding": "",
|
"packet_encoding": "",
|
||||||
"transport": {
|
"transport": {
|
||||||
"type": "mkcp",
|
"type": "mkcp",
|
||||||
"mtu": 1500
|
"mtu": 1350, // 576-1460
|
||||||
|
"tti": 50, // 10-100, ms
|
||||||
|
"uplink_capacity": 12, // MB/s
|
||||||
|
"downlink_capacity": 100, // MB/s
|
||||||
|
"congestion": false,
|
||||||
|
"read_buffer_size": 1, // MB
|
||||||
|
"write_buffer_size": 1, // MB
|
||||||
|
"header_type": "none", // none, srtp, utp, wechat-video, dtls, wireguard
|
||||||
|
"seed": "password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -24,7 +24,15 @@
|
|||||||
],
|
],
|
||||||
"transport": {
|
"transport": {
|
||||||
"type": "mkcp",
|
"type": "mkcp",
|
||||||
"mtu": 1500
|
"mtu": 1350, // 576-1460
|
||||||
|
"tti": 50, // 10-100, ms
|
||||||
|
"uplink_capacity": 12, // MB/s
|
||||||
|
"downlink_capacity": 100, // MB/s
|
||||||
|
"congestion": false,
|
||||||
|
"read_buffer_size": 1, // MB
|
||||||
|
"write_buffer_size": 1, // MB
|
||||||
|
"header_type": "none", // none, srtp, utp, wechat-video, dtls, wireguard
|
||||||
|
"seed": "password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
"concurrency": 8192,
|
"concurrency": 8192,
|
||||||
// domain_fronting_port is a port we use to connect to a fronting domain.
|
// domain_fronting_port is a port we use to connect to a fronting domain.
|
||||||
"domain_fronting_port": 443,
|
"domain_fronting_port": 443,
|
||||||
// domain_fronting_ip is an IP address to use when connecting to the fronting
|
// domain_fronting_host is the address (IP or hostname) to use when connecting
|
||||||
// domain instead of resolving the hostname from the secret via DNS.
|
// to the fronting domain instead of resolving the hostname from the secret via DNS.
|
||||||
"domain_fronting_ip": "",
|
"domain_fronting_host": "",
|
||||||
// domain_fronting_proxy_protocol is used if communication between upstream
|
// domain_fronting_proxy_protocol is used if communication between upstream
|
||||||
// endpoint and sing-box supports proxy protocol.
|
// endpoint and sing-box supports proxy protocol.
|
||||||
"domain_fronting_proxy_protocol": false,
|
"domain_fronting_proxy_protocol": false,
|
||||||
|
|||||||
48
examples/openvpn/auth-user-pass.json
Normal file
48
examples/openvpn/auth-user-pass.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "openvpn",
|
||||||
|
"tag": "openvpn-out",
|
||||||
|
"system": false,
|
||||||
|
"name": "openvpn0",
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"server": "vpn.example.com",
|
||||||
|
"server_port": 1194
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"proto": "udp", // udp, tcp
|
||||||
|
"username": "myuser",
|
||||||
|
"password": "mypassword",
|
||||||
|
"tls_crypt": "-----BEGIN OpenVPN Static key V1-----\n...\n-----END OpenVPN Static key V1-----",
|
||||||
|
// or: "tls_crypt_path": "/path/to/ta.key",
|
||||||
|
"tls": {
|
||||||
|
"ca": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
// or: "ca_path": "/path/to/ca.crt",
|
||||||
|
"cipher_suites": [],
|
||||||
|
"verify_x509_name": "",
|
||||||
|
"verify_x509_name_mode": "", // name-prefix, name-suffix, exact (default)
|
||||||
|
"fragment": false,
|
||||||
|
"fragment_fallback_delay": "300ms",
|
||||||
|
"record_fragment": false,
|
||||||
|
"kernel_tx": false,
|
||||||
|
"kernel_rx": false
|
||||||
|
}
|
||||||
|
// Dial Fields
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "openvpn-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
53
examples/openvpn/tls-auth.json
Normal file
53
examples/openvpn/tls-auth.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "openvpn",
|
||||||
|
"tag": "openvpn-out",
|
||||||
|
"system": false,
|
||||||
|
"name": "openvpn0",
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"server": "vpn.example.com",
|
||||||
|
"server_port": 1194
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"proto": "udp", // udp, tcp
|
||||||
|
"cipher": "AES-256-CBC",
|
||||||
|
"auth": "SHA1",
|
||||||
|
"tls_auth": "-----BEGIN OpenVPN Static key V1-----\n...\n-----END OpenVPN Static key V1-----",
|
||||||
|
// or: "tls_auth_path": "/path/to/ta.key",
|
||||||
|
"key_direction": 1,
|
||||||
|
"tls": {
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
// or: "certificate_path": "/path/to/client.crt",
|
||||||
|
"key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
|
||||||
|
// or: "key_path": "/path/to/client.key",
|
||||||
|
"ca": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
// or: "ca_path": "/path/to/ca.crt",
|
||||||
|
"cipher_suites": [],
|
||||||
|
"verify_x509_name": "",
|
||||||
|
"verify_x509_name_mode": "", // name-prefix, name-suffix, exact (default)
|
||||||
|
"fragment": false,
|
||||||
|
"fragment_fallback_delay": "300ms",
|
||||||
|
"record_fragment": false,
|
||||||
|
"kernel_tx": false,
|
||||||
|
"kernel_rx": false
|
||||||
|
}
|
||||||
|
// Dial Fields
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "openvpn-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
51
examples/openvpn/tls-crypt-v2.json
Normal file
51
examples/openvpn/tls-crypt-v2.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "openvpn",
|
||||||
|
"tag": "openvpn-out",
|
||||||
|
"system": false,
|
||||||
|
"name": "openvpn0",
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"server": "vpn.example.com",
|
||||||
|
"server_port": 1194
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"proto": "udp", // udp, tcp
|
||||||
|
"tls_crypt": "-----BEGIN OpenVPN tls-crypt-v2 client key-----\n...\n-----END OpenVPN tls-crypt-v2 client key-----",
|
||||||
|
// or: "tls_crypt_path": "/path/to/tls-crypt-v2.key",
|
||||||
|
"tls_crypt_v2": true,
|
||||||
|
"tls": {
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
// or: "certificate_path": "/path/to/client.crt",
|
||||||
|
"key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
|
||||||
|
// or: "key_path": "/path/to/client.key",
|
||||||
|
"ca": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
// or: "ca_path": "/path/to/ca.crt",
|
||||||
|
"cipher_suites": [],
|
||||||
|
"verify_x509_name": "",
|
||||||
|
"verify_x509_name_mode": "", // name-prefix, name-suffix, exact (default)
|
||||||
|
"fragment": false,
|
||||||
|
"fragment_fallback_delay": "300ms",
|
||||||
|
"record_fragment": false,
|
||||||
|
"kernel_tx": false,
|
||||||
|
"kernel_rx": false
|
||||||
|
}
|
||||||
|
// Dial Fields
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "openvpn-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
54
examples/openvpn/tls-crypt.json
Normal file
54
examples/openvpn/tls-crypt.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "openvpn",
|
||||||
|
"tag": "openvpn-out",
|
||||||
|
"system": false,
|
||||||
|
"name": "openvpn0",
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"server": "vpn.example.com",
|
||||||
|
"server_port": 1194
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"proto": "udp", // udp, tcp
|
||||||
|
"cipher": "AES-256-GCM", // AES-128-GCM, AES-192-GCM, AES-256-GCM, AES-128-CBC, AES-192-CBC, AES-256-CBC, CHACHA20-POLY1305
|
||||||
|
"auth": "SHA256", // SHA1, SHA256, SHA384, SHA512 (ignored for AEAD ciphers)
|
||||||
|
"tls_crypt": "-----BEGIN OpenVPN Static key V1-----\n...\n-----END OpenVPN Static key V1-----",
|
||||||
|
// or: "tls_crypt_path": "/path/to/ta.key",
|
||||||
|
"ping_interval": "10s",
|
||||||
|
"reconnect_delay": "30s",
|
||||||
|
"tls": {
|
||||||
|
"certificate": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
// or: "certificate_path": "/path/to/client.crt",
|
||||||
|
"key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----",
|
||||||
|
// or: "key_path": "/path/to/client.key",
|
||||||
|
"ca": "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
||||||
|
// or: "ca_path": "/path/to/ca.crt",
|
||||||
|
"cipher_suites": [],
|
||||||
|
"verify_x509_name": "",
|
||||||
|
"verify_x509_name_mode": "", // name-prefix, name-suffix, exact (default)
|
||||||
|
"fragment": false,
|
||||||
|
"fragment_fallback_delay": "300ms",
|
||||||
|
"record_fragment": false,
|
||||||
|
"kernel_tx": false,
|
||||||
|
"kernel_rx": false
|
||||||
|
}
|
||||||
|
// Dial Fields
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "openvpn-out",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,8 @@
|
|||||||
// - SIP008 (shadowsocks)
|
// - SIP008 (shadowsocks)
|
||||||
// - Raw shareable links (vless://, vmess://, ss://, trojan://, ...)
|
// - Raw shareable links (vless://, vmess://, ss://, trojan://, ...)
|
||||||
"path": "subscriptions/my-sub.txt",
|
"path": "subscriptions/my-sub.txt",
|
||||||
|
// Remove emoji flags from proxy names.
|
||||||
|
"remove_emojis": true,
|
||||||
"health_check": {
|
"health_check": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "https://www.gstatic.com/generate_204",
|
"url": "https://www.gstatic.com/generate_204",
|
||||||
|
|||||||
@@ -56,6 +56,8 @@
|
|||||||
// "exclude" wins over "include" when both match.
|
// "exclude" wins over "include" when both match.
|
||||||
"exclude": "(?i)expire|流量|官网",
|
"exclude": "(?i)expire|流量|官网",
|
||||||
"include": "(?i)hk|jp|sg|us",
|
"include": "(?i)hk|jp|sg|us",
|
||||||
|
// Remove emoji flags from proxy names.
|
||||||
|
"remove_emojis": true,
|
||||||
"health_check": {
|
"health_check": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"url": "https://www.gstatic.com/generate_204",
|
"url": "https://www.gstatic.com/generate_204",
|
||||||
|
|||||||
52
examples/ssh/client.json
Normal file
52
examples/ssh/client.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "error"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "local",
|
||||||
|
"tag": "default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ssh",
|
||||||
|
"tag": "ssh-out",
|
||||||
|
"server": "example.com",
|
||||||
|
"server_port": 2222,
|
||||||
|
"user": "user",
|
||||||
|
// Authentication: password or private key
|
||||||
|
"password": "password",
|
||||||
|
"private_key": [
|
||||||
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----"
|
||||||
|
],
|
||||||
|
// or: "private_key_path": "/path/to/id_ed25519",
|
||||||
|
"private_key_passphrase": "",
|
||||||
|
// Pin server host key (optional)
|
||||||
|
"host_key": [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA..."
|
||||||
|
],
|
||||||
|
"host_key_algorithms": ["ssh-ed25519"],
|
||||||
|
"client_version": "SSH-2.0-OpenSSH_9.6"
|
||||||
|
// Dial Fields
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "ssh-out",
|
||||||
|
"default_domain_resolver": "default",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
76
examples/ssh/server.json
Normal file
76
examples/ssh/server.json
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"log": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "local",
|
||||||
|
"tag": "default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "ssh",
|
||||||
|
"tag": "ssh-in",
|
||||||
|
"listen": "0.0.0.0",
|
||||||
|
"listen_port": 2222,
|
||||||
|
"host_key": [
|
||||||
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----"
|
||||||
|
],
|
||||||
|
// or: "host_key_path": ["/etc/sing-box/ssh_host_ed25519_key"],
|
||||||
|
"server_version": "SSH-2.0-OpenSSH_9.6",
|
||||||
|
"max_auth_tries": 3,
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "user1",
|
||||||
|
"password": "password1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "user2",
|
||||||
|
"authorized_keys": [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... user2@host"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"fallback": {
|
||||||
|
"server": "10.0.0.2",
|
||||||
|
"server_port": 22,
|
||||||
|
"ca": {
|
||||||
|
"private_key": [
|
||||||
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----"
|
||||||
|
],
|
||||||
|
// or: "private_key_path": "/etc/sing-box/ca_key",
|
||||||
|
"private_key_passphrase": ""
|
||||||
|
},
|
||||||
|
// Optional: separate CA for issuing upstream certs (defaults to ca)
|
||||||
|
"issue_ca": {
|
||||||
|
"private_key": [
|
||||||
|
"-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----"
|
||||||
|
],
|
||||||
|
// or: "private_key_path": "/etc/sing-box/issue_ca_key",
|
||||||
|
"private_key_passphrase": ""
|
||||||
|
},
|
||||||
|
"host_key": [
|
||||||
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... upstream-host-key"
|
||||||
|
],
|
||||||
|
// or: "host_key_path": ["/etc/sing-box/upstream_host_key.pub"],
|
||||||
|
"host_key_algorithms": ["ssh-ed25519"],
|
||||||
|
"client_version": "SSH-2.0-OpenSSH_9.6"
|
||||||
|
// Dial Fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "direct",
|
||||||
|
"default_domain_resolver": "default",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
31
examples/sudoku/client.json
Normal file
31
examples/sudoku/client.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"listen_port": 1080
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "sudoku",
|
||||||
|
"server": "your-server.com",
|
||||||
|
"server_port": 443,
|
||||||
|
"key": "your-secret-key"
|
||||||
|
// "aead_method": "chacha20-poly1305", // chacha20-poly1305 | aes-128-gcm | none
|
||||||
|
// "table_type": "prefer_ascii", // prefer_ascii | prefer_entropy | up_ascii_down_entropy | up_entropy_down_ascii
|
||||||
|
// "padding_min": 10, // 0-100
|
||||||
|
// "padding_max": 30, // 0-100, >= padding_min
|
||||||
|
// "enable_pure_downlink": true, // true | false
|
||||||
|
// "custom_table": "xpxvvpvv", // 8 chars: 2x, 2p, 4v
|
||||||
|
// "custom_tables": ["xpxvvpvv", "vxpvxvvp"],
|
||||||
|
// "http_mask": {
|
||||||
|
// "enabled": true, // true | false
|
||||||
|
// "mode": "stream", // legacy | stream | poll | auto | ws
|
||||||
|
// "host": "cdn.example.com", // optional, Host header / SNI override
|
||||||
|
// "path_root": "secret", // optional, URL path prefix (single segment)
|
||||||
|
// "multiplex": "auto" // off | auto | on
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
35
examples/sudoku/client_tls.json
Normal file
35
examples/sudoku/client_tls.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"listen": "127.0.0.1",
|
||||||
|
"listen_port": 1080
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "sudoku",
|
||||||
|
"server": "your-server.com",
|
||||||
|
"server_port": 443,
|
||||||
|
"key": "your-secret-key",
|
||||||
|
// "aead_method": "chacha20-poly1305", // chacha20-poly1305 | aes-128-gcm | none
|
||||||
|
// "table_type": "prefer_ascii", // prefer_ascii | prefer_entropy | up_ascii_down_entropy | up_entropy_down_ascii
|
||||||
|
// "padding_min": 10, // 0-100
|
||||||
|
// "padding_max": 30, // 0-100, >= padding_min
|
||||||
|
// "enable_pure_downlink": true, // true | false
|
||||||
|
// "custom_table": "xpxvvpvv", // 8 chars: 2x, 2p, 4v
|
||||||
|
// "custom_tables": ["xpxvvpvv", "vxpvxvvp"],
|
||||||
|
"http_mask": {
|
||||||
|
"enabled": true, // true | false
|
||||||
|
"mode": "stream", // legacy | stream | poll | auto | ws
|
||||||
|
"host": "cdn.example.com", // optional, Host header / SNI override
|
||||||
|
"path_root": "secret", // optional, URL path prefix (single segment)
|
||||||
|
"multiplex": "auto", // off | auto | on
|
||||||
|
"tls": { // https://sing-box.sagernet.org/configuration/shared/tls/#outbound
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": "cdn.example.com",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
27
examples/sudoku/server.json
Normal file
27
examples/sudoku/server.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "sudoku",
|
||||||
|
"listen": "::",
|
||||||
|
"listen_port": 443,
|
||||||
|
"key": "your-secret-key"
|
||||||
|
// "aead_method": "chacha20-poly1305", // chacha20-poly1305 | aes-128-gcm | none
|
||||||
|
// "table_type": "prefer_ascii", // prefer_ascii | prefer_entropy | up_ascii_down_entropy | up_entropy_down_ascii
|
||||||
|
// "padding_min": 10, // 0-100
|
||||||
|
// "padding_max": 30, // 0-100, >= padding_min
|
||||||
|
// "enable_pure_downlink": true, // true | false
|
||||||
|
// "handshake_timeout": 5, // seconds
|
||||||
|
// "custom_table": "xpxvvpvv", // 8 chars: 2x, 2p, 4v
|
||||||
|
// "custom_tables": ["xpxvvpvv", "vxpvxvvp"],
|
||||||
|
// "disable_http_mask": false, // true | false
|
||||||
|
// "http_mask_mode": "legacy", // legacy | stream | poll | auto | ws
|
||||||
|
// "path_root": "secret", // optional, URL path prefix (single segment)
|
||||||
|
// "fallback": "127.0.0.1:8080" // optional, fallback address for rejected connections
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
22
examples/sudoku/server_advanced.json
Normal file
22
examples/sudoku/server_advanced.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "sudoku",
|
||||||
|
"listen": "::",
|
||||||
|
"listen_port": 443,
|
||||||
|
"key": "your-secret-key",
|
||||||
|
"aead_method": "aes-128-gcm",
|
||||||
|
"table_type": "prefer_entropy",
|
||||||
|
"padding_min": 10,
|
||||||
|
"padding_max": 50,
|
||||||
|
"http_mask_mode": "stream",
|
||||||
|
"path_root": "secret",
|
||||||
|
"fallback": "127.0.0.1:8080"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
78
examples/trusttunnel/client.json
Normal file
78
examples/trusttunnel/client.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"dns": {
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "local",
|
||||||
|
"tag": "default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "mixed",
|
||||||
|
"tag": "mixed-in",
|
||||||
|
"listen_port": 7897
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "trusttunnel",
|
||||||
|
"tag": "trusttunnel-h2",
|
||||||
|
"server": "example.com",
|
||||||
|
"server_port": 443,
|
||||||
|
"username": "user1",
|
||||||
|
"password": "password1",
|
||||||
|
"network": ["tcp", "udp"],
|
||||||
|
"health_check": true,
|
||||||
|
"multiplex": {
|
||||||
|
"enabled": true,
|
||||||
|
"max_connections": 8,
|
||||||
|
"min_streams": 5
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": "example.com"
|
||||||
|
}
|
||||||
|
// Dial Fields
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "trusttunnel",
|
||||||
|
"tag": "trusttunnel-quic",
|
||||||
|
"server": "example.com",
|
||||||
|
"server_port": 443,
|
||||||
|
"username": "user1",
|
||||||
|
"password": "password1",
|
||||||
|
"network": ["tcp", "udp"],
|
||||||
|
"health_check": true,
|
||||||
|
"quic": true,
|
||||||
|
"congestion_controller": "bbr", // bbr, bbr_standard, bbr2, bbr2_variant, cubic, reno
|
||||||
|
"bbr_profile": "standard", // standard, conservative, aggressive
|
||||||
|
"cwnd": 32,
|
||||||
|
"multiplex": {
|
||||||
|
"enabled": true,
|
||||||
|
"max_connections": 8,
|
||||||
|
"min_streams": 5
|
||||||
|
},
|
||||||
|
"tls": {
|
||||||
|
"enabled": true,
|
||||||
|
"server_name": "example.com"
|
||||||
|
}
|
||||||
|
// Dial Fields
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "selector",
|
||||||
|
"tag": "trusttunnel-selector",
|
||||||
|
"outbounds": ["trusttunnel-h2", "trusttunnel-quic"],
|
||||||
|
"default": "trusttunnel-h2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"route": {
|
||||||
|
"final": "trusttunnel-selector",
|
||||||
|
"default_domain_resolver": "default",
|
||||||
|
"auto_detect_interface": true
|
||||||
|
}
|
||||||
|
}
|
||||||
32
examples/trusttunnel/server.json
Normal file
32
examples/trusttunnel/server.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"inbounds": [
|
||||||
|
{
|
||||||
|
"type": "trusttunnel",
|
||||||
|
"tag": "trusttunnel-in",
|
||||||
|
"listen": "::",
|
||||||
|
"listen_port": 443,
|
||||||
|
"network": ["tcp", "udp"],
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"name": "user1",
|
||||||
|
"password": "password1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"congestion_controller": "bbr", // bbr, bbr_standard, bbr2, bbr2_variant, cubic, reno
|
||||||
|
"bbr_profile": "standard", // standard, conservative, aggressive
|
||||||
|
"cwnd": 32,
|
||||||
|
"tls": {
|
||||||
|
"enabled": true,
|
||||||
|
"alpn": ["h2", "h3"],
|
||||||
|
"certificate_path": "/path/to/cert.pem",
|
||||||
|
"key_path": "/path/to/key.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outbounds": [
|
||||||
|
{
|
||||||
|
"type": "direct",
|
||||||
|
"tag": "direct"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -23,8 +23,7 @@
|
|||||||
"address": "example.com",
|
"address": "example.com",
|
||||||
"port": 10001,
|
"port": 10001,
|
||||||
"public_key": "3nk7jdnkcL95Fc/z+GCiH7jOovEKhFkLIGPT+U/uLEQ=",
|
"public_key": "3nk7jdnkcL95Fc/z+GCiH7jOovEKhFkLIGPT+U/uLEQ=",
|
||||||
"allowed_ips": ["0.0.0.0/0"],
|
"allowed_ips": ["0.0.0.0/0"]
|
||||||
"reserved": "AAAA"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"udp_timeout": "5m0s",
|
"udp_timeout": "5m0s",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func updateExternalUI(server *Server) func(w http.ResponseWriter, r *http.Reques
|
|||||||
render.JSON(w, r, newError(err.Error()))
|
render.JSON(w, r, newError(err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
server.logger.Info("updated external UI")
|
server.logger.Notice("updated external UI")
|
||||||
render.JSON(w, r, render.M{"status": "ok"})
|
render.JSON(w, r, render.M{"status": "ok"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ func (s *Server) Start(stage adapter.StartStage) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "external controller listen error")
|
return E.Cause(err, "external controller listen error")
|
||||||
}
|
}
|
||||||
s.logger.Info("restful api listening at ", listener.Addr())
|
s.logger.Notice("restful api listening at ", listener.Addr())
|
||||||
go func() {
|
go func() {
|
||||||
err = s.httpServer.Serve(listener)
|
err = s.httpServer.Serve(listener)
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
@@ -234,7 +234,7 @@ func (s *Server) SetMode(newMode string) {
|
|||||||
s.logger.Error(E.Cause(err, "save mode"))
|
s.logger.Error(E.Cause(err, "save mode"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.logger.Info("updated mode: ", newMode)
|
s.logger.Notice("updated mode: ", newMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HistoryStorage() adapter.URLTestHistoryStorage {
|
func (s *Server) HistoryStorage() adapter.URLTestHistoryStorage {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (s *Server) Start(stage adapter.StartStage) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.logger.Info("grpc server started at ", listener.Addr())
|
s.logger.Notice("grpc server started at ", listener.Addr())
|
||||||
s.tcpListener = listener
|
s.tcpListener = listener
|
||||||
go func() {
|
go func() {
|
||||||
err = s.grpcServer.Serve(listener)
|
err = s.grpcServer.Serve(listener)
|
||||||
|
|||||||
26
go.mod
26
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/sagernet/sing-box
|
module github.com/sagernet/sing-box
|
||||||
|
|
||||||
go 1.26.1
|
go 1.26.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/AliRizaAynaci/gorl/v2 v2.2.0
|
github.com/AliRizaAynaci/gorl/v2 v2.2.0
|
||||||
@@ -11,10 +11,11 @@ require (
|
|||||||
github.com/coder/websocket v1.8.14
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/database64128/tfo-go/v2 v2.3.2
|
github.com/database64128/tfo-go/v2 v2.3.2
|
||||||
github.com/enfein/mieru/v3 v3.17.1
|
github.com/enfein/mieru/v3 v3.33.0
|
||||||
github.com/go-chi/chi/v5 v5.2.5
|
github.com/go-chi/chi/v5 v5.2.5
|
||||||
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/go-playground/validator/v10 v10.30.1
|
||||||
|
github.com/gobwas/ws v1.4.0
|
||||||
github.com/godbus/dbus/v5 v5.2.2
|
github.com/godbus/dbus/v5 v5.2.2
|
||||||
github.com/gofrs/uuid/v5 v5.4.0
|
github.com/gofrs/uuid/v5 v5.4.0
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
@@ -53,7 +54,7 @@ require (
|
|||||||
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c
|
github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.0.2
|
github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.0.2
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
github.com/shtorm-7/workerpool v0.5.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/vishvananda/netns v0.0.5
|
github.com/vishvananda/netns v0.0.5
|
||||||
@@ -75,17 +76,24 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
github.com/OneOfOne/xxhash v1.2.8 // indirect
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/docker/docker v28.5.2+incompatible // indirect
|
||||||
|
github.com/docker/go-connections v0.6.0 // indirect
|
||||||
github.com/dunglas/httpsfv v1.1.0 // indirect
|
github.com/dunglas/httpsfv v1.1.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/panjf2000/ants/v2 v2.12.0 // indirect
|
github.com/panjf2000/ants/v2 v2.12.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||||
github.com/redis/go-redis/v9 v9.8.0 // indirect
|
github.com/redis/go-redis/v9 v9.8.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b // indirect
|
github.com/tylertreat/BoomFilters v0.0.0-20251117164519-53813c36cc1b // indirect
|
||||||
|
github.com/zeebo/assert v1.3.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.41.0 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||||
gvisor.dev/gvisor v0.0.0-20260408064518-65a410b0d584 // indirect
|
gvisor.dev/gvisor v0.0.0-20260408064518-65a410b0d584 // indirect
|
||||||
modernc.org/libc v1.72.0 // indirect
|
modernc.org/libc v1.72.0 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
@@ -93,14 +101,14 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0
|
||||||
github.com/AdguardTeam/golibs v0.32.7 // indirect
|
github.com/AdguardTeam/golibs v0.32.7 // 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/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/andybalholm/brotli v1.1.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.5 // indirect
|
github.com/caddyserver/zerossl v0.1.5 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||||
@@ -144,7 +152,7 @@ require (
|
|||||||
github.com/mdlayher/netlink v1.9.0 // indirect
|
github.com/mdlayher/netlink v1.9.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/pierrec/lz4/v4 v4.1.25 // indirect
|
||||||
github.com/pires/go-proxyproto v0.11.0 // indirect
|
github.com/pires/go-proxyproto v0.11.0 // 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
|
||||||
@@ -219,10 +227,12 @@ replace github.com/sagernet/sing-mux => github.com/shtorm-7/sing-mux v0.3.4-exte
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
replace github.com/sagernet/sing-vmess => github.com/starifly/sing-vmess v0.2.7-mod.9
|
replace github.com/sagernet/sing-vmess => github.com/shtorm-7/sing-vmess v0.2.7-extended-1.0.0
|
||||||
|
|
||||||
replace github.com/dolonet/mtg-multi => github.com/shtorm-7/mtg-multi v1.8.0-extended-1.0.1
|
replace github.com/dolonet/mtg-multi => github.com/shtorm-7/mtg-multi v1.11.0-extended-1.0.0
|
||||||
|
|
||||||
replace github.com/Diniboy1123/connect-ip-go => github.com/shtorm-7/connect-ip-go v1.0.0-extended-1.0.0
|
replace github.com/Diniboy1123/connect-ip-go => github.com/shtorm-7/connect-ip-go v1.0.0-extended-1.0.0
|
||||||
|
|
||||||
replace github.com/shtorm-7/go-cache/v2 => github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.1.0
|
replace github.com/shtorm-7/go-cache/v2 => github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.1.0
|
||||||
|
|
||||||
|
replace github.com/sagernet/sing => github.com/shtorm-7/sing v0.8.10-extended-1.1.0
|
||||||
|
|||||||
68
go.sum
68
go.sum
@@ -20,14 +20,16 @@ github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7V
|
|||||||
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/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
github.com/anthropics/anthropic-sdk-go v1.26.0 h1:oUTzFaUpAevfuELAP1sjL6CQJ9HHAfT7CoSYSac11PY=
|
||||||
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q=
|
||||||
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/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
|
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0=
|
||||||
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
|
github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
||||||
|
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@@ -75,10 +77,10 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
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/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54=
|
github.com/dunglas/httpsfv v1.1.0 h1:Jw76nAyKWKZKFrpMMcL76y35tOpYHqQPzHQiwDvpe54=
|
||||||
@@ -87,8 +89,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/enfein/mieru/v3 v3.17.1 h1:pIKbspsKRYNyUrORVI33t1/yz2syaaUkIanskAbGBHY=
|
github.com/enfein/mieru/v3 v3.33.0 h1:hv2jK8nqYHwpSG86U2rpZR2I8Aff1/J3ifRmd9NBbFc=
|
||||||
github.com/enfein/mieru/v3 v3.17.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
github.com/enfein/mieru/v3 v3.33.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=
|
github.com/florianl/go-nfqueue/v2 v2.0.2 h1:FL5lQTeetgpCvac1TRwSfgaXUn0YSO7WzGvWNIp3JPE=
|
||||||
@@ -129,12 +131,12 @@ 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=
|
||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
|
||||||
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
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 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA=
|
||||||
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE=
|
||||||
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=
|
||||||
@@ -243,16 +245,16 @@ github.com/openai/openai-go/v3 v3.26.0 h1:bRt6H/ozMNt/dDkN4gobnLqaEGrRGBzmbVs0xx
|
|||||||
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
github.com/openai/openai-go/v3 v3.26.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo=
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
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/panjf2000/ants/v2 v2.12.0 h1:u9JhESo83i/GkZnhfTNuFMMWcNt7mnV1bGJ6FT4wXH8=
|
github.com/panjf2000/ants/v2 v2.12.0 h1:u9JhESo83i/GkZnhfTNuFMMWcNt7mnV1bGJ6FT4wXH8=
|
||||||
github.com/panjf2000/ants/v2 v2.12.0/go.mod h1:tSQuaNQ6r6NRhPt+IZVUevvDyFMTs+eS4ztZc52uJTY=
|
github.com/panjf2000/ants/v2 v2.12.0/go.mod h1:tSQuaNQ6r6NRhPt+IZVUevvDyFMTs+eS4ztZc52uJTY=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
github.com/pires/go-proxyproto v0.11.0 h1:gUQpS85X/VJMdUsYyEgyn59uLJvGqPhJV5YvG68wXH4=
|
||||||
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
github.com/pires/go-proxyproto v0.11.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -357,8 +359,6 @@ github.com/sagernet/nftables v0.3.0-mod.2 h1:ck2KMU02OxL1eDFgGaWYglMDpoOZ7OHzxje
|
|||||||
github.com/sagernet/nftables v0.3.0-mod.2/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
|
github.com/sagernet/nftables v0.3.0-mod.2/go.mod h1:8kslHG4VvYNihcco+i6uxIX7qbT8A56T0y5q7U44ZaQ=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4 h1:6qvrUW79S+CrPwWz6cMePXohgjHoKxLo3c+MDhNwc3o=
|
||||||
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
github.com/sagernet/quic-go v0.59.0-sing-box-mod.4/go.mod h1:OqILvS182CyOol5zNNo6bguvOGgXzV459+chpRaUC+4=
|
||||||
github.com/sagernet/sing v0.8.10 h1:V5VZffy8rm4dtBVKIpKa8vibRR2SiJprtu/10DFUalU=
|
|
||||||
github.com/sagernet/sing v0.8.10/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
|
|
||||||
github.com/sagernet/sing-quic v0.6.1 h1:lx0tcm99wIA1RkyvILNzRSsMy1k7TTQYIhx71E/WBlw=
|
github.com/sagernet/sing-quic v0.6.1 h1:lx0tcm99wIA1RkyvILNzRSsMy1k7TTQYIhx71E/WBlw=
|
||||||
github.com/sagernet/sing-quic v0.6.1/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
github.com/sagernet/sing-quic v0.6.1/go.mod h1:K5bWvITOm4vE10fwLfrWpw27bCoVJ+tfQ79tOWg+Ko8=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
||||||
@@ -379,23 +379,25 @@ github.com/shtorm-7/dnscrypt/v2 v2.4.0-extended-1.0.0 h1:e5s7RKBd2rIPR0StbvZ2vTV
|
|||||||
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/go-cache/v2 v2.1.0-extended-1.1.0 h1:PLZ/YHqnApPx13wt6MX3ItqESp4ueBr1tGSi0bEGqYw=
|
github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.1.0 h1:PLZ/YHqnApPx13wt6MX3ItqESp4ueBr1tGSi0bEGqYw=
|
||||||
github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.1.0/go.mod h1:Ek4yz5OK6stwhLKgLsRRYDI+FA+ZWvRJiWLjsi/vMM4=
|
github.com/shtorm-7/go-cache/v2 v2.1.0-extended-1.1.0/go.mod h1:Ek4yz5OK6stwhLKgLsRRYDI+FA+ZWvRJiWLjsi/vMM4=
|
||||||
github.com/shtorm-7/mtg-multi v1.8.0-extended-1.0.1 h1:UeJkrCJJmIjTBywErVMx7fCSoBf4gh6QgT9bp9o1ajM=
|
github.com/shtorm-7/mtg-multi v1.11.0-extended-1.0.0 h1:iBLll4ZZG8ULQcHWs6gGslZWtBN72Zo1zjySzMVHF7g=
|
||||||
github.com/shtorm-7/mtg-multi v1.8.0-extended-1.0.1/go.mod h1:3rvdhwdPABkwKBdvgMt3VwMn9uSq8hpoHRezZ5jRJU0=
|
github.com/shtorm-7/mtg-multi v1.11.0-extended-1.0.0/go.mod h1:3rvdhwdPABkwKBdvgMt3VwMn9uSq8hpoHRezZ5jRJU0=
|
||||||
|
github.com/shtorm-7/sing v0.8.10-extended-1.1.0 h1:P4JL2cugjvEvnYu8tMmpR30SE1qsS45RcnNEwzDz5as=
|
||||||
|
github.com/shtorm-7/sing v0.8.10-extended-1.1.0/go.mod h1:olXxWQNqRW/l2Q6JI3b2Qmz8iQnIFlOeeH8bx6JhgUA=
|
||||||
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 h1:a5OoXr3e2ACbM6vDIaaGL44IdHQ6wPjcSoU13vfC0Sw=
|
||||||
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
github.com/shtorm-7/sing-mux v0.3.4-extended-1.0.0/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=
|
||||||
|
github.com/shtorm-7/sing-vmess v0.2.7-extended-1.0.0 h1:WVheKmQH5hSQbJU1ZTKthKSutkTLWSb2hp4JuQhJBow=
|
||||||
|
github.com/shtorm-7/sing-vmess v0.2.7-extended-1.0.0/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
||||||
github.com/shtorm-7/tailscale v1.92.4-sing-box-1.13-mod.7-extended-1.0.2 h1:hSMjh97OszszOd8HrzpaYUQH9dWRRBluJCbwQyz8ZOk=
|
github.com/shtorm-7/tailscale v1.92.4-sing-box-1.13-mod.7-extended-1.0.2 h1:hSMjh97OszszOd8HrzpaYUQH9dWRRBluJCbwQyz8ZOk=
|
||||||
github.com/shtorm-7/tailscale v1.92.4-sing-box-1.13-mod.7-extended-1.0.2/go.mod h1:TYIIqO5sZpWq873rLIeO2usszSMUpR3h6WdqVVs65ug=
|
github.com/shtorm-7/tailscale v1.92.4-sing-box-1.13-mod.7-extended-1.0.2/go.mod h1:TYIIqO5sZpWq873rLIeO2usszSMUpR3h6WdqVVs65ug=
|
||||||
github.com/shtorm-7/wireguard-go v0.0.2-beta.1-extended-1.4.3 h1:jtOA73D4F5qRV70//ahOt20KBnWvQimAFjtIiOtt0ps=
|
github.com/shtorm-7/wireguard-go v0.0.2-beta.1-extended-1.4.3 h1:jtOA73D4F5qRV70//ahOt20KBnWvQimAFjtIiOtt0ps=
|
||||||
github.com/shtorm-7/wireguard-go v0.0.2-beta.1-extended-1.4.3/go.mod h1:Me2JlCDYHxnd0mnuX7L5LXAeDHCltI7vSKq3eTE6SVE=
|
github.com/shtorm-7/wireguard-go v0.0.2-beta.1-extended-1.4.3/go.mod h1:Me2JlCDYHxnd0mnuX7L5LXAeDHCltI7vSKq3eTE6SVE=
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
github.com/shtorm-7/workerpool v0.5.0 h1:NPZuNgyH0EUm4aQsTL09xR1iV+7GCFw6jX9Z4aAVp2s=
|
||||||
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
github.com/shtorm-7/workerpool v0.5.0/go.mod h1:NI0pUZgmGu0BdKO9j3mct1DNZmgXbyTS9foorljdH6E=
|
||||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/starifly/sing-vmess v0.2.7-mod.9 h1:xobAmejSbBQ0A3f/EtJ9cJd3m6gK7dDPccPdeGz7tXY=
|
|
||||||
github.com/starifly/sing-vmess v0.2.7-mod.9/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=
|
|
||||||
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.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
@@ -443,12 +445,14 @@ github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zd
|
|||||||
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/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
|
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
|
||||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||||
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
|
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
|
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=
|
||||||
@@ -457,16 +461,16 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
|
|||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
|
||||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||||
@@ -521,8 +525,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
|
||||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||||
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-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
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=
|
||||||
|
|||||||
12
include/openvpn.go
Normal file
12
include/openvpn.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
//go:build with_openvpn
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/protocol/openvpn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerOpenVPNOutbound(registry *outbound.Registry) {
|
||||||
|
openvpn.RegisterOutbound(registry)
|
||||||
|
}
|
||||||
20
include/openvpn_stub.go
Normal file
20
include/openvpn_stub.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//go:build !with_openvpn
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerOpenVPNOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.OpenVPNOutboundOptions](registry, C.TypeOpenVPN, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.OpenVPNOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
return nil, E.New(`OpenVPN outbound is not included in this build, rebuild with -tags with_openvpn`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -80,13 +80,17 @@ func InboundRegistry() *inbound.Registry {
|
|||||||
shadowtls.RegisterInbound(registry)
|
shadowtls.RegisterInbound(registry)
|
||||||
vless.RegisterInbound(registry)
|
vless.RegisterInbound(registry)
|
||||||
anytls.RegisterInbound(registry)
|
anytls.RegisterInbound(registry)
|
||||||
|
mieru.RegisterInbound(registry)
|
||||||
|
ssh.RegisterInbound(registry)
|
||||||
|
|
||||||
bond.RegisterInbound(registry)
|
bond.RegisterInbound(registry)
|
||||||
failover.RegisterInbound(registry)
|
failover.RegisterInbound(registry)
|
||||||
|
registerTrustTunnelInbound(registry)
|
||||||
|
|
||||||
registerQUICInbounds(registry)
|
registerQUICInbounds(registry)
|
||||||
registerStubForRemovedInbounds(registry)
|
registerStubForRemovedInbounds(registry)
|
||||||
registerMTProxyInbound(registry)
|
registerMTProxyInbound(registry)
|
||||||
|
registerSudokuInbound(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
@@ -115,9 +119,11 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
mieru.RegisterOutbound(registry)
|
mieru.RegisterOutbound(registry)
|
||||||
anytls.RegisterOutbound(registry)
|
anytls.RegisterOutbound(registry)
|
||||||
registerMASQUEOutbound(registry)
|
registerMASQUEOutbound(registry)
|
||||||
|
registerOpenVPNOutbound(registry)
|
||||||
|
|
||||||
bond.RegisterOutbound(registry)
|
bond.RegisterOutbound(registry)
|
||||||
failover.RegisterOutbound(registry)
|
failover.RegisterOutbound(registry)
|
||||||
|
registerTrustTunnelOutbound(registry)
|
||||||
|
|
||||||
bandwidth.RegisterOutbound(registry)
|
bandwidth.RegisterOutbound(registry)
|
||||||
connection.RegisterOutbound(registry)
|
connection.RegisterOutbound(registry)
|
||||||
@@ -128,6 +134,7 @@ func OutboundRegistry() *outbound.Registry {
|
|||||||
|
|
||||||
registerQUICOutbounds(registry)
|
registerQUICOutbounds(registry)
|
||||||
registerStubForRemovedOutbounds(registry)
|
registerStubForRemovedOutbounds(registry)
|
||||||
|
registerSudokuOutbound(registry)
|
||||||
|
|
||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|||||||
17
include/sudoku.go
Normal file
17
include/sudoku.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//go:build with_sudoku
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/protocol/sudoku"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerSudokuInbound(registry *inbound.Registry) {
|
||||||
|
sudoku.RegisterInbound(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerSudokuOutbound(registry *outbound.Registry) {
|
||||||
|
sudoku.RegisterOutbound(registry)
|
||||||
|
}
|
||||||
27
include/sudoku_stub.go
Normal file
27
include/sudoku_stub.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build !with_sudoku
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerSudokuInbound(registry *inbound.Registry) {
|
||||||
|
inbound.Register[option.SudokuInboundOptions](registry, C.TypeSudoku, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SudokuInboundOptions) (adapter.Inbound, error) {
|
||||||
|
return nil, E.New(`Sudoku is not included in this build, rebuild with -tags with_sudoku`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerSudokuOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.SudokuOutboundOptions](registry, C.TypeSudoku, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SudokuOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
return nil, E.New(`Sudoku is not included in this build, rebuild with -tags with_sudoku`)
|
||||||
|
})
|
||||||
|
}
|
||||||
17
include/trusttunnel.go
Normal file
17
include/trusttunnel.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
//go:build with_trusttunnel
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/protocol/trusttunnel"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerTrustTunnelInbound(registry *inbound.Registry) {
|
||||||
|
trusttunnel.RegisterInbound(registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTrustTunnelOutbound(registry *outbound.Registry) {
|
||||||
|
trusttunnel.RegisterOutbound(registry)
|
||||||
|
}
|
||||||
27
include/trusttunnel_stub.go
Normal file
27
include/trusttunnel_stub.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//go:build !with_trusttunnel
|
||||||
|
|
||||||
|
package include
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func registerTrustTunnelInbound(registry *inbound.Registry) {
|
||||||
|
inbound.Register[option.TrustTunnelInboundOptions](registry, C.TypeTrustTunnel, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrustTunnelInboundOptions) (adapter.Inbound, error) {
|
||||||
|
return nil, E.New(`TrustTunnel is not included in this build, rebuild with -tags with_trusttunnel`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func registerTrustTunnelOutbound(registry *outbound.Registry) {
|
||||||
|
outbound.Register[option.TrustTunnelOutboundOptions](registry, C.TypeTrustTunnel, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrustTunnelOutboundOptions) (adapter.Outbound, error) {
|
||||||
|
return nil, E.New(`TrustTunnel is not included in this build, rebuild with -tags with_trusttunnel`)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -39,6 +39,10 @@ func Info(args ...any) {
|
|||||||
std.Info(args...)
|
std.Info(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Notice(args ...any) {
|
||||||
|
std.Notice(args...)
|
||||||
|
}
|
||||||
|
|
||||||
func Warn(args ...any) {
|
func Warn(args ...any) {
|
||||||
std.Warn(args...)
|
std.Warn(args...)
|
||||||
}
|
}
|
||||||
@@ -67,6 +71,10 @@ func InfoContext(ctx context.Context, args ...any) {
|
|||||||
std.InfoContext(ctx, args...)
|
std.InfoContext(ctx, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NoticeContext(ctx context.Context, args ...any) {
|
||||||
|
std.NoticeContext(ctx, args...)
|
||||||
|
}
|
||||||
|
|
||||||
func WarnContext(ctx context.Context, args ...any) {
|
func WarnContext(ctx context.Context, args ...any) {
|
||||||
std.WarnContext(ctx, args...)
|
std.WarnContext(ctx, args...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
|
|||||||
levelString = aurora.White(levelString).String()
|
levelString = aurora.White(levelString).String()
|
||||||
case LevelInfo:
|
case LevelInfo:
|
||||||
levelString = aurora.Cyan(levelString).String()
|
levelString = aurora.Cyan(levelString).String()
|
||||||
|
case LevelNotice:
|
||||||
|
levelString = aurora.Green(levelString).String()
|
||||||
case LevelWarn:
|
case LevelWarn:
|
||||||
levelString = aurora.Yellow(levelString).String()
|
levelString = aurora.Yellow(levelString).String()
|
||||||
case LevelError, LevelFatal, LevelPanic:
|
case LevelError, LevelFatal, LevelPanic:
|
||||||
@@ -97,6 +99,8 @@ func (f Formatter) FormatWithSimple(ctx context.Context, level Level, tag string
|
|||||||
levelString = aurora.White(levelString).String()
|
levelString = aurora.White(levelString).String()
|
||||||
case LevelInfo:
|
case LevelInfo:
|
||||||
levelString = aurora.Cyan(levelString).String()
|
levelString = aurora.Cyan(levelString).String()
|
||||||
|
case LevelNotice:
|
||||||
|
levelString = aurora.Green(levelString).String()
|
||||||
case LevelWarn:
|
case LevelWarn:
|
||||||
levelString = aurora.Yellow(levelString).String()
|
levelString = aurora.Yellow(levelString).String()
|
||||||
case LevelError, LevelFatal, LevelPanic:
|
case LevelError, LevelFatal, LevelPanic:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const (
|
|||||||
LevelFatal
|
LevelFatal
|
||||||
LevelError
|
LevelError
|
||||||
LevelWarn
|
LevelWarn
|
||||||
|
LevelNotice
|
||||||
LevelInfo
|
LevelInfo
|
||||||
LevelDebug
|
LevelDebug
|
||||||
LevelTrace
|
LevelTrace
|
||||||
@@ -24,6 +25,8 @@ func FormatLevel(level Level) string {
|
|||||||
return "debug"
|
return "debug"
|
||||||
case LevelInfo:
|
case LevelInfo:
|
||||||
return "info"
|
return "info"
|
||||||
|
case LevelNotice:
|
||||||
|
return "notice"
|
||||||
case LevelWarn:
|
case LevelWarn:
|
||||||
return "warn"
|
return "warn"
|
||||||
case LevelError:
|
case LevelError:
|
||||||
@@ -45,6 +48,8 @@ func ParseLevel(level string) (Level, error) {
|
|||||||
return LevelDebug, nil
|
return LevelDebug, nil
|
||||||
case "info":
|
case "info":
|
||||||
return LevelInfo, nil
|
return LevelInfo, nil
|
||||||
|
case "notice":
|
||||||
|
return LevelNotice, nil
|
||||||
case "warn", "warning":
|
case "warn", "warning":
|
||||||
return LevelWarn, nil
|
return LevelWarn, nil
|
||||||
case "error":
|
case "error":
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ func (f *nopFactory) Debug(args ...any) {
|
|||||||
func (f *nopFactory) Info(args ...any) {
|
func (f *nopFactory) Info(args ...any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *nopFactory) Notice(args ...any) {
|
||||||
|
}
|
||||||
|
|
||||||
func (f *nopFactory) Warn(args ...any) {
|
func (f *nopFactory) Warn(args ...any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +71,9 @@ func (f *nopFactory) DebugContext(ctx context.Context, args ...any) {
|
|||||||
func (f *nopFactory) InfoContext(ctx context.Context, args ...any) {
|
func (f *nopFactory) InfoContext(ctx context.Context, args ...any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *nopFactory) NoticeContext(ctx context.Context, args ...any) {
|
||||||
|
}
|
||||||
|
|
||||||
func (f *nopFactory) WarnContext(ctx context.Context, args ...any) {
|
func (f *nopFactory) WarnContext(ctx context.Context, args ...any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,10 @@ func (l *observableLogger) Info(args ...any) {
|
|||||||
l.InfoContext(context.Background(), args...)
|
l.InfoContext(context.Background(), args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) Notice(args ...any) {
|
||||||
|
l.NoticeContext(context.Background(), args...)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *observableLogger) Warn(args ...any) {
|
func (l *observableLogger) Warn(args ...any) {
|
||||||
l.WarnContext(context.Background(), args...)
|
l.WarnContext(context.Background(), args...)
|
||||||
}
|
}
|
||||||
@@ -182,6 +186,10 @@ func (l *observableLogger) InfoContext(ctx context.Context, args ...any) {
|
|||||||
l.Log(ctx, LevelInfo, args)
|
l.Log(ctx, LevelInfo, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *observableLogger) NoticeContext(ctx context.Context, args ...any) {
|
||||||
|
l.Log(ctx, LevelNotice, args)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *observableLogger) WarnContext(ctx context.Context, args ...any) {
|
func (l *observableLogger) WarnContext(ctx context.Context, args ...any) {
|
||||||
l.Log(ctx, LevelWarn, args)
|
l.Log(ctx, LevelWarn, args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MASQUEOutboundOptions struct {
|
type MASQUEOutboundOptions struct {
|
||||||
UseHTTP2 bool `json:"use_http2,omitempty"`
|
|
||||||
UseIPv6 bool `json:"use_ipv6,omitempty"`
|
|
||||||
Profile CloudflareProfile `json:"profile,omitempty"`
|
|
||||||
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
|
|
||||||
UDPKeepalivePeriod badoption.Duration `json:"udp_keepalive_period,omitempty"`
|
|
||||||
UDPInitialPacketSize uint16 `json:"udp_initial_packet_size,omitempty"`
|
|
||||||
ReconnectDelay badoption.Duration `json:"reconnect_delay,omitempty"`
|
|
||||||
MASQUEOutboundTLSOptions
|
|
||||||
DialerOptions
|
DialerOptions
|
||||||
|
System bool `json:"system,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"`
|
||||||
|
UseHTTP2 bool `json:"use_http2,omitempty"`
|
||||||
|
UseIPv6 bool `json:"use_ipv6,omitempty"`
|
||||||
|
Profile CloudflareProfile `json:"profile,omitempty"`
|
||||||
|
UDPTimeout badoption.Duration `json:"udp_timeout,omitempty"`
|
||||||
|
UDPKeepalivePeriod badoption.Duration `json:"udp_keepalive_period,omitempty"`
|
||||||
|
UDPInitialPacketSize uint16 `json:"udp_initial_packet_size,omitempty"`
|
||||||
|
ReconnectDelay badoption.Duration `json:"reconnect_delay,omitempty"`
|
||||||
|
MASQUEOutboundTLSOptionsContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
type MASQUEOutboundTLSOptions struct {
|
type MASQUEOutboundTLSOptions struct {
|
||||||
@@ -28,5 +33,5 @@ type MASQUEOutboundTLSOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MASQUEOutboundTLSOptionsContainer struct {
|
type MASQUEOutboundTLSOptionsContainer struct {
|
||||||
TLS *OutboundTLSOptions `json:"tls,omitempty"`
|
TLS *MASQUEOutboundTLSOptions `json:"tls,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,19 @@ type MieruOutboundOptions struct {
|
|||||||
UserName string `json:"username,omitempty"`
|
UserName string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
Multiplexing string `json:"multiplexing,omitempty"`
|
Multiplexing string `json:"multiplexing,omitempty"`
|
||||||
|
TrafficPattern string `json:"traffic_pattern,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MieruInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
ListenPorts badoption.Listable[string] `json:"listen_ports,omitempty"`
|
||||||
|
Users []MieruUser `json:"users,omitempty"`
|
||||||
|
Transport string `json:"transport,omitempty"`
|
||||||
|
TrafficPattern string `json:"traffic_pattern,omitempty"`
|
||||||
|
UserHintIsMandatory bool `json:"user_hint_is_mandatory,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MieruUser struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type MTProxyInboundOptions struct {
|
|||||||
Users []MTProxyUser `json:"users,omitempty"`
|
Users []MTProxyUser `json:"users,omitempty"`
|
||||||
Concurrency uint `json:"concurrency,omitempty"`
|
Concurrency uint `json:"concurrency,omitempty"`
|
||||||
DomainFrontingPort uint `json:"domain_fronting_port,omitempty"`
|
DomainFrontingPort uint `json:"domain_fronting_port,omitempty"`
|
||||||
DomainFrontingIP string `json:"domain_fronting_ip,omitempty"`
|
DomainFrontingHost string `json:"domain_fronting_host,omitempty"`
|
||||||
DomainFrontingProxyProtocol bool `json:"domain_fronting_proxy_protocol,omitempty"`
|
DomainFrontingProxyProtocol bool `json:"domain_fronting_proxy_protocol,omitempty"`
|
||||||
PreferIP string `json:"prefer_ip,omitempty"`
|
PreferIP string `json:"prefer_ip,omitempty"`
|
||||||
AutoUpdate bool `json:"auto_update,omitempty"`
|
AutoUpdate bool `json:"auto_update,omitempty"`
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
type NodeServiceOptions struct {
|
type NodeServiceOptions struct {
|
||||||
UUID string
|
UUID string `json:"uuid"`
|
||||||
Inbounds []string `json:"inbounds"`
|
Inbounds []string `json:"inbounds"`
|
||||||
ConnectionLimiters []string `json:"connection_limiters"`
|
ConnectionLimiters []string `json:"connection_limiters"`
|
||||||
BandwidthLimiters []string `json:"bandwidth_limiters"`
|
BandwidthLimiters []string `json:"bandwidth_limiters"`
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
type _NodeManagerAPIOptions struct {
|
type _NodeManagerAPIOptions struct {
|
||||||
@@ -57,8 +58,10 @@ func (o *NodeManagerAPIOptions) UnmarshalJSON(bytes []byte) error {
|
|||||||
type NodeManagerAPIServerOptions struct {
|
type NodeManagerAPIServerOptions struct {
|
||||||
ListenOptions
|
ListenOptions
|
||||||
InboundTLSOptionsContainer
|
InboundTLSOptionsContainer
|
||||||
Manager string `json:"manager"`
|
Manager string `json:"manager"`
|
||||||
APIKey string `json:"api_key"`
|
APIKey string `json:"api_key"`
|
||||||
|
KeepAlive badoption.Duration `json:"keep_alive,omitempty"`
|
||||||
|
KeepAliveTimeout badoption.Duration `json:"keep_alive_timeout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeManagerAPIClientOptions struct {
|
type NodeManagerAPIClientOptions struct {
|
||||||
|
|||||||
47
option/openvpn.go
Normal file
47
option/openvpn.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpenVPNOutboundOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
System bool `json:"system,omitempty"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
AllowedIPs badoption.Listable[netip.Prefix] `json:"allowed_ips,omitempty"`
|
||||||
|
Servers []ServerOptions `json:"servers"`
|
||||||
|
Proto string `json:"proto,omitempty"`
|
||||||
|
Cipher string `json:"cipher,omitempty"`
|
||||||
|
Auth string `json:"auth,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
TLSCrypt string `json:"tls_crypt,omitempty"`
|
||||||
|
TLSCryptPath string `json:"tls_crypt_path,omitempty"`
|
||||||
|
TLSCryptV2 bool `json:"tls_crypt_v2,omitempty"`
|
||||||
|
TLSAuth string `json:"tls_auth,omitempty"`
|
||||||
|
TLSAuthPath string `json:"tls_auth_path,omitempty"`
|
||||||
|
KeyDirection int `json:"key_direction,omitempty"`
|
||||||
|
ReconnectDelay badoption.Duration `json:"reconnect_delay,omitempty"`
|
||||||
|
PingInterval badoption.Duration `json:"ping_interval,omitempty"`
|
||||||
|
OpenVPNOutboundTLSOptionsContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenVPNTLSOptions struct {
|
||||||
|
Certificate string `json:"certificate,omitempty"`
|
||||||
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
|
Key string `json:"key,omitempty"`
|
||||||
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
|
CA string `json:"ca,omitempty"`
|
||||||
|
CAPath string `json:"ca_path,omitempty"`
|
||||||
|
CipherSuites badoption.Listable[string] `json:"cipher_suites,omitempty"`
|
||||||
|
VerifyX509Name string `json:"verify_x509_name,omitempty"`
|
||||||
|
VerifyX509NameMode string `json:"verify_x509_name_mode,omitempty"`
|
||||||
|
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||||
|
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenVPNOutboundTLSOptionsContainer struct {
|
||||||
|
TLS *OpenVPNTLSOptions `json:"tls,omitempty"`
|
||||||
|
}
|
||||||
@@ -1,9 +1,5 @@
|
|||||||
package option
|
package option
|
||||||
|
|
||||||
import "github.com/sagernet/sing/common/json/badoption"
|
|
||||||
|
|
||||||
type ProfilerServiceOptions struct {
|
type ProfilerServiceOptions struct {
|
||||||
Listen string `json:"listen,omitempty"`
|
ListenOptions
|
||||||
ReadTimeout badoption.Duration `json:"read_timeout,omitempty"`
|
|
||||||
WriteTimeout badoption.Duration `json:"write_timeout,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,9 @@ func (h *Provider) UnmarshalJSONContext(ctx context.Context, content []byte) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProviderLocalOptions struct {
|
type ProviderLocalOptions struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
HealthCheck ProviderHealthCheckOptions `json:"health_check,omitempty"`
|
RemoveEmojis bool `json:"remove_emojis,omitempty"`
|
||||||
|
HealthCheck ProviderHealthCheckOptions `json:"health_check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderRemoteOptions struct {
|
type ProviderRemoteOptions struct {
|
||||||
@@ -57,14 +58,16 @@ type ProviderRemoteOptions struct {
|
|||||||
DownloadDetour string `json:"download_detour,omitempty"`
|
DownloadDetour string `json:"download_detour,omitempty"`
|
||||||
UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
|
UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
|
||||||
|
|
||||||
Exclude *badoption.Regexp `json:"exclude,omitempty"`
|
Exclude *badoption.Regexp `json:"exclude,omitempty"`
|
||||||
Include *badoption.Regexp `json:"include,omitempty"`
|
Include *badoption.Regexp `json:"include,omitempty"`
|
||||||
HealthCheck ProviderHealthCheckOptions `json:"health_check,omitempty"`
|
RemoveEmojis bool `json:"remove_emojis,omitempty"`
|
||||||
|
HealthCheck ProviderHealthCheckOptions `json:"health_check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderInlineOptions struct {
|
type ProviderInlineOptions struct {
|
||||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||||
HealthCheck ProviderHealthCheckOptions `json:"health_check,omitempty"`
|
RemoveEmojis bool `json:"remove_emojis,omitempty"`
|
||||||
|
HealthCheck ProviderHealthCheckOptions `json:"health_check,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderHealthCheckOptions struct {
|
type ProviderHealthCheckOptions struct {
|
||||||
|
|||||||
@@ -2,6 +2,39 @@ package option
|
|||||||
|
|
||||||
import "github.com/sagernet/sing/common/json/badoption"
|
import "github.com/sagernet/sing/common/json/badoption"
|
||||||
|
|
||||||
|
type SSHInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
Users []SSHUser `json:"users,omitempty"`
|
||||||
|
HostKey badoption.Listable[string] `json:"host_key,omitempty"`
|
||||||
|
HostKeyPath badoption.Listable[string] `json:"host_key_path,omitempty"`
|
||||||
|
ServerVersion string `json:"server_version,omitempty"`
|
||||||
|
MaxAuthTries int `json:"max_auth_tries,omitempty"`
|
||||||
|
Fallback *SSHFallbackServerOptions `json:"fallback,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHFallbackServerOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
CA *SSHCAOptions `json:"ca,omitempty"`
|
||||||
|
IssueCA *SSHCAOptions `json:"issue_ca,omitempty"`
|
||||||
|
HostKey badoption.Listable[string] `json:"host_key,omitempty"`
|
||||||
|
HostKeyPath badoption.Listable[string] `json:"host_key_path,omitempty"`
|
||||||
|
HostKeyAlgorithms badoption.Listable[string] `json:"host_key_algorithms,omitempty"`
|
||||||
|
ClientVersion string `json:"client_version,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHCAOptions struct {
|
||||||
|
PrivateKey badoption.Listable[string] `json:"private_key,omitempty"`
|
||||||
|
PrivateKeyPath string `json:"private_key_path,omitempty"`
|
||||||
|
PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHUser struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
AuthorizedKeys badoption.Listable[string] `json:"authorized_keys,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type SSHOutboundOptions struct {
|
type SSHOutboundOptions struct {
|
||||||
DialerOptions
|
DialerOptions
|
||||||
ServerOptions
|
ServerOptions
|
||||||
|
|||||||
41
option/sudoku.go
Normal file
41
option/sudoku.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type SudokuOutboundOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
Key string `json:"key"`
|
||||||
|
AEADMethod string `json:"aead_method,omitempty"`
|
||||||
|
PaddingMin *int `json:"padding_min,omitempty"`
|
||||||
|
PaddingMax *int `json:"padding_max,omitempty"`
|
||||||
|
TableType string `json:"table_type,omitempty"`
|
||||||
|
EnablePureDownlink *bool `json:"enable_pure_downlink,omitempty"`
|
||||||
|
CustomTable string `json:"custom_table,omitempty"`
|
||||||
|
CustomTables []string `json:"custom_tables,omitempty"`
|
||||||
|
HTTPMask *SudokuHTTPMask `json:"http_mask,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SudokuHTTPMask struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
Mode string `json:"mode,omitempty"`
|
||||||
|
Host string `json:"host,omitempty"`
|
||||||
|
PathRoot string `json:"path_root,omitempty"`
|
||||||
|
Multiplex string `json:"multiplex,omitempty"`
|
||||||
|
OutboundTLSOptionsContainer
|
||||||
|
}
|
||||||
|
|
||||||
|
type SudokuInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
Key string `json:"key"`
|
||||||
|
AEADMethod string `json:"aead_method,omitempty"`
|
||||||
|
PaddingMin *int `json:"padding_min,omitempty"`
|
||||||
|
PaddingMax *int `json:"padding_max,omitempty"`
|
||||||
|
TableType string `json:"table_type,omitempty"`
|
||||||
|
HandshakeTimeout *int `json:"handshake_timeout,omitempty"`
|
||||||
|
EnablePureDownlink *bool `json:"enable_pure_downlink,omitempty"`
|
||||||
|
CustomTable string `json:"custom_table,omitempty"`
|
||||||
|
CustomTables []string `json:"custom_tables,omitempty"`
|
||||||
|
DisableHTTPMask bool `json:"disable_http_mask,omitempty"`
|
||||||
|
HTTPMaskMode string `json:"http_mask_mode,omitempty"`
|
||||||
|
PathRoot string `json:"path_root,omitempty"`
|
||||||
|
Fallback string `json:"fallback,omitempty"`
|
||||||
|
}
|
||||||
38
option/trusttunnel.go
Normal file
38
option/trusttunnel.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package option
|
||||||
|
|
||||||
|
type TrustTunnelInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
InboundTLSOptionsContainer
|
||||||
|
Users []TrustTunnelUser `json:"users,omitempty"`
|
||||||
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
CongestionController string `json:"congestion_controller,omitempty"`
|
||||||
|
BBRProfile string `json:"bbr_profile,omitempty"`
|
||||||
|
CWND int `json:"cwnd,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustTunnelUser struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustTunnelMultiplexOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
MaxConnections int `json:"max_connections,omitempty"`
|
||||||
|
MinStreams int `json:"min_streams,omitempty"`
|
||||||
|
MaxStreams int `json:"max_streams,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrustTunnelOutboundOptions struct {
|
||||||
|
DialerOptions
|
||||||
|
ServerOptions
|
||||||
|
OutboundTLSOptionsContainer
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
HealthCheck bool `json:"health_check,omitempty"`
|
||||||
|
QUIC bool `json:"quic,omitempty"`
|
||||||
|
CongestionController string `json:"congestion_controller,omitempty"`
|
||||||
|
BBRProfile string `json:"bbr_profile,omitempty"`
|
||||||
|
CWND int `json:"cwnd,omitempty"`
|
||||||
|
Multiplex *TrustTunnelMultiplexOptions `json:"multiplex,omitempty"`
|
||||||
|
}
|
||||||
@@ -96,6 +96,12 @@ func (h *Inbound) Close() error {
|
|||||||
return common.Close(h.listener, h.tlsConfig)
|
return common.Close(h.listener, h.tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []option.AnyTLSUser) {
|
||||||
|
h.service.UpdateUsers(common.Map(users, func(it option.AnyTLSUser) anytls.User {
|
||||||
|
return anytls.User(it)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
if h.tlsConfig != nil {
|
||||||
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func NewBondedConn(conns []net.Conn, downloadRatios, uploadRatios []uint8) *bond
|
|||||||
conns: conns,
|
conns: conns,
|
||||||
downloadRatios: downloadRatios,
|
downloadRatios: downloadRatios,
|
||||||
uploadRatios: uploadRatios,
|
uploadRatios: uploadRatios,
|
||||||
readBuffer: bytes.NewBuffer(make([]byte, 0, 65536)),
|
readBuffer: bytes.NewBuffer(make([]byte, 0, 4096)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,13 +37,13 @@ type failoverConn struct {
|
|||||||
func NewFailoverConn(ctx context.Context, conn net.Conn, dial dial, onClose func()) *failoverConn {
|
func NewFailoverConn(ctx context.Context, conn net.Conn, dial dial, onClose func()) *failoverConn {
|
||||||
var writeBuffers [BufferSize][]byte
|
var writeBuffers [BufferSize][]byte
|
||||||
for i := range BufferSize {
|
for i := range BufferSize {
|
||||||
writeBuffers[i] = make([]byte, 0, 1000)
|
writeBuffers[i] = make([]byte, 0, 1024)
|
||||||
}
|
}
|
||||||
return &failoverConn{
|
return &failoverConn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dial: dial,
|
dial: dial,
|
||||||
readBuffer: bytes.NewBuffer(make([]byte, 0, 1000)),
|
readBuffer: bytes.NewBuffer(make([]byte, 0, 1024)),
|
||||||
writeBuffers: writeBuffers,
|
writeBuffers: writeBuffers,
|
||||||
onClose: onClose,
|
onClose: onClose,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ func (h *Inbound) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []auth.User) {
|
||||||
|
h.authenticator.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
if h.tlsConfig != nil {
|
||||||
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
tlsConn, err := tls.ServerHandshake(ctx, conn, h.tlsConfig)
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ package bandwidth
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/onclose"
|
||||||
)
|
)
|
||||||
|
|
||||||
type connWithDownloadBandwidthLimiter struct {
|
type connWithDownloadBandwidthLimiter struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
limiter Limiter
|
limiter BandwidthLimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter Limiter) *connWithDownloadBandwidthLimiter {
|
func NewConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter BandwidthLimiter) *connWithDownloadBandwidthLimiter {
|
||||||
return &connWithDownloadBandwidthLimiter{conn, ctx, limiter}
|
return &connWithDownloadBandwidthLimiter{conn, ctx, limiter}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,10 +28,10 @@ func (conn *connWithDownloadBandwidthLimiter) Write(p []byte) (n int, err error)
|
|||||||
type connWithUploadBandwidthLimiter struct {
|
type connWithUploadBandwidthLimiter struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
limiter Limiter
|
limiter BandwidthLimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnWithUploadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter Limiter) *connWithUploadBandwidthLimiter {
|
func NewConnWithUploadBandwidthLimiter(ctx context.Context, conn net.Conn, limiter BandwidthLimiter) *connWithUploadBandwidthLimiter {
|
||||||
return &connWithUploadBandwidthLimiter{conn, ctx, limiter}
|
return &connWithUploadBandwidthLimiter{conn, ctx, limiter}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,10 +49,10 @@ func (conn *connWithUploadBandwidthLimiter) Read(p []byte) (n int, err error) {
|
|||||||
|
|
||||||
type connWithCloseHandler struct {
|
type connWithCloseHandler struct {
|
||||||
net.Conn
|
net.Conn
|
||||||
onClose CloseHandlerFunc
|
onClose onclose.CloseHandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConnWithCloseHandler(conn net.Conn, onClose CloseHandlerFunc) *connWithCloseHandler {
|
func NewConnWithCloseHandler(conn net.Conn, onClose onclose.CloseHandlerFunc) *connWithCloseHandler {
|
||||||
return &connWithCloseHandler{conn, onClose}
|
return &connWithCloseHandler{conn, onClose}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,10 +64,10 @@ func (conn *connWithCloseHandler) Close() error {
|
|||||||
type packetConnWithDownloadBandwidthLimiter struct {
|
type packetConnWithDownloadBandwidthLimiter struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
limiter Limiter
|
limiter BandwidthLimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter Limiter) *packetConnWithDownloadBandwidthLimiter {
|
func NewPacketConnWithDownloadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter BandwidthLimiter) *packetConnWithDownloadBandwidthLimiter {
|
||||||
return &packetConnWithDownloadBandwidthLimiter{conn, ctx, limiter}
|
return &packetConnWithDownloadBandwidthLimiter{conn, ctx, limiter}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,10 +82,10 @@ func (conn *packetConnWithDownloadBandwidthLimiter) WriteTo(p []byte, addr net.A
|
|||||||
type packetConnWithUploadBandwidthLimiter struct {
|
type packetConnWithUploadBandwidthLimiter struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
limiter Limiter
|
limiter BandwidthLimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketConnWithUploadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter Limiter) *packetConnWithUploadBandwidthLimiter {
|
func NewPacketConnWithUploadBandwidthLimiter(ctx context.Context, conn net.PacketConn, limiter BandwidthLimiter) *packetConnWithUploadBandwidthLimiter {
|
||||||
return &packetConnWithUploadBandwidthLimiter{conn, ctx, limiter}
|
return &packetConnWithUploadBandwidthLimiter{conn, ctx, limiter}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,10 +103,10 @@ func (conn *packetConnWithUploadBandwidthLimiter) ReadFrom(p []byte) (n int, add
|
|||||||
|
|
||||||
type packetConnWithCloseHandler struct {
|
type packetConnWithCloseHandler struct {
|
||||||
net.PacketConn
|
net.PacketConn
|
||||||
onClose CloseHandlerFunc
|
onClose onclose.CloseHandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacketConnWithCloseHandler(conn net.PacketConn, onClose CloseHandlerFunc) *packetConnWithCloseHandler {
|
func NewPacketConnWithCloseHandler(conn net.PacketConn, onClose onclose.CloseHandlerFunc) *packetConnWithCloseHandler {
|
||||||
return &packetConnWithCloseHandler{conn, onClose}
|
return &packetConnWithCloseHandler{conn, onClose}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,38 +115,38 @@ func (conn *packetConnWithCloseHandler) Close() error {
|
|||||||
return conn.PacketConn.Close()
|
return conn.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func connWithDownloadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn {
|
func connWithDownloadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter BandwidthLimiter, reverse bool) net.Conn {
|
||||||
if reverse {
|
if reverse {
|
||||||
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func connWithUploadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn {
|
func connWithUploadBandwidthWrapper(ctx context.Context, conn net.Conn, limiter BandwidthLimiter, reverse bool) net.Conn {
|
||||||
if reverse {
|
if reverse {
|
||||||
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
return NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
return NewConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func connWithBidirectionalBandwidthWrapper(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn {
|
func connWithBidirectionalBandwidthWrapper(ctx context.Context, conn net.Conn, limiter BandwidthLimiter, reverse bool) net.Conn {
|
||||||
return NewConnWithUploadBandwidthLimiter(ctx, NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
return NewConnWithUploadBandwidthLimiter(ctx, NewConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func packetConnWithDownloadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn {
|
func packetConnWithDownloadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter BandwidthLimiter, reverse bool) net.PacketConn {
|
||||||
if reverse {
|
if reverse {
|
||||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func packetConnWithUploadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn {
|
func packetConnWithUploadBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter BandwidthLimiter, reverse bool) net.PacketConn {
|
||||||
if reverse {
|
if reverse {
|
||||||
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
return NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
return NewPacketConnWithUploadBandwidthLimiter(ctx, conn, limiter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func packetConnWithBidirectionalBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn {
|
func packetConnWithBidirectionalBandwidthWrapper(ctx context.Context, conn net.PacketConn, limiter BandwidthLimiter, reverse bool) net.PacketConn {
|
||||||
return NewPacketConnWithUploadBandwidthLimiter(ctx, NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
return NewPacketConnWithUploadBandwidthLimiter(ctx, NewPacketConnWithDownloadBandwidthLimiter(ctx, conn, limiter), limiter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Limiter interface {
|
type BandwidthLimiter interface {
|
||||||
WaitN(ctx context.Context, n int) (err error)
|
WaitN(ctx context.Context, n int) (err error)
|
||||||
|
SetSpeed(speed uint64)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FlowKeysLimiter struct {
|
type FlowKeysLimiter struct {
|
||||||
limiter Limiter
|
limiter BandwidthLimiter
|
||||||
connIDGetter ConnIDGetter
|
connIDGetter ConnIDGetter
|
||||||
|
|
||||||
waits map[string][]*wait
|
waits map[string][]*wait
|
||||||
@@ -25,7 +26,7 @@ type FlowKeysLimiter struct {
|
|||||||
mtx sync.Mutex
|
mtx sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFlowKeysLimiter(connIDGetter ConnIDGetter, limiter Limiter) *FlowKeysLimiter {
|
func NewFlowKeysLimiter(connIDGetter ConnIDGetter, limiter BandwidthLimiter) *FlowKeysLimiter {
|
||||||
return &FlowKeysLimiter{
|
return &FlowKeysLimiter{
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
connIDGetter: connIDGetter,
|
connIDGetter: connIDGetter,
|
||||||
@@ -36,6 +37,10 @@ func NewFlowKeysLimiter(connIDGetter ConnIDGetter, limiter Limiter) *FlowKeysLim
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *FlowKeysLimiter) SetSpeed(speed uint64) {
|
||||||
|
l.limiter.SetSpeed(speed)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *FlowKeysLimiter) WaitN(ctx context.Context, n int) error {
|
func (l *FlowKeysLimiter) WaitN(ctx context.Context, n int) error {
|
||||||
id, _ := l.connIDGetter(ctx, adapter.ContextFrom(ctx))
|
id, _ := l.connIDGetter(ctx, adapter.ContextFrom(ctx))
|
||||||
mainWait := &wait{ctx, make(chan struct{}), n}
|
mainWait := &wait{ctx, make(chan struct{}), n}
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/onclose"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CloseHandlerFunc = func()
|
|
||||||
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
||||||
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter Limiter, reverse bool) net.Conn
|
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter BandwidthLimiter, reverse bool) net.Conn
|
||||||
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter Limiter, reverse bool) net.PacketConn
|
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter BandwidthLimiter, reverse bool) net.PacketConn
|
||||||
)
|
)
|
||||||
|
|
||||||
type BandwidthStrategy interface {
|
type BandwidthStrategy interface {
|
||||||
@@ -24,8 +24,12 @@ type BandwidthStrategy interface {
|
|||||||
wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error)
|
wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SpeedUpdater interface {
|
||||||
|
SetSpeed(speed uint64)
|
||||||
|
}
|
||||||
|
|
||||||
type BandwidthLimiterStrategy interface {
|
type BandwidthLimiterStrategy interface {
|
||||||
getLimiter(ctx context.Context, metadata *adapter.InboundContext) (Limiter, CloseHandlerFunc, error)
|
getLimiter(ctx context.Context, metadata *adapter.InboundContext) (BandwidthLimiter, onclose.CloseHandlerFunc, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type DefaultWrapStrategy struct {
|
type DefaultWrapStrategy struct {
|
||||||
@@ -54,8 +58,14 @@ func (s *DefaultWrapStrategy) wrapPacketConn(ctx context.Context, conn net.Packe
|
|||||||
return NewPacketConnWithCloseHandler(s.packetConnWrapper(ctx, conn, limiter, reverse), onClose), nil
|
return NewPacketConnWithCloseHandler(s.packetConnWrapper(ctx, conn, limiter, reverse), onClose), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *DefaultWrapStrategy) SetSpeed(speed uint64) {
|
||||||
|
if updater, ok := s.limiterStrategy.(SpeedUpdater); ok {
|
||||||
|
updater.SetSpeed(speed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type GlobalBandwidthStrategy struct {
|
type GlobalBandwidthStrategy struct {
|
||||||
limiter Limiter
|
limiter BandwidthLimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGlobalBandwidthStrategy(speed uint64, flowKeys []string) (*GlobalBandwidthStrategy, error) {
|
func NewGlobalBandwidthStrategy(speed uint64, flowKeys []string) (*GlobalBandwidthStrategy, error) {
|
||||||
@@ -68,12 +78,16 @@ func NewGlobalBandwidthStrategy(speed uint64, flowKeys []string) (*GlobalBandwid
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GlobalBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (Limiter, CloseHandlerFunc, error) {
|
func (s *GlobalBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (BandwidthLimiter, onclose.CloseHandlerFunc, error) {
|
||||||
return s.limiter, func() {}, nil
|
return s.limiter, func() {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *GlobalBandwidthStrategy) SetSpeed(speed uint64) {
|
||||||
|
s.limiter.SetSpeed(speed)
|
||||||
|
}
|
||||||
|
|
||||||
type idBandwidthLimiter struct {
|
type idBandwidthLimiter struct {
|
||||||
limiter Limiter
|
limiter BandwidthLimiter
|
||||||
handles uint32
|
handles uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +108,7 @@ func NewConnectionBandwidthStrategy(connIDGetter ConnIDGetter, speed uint64, flo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ConnectionBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (Limiter, CloseHandlerFunc, error) {
|
func (s *ConnectionBandwidthStrategy) getLimiter(ctx context.Context, metadata *adapter.InboundContext) (BandwidthLimiter, onclose.CloseHandlerFunc, error) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
defer s.mtx.Unlock()
|
||||||
id, ok := s.connIDGetter(ctx, metadata)
|
id, ok := s.connIDGetter(ctx, metadata)
|
||||||
@@ -126,6 +140,15 @@ func (s *ConnectionBandwidthStrategy) getLimiter(ctx context.Context, metadata *
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ConnectionBandwidthStrategy) SetSpeed(speed uint64) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
s.speed = speed
|
||||||
|
for _, limiter := range s.limiters {
|
||||||
|
limiter.limiter.SetSpeed(speed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type UsersBandwidthStrategy struct {
|
type UsersBandwidthStrategy struct {
|
||||||
strategies map[string]BandwidthStrategy
|
strategies map[string]BandwidthStrategy
|
||||||
mtx sync.Mutex
|
mtx sync.Mutex
|
||||||
@@ -167,20 +190,84 @@ func (s *UsersBandwidthStrategy) getStrategy(ctx context.Context, metadata *adap
|
|||||||
return nil, E.New("user strategy not found: ", user)
|
return nil, E.New("user strategy not found: ", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bwConnEntry struct {
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
type ManagerBandwidthStrategy struct {
|
type ManagerBandwidthStrategy struct {
|
||||||
*UsersBandwidthStrategy
|
strategies map[string]BandwidthStrategy
|
||||||
|
conns map[string][]*bwConnEntry
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManagerBandwidthStrategy() *ManagerBandwidthStrategy {
|
func NewManagerBandwidthStrategy() *ManagerBandwidthStrategy {
|
||||||
return &ManagerBandwidthStrategy{
|
return &ManagerBandwidthStrategy{
|
||||||
UsersBandwidthStrategy: NewUsersBandwidthStrategy(map[string]BandwidthStrategy{}),
|
strategies: make(map[string]BandwidthStrategy),
|
||||||
|
conns: make(map[string][]*bwConnEntry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ManagerBandwidthStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
var user string
|
||||||
|
if metadata != nil {
|
||||||
|
user = metadata.User
|
||||||
|
}
|
||||||
|
strategy, ok := s.strategies[user]
|
||||||
|
s.mtx.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return nil, E.New("user strategy not found: ", user)
|
||||||
|
}
|
||||||
|
wrapped, err := strategy.wrapConn(ctx, conn, metadata, reverse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry := &bwConnEntry{conn: conn}
|
||||||
|
s.mtx.Lock()
|
||||||
|
s.conns[user] = append(s.conns[user], entry)
|
||||||
|
s.mtx.Unlock()
|
||||||
|
return onclose.NewConn(wrapped, func() {
|
||||||
|
s.mtx.Lock()
|
||||||
|
entries := s.conns[user]
|
||||||
|
for i, e := range entries {
|
||||||
|
if e == entry {
|
||||||
|
s.conns[user] = append(entries[:i], entries[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mtx.Unlock()
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ManagerBandwidthStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
var user string
|
||||||
|
if metadata != nil {
|
||||||
|
user = metadata.User
|
||||||
|
}
|
||||||
|
strategy, ok := s.strategies[user]
|
||||||
|
s.mtx.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return nil, E.New("user strategy not found: ", user)
|
||||||
|
}
|
||||||
|
return strategy.wrapPacketConn(ctx, conn, metadata, reverse)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ManagerBandwidthStrategy) UpdateStrategies(strategies map[string]BandwidthStrategy) {
|
func (s *ManagerBandwidthStrategy) UpdateStrategies(strategies map[string]BandwidthStrategy) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
var closedEntries []*bwConnEntry
|
||||||
|
for user, entries := range s.conns {
|
||||||
|
if _, exists := strategies[user]; !exists {
|
||||||
|
closedEntries = append(closedEntries, entries...)
|
||||||
|
delete(s.conns, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
s.strategies = strategies
|
s.strategies = strategies
|
||||||
|
s.mtx.Unlock()
|
||||||
|
for _, entry := range closedEntries {
|
||||||
|
entry.conn.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type BypassBandwidthStrategy struct{}
|
type BypassBandwidthStrategy struct{}
|
||||||
@@ -263,8 +350,8 @@ func CreateStrategy(strategy string, mode string, connectionType string, speed u
|
|||||||
return NewDefaultWrapStrategy(limiterStrategy, connWrapper, packetConnWrapper), nil
|
return NewDefaultWrapStrategy(limiterStrategy, connWrapper, packetConnWrapper), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSpeedLimiter(speed uint64, flowKeys []string) (Limiter, error) {
|
func createSpeedLimiter(speed uint64, flowKeys []string) (BandwidthLimiter, error) {
|
||||||
var limiter Limiter = rate.NewLimiter(rate.Limit(float64(speed)), 65536)
|
var limiter BandwidthLimiter = &speedLimiter{limiter: rate.NewLimiter(rate.Limit(float64(speed)), 65536)}
|
||||||
for i := len(flowKeys) - 1; i >= 0; i-- {
|
for i := len(flowKeys) - 1; i >= 0; i-- {
|
||||||
getter, err := flowKeysConnIDGetter(flowKeys[i])
|
getter, err := flowKeysConnIDGetter(flowKeys[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -275,16 +362,24 @@ func createSpeedLimiter(speed uint64, flowKeys []string) (Limiter, error) {
|
|||||||
return limiter, nil
|
return limiter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type speedLimiter struct {
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *speedLimiter) WaitN(ctx context.Context, n int) error {
|
||||||
|
return r.limiter.WaitN(ctx, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *speedLimiter) SetSpeed(speed uint64) {
|
||||||
|
r.limiter.SetLimit(rate.Limit(float64(speed)))
|
||||||
|
}
|
||||||
|
|
||||||
func flowKeysConnIDGetter(name string) (ConnIDGetter, error) {
|
func flowKeysConnIDGetter(name string) (ConnIDGetter, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "user":
|
case "user":
|
||||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
return metadata.User, true
|
return metadata.User, true
|
||||||
}, nil
|
}, nil
|
||||||
case "destination":
|
|
||||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
|
||||||
return metadata.Destination.String(), true
|
|
||||||
}, nil
|
|
||||||
case "source_ip":
|
case "source_ip":
|
||||||
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
return metadata.Source.IPAddr().String(), true
|
return metadata.Source.IPAddr().String(), true
|
||||||
@@ -302,6 +397,14 @@ func flowKeysConnIDGetter(name string) (ConnIDGetter, error) {
|
|||||||
}
|
}
|
||||||
return strconv.FormatUint(uint64(id.ID), 10), ok
|
return strconv.FormatUint(uint64(id.ID), 10), ok
|
||||||
}, nil
|
}, nil
|
||||||
|
case "protocol":
|
||||||
|
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
return metadata.Protocol, metadata.Protocol != ""
|
||||||
|
}, nil
|
||||||
|
case "destination":
|
||||||
|
return func(ctx context.Context, metadata *adapter.InboundContext) (string, bool) {
|
||||||
|
return metadata.Destination.String(), true
|
||||||
|
}, nil
|
||||||
default:
|
default:
|
||||||
return nil, E.New("flow key not found: ", name)
|
return nil, E.New("flow key not found: ", name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/common/onclose"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDefaultLock(max uint32) LockIDGetter {
|
func NewDefaultLock(max uint32) LockIDGetter {
|
||||||
locks := make(map[string]*uint32)
|
locks := make(map[string]*uint32)
|
||||||
mtx := sync.Mutex{}
|
mtx := sync.Mutex{}
|
||||||
return func(id string) (CloseHandlerFunc, context.Context, error) {
|
return func(id string) (onclose.CloseHandlerFunc, context.Context, error) {
|
||||||
mtx.Lock()
|
mtx.Lock()
|
||||||
defer mtx.Unlock()
|
defer mtx.Unlock()
|
||||||
handles, ok := locks[id]
|
handles, ok := locks[id]
|
||||||
@@ -22,16 +23,13 @@ func NewDefaultLock(max uint32) LockIDGetter {
|
|||||||
locks[id] = handles
|
locks[id] = handles
|
||||||
}
|
}
|
||||||
*handles++
|
*handles++
|
||||||
var once sync.Once
|
|
||||||
return func() {
|
return func() {
|
||||||
once.Do(func() {
|
mtx.Lock()
|
||||||
mtx.Lock()
|
defer mtx.Unlock()
|
||||||
defer mtx.Unlock()
|
*handles--
|
||||||
*handles--
|
if *handles == 0 {
|
||||||
if *handles == 0 {
|
delete(locks, id)
|
||||||
delete(locks, id)
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
}, nil, nil
|
}, nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
|
"github.com/sagernet/sing-box/common/onclose"
|
||||||
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"
|
||||||
@@ -110,7 +111,7 @@ func (h *Outbound) DialContext(ctx context.Context, network string, destination
|
|||||||
onClose()
|
onClose()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn = newConnWithCloseHandlerFunc(conn, onClose)
|
conn = onclose.NewConn(conn, onClose)
|
||||||
if lockCtx != nil {
|
if lockCtx != nil {
|
||||||
go connChecker(lockCtx, conn.Close)
|
go connChecker(lockCtx, conn.Close)
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (n
|
|||||||
onClose()
|
onClose()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn = newPacketConnWithCloseHandlerFunc(conn, onClose)
|
conn = onclose.NewPacketConn(conn, onClose)
|
||||||
if lockCtx != nil {
|
if lockCtx != nil {
|
||||||
go connChecker(lockCtx, conn.Close)
|
go connChecker(lockCtx, conn.Close)
|
||||||
}
|
}
|
||||||
@@ -141,7 +142,7 @@ func (h *Outbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata
|
|||||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn = newConnWithCloseHandlerFunc(conn, limiterOnClose)
|
conn = onclose.NewConn(conn, limiterOnClose)
|
||||||
if lockCtx != nil {
|
if lockCtx != nil {
|
||||||
go connChecker(lockCtx, conn.Close)
|
go connChecker(lockCtx, conn.Close)
|
||||||
}
|
}
|
||||||
@@ -158,7 +159,7 @@ func (h *Outbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conn = bufio.NewPacketConn(newPacketConnWithCloseHandlerFunc(bufio.NewNetPacketConn(conn), limiterOnClose))
|
conn = bufio.NewPacketConn(onclose.NewPacketConn(bufio.NewNetPacketConn(conn), limiterOnClose))
|
||||||
if lockCtx != nil {
|
if lockCtx != nil {
|
||||||
go connChecker(lockCtx, conn.Close)
|
go connChecker(lockCtx, conn.Close)
|
||||||
}
|
}
|
||||||
@@ -172,34 +173,6 @@ func (h *Outbound) GetStrategy() ConnectionStrategy {
|
|||||||
return h.strategy
|
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) {
|
func connChecker(ctx context.Context, closeFunc func() error) {
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
closeFunc()
|
closeFunc()
|
||||||
|
|||||||
@@ -6,18 +6,17 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/onclose"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CloseHandlerFunc = func()
|
|
||||||
|
|
||||||
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
ConnIDGetter = func(context.Context, *adapter.InboundContext) (string, bool)
|
||||||
LockIDGetter = func(string) (CloseHandlerFunc, context.Context, error)
|
LockIDGetter = func(string) (onclose.CloseHandlerFunc, context.Context, error)
|
||||||
|
|
||||||
ConnectionStrategy interface {
|
ConnectionStrategy interface {
|
||||||
request(ctx context.Context, metadata *adapter.InboundContext) (onClose CloseHandlerFunc, lockCtx context.Context, err error)
|
request(ctx context.Context, metadata *adapter.InboundContext) (onClose onclose.CloseHandlerFunc, lockCtx context.Context, err error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ func NewDefaultConnectionStrategy(connIDGetter ConnIDGetter, lockIDGetter LockID
|
|||||||
return outbound
|
return outbound
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (CloseHandlerFunc, context.Context, error) {
|
func (s *DefaultConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (onclose.CloseHandlerFunc, context.Context, error) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
defer s.mtx.Unlock()
|
||||||
id, ok := s.connIDGetter(ctx, metadata)
|
id, ok := s.connIDGetter(ctx, metadata)
|
||||||
@@ -57,7 +56,7 @@ func NewUsersConnectionStrategy(strategies map[string]ConnectionStrategy) *Users
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UsersConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (CloseHandlerFunc, context.Context, error) {
|
func (s *UsersConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (onclose.CloseHandlerFunc, context.Context, error) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
defer s.mtx.Unlock()
|
||||||
var user string
|
var user string
|
||||||
@@ -71,20 +70,78 @@ func (s *UsersConnectionStrategy) request(ctx context.Context, metadata *adapter
|
|||||||
return nil, nil, E.New("user strategy not found: ", user)
|
return nil, nil, E.New("user strategy not found: ", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type cancelEntry struct {
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
type ManagerConnectionStrategy struct {
|
type ManagerConnectionStrategy struct {
|
||||||
*UsersConnectionStrategy
|
strategies map[string]ConnectionStrategy
|
||||||
|
cancels map[string][]*cancelEntry
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManagerConnectionStrategy() *ManagerConnectionStrategy {
|
func NewManagerConnectionStrategy() *ManagerConnectionStrategy {
|
||||||
return &ManagerConnectionStrategy{
|
return &ManagerConnectionStrategy{
|
||||||
UsersConnectionStrategy: NewUsersConnectionStrategy(map[string]ConnectionStrategy{}),
|
strategies: make(map[string]ConnectionStrategy),
|
||||||
|
cancels: make(map[string][]*cancelEntry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ManagerConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (onclose.CloseHandlerFunc, context.Context, error) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
var user string
|
||||||
|
if metadata != nil {
|
||||||
|
user = metadata.User
|
||||||
|
}
|
||||||
|
strategy, ok := s.strategies[user]
|
||||||
|
if !ok {
|
||||||
|
s.mtx.Unlock()
|
||||||
|
return nil, nil, E.New("user strategy not found: ", user)
|
||||||
|
}
|
||||||
|
s.mtx.Unlock()
|
||||||
|
onClose, _, err := strategy.request(ctx, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
entry := &cancelEntry{cancel: cancel}
|
||||||
|
s.mtx.Lock()
|
||||||
|
s.cancels[user] = append(s.cancels[user], entry)
|
||||||
|
s.mtx.Unlock()
|
||||||
|
originalOnClose := onClose
|
||||||
|
wrappedOnClose := func() {
|
||||||
|
s.mtx.Lock()
|
||||||
|
entries := s.cancels[user]
|
||||||
|
for i, e := range entries {
|
||||||
|
if e == entry {
|
||||||
|
s.cancels[user] = append(entries[:i], entries[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mtx.Unlock()
|
||||||
|
cancel()
|
||||||
|
if originalOnClose != nil {
|
||||||
|
originalOnClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrappedOnClose, cancelCtx, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ManagerConnectionStrategy) UpdateStrategies(strategies map[string]ConnectionStrategy) {
|
func (s *ManagerConnectionStrategy) UpdateStrategies(strategies map[string]ConnectionStrategy) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
var entries []*cancelEntry
|
||||||
|
for user, cancels := range s.cancels {
|
||||||
|
if _, exists := strategies[user]; !exists {
|
||||||
|
entries = append(entries, cancels...)
|
||||||
|
delete(s.cancels, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
s.strategies = strategies
|
s.strategies = strategies
|
||||||
|
s.mtx.Unlock()
|
||||||
|
for _, entry := range entries {
|
||||||
|
entry.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type BypassConnectionStrategy struct{}
|
type BypassConnectionStrategy struct{}
|
||||||
@@ -93,7 +150,7 @@ func NewBypassConnectionStrategy() *BypassConnectionStrategy {
|
|||||||
return &BypassConnectionStrategy{}
|
return &BypassConnectionStrategy{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BypassConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (CloseHandlerFunc, context.Context, error) {
|
func (s *BypassConnectionStrategy) request(ctx context.Context, metadata *adapter.InboundContext) (onclose.CloseHandlerFunc, context.Context, error) {
|
||||||
return func() {}, nil, nil
|
return func() {}, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/onclose"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
CloseHandlerFunc = func()
|
|
||||||
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter TrafficLimiter, reverse bool) net.Conn
|
ConnWrapper = func(ctx context.Context, conn net.Conn, limiter TrafficLimiter, reverse bool) net.Conn
|
||||||
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter, reverse bool) net.PacketConn
|
PacketConnWrapper = func(ctx context.Context, conn net.PacketConn, limiter TrafficLimiter, reverse bool) net.PacketConn
|
||||||
)
|
)
|
||||||
@@ -72,32 +72,58 @@ func (s *GlobalTrafficStrategy) getLimiter(ctx context.Context, metadata *adapte
|
|||||||
return s.limiter, nil
|
return s.limiter, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type connEntry struct {
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
type ManagerTrafficStrategy struct {
|
type ManagerTrafficStrategy struct {
|
||||||
strategies map[string]TrafficStrategy
|
strategies map[string]TrafficStrategy
|
||||||
mtx sync.Mutex
|
conns map[string][]*connEntry
|
||||||
|
|
||||||
|
mtx sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManagerTrafficStrategy() *ManagerTrafficStrategy {
|
func NewManagerTrafficStrategy() *ManagerTrafficStrategy {
|
||||||
return &ManagerTrafficStrategy{}
|
return &ManagerTrafficStrategy{
|
||||||
|
conns: make(map[string][]*connEntry),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
|
func (s *ManagerTrafficStrategy) wrapConn(ctx context.Context, conn net.Conn, metadata *adapter.InboundContext, reverse bool) (net.Conn, error) {
|
||||||
strategy, err := s.getStrategy(ctx, metadata)
|
strategy, user, err := s.getStrategy(ctx, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return strategy.wrapConn(ctx, conn, metadata, reverse)
|
wrapped, err := strategy.wrapConn(ctx, conn, metadata, reverse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entry := &connEntry{conn: conn}
|
||||||
|
s.mtx.Lock()
|
||||||
|
s.conns[user] = append(s.conns[user], entry)
|
||||||
|
s.mtx.Unlock()
|
||||||
|
return onclose.NewConn(wrapped, func() {
|
||||||
|
s.mtx.Lock()
|
||||||
|
entries := s.conns[user]
|
||||||
|
for i, e := range entries {
|
||||||
|
if e == entry {
|
||||||
|
s.conns[user] = append(entries[:i], entries[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.mtx.Unlock()
|
||||||
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ManagerTrafficStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
|
func (s *ManagerTrafficStrategy) wrapPacketConn(ctx context.Context, conn net.PacketConn, metadata *adapter.InboundContext, reverse bool) (net.PacketConn, error) {
|
||||||
strategy, err := s.getStrategy(ctx, metadata)
|
strategy, _, err := s.getStrategy(ctx, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return strategy.wrapPacketConn(ctx, conn, metadata, reverse)
|
return strategy.wrapPacketConn(ctx, conn, metadata, reverse)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ManagerTrafficStrategy) getStrategy(ctx context.Context, metadata *adapter.InboundContext) (TrafficStrategy, error) {
|
func (s *ManagerTrafficStrategy) getStrategy(ctx context.Context, metadata *adapter.InboundContext) (TrafficStrategy, string, error) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
defer s.mtx.Unlock()
|
||||||
var user string
|
var user string
|
||||||
@@ -106,15 +132,25 @@ func (s *ManagerTrafficStrategy) getStrategy(ctx context.Context, metadata *adap
|
|||||||
}
|
}
|
||||||
strategy, ok := s.strategies[user]
|
strategy, ok := s.strategies[user]
|
||||||
if ok {
|
if ok {
|
||||||
return strategy, nil
|
return strategy, user, nil
|
||||||
}
|
}
|
||||||
return nil, E.New("user strategy not found: ", user)
|
return nil, user, E.New("user strategy not found: ", user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ManagerTrafficStrategy) UpdateStrategies(strategies map[string]TrafficStrategy) {
|
func (s *ManagerTrafficStrategy) UpdateStrategies(strategies map[string]TrafficStrategy) {
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
var closedEntries []*connEntry
|
||||||
|
for user, entries := range s.conns {
|
||||||
|
if _, exists := strategies[user]; !exists {
|
||||||
|
closedEntries = append(closedEntries, entries...)
|
||||||
|
delete(s.conns, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
s.strategies = strategies
|
s.strategies = strategies
|
||||||
|
s.mtx.Unlock()
|
||||||
|
for _, entry := range closedEntries {
|
||||||
|
entry.conn.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type BypassTrafficStrategy struct{}
|
type BypassTrafficStrategy struct{}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/transport/masque"
|
"github.com/sagernet/sing-box/transport/masque"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
@@ -99,7 +100,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
logger.ErrorContext(ctx, E.New("failed to generate cert: ", err))
|
logger.ErrorContext(ctx, E.New("failed to generate cert: ", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tlsConfig, err := tls.NewMASQUEClient(ctx, logger, "consumer-masque.cloudflareclient.com", cert, privKey, peerPubKey, options.MASQUEOutboundTLSOptions)
|
tlsConfig, err := tls.NewMASQUEClient(ctx, logger, "consumer-masque.cloudflareclient.com", cert, privKey, peerPubKey, common.PtrValueOrDefault(options.TLS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorContext(ctx, E.New("failed to prepare TLS config: ", err))
|
logger.ErrorContext(ctx, E.New("failed to prepare TLS config: ", err))
|
||||||
return
|
return
|
||||||
@@ -135,11 +136,19 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
ctx,
|
ctx,
|
||||||
logger,
|
logger,
|
||||||
masque.TunnelOptions{
|
masque.TunnelOptions{
|
||||||
|
System: options.System,
|
||||||
|
Name: options.Name,
|
||||||
|
CreateDialer: func(interfaceName string) N.Dialer {
|
||||||
|
return common.Must1(dialer.NewDefault(ctx, option.DialerOptions{
|
||||||
|
BindInterface: interfaceName,
|
||||||
|
}))
|
||||||
|
},
|
||||||
Dialer: outboundDialer,
|
Dialer: outboundDialer,
|
||||||
Address: []netip.Prefix{
|
Address: []netip.Prefix{
|
||||||
netip.MustParsePrefix(appConfig.IPv4 + "/32"),
|
netip.MustParsePrefix(appConfig.IPv4 + "/32"),
|
||||||
netip.MustParsePrefix(appConfig.IPv6 + "/128"),
|
netip.MustParsePrefix(appConfig.IPv6 + "/128"),
|
||||||
},
|
},
|
||||||
|
AllowedAddress: options.AllowedIPs,
|
||||||
Endpoint: endpoint,
|
Endpoint: endpoint,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
UseHTTP2: options.UseHTTP2,
|
UseHTTP2: options.UseHTTP2,
|
||||||
|
|||||||
347
protocol/mieru/inbound.go
Normal file
347
protocol/mieru/inbound.go
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
package mieru
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
|
"github.com/sagernet/sing-box/common/listener"
|
||||||
|
"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"
|
||||||
|
"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"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
|
mierucommon "github.com/enfein/mieru/v3/apis/common"
|
||||||
|
mieruconstant "github.com/enfein/mieru/v3/apis/constant"
|
||||||
|
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
||||||
|
mieruserver "github.com/enfein/mieru/v3/apis/server"
|
||||||
|
mierutp "github.com/enfein/mieru/v3/apis/trafficpattern"
|
||||||
|
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterInbound(registry *inbound.Registry) {
|
||||||
|
inbound.Register[option.MieruInboundOptions](registry, C.TypeMieru, NewInbound)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Inbound struct {
|
||||||
|
inbound.Adapter
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.ConnectionRouterEx
|
||||||
|
logger log.ContextLogger
|
||||||
|
listener *listener.Listener
|
||||||
|
server mieruserver.Server
|
||||||
|
userNames []string
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MieruInboundOptions) (adapter.Inbound, error) {
|
||||||
|
config, userNames, err := buildMieruServerConfig(ctx, options)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build mieru server config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := mieruserver.NewServer()
|
||||||
|
if err := s.Store(config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to store mieru server config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inboundInstance := &Inbound{
|
||||||
|
Adapter: inbound.NewAdapter(C.TypeMieru, tag),
|
||||||
|
ctx: ctx,
|
||||||
|
router: uot.NewRouter(router, logger),
|
||||||
|
logger: logger,
|
||||||
|
server: s,
|
||||||
|
userNames: userNames,
|
||||||
|
}
|
||||||
|
inboundInstance.listener = listener.New(listener.Options{
|
||||||
|
Context: ctx,
|
||||||
|
Logger: logger,
|
||||||
|
Network: []string{N.NetworkTCP, N.NetworkUDP},
|
||||||
|
Listen: options.ListenOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
return inboundInstance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) Start(stage adapter.StartStage) error {
|
||||||
|
if stage != adapter.StartStateStart {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
if err := h.server.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start mieru server: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.logger.Notice("mieru server is started")
|
||||||
|
go h.acceptLoop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) Close() error {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
|
if h.server.IsRunning() {
|
||||||
|
return h.server.Stop()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) acceptLoop() {
|
||||||
|
for {
|
||||||
|
conn, request, err := h.server.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if !h.server.IsRunning() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.logger.Debug("failed to accept mieru connection: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go h.handleConnection(conn, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) handleConnection(conn net.Conn, request *mierumodel.Request) {
|
||||||
|
ctx := log.ContextWithNewID(h.ctx)
|
||||||
|
|
||||||
|
// Send fake SOCKS5 response back to proxy client.
|
||||||
|
resp := &mierumodel.Response{
|
||||||
|
Reply: mieruconstant.Socks5ReplySuccess,
|
||||||
|
BindAddr: mierumodel.AddrSpec{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Port: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := resp.WriteToSocks5(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
h.logger.DebugContext(ctx, "failed to write mieru response: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build metadata.
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
metadata.Inbound = h.Tag()
|
||||||
|
metadata.InboundType = h.Type()
|
||||||
|
//nolint:staticcheck
|
||||||
|
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
||||||
|
metadata.UDPDisableDomainUnmapping = h.listener.ListenOptions().UDPDisableDomainUnmapping
|
||||||
|
|
||||||
|
// Parse source address.
|
||||||
|
if remoteAddr := conn.RemoteAddr(); remoteAddr != nil {
|
||||||
|
metadata.Source = M.SocksaddrFromNet(remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse destination from request.
|
||||||
|
if request.DstAddr.FQDN != "" {
|
||||||
|
metadata.Destination = M.Socksaddr{
|
||||||
|
Fqdn: request.DstAddr.FQDN,
|
||||||
|
Port: uint16(request.DstAddr.Port),
|
||||||
|
}
|
||||||
|
} else if request.DstAddr.IP != nil {
|
||||||
|
addr, _ := netip.AddrFromSlice(request.DstAddr.IP)
|
||||||
|
metadata.Destination = M.Socksaddr{
|
||||||
|
Addr: addr.Unmap(),
|
||||||
|
Port: uint16(request.DstAddr.Port),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get username from connection.
|
||||||
|
if userCtx, ok := conn.(mierucommon.UserContext); ok {
|
||||||
|
metadata.User = userCtx.UserName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle request.
|
||||||
|
switch request.Command {
|
||||||
|
case mieruconstant.Socks5ConnectCmd:
|
||||||
|
h.logger.InfoContext(ctx, "inbound TCP connection from ", metadata.Source, " to ", metadata.Destination)
|
||||||
|
if metadata.User != "" {
|
||||||
|
h.logger.InfoContext(ctx, "[", metadata.User, "] inbound TCP connection")
|
||||||
|
}
|
||||||
|
h.router.RouteConnectionEx(ctx, conn, metadata, nil)
|
||||||
|
case mieruconstant.Socks5UDPAssociateCmd:
|
||||||
|
h.logger.InfoContext(ctx, "inbound UDP connection from ", metadata.Source, " to ", metadata.Destination)
|
||||||
|
if metadata.User != "" {
|
||||||
|
h.logger.InfoContext(ctx, "[", metadata.User, "] inbound UDP connection")
|
||||||
|
}
|
||||||
|
h.handleUDP(ctx, conn, metadata)
|
||||||
|
default:
|
||||||
|
conn.Close()
|
||||||
|
h.logger.WarnContext(ctx, "unsupported mieru command: ", request.Command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) handleUDP(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) {
|
||||||
|
pc := mierucommon.NewPacketOverStreamTunnel(conn)
|
||||||
|
packetConn := &mieruPacketConn{
|
||||||
|
PacketConn: pc,
|
||||||
|
destination: metadata.Destination,
|
||||||
|
}
|
||||||
|
h.router.RoutePacketConnectionEx(ctx, packetConn, metadata, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mieruPacketConn wraps mieru's PacketConn to implement N.PacketConn
|
||||||
|
type mieruPacketConn struct {
|
||||||
|
net.PacketConn
|
||||||
|
destination M.Socksaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ N.PacketConn = (*mieruPacketConn)(nil)
|
||||||
|
|
||||||
|
// ReadPacket parses the SOCKS5 UDP header and returns the destination address.
|
||||||
|
func (c *mieruPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) {
|
||||||
|
n, _, err := c.PacketConn.ReadFrom(buffer.FreeBytes())
|
||||||
|
if err != nil {
|
||||||
|
return M.Socksaddr{}, err
|
||||||
|
}
|
||||||
|
buffer.Truncate(n)
|
||||||
|
if buffer.Len() < 3 {
|
||||||
|
return M.Socksaddr{}, io.ErrShortBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip RSV (2 bytes) and FRAG (1 byte).
|
||||||
|
buffer.Advance(3)
|
||||||
|
|
||||||
|
var addr mierumodel.AddrSpec
|
||||||
|
if err := addr.ReadFromSocks5(buffer); err != nil {
|
||||||
|
return M.Socksaddr{}, err
|
||||||
|
}
|
||||||
|
if addr.FQDN != "" {
|
||||||
|
destination = M.Socksaddr{
|
||||||
|
Fqdn: addr.FQDN,
|
||||||
|
Port: uint16(addr.Port),
|
||||||
|
}
|
||||||
|
} else if addr.IP != nil {
|
||||||
|
netAddr, _ := netip.AddrFromSlice(addr.IP)
|
||||||
|
destination = M.Socksaddr{
|
||||||
|
Addr: netAddr.Unmap(),
|
||||||
|
Port: uint16(addr.Port),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return destination, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WritePacket writes the SOCKS5 UDP header and the payload.
|
||||||
|
func (c *mieruPacketConn) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
|
||||||
|
header := buf.NewSize(3 + M.MaxSocksaddrLength)
|
||||||
|
defer header.Release()
|
||||||
|
|
||||||
|
// RSV (2 bytes) + FRAG (1 byte)
|
||||||
|
common.Must(header.WriteZeroN(3))
|
||||||
|
|
||||||
|
var addr mierumodel.AddrSpec
|
||||||
|
if destination.IsFqdn() {
|
||||||
|
addr.FQDN = destination.Fqdn
|
||||||
|
} else {
|
||||||
|
addr.IP = destination.Addr.AsSlice()
|
||||||
|
}
|
||||||
|
addr.Port = int(destination.Port)
|
||||||
|
if err := addr.WriteToSocks5(header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := buf.NewSize(header.Len() + buffer.Len())
|
||||||
|
defer packet.Release()
|
||||||
|
common.Must1(packet.Write(header.Bytes()))
|
||||||
|
common.Must1(packet.Write(buffer.Bytes()))
|
||||||
|
_, err := c.PacketConn.WriteTo(packet.Bytes(), nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMieruServerConfig(_ context.Context, options option.MieruInboundOptions) (*mieruserver.ServerConfig, []string, error) {
|
||||||
|
if err := validateMieruInboundOptions(options); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to validate mieru options: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var transportProtocol *mierupb.TransportProtocol
|
||||||
|
switch options.Transport {
|
||||||
|
case "TCP":
|
||||||
|
transportProtocol = mierupb.TransportProtocol_TCP.Enum()
|
||||||
|
case "UDP":
|
||||||
|
transportProtocol = mierupb.TransportProtocol_UDP.Enum()
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.ListenOptions.ListenPort == 0 && len(options.ListenPorts) == 0 {
|
||||||
|
return nil, nil, E.New("either listen_port or listen_ports must be set")
|
||||||
|
}
|
||||||
|
var portBindings []*mierupb.PortBinding
|
||||||
|
if options.ListenOptions.ListenPort != 0 {
|
||||||
|
portBindings = append(portBindings, &mierupb.PortBinding{
|
||||||
|
Port: proto.Int32(int32(options.ListenOptions.ListenPort)),
|
||||||
|
Protocol: transportProtocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, pr := range options.ListenPorts {
|
||||||
|
portBindings = append(portBindings, &mierupb.PortBinding{
|
||||||
|
PortRange: proto.String(pr),
|
||||||
|
Protocol: transportProtocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var users []*mierupb.User
|
||||||
|
var userNames []string
|
||||||
|
for _, user := range options.Users {
|
||||||
|
users = append(users, &mierupb.User{
|
||||||
|
Name: proto.String(user.Name),
|
||||||
|
Password: proto.String(user.Password),
|
||||||
|
})
|
||||||
|
userNames = append(userNames, user.Name)
|
||||||
|
}
|
||||||
|
var trafficPattern *mierupb.TrafficPattern
|
||||||
|
trafficPattern, _ = mierutp.Decode(options.TrafficPattern)
|
||||||
|
var advancedSettings *mierupb.ServerAdvancedSettings
|
||||||
|
if options.UserHintIsMandatory {
|
||||||
|
advancedSettings = &mierupb.ServerAdvancedSettings{
|
||||||
|
UserHintIsMandatory: proto.Bool(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &mieruserver.ServerConfig{
|
||||||
|
Config: &mierupb.ServerConfig{
|
||||||
|
PortBindings: portBindings,
|
||||||
|
Users: users,
|
||||||
|
TrafficPattern: trafficPattern,
|
||||||
|
AdvancedSettings: advancedSettings,
|
||||||
|
},
|
||||||
|
}, userNames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMieruInboundOptions(options option.MieruInboundOptions) error {
|
||||||
|
if options.Transport != "TCP" && options.Transport != "UDP" {
|
||||||
|
return E.New("transport must be TCP or UDP")
|
||||||
|
}
|
||||||
|
if len(options.Users) == 0 {
|
||||||
|
return E.New("users is empty")
|
||||||
|
}
|
||||||
|
for _, user := range options.Users {
|
||||||
|
if user.Name == "" {
|
||||||
|
return E.New("username is empty")
|
||||||
|
}
|
||||||
|
if user.Password == "" {
|
||||||
|
return E.New("password is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.TrafficPattern != "" {
|
||||||
|
trafficPattern, err := mierutp.Decode(options.TrafficPattern)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode traffic pattern %q: %w", options.TrafficPattern, err)
|
||||||
|
}
|
||||||
|
if err := mierutp.Validate(trafficPattern); err != nil {
|
||||||
|
return fmt.Errorf("invalid traffic pattern %q: %w", options.TrafficPattern, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
mieruclient "github.com/enfein/mieru/v3/apis/client"
|
mieruclient "github.com/enfein/mieru/v3/apis/client"
|
||||||
mierucommon "github.com/enfein/mieru/v3/apis/common"
|
mierucommon "github.com/enfein/mieru/v3/apis/common"
|
||||||
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
||||||
|
mierutp "github.com/enfein/mieru/v3/apis/trafficpattern"
|
||||||
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
@@ -36,7 +37,7 @@ func RegisterOutbound(registry *outbound.Registry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MieruOutboundOptions) (adapter.Outbound, error) {
|
func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MieruOutboundOptions) (adapter.Outbound, error) {
|
||||||
outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain())
|
outboundDialer, err := dialer.New(ctx, options.DialerOptions, M.IsDomainName(options.Server))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -52,7 +53,7 @@ func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextL
|
|||||||
if err := c.Start(); err != nil {
|
if err := c.Start(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to start mieru client: %w", err)
|
return nil, fmt.Errorf("failed to start mieru client: %w", err)
|
||||||
}
|
}
|
||||||
logger.InfoContext(ctx, "mieru client is started")
|
logger.NoticeContext(ctx, "mieru client is started")
|
||||||
|
|
||||||
return &Outbound{
|
return &Outbound{
|
||||||
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMieru, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
|
Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMieru, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions),
|
||||||
@@ -123,7 +124,15 @@ func (md mieruDialer) DialContext(ctx context.Context, network, address string)
|
|||||||
return md.dialer.DialContext(ctx, network, addr)
|
return md.dialer.DialContext(ctx, network, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ mierucommon.Dialer = (*mieruDialer)(nil)
|
func (md mieruDialer) ListenPacket(ctx context.Context, network, laddr, raddr string) (net.PacketConn, error) {
|
||||||
|
addr := M.ParseSocksaddr(raddr)
|
||||||
|
return md.dialer.ListenPacket(ctx, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ mierucommon.Dialer = (*mieruDialer)(nil)
|
||||||
|
_ mierucommon.PacketDialer = (*mieruDialer)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
// streamer converts a net.PacketConn to a net.Conn.
|
// streamer converts a net.PacketConn to a net.Conn.
|
||||||
type streamer struct {
|
type streamer struct {
|
||||||
@@ -161,7 +170,13 @@ func buildMieruClientConfig(options option.MieruOutboundOptions, dialer mieruDia
|
|||||||
return nil, fmt.Errorf("failed to validate mieru options: %w", err)
|
return nil, fmt.Errorf("failed to validate mieru options: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
transportProtocol := mierupb.TransportProtocol_TCP.Enum()
|
var transportProtocol *mierupb.TransportProtocol
|
||||||
|
switch options.Transport {
|
||||||
|
case "TCP":
|
||||||
|
transportProtocol = mierupb.TransportProtocol_TCP.Enum()
|
||||||
|
case "UDP":
|
||||||
|
transportProtocol = mierupb.TransportProtocol_UDP.Enum()
|
||||||
|
}
|
||||||
server := &mierupb.ServerEndpoint{}
|
server := &mierupb.ServerEndpoint{}
|
||||||
if options.ServerPort != 0 {
|
if options.ServerPort != 0 {
|
||||||
server.PortBindings = append(server.PortBindings, &mierupb.PortBinding{
|
server.PortBindings = append(server.PortBindings, &mierupb.PortBinding{
|
||||||
@@ -189,13 +204,21 @@ func buildMieruClientConfig(options option.MieruOutboundOptions, dialer mieruDia
|
|||||||
},
|
},
|
||||||
Servers: []*mierupb.ServerEndpoint{server},
|
Servers: []*mierupb.ServerEndpoint{server},
|
||||||
},
|
},
|
||||||
Dialer: dialer,
|
Dialer: dialer,
|
||||||
|
PacketDialer: dialer,
|
||||||
|
DNSConfig: &mierucommon.ClientDNSConfig{
|
||||||
|
BypassDialerDNS: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if multiplexing, ok := mierupb.MultiplexingLevel_value[options.Multiplexing]; ok {
|
if multiplexing, ok := mierupb.MultiplexingLevel_value[options.Multiplexing]; ok {
|
||||||
config.Profile.Multiplexing = &mierupb.MultiplexingConfig{
|
config.Profile.Multiplexing = &mierupb.MultiplexingConfig{
|
||||||
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
|
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if options.TrafficPattern != "" {
|
||||||
|
trafficPattern, _ := mierutp.Decode(options.TrafficPattern)
|
||||||
|
config.Profile.TrafficPattern = trafficPattern
|
||||||
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,8 +244,8 @@ func validateMieruOptions(options option.MieruOutboundOptions) error {
|
|||||||
return fmt.Errorf("begin port must be less than or equal to end port")
|
return fmt.Errorf("begin port must be less than or equal to end port")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.Transport != "TCP" {
|
if options.Transport != "TCP" && options.Transport != "UDP" {
|
||||||
return fmt.Errorf("transport must be TCP")
|
return fmt.Errorf("transport must be TCP or UDP")
|
||||||
}
|
}
|
||||||
if options.UserName == "" {
|
if options.UserName == "" {
|
||||||
return fmt.Errorf("username is empty")
|
return fmt.Errorf("username is empty")
|
||||||
@@ -235,6 +258,15 @@ func validateMieruOptions(options option.MieruOutboundOptions) error {
|
|||||||
return fmt.Errorf("invalid multiplexing level: %s", options.Multiplexing)
|
return fmt.Errorf("invalid multiplexing level: %s", options.Multiplexing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if options.TrafficPattern != "" {
|
||||||
|
trafficPattern, err := mierutp.Decode(options.TrafficPattern)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to decode traffic pattern %q: %w", options.TrafficPattern, err)
|
||||||
|
}
|
||||||
|
if err := mierutp.Validate(trafficPattern); err != nil {
|
||||||
|
return fmt.Errorf("invalid traffic pattern %q: %w", options.TrafficPattern, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ func (h *Inbound) Close() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Inbound) UpdateUsers(users []auth.User) {
|
||||||
|
h.authenticator.UpdateUsers(users)
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
err := h.newConnection(ctx, conn, metadata, onClose)
|
err := h.newConnection(ctx, conn, metadata, onClose)
|
||||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user