mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-06 22:05:20 +03:00
Compare commits
140 Commits
v1.13.16-e
...
v1.13.12-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca0b484d7a | ||
|
|
6c1d568876 | ||
|
|
41040ba1e2 | ||
|
|
a47bdbe2ae | ||
|
|
8e8eca01fe | ||
|
|
41e815e18b | ||
|
|
b586c4f313 | ||
|
|
3bd162ed6f | ||
|
|
652e0baf57 | ||
|
|
8be5c8fabe | ||
|
|
eb36c934a7 | ||
|
|
52edfdb059 | ||
|
|
eecab479fa | ||
|
|
d8670a742a | ||
|
|
e2ef7d83a1 | ||
|
|
af75b8074f | ||
|
|
2e0306ae41 | ||
|
|
ee2945ac8f | ||
|
|
b2503ca860 | ||
|
|
dcb1da8683 | ||
|
|
22aeb48794 | ||
|
|
019103587b | ||
|
|
3139f18bf1 | ||
|
|
493538c743 | ||
|
|
578bc159fb | ||
|
|
f0c317cb0b | ||
|
|
32bc1a9fce | ||
|
|
41922ba731 | ||
|
|
bf8fe79a7a | ||
|
|
398b6387ab | ||
|
|
04908a6a67 | ||
|
|
88a80e961b | ||
|
|
05b82f63da | ||
|
|
00c08995c7 | ||
|
|
09f9f114aa | ||
|
|
1995ba4279 | ||
|
|
2d33dee415 | ||
|
|
cb3131b3d4 | ||
|
|
20bf40e822 | ||
|
|
861aff60f0 | ||
|
|
47a62927f0 | ||
|
|
2df1cffb0f | ||
|
|
f683fcb3cb | ||
|
|
bc6ca6e2ea | ||
|
|
6cd8b832fe | ||
|
|
c0d45aebfa | ||
|
|
0503006f48 | ||
|
|
517f5152e7 | ||
|
|
b1b7aa81cd | ||
|
|
195e941c35 | ||
|
|
35bc351564 | ||
|
|
290dbed7b8 | ||
|
|
d7a8207f44 | ||
|
|
57c5ca13eb | ||
|
|
7fc33134fb | ||
|
|
881ab6d436 | ||
|
|
0443b93328 | ||
|
|
75557830a8 | ||
|
|
9d5273ba1e | ||
|
|
5f2a65f01b | ||
|
|
06a519db27 | ||
|
|
65e73fe817 | ||
|
|
c0aa3480c5 | ||
|
|
69f6c75dd7 | ||
|
|
f95b34a8a7 | ||
|
|
b62000e924 | ||
|
|
a03af44c61 | ||
|
|
aa103fdfc6 | ||
|
|
c82e613c52 | ||
|
|
3d16078651 | ||
|
|
18b1101fbe | ||
|
|
4ebe870306 | ||
|
|
50c5e9df0d | ||
|
|
c8a993834e | ||
|
|
260bbbfb45 | ||
|
|
82337299b9 | ||
|
|
c229c79dcc | ||
|
|
f63091d14d | ||
|
|
1c4a01ee90 | ||
|
|
4d7f99310c | ||
|
|
6fc511f56e | ||
|
|
d18d2b352a | ||
|
|
534128bba9 | ||
|
|
736a7368c6 | ||
|
|
e7a9c90213 | ||
|
|
0f3774e501 | ||
|
|
2f8e656522 | ||
|
|
3ba30e3f00 | ||
|
|
f2639a5829 | ||
|
|
69bebbda82 | ||
|
|
00b2c042ee | ||
|
|
d9eb8f3ab6 | ||
|
|
58025a01f8 | ||
|
|
99cad72ea8 | ||
|
|
6e96d620fe | ||
|
|
596291567f | ||
|
|
a2a5f46cb6 | ||
|
|
f6da8e52b4 | ||
|
|
b27d707668 | ||
|
|
287fe834db | ||
|
|
d7f0cea4ff | ||
|
|
d8b470d1ba | ||
|
|
984fc295b3 | ||
|
|
6e4b7ed744 | ||
|
|
855d400654 | ||
|
|
4c5e2c6645 | ||
|
|
725eccdea8 | ||
|
|
2ff042abd2 | ||
|
|
ffb282e47e | ||
|
|
91f9134379 | ||
|
|
5a4de7b242 | ||
|
|
bc91312a73 | ||
|
|
1d603e24fd | ||
|
|
9dc526ea1f | ||
|
|
93eb435e26 | ||
|
|
e22416f0d9 | ||
|
|
89497dbfd5 | ||
|
|
8388abbb77 | ||
|
|
180b7c4134 | ||
|
|
deda2cca5e | ||
|
|
691ecd45a9 | ||
|
|
209b89a4a3 | ||
|
|
765111a552 | ||
|
|
824eac453b | ||
|
|
85a1a8a53b | ||
|
|
ae9e7aa5f4 | ||
|
|
52b71c6f00 | ||
|
|
5d04673783 | ||
|
|
307c429715 | ||
|
|
6801b58d96 | ||
|
|
6272596ebc | ||
|
|
7c4c2d5ca8 | ||
|
|
6768c77fa0 | ||
|
|
0dff811977 | ||
|
|
e1a58d12fe | ||
|
|
4e74a4108d | ||
|
|
0238c54261 | ||
|
|
5c911c97d8 | ||
|
|
2cfc8092ad | ||
|
|
26bd698462 |
@@ -1,16 +1,12 @@
|
||||
-s dir
|
||||
--name sing-box-extended
|
||||
--name sing-box
|
||||
--category net
|
||||
--license GPL-3.0-or-later
|
||||
--description "The universal proxy platform (extended)."
|
||||
--description "The universal proxy platform."
|
||||
--url "https://sing-box.sagernet.org/"
|
||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||
--no-deb-generate-changes
|
||||
|
||||
--provides sing-box
|
||||
--conflicts sing-box
|
||||
--replaces sing-box
|
||||
|
||||
--config-files /etc/config/sing-box
|
||||
--config-files /etc/sing-box/config.json
|
||||
|
||||
|
||||
2
.github/CRONET_GO_VERSION
vendored
2
.github/CRONET_GO_VERSION
vendored
@@ -1 +1 @@
|
||||
617d38f41f935b46a68f550d9add2e38abb3f168
|
||||
2faf34666c2cc8234f10f2ab6d4c4d6104d34ae2
|
||||
|
||||
12
.github/build_openwrt_apk.sh
vendored
12
.github/build_openwrt_apk.sh
vendored
@@ -27,7 +27,10 @@ fi
|
||||
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
||||
|
||||
# Convert version to APK format:
|
||||
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/' | sed -E 's/-[a-z]+-/./g')
|
||||
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
|
||||
# 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"
|
||||
|
||||
ROOT_DIR=$(mktemp -d)
|
||||
@@ -75,16 +78,15 @@ done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
||||
|
||||
# Build APK
|
||||
apk --root "$APK_ROOT_DIR" mkpkg \
|
||||
--info "name:sing-box-extended" \
|
||||
--info "name:sing-box" \
|
||||
--info "version:${APK_VERSION}" \
|
||||
--info "description:The universal proxy platform (extended)." \
|
||||
--info "description:The universal proxy platform." \
|
||||
--info "arch:${ARCHITECTURE}" \
|
||||
--info "license:GPL-3.0-or-later" \
|
||||
--info "origin:sing-box-extended" \
|
||||
--info "origin:sing-box" \
|
||||
--info "url:https://sing-box.sagernet.org/" \
|
||||
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
||||
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
|
||||
--info "provides:sing-box" \
|
||||
--info "provider-priority:100" \
|
||||
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
||||
--files "$ROOT_DIR" \
|
||||
|
||||
57
.github/build_openwrt_packages.sh
vendored
57
.github/build_openwrt_packages.sh
vendored
@@ -1,57 +0,0 @@
|
||||
#!/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
|
||||
2
.github/setup_go_for_macos1013.sh
vendored
2
.github/setup_go_for_macos1013.sh
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="1.25.12"
|
||||
VERSION="1.25.9"
|
||||
PATCH_COMMITS=(
|
||||
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
||||
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
||||
|
||||
14
.github/setup_go_for_windows7.sh
vendored
14
.github/setup_go_for_windows7.sh
vendored
@@ -2,14 +2,14 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="1.25.12"
|
||||
VERSION="1.25.9"
|
||||
PATCH_COMMITS=(
|
||||
"da4094da73b3b419e3f347594d805e2831f65667"
|
||||
"824aa60e77f06dbae86c20a164c78df722eb7047"
|
||||
"a3b6ba31c8cc67b6d899b978bba7b53e95afc46b"
|
||||
"edfa8de63435a409a59f60731b66ab5940d6d3a4"
|
||||
"284f9b24d6284984966a8431e30fdc2583938f96"
|
||||
"9864798dee8dd47b55d1d5100d2f1b909a2a6e6c"
|
||||
"466f6c7a29bc098b0d4c987b803c779222894a11"
|
||||
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
|
||||
"a90777dcf692dd2168577853ba743b4338721b06"
|
||||
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
|
||||
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
|
||||
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
|
||||
)
|
||||
CURL_ARGS=(
|
||||
-fL
|
||||
|
||||
807
.github/workflows/build.yml
vendored
807
.github/workflows/build.yml
vendored
File diff suppressed because it is too large
Load Diff
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -55,7 +55,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.25.12
|
||||
go-version: ~1.25.9
|
||||
- name: Clone cronet-go
|
||||
if: matrix.naive
|
||||
run: |
|
||||
|
||||
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.25.12
|
||||
go-version: ~1.25.9
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.25.12
|
||||
go-version: ~1.25.9
|
||||
- name: Clone cronet-go
|
||||
if: matrix.naive
|
||||
run: |
|
||||
@@ -194,7 +194,6 @@ jobs:
|
||||
run: |-
|
||||
set -xeuo pipefail
|
||||
sudo gem install fpm
|
||||
echo '%_rpmformat 4' > "$HOME/.rpmmacros"
|
||||
cp .fpm_systemd .fpm
|
||||
fpm -t rpm \
|
||||
--name "${NAME}" \
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -22,11 +22,4 @@ CLAUDE.md
|
||||
AGENTS.md
|
||||
/.claude/
|
||||
dist
|
||||
logs
|
||||
/*.so
|
||||
/*.log
|
||||
/*.db-shm
|
||||
/*.db-wal
|
||||
/*.db.backup.*
|
||||
/test_download.bin
|
||||
/wget-log
|
||||
logs
|
||||
367
.goreleaser.yaml
367
.goreleaser.yaml
@@ -11,7 +11,6 @@ builds:
|
||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||
- -s
|
||||
- -buildid=
|
||||
- -checklinkname=0
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
@@ -23,16 +22,8 @@ builds:
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_manager
|
||||
- with_admin_panel
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOTOOLCHAIN=local
|
||||
@@ -63,14 +54,6 @@ builds:
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
targets:
|
||||
- linux_mips
|
||||
- linux_mips_softfloat
|
||||
@@ -111,247 +94,6 @@ builds:
|
||||
- android_arm64
|
||||
- android_386
|
||||
- android_amd64
|
||||
- id: naive-purego-linux-amd64
|
||||
<<: *template
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_manager
|
||||
- with_admin_panel
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
- with_naive_outbound
|
||||
- with_purego
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOTOOLCHAIN=local
|
||||
hooks:
|
||||
post:
|
||||
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- linux_amd64_v1
|
||||
- id: naive-purego-linux-arm64
|
||||
<<: *template
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_manager
|
||||
- with_admin_panel
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
- with_naive_outbound
|
||||
- with_purego
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOTOOLCHAIN=local
|
||||
hooks:
|
||||
post:
|
||||
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- linux_arm64
|
||||
- id: naive-purego-windows-amd64
|
||||
<<: *template
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_manager
|
||||
- with_admin_panel
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
- with_naive_outbound
|
||||
- with_purego
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOTOOLCHAIN=local
|
||||
hooks:
|
||||
post:
|
||||
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- windows_amd64_v1
|
||||
- id: naive-purego-windows-arm64
|
||||
<<: *template
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_manager
|
||||
- with_admin_panel
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
- with_naive_outbound
|
||||
- with_purego
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
- GOTOOLCHAIN=local
|
||||
hooks:
|
||||
post:
|
||||
- cmd: go run -C {{ .Env.CRONET_GO_PATH }} ./cmd/build-naive extract-lib --target {{ .Os }}/{{ .Arch }} -o {{ dir .Path }}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- windows_arm64
|
||||
- id: naive-glibc
|
||||
<<: *template
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_manager
|
||||
- with_admin_panel
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
- with_naive_outbound
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- GOTOOLCHAIN=local
|
||||
- CGO_LDFLAGS=-fuse-ld=lld -Wl,-z,notext -Wl,-z,execstack
|
||||
- CC_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=x86_64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_amd64_staging
|
||||
- CXX_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=x86_64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_amd64_staging
|
||||
- CC_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=aarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_arm64_staging
|
||||
- CXX_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=aarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_arm64_staging
|
||||
- CC_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=i686-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_i386_staging
|
||||
- CXX_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=i686-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_i386_staging
|
||||
- CC_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=arm-linux-gnueabihf --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_armhf_staging
|
||||
- CXX_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=arm-linux-gnueabihf --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_armhf_staging
|
||||
- CC_linux_mips64le={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=mips64el-linux-gnuabi64 --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_mips64el_staging
|
||||
- CXX_linux_mips64le={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=mips64el-linux-gnuabi64 --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/bullseye/bullseye_mips64el_staging
|
||||
- CC_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=riscv64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/trixie/trixie_riscv64_staging
|
||||
- CXX_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=riscv64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/trixie/trixie_riscv64_staging
|
||||
- CC_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=loongarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/sid/sid_loong64_staging
|
||||
- CXX_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=loongarch64-linux-gnu --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/sid/sid_loong64_staging
|
||||
- 'CC={{ index .Env (print "CC_" .Os "_" .Arch) }}'
|
||||
- 'CXX={{ index .Env (print "CXX_" .Os "_" .Arch) }}'
|
||||
targets:
|
||||
- linux_amd64_v1
|
||||
- linux_arm64
|
||||
- linux_386
|
||||
- linux_arm_7
|
||||
- linux_riscv64
|
||||
- linux_loong64
|
||||
- id: naive-musl
|
||||
<<: *template
|
||||
tags:
|
||||
- with_gvisor
|
||||
- with_quic
|
||||
- with_dhcp
|
||||
- with_wireguard
|
||||
- with_utls
|
||||
- with_acme
|
||||
- with_clash_api
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_manager
|
||||
- with_admin_panel
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
- with_naive_outbound
|
||||
- with_musl
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
- GOTOOLCHAIN=local
|
||||
- CGO_LDFLAGS=-fuse-ld=lld -Wl,-z,notext -Wl,-z,execstack
|
||||
- CC_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=x86_64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/x86_64
|
||||
- CXX_linux_amd64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=x86_64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/x86_64
|
||||
- CC_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=aarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/aarch64
|
||||
- CXX_linux_arm64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=aarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/aarch64
|
||||
- CC_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=i486-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/i386_pentium4
|
||||
- CXX_linux_386={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=i486-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/i386_pentium4
|
||||
- CC_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=arm-openwrt-linux-musleabi --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/arm_cortex-a15_neon-vfpv4
|
||||
- CXX_linux_arm={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=arm-openwrt-linux-musleabi --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/arm_cortex-a15_neon-vfpv4
|
||||
- CC_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=riscv64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/riscv64
|
||||
- CXX_linux_riscv64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=riscv64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/23.05.5/riscv64
|
||||
- CC_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang --target=loongarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/24.10.5/loongarch64
|
||||
- CXX_linux_loong64={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/third_party/llvm-build/Release+Asserts/bin/clang++ --target=loongarch64-openwrt-linux-musl --sysroot={{ .Env.CRONET_GO_PATH }}/naiveproxy/src/out/sysroot-build/openwrt/24.10.5/loongarch64
|
||||
- 'CC={{ index .Env (print "CC_" .Os "_" .Arch) }}'
|
||||
- 'CXX={{ index .Env (print "CXX_" .Os "_" .Arch) }}'
|
||||
targets:
|
||||
- linux_amd64_v1
|
||||
- linux_arm64
|
||||
- linux_386
|
||||
- linux_arm_7
|
||||
- linux_riscv64
|
||||
- linux_loong64
|
||||
- id: compressed
|
||||
<<: *template
|
||||
targets:
|
||||
@@ -374,14 +116,6 @@ builds:
|
||||
- with_tailscale
|
||||
- with_masque
|
||||
- with_mtproxy
|
||||
- with_openvpn
|
||||
- with_trusttunnel
|
||||
- with_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- with_profiler
|
||||
- badlinkname
|
||||
- tfogo_checklinkname0
|
||||
targets:
|
||||
- linux_mips
|
||||
- linux_mips_softfloat
|
||||
@@ -420,52 +154,6 @@ builds:
|
||||
- android_arm64
|
||||
- android_386
|
||||
- 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_call
|
||||
- with_sudoku
|
||||
- with_snell
|
||||
- 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:
|
||||
- enabled: true
|
||||
ids:
|
||||
@@ -491,52 +179,11 @@ archives:
|
||||
files:
|
||||
- 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 }}'
|
||||
- id: archive-naive-purego-linux-amd64
|
||||
- id: archive-legacy
|
||||
<<: *template
|
||||
builds:
|
||||
- naive-purego-linux-amd64
|
||||
files:
|
||||
- LICENSE
|
||||
- src: dist/naive-purego-linux-amd64_*/libcronet*
|
||||
strip_parent: true
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
|
||||
- id: archive-naive-purego-linux-arm64
|
||||
<<: *template
|
||||
builds:
|
||||
- naive-purego-linux-arm64
|
||||
files:
|
||||
- LICENSE
|
||||
- src: dist/naive-purego-linux-arm64_*/libcronet*
|
||||
strip_parent: true
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
|
||||
- id: archive-naive-purego-windows-amd64
|
||||
<<: *template
|
||||
builds:
|
||||
- naive-purego-windows-amd64
|
||||
files:
|
||||
- LICENSE
|
||||
- src: dist/naive-purego-windows-amd64_*/libcronet*
|
||||
strip_parent: true
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
|
||||
- id: archive-naive-purego-windows-arm64
|
||||
<<: *template
|
||||
builds:
|
||||
- naive-purego-windows-arm64
|
||||
files:
|
||||
- LICENSE
|
||||
- src: dist/naive-purego-windows-arm64_*/libcronet*
|
||||
strip_parent: true
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
|
||||
- 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'
|
||||
- legacy
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||
- id: archive-compressed
|
||||
<<: *template
|
||||
builds:
|
||||
@@ -562,12 +209,6 @@ release:
|
||||
mode: replace
|
||||
ids:
|
||||
- archive
|
||||
- archive-naive-glibc
|
||||
- archive-naive-musl
|
||||
- archive-naive-purego-linux-amd64
|
||||
- archive-naive-purego-linux-arm64
|
||||
- archive-naive-purego-windows-amd64
|
||||
- archive-naive-purego-windows-arm64
|
||||
- archive-compressed
|
||||
- archive-openwrt
|
||||
- package
|
||||
skip_upload: true
|
||||
|
||||
19
Dockerfile
19
Dockerfile
@@ -1,23 +1,18 @@
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26 AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
|
||||
LABEL maintainer="shtorm-7"
|
||||
COPY . /go/src/github.com/sagernet/sing-box
|
||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||
ARG TARGETOS TARGETARCH
|
||||
ARG GOPROXY=""
|
||||
ARG CRONET_GO_PATH=/go/src/github.com/sagernet/sing-box/cronet-go
|
||||
ENV GOPROXY=${GOPROXY}
|
||||
ENV CGO_ENABLED=1
|
||||
ENV GOPROXY ${GOPROXY}
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOOS=$TARGETOS
|
||||
ENV GOARCH=$TARGETARCH
|
||||
ENV CRONET_GO_PATH=${CRONET_GO_PATH}
|
||||
RUN set -ex \
|
||||
&& git config --global --add safe.directory /go/src/github.com/sagernet/sing-box \
|
||||
&& export GOTOOLCHAIN=local \
|
||||
&& export CGO_ENABLED=1 GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
||||
&& eval "$(CGO_ENABLED=0 GOOS= GOARCH= go run -C "$CRONET_GO_PATH" ./cmd/build-naive --target=$GOOS/$GOARCH --libc=musl env --export)" \
|
||||
&& export CGO_LDFLAGS="$CGO_LDFLAGS -Wl,-z,notext -Wl,-z,execstack" \
|
||||
&& export VERSION=$(CGO_ENABLED=0 GOOS= GOARCH= go run ./cmd/internal/read_tag) \
|
||||
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_DOCKER) \
|
||||
&& apk add git build-base \
|
||||
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
|
||||
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
|
||||
&& go build -v -trimpath -tags "$TAGS" \
|
||||
-o /go/bin/sing-box \
|
||||
|
||||
26
Makefile
26
Makefile
@@ -22,11 +22,10 @@ ADMIN_PANEL_TAGS = $(TAGS),with_admin_panel
|
||||
DOCKER_IMAGE ?= shtorm7/sing-box-extended
|
||||
DOCKER_PLATFORMS ?= linux/amd64,linux/arm64
|
||||
|
||||
CRONET_GO_PATH ?= $(shell pwd)/cronet-go
|
||||
|
||||
.PHONY: test release docs build
|
||||
|
||||
build:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
build_admin_panel:
|
||||
@@ -36,15 +35,6 @@ build_admin_panel:
|
||||
go run ./cmd/internal/admin_panel_pack \
|
||||
-dir $(ADMIN_PANEL_DIST)
|
||||
|
||||
build_naive:
|
||||
cd $(CRONET_GO_PATH) && \
|
||||
for arch in amd64 arm64 386 arm mipsle mips64le riscv64 loong64; do \
|
||||
go run ./cmd/build-naive --target=linux/$$arch download-toolchain; \
|
||||
done && \
|
||||
for arch in amd64 arm64 386 arm mipsle riscv64 loong64; do \
|
||||
go run ./cmd/build-naive --target=linux/$$arch --libc=musl download-toolchain; \
|
||||
done
|
||||
|
||||
race:
|
||||
export GOTOOLCHAIN=local && \
|
||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
||||
@@ -88,16 +78,13 @@ proto_install:
|
||||
update_certificates:
|
||||
go run ./cmd/internal/update_certificates
|
||||
|
||||
release: build_admin_panel build_naive
|
||||
CRONET_GO_PATH=$(CRONET_GO_PATH) go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
|
||||
release: build_admin_panel
|
||||
go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
|
||||
mkdir dist/release
|
||||
mv dist/*.tar.gz \
|
||||
dist/*.zip \
|
||||
dist/*.ipk \
|
||||
dist/*.apk \
|
||||
dist/release
|
||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||
rm -r dist/release
|
||||
|
||||
release_repo:
|
||||
@@ -124,14 +111,11 @@ build_android:
|
||||
upload_android:
|
||||
mkdir -p dist/release_android
|
||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
|
||||
VERSION_CODE=$$(grep VERSION_CODE ../sing-box-for-android/version.properties | cut -d= -f2); \
|
||||
VERSION_NAME=$$(grep VERSION_NAME ../sing-box-for-android/version.properties | cut -d= -f2); \
|
||||
printf '{\n "version_code": %s,\n "version_name": "%s"\n}\n' "$$VERSION_CODE" "$$VERSION_NAME" > dist/release_android/SFA-version-metadata.json
|
||||
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
|
||||
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||
rm -rf dist/release_android
|
||||
|
||||
release_android: lib_android update_android_version build_android upload_android
|
||||
release_android: lib_android update_android_version build_android
|
||||
|
||||
publish_android:
|
||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||
|
||||
20
README.md
20
README.md
@@ -1,26 +1,15 @@
|
||||
# sing-box-extended
|
||||
|
||||
[](LICENSE)
|
||||
[](go.mod)
|
||||
[](https://codeberg.org/shtorm-7/sing-box-extended)
|
||||
[](https://t.me/sing_box_extended)
|
||||
|
||||
Sing-box with extended features.
|
||||
|
||||
## 🔥 Features
|
||||
|
||||
### Protocols
|
||||
### Outbounds
|
||||
- **WARP** — Cloudflare WARP integration through WireGuard
|
||||
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
|
||||
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
|
||||
- **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
|
||||
- **Snell** — Lightweight encrypted proxy (v1–v5) with TLS / HTTP obfuscation
|
||||
- **SSH** — SSH client and server with certificate authentication and upstream fallback
|
||||
- **Call** — Traffic tunneling through video-call platforms (VK, Dion, Telemost, WBStream)
|
||||
- **VPN** — Routed tunnel over any sing-box protocol
|
||||
- **VPN** — Routed tunnel over any TCP sing-box protocol
|
||||
- **Bond** — Link aggregation for increasing throughput
|
||||
- **Fallback** — Outbound group with priority-based switching
|
||||
- **Failover** — Automatic outbound switching with session recovery for high availability
|
||||
@@ -36,13 +25,12 @@ Sing-box with extended features.
|
||||
- **Rate Limiter** — Request rate limiting
|
||||
|
||||
### Encryption & Obfuscation
|
||||
- **Amnezia 3.0** — WireGuard traffic obfuscation
|
||||
- **Amnezia 2.0** — WireGuard traffic obfuscation
|
||||
- **VLESS encryption** — XRAY encryption for VLESS protocol
|
||||
|
||||
### Transports
|
||||
- **mKCP** — Reliable UDP-based transport
|
||||
- **XHTTP** — Modern XRAY transport
|
||||
- **rmux** — Improved smux multiplex protocol
|
||||
|
||||
### Services
|
||||
- **Admin Panel** — Web-based management interface
|
||||
@@ -52,7 +40,7 @@ Sing-box with extended features.
|
||||
|
||||
### Miscellaneous
|
||||
- **Providers** — Outbound subscriptions from local files, inline lists, or remote URLs (sing-box JSON, Clash YAML, SIP008, share links)
|
||||
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC, AnyTLS)
|
||||
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC)
|
||||
- **Extended WireGuard options** — Advanced configuration capabilities
|
||||
- **Unified Delay** — Unified latency measurement
|
||||
|
||||
|
||||
@@ -27,11 +27,6 @@ type OutboundWithPreferredRoutes interface {
|
||||
PreferredAddress(address netip.Addr) bool
|
||||
}
|
||||
|
||||
type OutboundWithMultiplex interface {
|
||||
Outbound
|
||||
MultiplexEnabled() bool
|
||||
}
|
||||
|
||||
type DirectRouteOutbound interface {
|
||||
Outbound
|
||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
||||
|
||||
@@ -233,7 +233,7 @@ func (m *Manager) Remove(tag string) error {
|
||||
if m.defaultOutbound == outbound {
|
||||
if len(m.outbounds) > 0 {
|
||||
m.defaultOutbound = m.outbounds[0]
|
||||
m.logger.Notice("updated default outbound to ", m.defaultOutbound.Tag())
|
||||
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
|
||||
} else {
|
||||
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) {
|
||||
m.defaultOutbound = outbound
|
||||
if m.started {
|
||||
m.logger.Notice("updated default outbound to ", outbound.Tag())
|
||||
m.logger.Info("updated default outbound to ", outbound.Tag())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -13,7 +13,6 @@ type PlatformInterface interface {
|
||||
|
||||
UsePlatformAutoDetectInterfaceControl() bool
|
||||
AutoDetectInterfaceControl(fd int) error
|
||||
BindInterfaceControl(fd int, interfaceName string) error
|
||||
|
||||
UsePlatformInterface() bool
|
||||
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||
|
||||
@@ -3,8 +3,6 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -36,11 +34,10 @@ type Adapter struct {
|
||||
callbackAccess sync.Mutex
|
||||
callbacks list.List[adapter.ProviderUpdateCallback]
|
||||
|
||||
link string
|
||||
enabled bool
|
||||
removeEmojis bool
|
||||
timeout time.Duration
|
||||
interval time.Duration
|
||||
link string
|
||||
enabled bool
|
||||
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 {
|
||||
@@ -71,10 +68,6 @@ 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 {
|
||||
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
|
||||
if a.history == nil {
|
||||
@@ -109,10 +102,6 @@ func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
|
||||
}
|
||||
|
||||
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
|
||||
if a.removeEmojis {
|
||||
removeEmojisFromTags(newOpts)
|
||||
}
|
||||
uniquifyTags(newOpts)
|
||||
a.removeUseless(newOpts)
|
||||
var (
|
||||
oldOptByTag = make(map[string]option.Outbound)
|
||||
@@ -276,34 +265,3 @@ 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,}`)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
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()
|
||||
return err
|
||||
}
|
||||
s.logger.Notice("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -477,7 +477,7 @@ func (s *Box) Start() error {
|
||||
s.Close()
|
||||
return err
|
||||
}
|
||||
s.logger.Notice("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ func prepareAppStore(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
if len(builds.Data) == 0 {
|
||||
log.Fatal(string(platform), " ", tag, " no build found")
|
||||
log.Fatal(platform, " ", tag, " no build found")
|
||||
}
|
||||
buildID := common.Ptr(builds.Data[0].ID)
|
||||
if version.ID == "" {
|
||||
|
||||
@@ -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")
|
||||
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_trusttunnel", "with_call", "with_openvpn", "with_sudoku", "with_snell", "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_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
|
||||
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
||||
// 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")
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"crypto/sha256"
|
||||
"flag"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var outputPath string
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&outputPath, "output", "", "output AAR path")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
err := merge()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func merge() error {
|
||||
inputPaths := flag.Args()
|
||||
if outputPath == "" {
|
||||
return E.New("missing output path")
|
||||
}
|
||||
if len(inputPaths) == 0 {
|
||||
return E.New("missing input AAR paths")
|
||||
}
|
||||
archiveReaders := make([]*zip.ReadCloser, 0, len(inputPaths))
|
||||
for _, inputPath := range inputPaths {
|
||||
archiveReader, err := zip.OpenReader(inputPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "open input AAR: ", inputPath)
|
||||
}
|
||||
archiveReaders = append(archiveReaders, archiveReader)
|
||||
}
|
||||
defer func() {
|
||||
for _, archiveReader := range archiveReaders {
|
||||
archiveReader.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
referenceEntries := make(map[string][sha256.Size]byte)
|
||||
selectedEntries := make([]*zip.File, 0)
|
||||
selectedJNIEntries := make(map[string]bool)
|
||||
for inputIndex, archiveReader := range archiveReaders {
|
||||
seenEntries := make(map[string]bool)
|
||||
for _, archiveFile := range archiveReader.File {
|
||||
if strings.HasPrefix(archiveFile.Name, "jni/") {
|
||||
if archiveFile.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
if selectedJNIEntries[archiveFile.Name] {
|
||||
return E.New("duplicate AAR JNI entry: ", archiveFile.Name)
|
||||
}
|
||||
selectedJNIEntries[archiveFile.Name] = true
|
||||
selectedEntries = append(selectedEntries, archiveFile)
|
||||
continue
|
||||
}
|
||||
entryDigest, err := digestEntry(archiveFile)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read AAR entry: ", archiveFile.Name)
|
||||
}
|
||||
if inputIndex == 0 {
|
||||
referenceEntries[archiveFile.Name] = entryDigest
|
||||
selectedEntries = append(selectedEntries, archiveFile)
|
||||
} else {
|
||||
referenceDigest, loaded := referenceEntries[archiveFile.Name]
|
||||
if !loaded {
|
||||
return E.New("unexpected AAR entry: ", archiveFile.Name)
|
||||
}
|
||||
if referenceDigest != entryDigest {
|
||||
return E.New("AAR entry differs between architectures: ", archiveFile.Name)
|
||||
}
|
||||
}
|
||||
seenEntries[archiveFile.Name] = true
|
||||
}
|
||||
if inputIndex > 0 {
|
||||
for referenceName := range referenceEntries {
|
||||
if !seenEntries[referenceName] {
|
||||
return E.New("missing AAR entry: ", referenceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
absoluteOutputPath, err := filepath.Abs(outputPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "resolve output AAR path")
|
||||
}
|
||||
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
|
||||
if err != nil {
|
||||
return E.Cause(err, "create output AAR directory")
|
||||
}
|
||||
temporaryFile, err := os.CreateTemp(filepath.Dir(absoluteOutputPath), ".merge-aar-*.aar")
|
||||
if err != nil {
|
||||
return E.Cause(err, "create temporary output AAR")
|
||||
}
|
||||
temporaryPath := temporaryFile.Name()
|
||||
defer os.Remove(temporaryPath)
|
||||
archiveWriter := zip.NewWriter(temporaryFile)
|
||||
for _, archiveFile := range selectedEntries {
|
||||
rawReader, openErr := archiveFile.OpenRaw()
|
||||
if openErr != nil {
|
||||
archiveWriter.Close()
|
||||
temporaryFile.Close()
|
||||
return E.Cause(openErr, "open raw AAR entry: ", archiveFile.Name)
|
||||
}
|
||||
header := archiveFile.FileHeader
|
||||
rawWriter, createErr := archiveWriter.CreateRaw(&header)
|
||||
if createErr != nil {
|
||||
archiveWriter.Close()
|
||||
temporaryFile.Close()
|
||||
return E.Cause(createErr, "create output AAR entry: ", archiveFile.Name)
|
||||
}
|
||||
_, copyErr := io.Copy(rawWriter, rawReader)
|
||||
if copyErr != nil {
|
||||
archiveWriter.Close()
|
||||
temporaryFile.Close()
|
||||
return E.Cause(copyErr, "copy output AAR entry: ", archiveFile.Name)
|
||||
}
|
||||
}
|
||||
err = archiveWriter.Close()
|
||||
if err != nil {
|
||||
temporaryFile.Close()
|
||||
return E.Cause(err, "finalize output AAR")
|
||||
}
|
||||
err = temporaryFile.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "close output AAR")
|
||||
}
|
||||
err = os.Rename(temporaryPath, absoluteOutputPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "replace output AAR")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func digestEntry(archiveFile *zip.File) ([sha256.Size]byte, error) {
|
||||
entryReader, err := archiveFile.Open()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, err
|
||||
}
|
||||
digest := sha256.New()
|
||||
_, err = io.Copy(digest, entryReader)
|
||||
closeErr := entryReader.Close()
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, err
|
||||
}
|
||||
if closeErr != nil {
|
||||
return [sha256.Size]byte{}, closeErr
|
||||
}
|
||||
var result [sha256.Size]byte
|
||||
copy(result[:], digest.Sum(nil))
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/log"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
|
||||
"howett.net/plist"
|
||||
)
|
||||
|
||||
type xcFrameworkInfo struct {
|
||||
AvailableLibraries []xcFrameworkLibrary `plist:"AvailableLibraries"`
|
||||
}
|
||||
|
||||
type xcFrameworkLibrary struct {
|
||||
BinaryPath string `plist:"BinaryPath"`
|
||||
LibraryIdentifier string `plist:"LibraryIdentifier"`
|
||||
LibraryPath string `plist:"LibraryPath"`
|
||||
SupportedArchitectures []string `plist:"SupportedArchitectures"`
|
||||
SupportedPlatform string `plist:"SupportedPlatform"`
|
||||
SupportedPlatformVariant string `plist:"SupportedPlatformVariant"`
|
||||
}
|
||||
|
||||
type frameworkSlice struct {
|
||||
rootPath string
|
||||
library xcFrameworkLibrary
|
||||
}
|
||||
|
||||
var outputPath string
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&outputPath, "output", "", "output XCFramework path")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
err := merge()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func merge() error {
|
||||
inputPaths := flag.Args()
|
||||
if outputPath == "" {
|
||||
return E.New("missing output path")
|
||||
}
|
||||
if len(inputPaths) == 0 {
|
||||
return E.New("missing input XCFramework paths")
|
||||
}
|
||||
frameworkGroups := make(map[string][]frameworkSlice)
|
||||
for _, inputPath := range inputPaths {
|
||||
infoFile, err := os.Open(filepath.Join(inputPath, "Info.plist"))
|
||||
if err != nil {
|
||||
return E.Cause(err, "open XCFramework metadata: ", inputPath)
|
||||
}
|
||||
var info xcFrameworkInfo
|
||||
decoder := plist.NewDecoder(infoFile)
|
||||
err = decoder.Decode(&info)
|
||||
closeErr := infoFile.Close()
|
||||
if err != nil {
|
||||
return E.Cause(err, "decode XCFramework metadata: ", inputPath)
|
||||
}
|
||||
if closeErr != nil {
|
||||
return E.Cause(closeErr, "close XCFramework metadata: ", inputPath)
|
||||
}
|
||||
for _, library := range info.AvailableLibraries {
|
||||
groupName := library.SupportedPlatform + "|" + library.SupportedPlatformVariant
|
||||
frameworkGroups[groupName] = append(frameworkGroups[groupName], frameworkSlice{
|
||||
rootPath: inputPath,
|
||||
library: library,
|
||||
})
|
||||
}
|
||||
}
|
||||
groupNames := make([]string, 0, len(frameworkGroups))
|
||||
for groupName := range frameworkGroups {
|
||||
groupNames = append(groupNames, groupName)
|
||||
}
|
||||
sort.Strings(groupNames)
|
||||
absoluteOutputPath, err := filepath.Abs(outputPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "resolve output XCFramework path")
|
||||
}
|
||||
err = os.MkdirAll(filepath.Dir(absoluteOutputPath), 0o755)
|
||||
if err != nil {
|
||||
return E.Cause(err, "create output XCFramework directory")
|
||||
}
|
||||
temporaryDirectory, err := os.MkdirTemp(filepath.Dir(absoluteOutputPath), ".merge-xcframework-*")
|
||||
if err != nil {
|
||||
return E.Cause(err, "create XCFramework merge directory")
|
||||
}
|
||||
defer os.RemoveAll(temporaryDirectory)
|
||||
frameworkPaths := make([]string, 0, len(groupNames))
|
||||
for groupIndex, groupName := range groupNames {
|
||||
frameworkSlices := frameworkGroups[groupName]
|
||||
firstSlice := frameworkSlices[0]
|
||||
firstFrameworkPath := filepath.Join(firstSlice.rootPath, firstSlice.library.LibraryIdentifier, firstSlice.library.LibraryPath)
|
||||
if len(frameworkSlices) == 1 {
|
||||
frameworkPaths = append(frameworkPaths, firstFrameworkPath)
|
||||
continue
|
||||
}
|
||||
architectures := make(map[string]bool)
|
||||
binaryPaths := make([]string, 0, len(frameworkSlices))
|
||||
for _, currentSlice := range frameworkSlices {
|
||||
if currentSlice.library.LibraryPath != firstSlice.library.LibraryPath || currentSlice.library.BinaryPath != firstSlice.library.BinaryPath {
|
||||
return E.New("incompatible XCFramework slices for platform: ", currentSlice.library.SupportedPlatform)
|
||||
}
|
||||
for _, architecture := range currentSlice.library.SupportedArchitectures {
|
||||
if architectures[architecture] {
|
||||
return E.New("duplicate XCFramework architecture: ", architecture)
|
||||
}
|
||||
architectures[architecture] = true
|
||||
}
|
||||
binaryPaths = append(binaryPaths, filepath.Join(currentSlice.rootPath, currentSlice.library.LibraryIdentifier, currentSlice.library.BinaryPath))
|
||||
}
|
||||
mergedFrameworkPath := filepath.Join(temporaryDirectory, "framework-"+strconv.Itoa(groupIndex), filepath.Base(firstSlice.library.LibraryPath))
|
||||
copyCommand := exec.Command("ditto", firstFrameworkPath, mergedFrameworkPath)
|
||||
copyCommand.Stdout = os.Stdout
|
||||
copyCommand.Stderr = os.Stderr
|
||||
err = copyCommand.Run()
|
||||
if err != nil {
|
||||
return E.Cause(err, "copy XCFramework slice")
|
||||
}
|
||||
binaryRelativePath, relativeErr := filepath.Rel(firstSlice.library.LibraryPath, firstSlice.library.BinaryPath)
|
||||
if relativeErr != nil {
|
||||
return E.Cause(relativeErr, "resolve XCFramework binary path")
|
||||
}
|
||||
if binaryRelativePath == "." || strings.HasPrefix(binaryRelativePath, ".."+string(filepath.Separator)) {
|
||||
return E.New("invalid XCFramework binary path: ", firstSlice.library.BinaryPath)
|
||||
}
|
||||
mergedBinaryPath := filepath.Join(mergedFrameworkPath, binaryRelativePath)
|
||||
temporaryBinaryPath := mergedBinaryPath + ".merged"
|
||||
lipoArguments := append([]string{"lipo", "-create"}, binaryPaths...)
|
||||
lipoArguments = append(lipoArguments, "-output", temporaryBinaryPath)
|
||||
lipoCommand := exec.Command("xcrun", lipoArguments...)
|
||||
lipoCommand.Stdout = os.Stdout
|
||||
lipoCommand.Stderr = os.Stderr
|
||||
err = lipoCommand.Run()
|
||||
if err != nil {
|
||||
return E.Cause(err, "merge XCFramework binaries")
|
||||
}
|
||||
err = os.Rename(temporaryBinaryPath, mergedBinaryPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "replace merged XCFramework binary")
|
||||
}
|
||||
frameworkPaths = append(frameworkPaths, mergedFrameworkPath)
|
||||
}
|
||||
err = os.RemoveAll(absoluteOutputPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "remove output XCFramework")
|
||||
}
|
||||
xcodebuildArguments := []string{"-create-xcframework"}
|
||||
for _, frameworkPath := range frameworkPaths {
|
||||
xcodebuildArguments = append(xcodebuildArguments, "-framework", frameworkPath)
|
||||
}
|
||||
xcodebuildArguments = append(xcodebuildArguments, "-output", absoluteOutputPath)
|
||||
xcodebuildCommand := exec.Command("xcodebuild", xcodebuildArguments...)
|
||||
xcodebuildCommand.Stdout = os.Stdout
|
||||
xcodebuildCommand.Stderr = os.Stderr
|
||||
err = xcodebuildCommand.Run()
|
||||
if err != nil {
|
||||
return E.Cause(err, "create XCFramework")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -106,7 +106,6 @@ func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent stri
|
||||
}
|
||||
|
||||
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
||||
globalSettings := collectBuildSettings(objectsMap)
|
||||
var objectKeyList []string
|
||||
for objectKey, object := range objectsMap {
|
||||
buildSettings := object.(map[string]any)["buildSettings"]
|
||||
@@ -117,51 +116,13 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
||||
if bundleIDObject == nil {
|
||||
continue
|
||||
}
|
||||
bundleID := expandBuildVariables(bundleIDObject.(string), globalSettings)
|
||||
if common.Contains(bundleIDList, bundleID) {
|
||||
if common.Contains(bundleIDList, bundleIDObject.(string)) {
|
||||
objectKeyList = append(objectKeyList, objectKey)
|
||||
}
|
||||
}
|
||||
return objectKeyList
|
||||
}
|
||||
|
||||
func collectBuildSettings(objectsMap map[string]any) map[string]string {
|
||||
settings := make(map[string]string)
|
||||
for _, object := range objectsMap {
|
||||
buildSettings, loaded := object.(map[string]any)["buildSettings"].(map[string]any)
|
||||
if !loaded {
|
||||
continue
|
||||
}
|
||||
for key, value := range buildSettings {
|
||||
valueString, isString := value.(string)
|
||||
if !isString {
|
||||
continue
|
||||
}
|
||||
settings[key] = valueString
|
||||
}
|
||||
}
|
||||
return settings
|
||||
}
|
||||
|
||||
var buildVariableRegexp = regexp.MustCompile(`\$[({]([A-Za-z0-9_]+)[)}]`)
|
||||
|
||||
func expandBuildVariables(value string, settings map[string]string) string {
|
||||
for {
|
||||
expanded := buildVariableRegexp.ReplaceAllStringFunc(value, func(match string) string {
|
||||
name := buildVariableRegexp.FindStringSubmatch(match)[1]
|
||||
replacement, loaded := settings[name]
|
||||
if !loaded {
|
||||
return match
|
||||
}
|
||||
return replacement
|
||||
})
|
||||
if expanded == value {
|
||||
return expanded
|
||||
}
|
||||
value = expanded
|
||||
}
|
||||
}
|
||||
|
||||
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
|
||||
var objectKeyList []string
|
||||
for objectKey, object := range objectsMap {
|
||||
|
||||
@@ -179,7 +179,6 @@ func run() error {
|
||||
for {
|
||||
osSignal := <-osSignals
|
||||
if osSignal == syscall.SIGHUP {
|
||||
log.Notice("received SIGHUP, reloading...")
|
||||
err = check()
|
||||
if err != nil {
|
||||
log.Error(E.Cause(err, "reload service"))
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Usage: ./codeberg-release.sh [--replace] [--draft] [--prerelease] [-p N] TAG PATH
|
||||
|
||||
OWNER="shtorm-7"
|
||||
REPO="sing-box-extended"
|
||||
API="https://codeberg.org/api/v1"
|
||||
TOKEN="${CODEBERG_TOKEN:?Set CODEBERG_TOKEN}"
|
||||
|
||||
REPLACE=false
|
||||
DRAFT=false
|
||||
PRERELEASE=false
|
||||
PARALLEL=1
|
||||
|
||||
while [[ $# -gt 2 ]]; do
|
||||
case "$1" in
|
||||
--replace) REPLACE=true; shift ;;
|
||||
--draft) DRAFT=true; shift ;;
|
||||
--prerelease) PRERELEASE=true; shift ;;
|
||||
-p) PARALLEL="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1"; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
TAG="$1"
|
||||
DIR="$2"
|
||||
|
||||
if [[ ! -d "$DIR" ]]; then
|
||||
echo "Error: $DIR is not a directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RELEASE_URL="$API/repos/$OWNER/$REPO/releases"
|
||||
RELEASE_ID=$(curl -s -H "Authorization: token $TOKEN" "$RELEASE_URL/tags/$TAG" | jq -r '.id // empty')
|
||||
|
||||
if [[ -n "$RELEASE_ID" && "$REPLACE" == "true" ]]; then
|
||||
curl -s -H "Authorization: token $TOKEN" "$RELEASE_URL/$RELEASE_ID/assets" | \
|
||||
jq -r '.[].id' | while read -r aid; do
|
||||
curl -s -X DELETE -H "Authorization: token $TOKEN" "$RELEASE_URL/$RELEASE_ID/assets/$aid" >/dev/null
|
||||
done
|
||||
curl -s -X PATCH -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
"$RELEASE_URL/$RELEASE_ID" \
|
||||
-d "{\"draft\":$DRAFT,\"prerelease\":$PRERELEASE}" >/dev/null
|
||||
elif [[ -z "$RELEASE_ID" ]]; then
|
||||
RELEASE_ID=$(curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
|
||||
"$RELEASE_URL" \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"draft\":$DRAFT,\"prerelease\":$PRERELEASE}" | jq -r '.id')
|
||||
fi
|
||||
|
||||
if [[ -z "$RELEASE_ID" || "$RELEASE_ID" == "null" ]]; then
|
||||
echo "Error: failed to get or create release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Release ID: $RELEASE_ID"
|
||||
echo "Uploading files from $DIR (parallelism: $PARALLEL)..."
|
||||
|
||||
export TOKEN RELEASE_URL RELEASE_ID
|
||||
|
||||
upload() {
|
||||
local file="$1"
|
||||
local name
|
||||
name=$(basename "$file")
|
||||
if curl -s -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
"$RELEASE_URL/$RELEASE_ID/assets?name=$name" \
|
||||
-F "attachment=@$file" >/dev/null; then
|
||||
echo "✓ $name"
|
||||
else
|
||||
echo "✗ $name"
|
||||
fi
|
||||
}
|
||||
export -f upload
|
||||
|
||||
find "$DIR" -maxdepth 1 -type f -print0 | xargs -0 -P "$PARALLEL" -I {} bash -c 'upload "$@"' _ {}
|
||||
|
||||
echo "Done."
|
||||
74
common/byteformats/formats.go
Normal file
74
common/byteformats/formats.go
Normal file
@@ -0,0 +1,74 @@
|
||||
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)
|
||||
}
|
||||
218
common/byteformats/json.go
Normal file
218
common/byteformats/json.go
Normal file
@@ -0,0 +1,218 @@
|
||||
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
|
||||
}
|
||||
114
common/byteformats/json_test.go
Normal file
114
common/byteformats/json_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package congestion
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/quic-go"
|
||||
"github.com/sagernet/quic-go/congestion"
|
||||
"github.com/sagernet/sing-quic/congestion_bbr1"
|
||||
"github.com/sagernet/sing-quic/congestion_bbr2"
|
||||
congestion_meta1 "github.com/sagernet/sing-quic/congestion_meta1"
|
||||
congestion_meta2 "github.com/sagernet/sing-quic/congestion_meta2"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
func NewCongestionControl(name string, cwnd int, timeFunc func() time.Time) (func(conn *quic.Conn) congestion.CongestionControl, error) {
|
||||
if timeFunc == nil {
|
||||
timeFunc = time.Now
|
||||
}
|
||||
if cwnd == 0 {
|
||||
cwnd = 32
|
||||
}
|
||||
switch name {
|
||||
case "", "bbr":
|
||||
return func(conn *quic.Conn) congestion.CongestionControl {
|
||||
return congestion_meta2.NewBbrSender(
|
||||
congestion_meta2.DefaultClock{TimeFunc: timeFunc},
|
||||
congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
congestion.ByteCount(cwnd)*congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
)
|
||||
}, nil
|
||||
case "bbr_standard":
|
||||
return func(conn *quic.Conn) congestion.CongestionControl {
|
||||
return congestion_bbr1.NewBbrSender(
|
||||
congestion_bbr1.DefaultClock{TimeFunc: timeFunc},
|
||||
congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
congestion_bbr1.InitialCongestionWindowPackets,
|
||||
congestion_bbr1.MaxCongestionWindowPackets,
|
||||
)
|
||||
}, nil
|
||||
case "bbr2":
|
||||
return func(conn *quic.Conn) congestion.CongestionControl {
|
||||
return congestion_bbr2.NewBBR2Sender(
|
||||
congestion_bbr2.DefaultClock{TimeFunc: timeFunc},
|
||||
congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
0,
|
||||
false,
|
||||
)
|
||||
}, nil
|
||||
case "bbr2_variant":
|
||||
return func(conn *quic.Conn) congestion.CongestionControl {
|
||||
return congestion_bbr2.NewBBR2Sender(
|
||||
congestion_bbr2.DefaultClock{TimeFunc: timeFunc},
|
||||
congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
32*congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
true,
|
||||
)
|
||||
}, nil
|
||||
case "cubic":
|
||||
return func(conn *quic.Conn) congestion.CongestionControl {
|
||||
return congestion_meta1.NewCubicSender(
|
||||
congestion_meta1.DefaultClock{TimeFunc: timeFunc},
|
||||
congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
false,
|
||||
)
|
||||
}, nil
|
||||
case "reno":
|
||||
return func(conn *quic.Conn) congestion.CongestionControl {
|
||||
return congestion_meta1.NewCubicSender(
|
||||
congestion_meta1.DefaultClock{TimeFunc: timeFunc},
|
||||
congestion.ByteCount(conn.Config().InitialPacketSize),
|
||||
true,
|
||||
)
|
||||
}, nil
|
||||
default:
|
||||
return nil, E.New("unknown congestion control: ", name)
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ type DefaultDialer struct {
|
||||
udpAddr4 string
|
||||
udpAddr6 string
|
||||
netns string
|
||||
autoDetectBindFunc control.Func
|
||||
connectionManager adapter.ConnectionManager
|
||||
networkManager adapter.NetworkManager
|
||||
networkStrategy *C.NetworkStrategy
|
||||
@@ -61,7 +60,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
networkType []C.InterfaceType
|
||||
fallbackNetworkType []C.InterfaceType
|
||||
networkFallbackDelay time.Duration
|
||||
autoDetectBindFunc control.Func
|
||||
)
|
||||
if networkManager != nil {
|
||||
interfaceFinder = networkManager.InterfaceFinder()
|
||||
@@ -72,20 +70,9 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
||||
}
|
||||
if platformInterface != nil && platformInterface.UsePlatformAutoDetectInterfaceControl() {
|
||||
interfaceName := options.BindInterface
|
||||
bindFunc := func(network, address string, conn syscall.RawConn) error {
|
||||
return control.Raw(conn, func(fd uintptr) error {
|
||||
return platformInterface.BindInterfaceControl(int(fd), interfaceName)
|
||||
})
|
||||
}
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else {
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
if options.RoutingMark > 0 {
|
||||
if !C.IsLinux {
|
||||
@@ -132,7 +119,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
autoDetectBindFunc = bindFunc
|
||||
}
|
||||
}
|
||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||
@@ -227,7 +213,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
||||
udpAddr4: udpAddr4,
|
||||
udpAddr6: udpAddr6,
|
||||
netns: options.NetNs,
|
||||
autoDetectBindFunc: autoDetectBindFunc,
|
||||
connectionManager: connectionManager,
|
||||
networkManager: networkManager,
|
||||
networkStrategy: networkStrategy,
|
||||
@@ -332,18 +317,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
|
||||
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||
if d.networkStrategy == nil {
|
||||
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
||||
listenConfig := d.udpListener
|
||||
if d.autoDetectBindFunc != nil && destination.Addr.IsValid() {
|
||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
||||
return d.autoDetectBindFunc(network, destination.String(), conn)
|
||||
})
|
||||
}
|
||||
if destination.IsIPv6() {
|
||||
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
|
||||
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
|
||||
return listenConfig.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
|
||||
} else {
|
||||
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
|
||||
@@ -182,10 +182,10 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
|
||||
|
||||
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
|
||||
interfaces := networkManager.NetworkInterfaces()
|
||||
myInterfaces := networkManager.InterfaceMonitor().MyInterfaces()
|
||||
if len(myInterfaces) > 0 {
|
||||
myInterface := networkManager.InterfaceMonitor().MyInterface()
|
||||
if myInterface != "" {
|
||||
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||
return !common.Contains(myInterfaces, it.Name)
|
||||
return it.Name != myInterface
|
||||
})
|
||||
}
|
||||
switch strategy {
|
||||
|
||||
@@ -29,8 +29,8 @@ type Conn struct {
|
||||
|
||||
func (c *Conn) Close() error {
|
||||
c.group.access.Lock()
|
||||
defer c.group.access.Unlock()
|
||||
c.group.connections.Remove(c.element)
|
||||
c.group.access.Unlock()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ type PacketConn struct {
|
||||
|
||||
func (c *PacketConn) Close() error {
|
||||
c.group.access.Lock()
|
||||
defer c.group.access.Unlock()
|
||||
c.group.connections.Remove(c.element)
|
||||
c.group.access.Unlock()
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ type SingPacketConn struct {
|
||||
|
||||
func (c *SingPacketConn) Close() error {
|
||||
c.group.access.Lock()
|
||||
defer c.group.access.Unlock()
|
||||
c.group.connections.Remove(c.element)
|
||||
c.group.access.Unlock()
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
|
||||
|
||||
@@ -47,19 +47,15 @@ func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool, isProvider
|
||||
|
||||
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||
g.access.Lock()
|
||||
defer g.access.Unlock()
|
||||
var toDelete []*list.Element[*groupConnItem]
|
||||
var toClose []io.Closer
|
||||
for element := g.connections.Front(); element != nil; element = element.Next() {
|
||||
if !element.Value.isExternal || interruptExternalConnections {
|
||||
element.Value.conn.Close()
|
||||
toDelete = append(toDelete, element)
|
||||
toClose = append(toClose, element.Value.conn)
|
||||
}
|
||||
}
|
||||
for _, element := range toDelete {
|
||||
g.connections.Remove(element)
|
||||
}
|
||||
g.access.Unlock()
|
||||
for _, conn := range toClose {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package interrupt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type closeBarrierConn struct {
|
||||
net.Conn
|
||||
barrier *sync.WaitGroup
|
||||
}
|
||||
|
||||
func (c *closeBarrierConn) Close() error {
|
||||
c.barrier.Done()
|
||||
c.barrier.Wait()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
|
||||
func TestNestedGroupsInterruptWithoutDeadlock(t *testing.T) {
|
||||
groupA := NewGroup()
|
||||
groupB := NewGroup()
|
||||
barrier := &sync.WaitGroup{}
|
||||
barrier.Add(2)
|
||||
|
||||
barrierA, barrierAPeer := net.Pipe()
|
||||
barrierB, barrierBPeer := net.Pipe()
|
||||
t.Cleanup(func() {
|
||||
barrierAPeer.Close()
|
||||
barrierBPeer.Close()
|
||||
})
|
||||
groupA.NewConn(&closeBarrierConn{Conn: barrierA, barrier: barrier}, true, false)
|
||||
groupB.NewConn(&closeBarrierConn{Conn: barrierB, barrier: barrier}, true, false)
|
||||
|
||||
connA, connAPeer := net.Pipe()
|
||||
connB, connBPeer := net.Pipe()
|
||||
t.Cleanup(func() {
|
||||
connAPeer.Close()
|
||||
connBPeer.Close()
|
||||
})
|
||||
wrapperA := groupA.NewConn(connA, true, false)
|
||||
wrapperB := groupB.NewConn(connB, true, false)
|
||||
groupA.NewConn(wrapperB, true, false)
|
||||
groupB.NewConn(wrapperA, true, false)
|
||||
|
||||
done := make(chan struct{}, 2)
|
||||
go func() {
|
||||
groupA.Interrupt(true)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
go func() {
|
||||
groupB.Interrupt(true)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
timeout := time.NewTimer(time.Second)
|
||||
defer timeout.Stop()
|
||||
for range 2 {
|
||||
select {
|
||||
case <-done:
|
||||
case <-timeout.C:
|
||||
t.Fatal("nested group interrupt deadlocked")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package list
|
||||
|
||||
// Element is an element of a linked list.
|
||||
type Element[T any] struct {
|
||||
next, prev *Element[T]
|
||||
list *List[T]
|
||||
Value T
|
||||
}
|
||||
|
||||
func (e *Element[T]) Next() *Element[T] {
|
||||
if p := e.next; e.list != nil && p != &e.list.root {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Element[T]) Prev() *Element[T] {
|
||||
if p := e.prev; e.list != nil && p != &e.list.root {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Element[T]) Remove() bool {
|
||||
if e.list == nil {
|
||||
return false
|
||||
}
|
||||
e.list.remove(e)
|
||||
return true
|
||||
}
|
||||
|
||||
type List[T any] struct {
|
||||
root Element[T]
|
||||
len int
|
||||
}
|
||||
|
||||
func (l *List[T]) Init() *List[T] {
|
||||
l.root.next = &l.root
|
||||
l.root.prev = &l.root
|
||||
l.len = 0
|
||||
return l
|
||||
}
|
||||
|
||||
func New[T any]() *List[T] { return new(List[T]).Init() }
|
||||
|
||||
func (l *List[T]) Len() int { return l.len }
|
||||
|
||||
func (l *List[T]) Front() *Element[T] {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.root.next
|
||||
}
|
||||
|
||||
func (l *List[T]) Back() *Element[T] {
|
||||
if l.len == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.root.prev
|
||||
}
|
||||
|
||||
func (l *List[T]) lazyInit() {
|
||||
if l.root.next == nil {
|
||||
l.Init()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *List[T]) insert(e, at *Element[T]) *Element[T] {
|
||||
e.prev = at
|
||||
e.next = at.next
|
||||
e.prev.next = e
|
||||
e.next.prev = e
|
||||
e.list = l
|
||||
l.len++
|
||||
return e
|
||||
}
|
||||
|
||||
func (l *List[T]) insertValue(v T, at *Element[T]) *Element[T] {
|
||||
return l.insert(&Element[T]{Value: v}, at)
|
||||
}
|
||||
|
||||
func (l *List[T]) remove(e *Element[T]) {
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
e.next = nil
|
||||
e.prev = nil
|
||||
e.list = nil
|
||||
l.len--
|
||||
}
|
||||
|
||||
func (l *List[T]) Remove(e *Element[T]) T {
|
||||
if e.list == l {
|
||||
l.remove(e)
|
||||
}
|
||||
return e.Value
|
||||
}
|
||||
|
||||
func (l *List[T]) PushFront(v T) *Element[T] {
|
||||
l.lazyInit()
|
||||
return l.insertValue(v, &l.root)
|
||||
}
|
||||
|
||||
func (l *List[T]) PushBack(v T) *Element[T] {
|
||||
l.lazyInit()
|
||||
return l.insertValue(v, l.root.prev)
|
||||
}
|
||||
|
||||
func (l *List[T]) InsertBefore(v T, mark *Element[T]) *Element[T] {
|
||||
if mark.list != l {
|
||||
return nil
|
||||
}
|
||||
return l.insertValue(v, mark.prev)
|
||||
}
|
||||
|
||||
func (l *List[T]) InsertAfter(v T, mark *Element[T]) *Element[T] {
|
||||
if mark.list != l {
|
||||
return nil
|
||||
}
|
||||
return l.insertValue(v, mark)
|
||||
}
|
||||
|
||||
func (l *List[T]) MoveToFront(e *Element[T]) {
|
||||
if e.list != l || l.root.next == e {
|
||||
return
|
||||
}
|
||||
l.move(e, &l.root)
|
||||
}
|
||||
|
||||
func (l *List[T]) MoveToBack(e *Element[T]) {
|
||||
if e.list != l || l.root.prev == e {
|
||||
return
|
||||
}
|
||||
l.move(e, l.root.prev)
|
||||
}
|
||||
|
||||
func (l *List[T]) MoveBefore(e, mark *Element[T]) {
|
||||
if e.list != l || e == mark || mark.list != l {
|
||||
return
|
||||
}
|
||||
l.move(e, mark.prev)
|
||||
}
|
||||
|
||||
func (l *List[T]) MoveAfter(e, mark *Element[T]) {
|
||||
if e.list != l || e == mark || mark.list != l {
|
||||
return
|
||||
}
|
||||
l.move(e, mark)
|
||||
}
|
||||
|
||||
func (l *List[T]) move(e, at *Element[T]) {
|
||||
if e == at {
|
||||
return
|
||||
}
|
||||
e.prev.next = e.next
|
||||
e.next.prev = e.prev
|
||||
e.prev = at
|
||||
e.next = at.next
|
||||
e.prev.next = e
|
||||
e.next.prev = e
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.logger.Notice("tcp server started at ", tcpListener.Addr())
|
||||
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||
l.tcpListener = tcpListener
|
||||
return tcpListener, err
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
||||
}
|
||||
l.udpConn = udpConn.(*net.UDPConn)
|
||||
l.udpAddr = bindAddr
|
||||
l.logger.Notice("udp server started at ", udpConn.LocalAddr())
|
||||
l.logger.Info("udp server started at ", udpConn.LocalAddr())
|
||||
return udpConn, err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
var _ Searcher = (*androidSearcher)(nil)
|
||||
@@ -17,9 +16,6 @@ type androidSearcher struct {
|
||||
}
|
||||
|
||||
func NewSearcher(config Config) (Searcher, error) {
|
||||
if config.PackageManager == nil {
|
||||
return nil, E.New("missing package manager")
|
||||
}
|
||||
return &androidSearcher{config.PackageManager}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type SliceJSON[T any] []T
|
||||
|
||||
func (s *SliceJSON[T]) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
*s = nil
|
||||
return nil
|
||||
}
|
||||
var data []byte
|
||||
switch v := src.(type) {
|
||||
case []byte:
|
||||
data = v
|
||||
case string:
|
||||
data = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("sliceJSON.Scan: unsupported type %T", src)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
*s = nil
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, (*[]T)(s))
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package sql
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
const timestampFormat = "2006-01-02 15:04:05.000"
|
||||
|
||||
func FormatTimestamp(t time.Time) string {
|
||||
return t.UTC().Format(timestampFormat)
|
||||
}
|
||||
|
||||
func ParseTimestamp(value string) (time.Time, error) {
|
||||
return time.ParseInLocation(timestampFormat, value, time.UTC)
|
||||
}
|
||||
|
||||
func ParseFilterTimestamp(value string) (time.Time, error) {
|
||||
if t, err := time.Parse(time.RFC3339Nano, value); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if t, err := time.Parse(time.RFC3339, value); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if t, err := ParseTimestamp(value); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
return time.Time{}, E.New("invalid timestamp: ", value)
|
||||
}
|
||||
|
||||
func TimestampGreaterEqualThanFilter(field string) Filter {
|
||||
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||
t, err := ParseFilterTimestamp(value[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.Where(sb.GreaterEqualThan(field, FormatTimestamp(t)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TimestampLessEqualThanFilter(field string) Filter {
|
||||
return func(sb *sqlbuilder.SelectBuilder, value []string) error {
|
||||
t, err := ParseFilterTimestamp(value[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sb.Where(sb.LessEqualThan(field, FormatTimestamp(t)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -168,9 +168,9 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
|
||||
}
|
||||
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||
|
||||
hello.SessionId[0] = 26
|
||||
hello.SessionId[1] = 7
|
||||
hello.SessionId[2] = 11
|
||||
hello.SessionId[0] = 1
|
||||
hello.SessionId[1] = 8
|
||||
hello.SessionId[2] = 1
|
||||
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
||||
copy(hello.SessionId[8:], e.shortID[:])
|
||||
if debug.Enabled {
|
||||
|
||||
@@ -165,7 +165,7 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
|
||||
if len(certificate) > 0 {
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(certificate) {
|
||||
return nil, E.New("failed to parse certificate:\n\n", string(certificate))
|
||||
return nil, E.New("failed to parse certificate:\n\n", certificate)
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
|
||||
@@ -164,14 +164,14 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
config.Certificates = []tls.Certificate{keyPair}
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
c.logger.Notice("reloaded TLS certificate")
|
||||
c.logger.Info("reloaded TLS certificate")
|
||||
} else if common.Contains(c.clientCertificatePath, path) {
|
||||
clientCertificateCA := x509.NewCertPool()
|
||||
var reloaded bool
|
||||
for _, certPath := range c.clientCertificatePath {
|
||||
content, err := os.ReadFile(certPath)
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "reload certificate from ", certPath))
|
||||
c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath))
|
||||
continue
|
||||
}
|
||||
if !clientCertificateCA.AppendCertsFromPEM(content) {
|
||||
@@ -188,7 +188,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
config.ClientCAs = clientCertificateCA
|
||||
c.config = config
|
||||
c.access.Unlock()
|
||||
c.logger.Notice("reloaded client certificates")
|
||||
c.logger.Info("reloaded client certificates")
|
||||
} else if path == c.echKeyPath {
|
||||
echKey, err := os.ReadFile(c.echKeyPath)
|
||||
if err != nil {
|
||||
@@ -198,7 +198,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.logger.Notice("reloaded ECH keys")
|
||||
c.logger.Info("reloaded ECH keys")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
|
||||
if len(certificate) > 0 {
|
||||
certPool := x509.NewCertPool()
|
||||
if !certPool.AppendCertsFromPEM(certificate) {
|
||||
return nil, E.New("failed to parse certificate:\n\n", string(certificate))
|
||||
return nil, E.New("failed to parse certificate:\n\n", certificate)
|
||||
}
|
||||
tlsConfig.RootCAs = certPool
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
@@ -73,18 +72,7 @@ func (s *HistoryStorage) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (uint16, error) {
|
||||
multiplexOutbound, isMultiplexOutbound := common.Cast[adapter.OutboundWithMultiplex](detour)
|
||||
if isMultiplexOutbound && multiplexOutbound.MultiplexEnabled() {
|
||||
_, err := urlTest(ctx, link, detour)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return urlTest(ctx, link, detour)
|
||||
}
|
||||
|
||||
func urlTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
|
||||
if link == "" {
|
||||
link = "https://www.gstatic.com/generate_204"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
Xbadoption "github.com/sagernet/sing-box/common/xray/json/badoption"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
)
|
||||
|
||||
@@ -68,8 +69,8 @@ func DecodeBase64URLSafe(content string) (string, error) {
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func ParseXHTTPRange(value string) (badoption.Range[int], error) {
|
||||
result := badoption.Range[int]{}
|
||||
func ParseXHTTPRange(value string) (Xbadoption.Range, error) {
|
||||
result := Xbadoption.Range{}
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return result, err
|
||||
|
||||
83
common/xray/json/badoption/range.go
Normal file
83
common/xray/json/badoption/range.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package badoption
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/xray/crypto"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
)
|
||||
|
||||
type Range struct {
|
||||
From int32 `json:"from"`
|
||||
To int32 `json:"to"`
|
||||
}
|
||||
|
||||
func (c *Range) Build() *Range {
|
||||
return (*Range)(c)
|
||||
}
|
||||
|
||||
func (c *Range) MarshalJSON() ([]byte, error) {
|
||||
if c.From == c.To {
|
||||
return json.Marshal(c.From)
|
||||
}
|
||||
return json.Marshal(fmt.Sprintf("%d-%d", c.From, c.To))
|
||||
}
|
||||
|
||||
func (c *Range) UnmarshalJSON(content []byte) error {
|
||||
var rangeValue struct {
|
||||
From int32 `json:"from"`
|
||||
To int32 `json:"to"`
|
||||
}
|
||||
var stringValue string
|
||||
err := json.Unmarshal(content, &stringValue)
|
||||
if err == nil {
|
||||
parts := strings.Split(stringValue, "-")
|
||||
if len(parts) != 2 {
|
||||
from, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rangeValue.From, rangeValue.To = int32(from), int32(from)
|
||||
} else {
|
||||
from, err := strconv.ParseInt(parts[0], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to, err := strconv.ParseInt(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rangeValue.From, rangeValue.To = int32(from), int32(to)
|
||||
}
|
||||
} else {
|
||||
var int32Value int32
|
||||
err := json.Unmarshal(content, &int32Value)
|
||||
if err == nil {
|
||||
rangeValue.From, rangeValue.To = int32Value, int32Value
|
||||
} else {
|
||||
err := json.Unmarshal(content, &rangeValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if rangeValue.From > rangeValue.To {
|
||||
return E.New("invalid range")
|
||||
}
|
||||
*c = Range{rangeValue.From, rangeValue.To}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Range) String() string {
|
||||
if c.From == c.To {
|
||||
return strconv.FormatInt(int64(c.From), 10)
|
||||
}
|
||||
return fmt.Sprintf("%d-%d", c.From, c.To)
|
||||
}
|
||||
|
||||
func (c Range) Rand() int32 {
|
||||
return int32(crypto.RandBetween(int64(c.From), int64(c.To)))
|
||||
}
|
||||
@@ -13,12 +13,10 @@ const (
|
||||
TypeShadowsocks = "shadowsocks"
|
||||
TypeVMess = "vmess"
|
||||
TypeTrojan = "trojan"
|
||||
TypeTrustTunnel = "trusttunnel"
|
||||
TypeNaive = "naive"
|
||||
TypeWireGuard = "wireguard"
|
||||
TypeWARP = "warp"
|
||||
TypeMASQUE = "masque"
|
||||
TypeOpenVPN = "openvpn"
|
||||
TypeMTProxy = "mtproxy"
|
||||
TypeParser = "parser"
|
||||
TypeHysteria = "hysteria"
|
||||
@@ -27,9 +25,6 @@ const (
|
||||
TypeShadowTLS = "shadowtls"
|
||||
TypeMieru = "mieru"
|
||||
TypeAnyTLS = "anytls"
|
||||
TypeSudoku = "sudoku"
|
||||
TypeSnell = "snell"
|
||||
TypeCall = "call"
|
||||
TypeShadowsocksR = "shadowsocksr"
|
||||
TypeVLESS = "vless"
|
||||
TypeTUIC = "tuic"
|
||||
@@ -43,7 +38,6 @@ const (
|
||||
TypeBandwidthLimiter = "bandwidth-limiter"
|
||||
TypeTrafficLimiter = "traffic-limiter"
|
||||
TypeRateLimiter = "rate-limiter"
|
||||
TypeFairQueue = "fair-queue"
|
||||
TypeAdminPanel = "admin-panel"
|
||||
TypeManagerAPI = "manager-api"
|
||||
TypeNodeManagerAPI = "node-manager-api"
|
||||
@@ -90,8 +84,6 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "VMess"
|
||||
case TypeTrojan:
|
||||
return "Trojan"
|
||||
case TypeTrustTunnel:
|
||||
return "TrustTunnel"
|
||||
case TypeNaive:
|
||||
return "Naive"
|
||||
case TypeWireGuard:
|
||||
@@ -100,8 +92,6 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "WARP"
|
||||
case TypeMASQUE:
|
||||
return "MASQUE"
|
||||
case TypeOpenVPN:
|
||||
return "OpenVPN"
|
||||
case TypeMTProxy:
|
||||
return "MTProxy"
|
||||
case TypeParser:
|
||||
@@ -130,12 +120,6 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "Mieru"
|
||||
case TypeAnyTLS:
|
||||
return "AnyTLS"
|
||||
case TypeSudoku:
|
||||
return "Sudoku"
|
||||
case TypeSnell:
|
||||
return "Snell"
|
||||
case TypeCall:
|
||||
return "Call"
|
||||
case TypeFallback:
|
||||
return "Fallback"
|
||||
case TypeTailscale:
|
||||
@@ -152,8 +136,6 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "Traffic Limiter"
|
||||
case TypeRateLimiter:
|
||||
return "Rate Limiter"
|
||||
case TypeFairQueue:
|
||||
return "Fair Queue"
|
||||
case TypeVPNClient:
|
||||
return "VPN Client"
|
||||
case TypeVPNServer:
|
||||
|
||||
@@ -10,7 +10,6 @@ const (
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
UDPTimeout = 5 * time.Minute
|
||||
ICMPTimeout = 10 * time.Second
|
||||
DefaultURLTestInterval = 3 * time.Minute
|
||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||
StartTimeout = 10 * time.Second
|
||||
|
||||
@@ -20,14 +20,13 @@ const (
|
||||
type LogLevel int32
|
||||
|
||||
const (
|
||||
LogLevel_PANIC LogLevel = 0
|
||||
LogLevel_FATAL LogLevel = 1
|
||||
LogLevel_ERROR LogLevel = 2
|
||||
LogLevel_WARN LogLevel = 3
|
||||
LogLevel_NOTICE LogLevel = 4
|
||||
LogLevel_INFO LogLevel = 5
|
||||
LogLevel_DEBUG LogLevel = 6
|
||||
LogLevel_TRACE LogLevel = 7
|
||||
LogLevel_PANIC LogLevel = 0
|
||||
LogLevel_FATAL LogLevel = 1
|
||||
LogLevel_ERROR LogLevel = 2
|
||||
LogLevel_WARN LogLevel = 3
|
||||
LogLevel_INFO LogLevel = 4
|
||||
LogLevel_DEBUG LogLevel = 5
|
||||
LogLevel_TRACE LogLevel = 6
|
||||
)
|
||||
|
||||
// Enum value maps for LogLevel.
|
||||
@@ -37,20 +36,18 @@ var (
|
||||
1: "FATAL",
|
||||
2: "ERROR",
|
||||
3: "WARN",
|
||||
4: "NOTICE",
|
||||
5: "INFO",
|
||||
6: "DEBUG",
|
||||
7: "TRACE",
|
||||
4: "INFO",
|
||||
5: "DEBUG",
|
||||
6: "TRACE",
|
||||
}
|
||||
LogLevel_value = map[string]int32{
|
||||
"PANIC": 0,
|
||||
"FATAL": 1,
|
||||
"ERROR": 2,
|
||||
"WARN": 3,
|
||||
"NOTICE": 4,
|
||||
"INFO": 5,
|
||||
"DEBUG": 6,
|
||||
"TRACE": 7,
|
||||
"PANIC": 0,
|
||||
"FATAL": 1,
|
||||
"ERROR": 2,
|
||||
"WARN": 3,
|
||||
"INFO": 4,
|
||||
"DEBUG": 5,
|
||||
"TRACE": 6,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -59,10 +59,9 @@ enum LogLevel {
|
||||
FATAL = 1;
|
||||
ERROR = 2;
|
||||
WARN = 3;
|
||||
NOTICE = 4;
|
||||
INFO = 5;
|
||||
DEBUG = 6;
|
||||
TRACE = 7;
|
||||
INFO = 4;
|
||||
DEBUG = 5;
|
||||
TRACE = 6;
|
||||
}
|
||||
|
||||
message Log {
|
||||
|
||||
@@ -41,9 +41,9 @@ type Client struct {
|
||||
initRDRCFunc func() adapter.RDRCStore
|
||||
logger logger.ContextLogger
|
||||
cache freelru.Cache[dns.Question, *dns.Msg]
|
||||
cacheLock compatible.Map[transportCacheKey, chan struct{}]
|
||||
cacheLock compatible.Map[dns.Question, chan struct{}]
|
||||
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||
transportCacheLock compatible.Map[transportCacheKey, chan struct{}]
|
||||
transportCacheLock compatible.Map[dns.Question, chan struct{}]
|
||||
}
|
||||
|
||||
type ClientOptions struct {
|
||||
@@ -106,23 +106,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func stripDNSPadding(response *dns.Msg) {
|
||||
for _, record := range response.Extra {
|
||||
opt, isOpt := record.(*dns.OPT)
|
||||
if !isOpt {
|
||||
continue
|
||||
}
|
||||
opt.Option = common.Filter(opt.Option, func(it dns.EDNS0) bool {
|
||||
return it.Option() != dns.EDNS0PADDING
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
|
||||
transportStack := transportStackFromContext(ctx)
|
||||
if containsTransport(transportStack, transport.Tag()) {
|
||||
return nil, E.New("DNS resolution loop detected: ", formatTransportLoop(transportStack, transport.Tag()))
|
||||
}
|
||||
if len(message.Question) == 0 {
|
||||
if c.logger != nil {
|
||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
||||
@@ -153,11 +137,9 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
len(message.Extra[0].(*dns.OPT).Option) == 0) &&
|
||||
!options.ClientSubnet.IsValid()
|
||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||
ctx = contextWithTransportStack(ctx, append(transportStack, transport.Tag()))
|
||||
if !disableCache {
|
||||
cacheKey := transportCacheKey{Question: question, transportTag: transport.Tag()}
|
||||
if c.cache != nil {
|
||||
cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
||||
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
|
||||
if loaded {
|
||||
select {
|
||||
case <-cond:
|
||||
@@ -166,12 +148,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
}
|
||||
} else {
|
||||
defer func() {
|
||||
c.cacheLock.Delete(cacheKey)
|
||||
c.cacheLock.Delete(question)
|
||||
close(cond)
|
||||
}()
|
||||
}
|
||||
} else if c.transportCache != nil {
|
||||
cond, loaded := c.transportCacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
||||
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
|
||||
if loaded {
|
||||
select {
|
||||
case <-cond:
|
||||
@@ -180,7 +162,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
}
|
||||
} else {
|
||||
defer func() {
|
||||
c.transportCacheLock.Delete(cacheKey)
|
||||
c.transportCacheLock.Delete(question)
|
||||
close(cond)
|
||||
}()
|
||||
}
|
||||
@@ -194,6 +176,11 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
}
|
||||
|
||||
messageId := message.Id
|
||||
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
|
||||
if clientSubnetLoaded && transport.Tag() == contextTransport {
|
||||
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
|
||||
}
|
||||
ctx = contextWithTransportTag(ctx, transport.Tag())
|
||||
if !disableCache && responseChecker != nil && c.rdrc != nil {
|
||||
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
||||
if rejected {
|
||||
@@ -210,8 +197,6 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
stripDNSPadding(response)
|
||||
}
|
||||
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||
validResponse := response
|
||||
@@ -561,36 +546,15 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
||||
return addresses
|
||||
}
|
||||
|
||||
type transportStackKey struct{}
|
||||
type transportKey struct{}
|
||||
|
||||
func contextWithTransportStack(ctx context.Context, stack []string) context.Context {
|
||||
return context.WithValue(ctx, transportStackKey{}, stack)
|
||||
func contextWithTransportTag(ctx context.Context, transportTag string) context.Context {
|
||||
return context.WithValue(ctx, transportKey{}, transportTag)
|
||||
}
|
||||
|
||||
func transportStackFromContext(ctx context.Context) []string {
|
||||
value, _ := ctx.Value(transportStackKey{}).([]string)
|
||||
return value
|
||||
}
|
||||
|
||||
func containsTransport(stack []string, tag string) bool {
|
||||
for _, t := range stack {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func formatTransportLoop(stack []string, loopTag string) string {
|
||||
result := ""
|
||||
for i, t := range stack {
|
||||
if i > 0 {
|
||||
result += " -> "
|
||||
}
|
||||
result += t
|
||||
}
|
||||
result += " -> " + loopTag
|
||||
return result
|
||||
func transportTagFromContext(ctx context.Context) (string, bool) {
|
||||
value, loaded := ctx.Value(transportKey{}).(string)
|
||||
return value, loaded
|
||||
}
|
||||
|
||||
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||
|
||||
@@ -111,7 +111,6 @@ func (t *Transport) Close() error {
|
||||
func (t *Transport) Reset() {
|
||||
t.transportLock.Lock()
|
||||
t.updatedAt = time.Time{}
|
||||
t.lastError = nil
|
||||
t.servers = nil
|
||||
t.transportLock.Unlock()
|
||||
}
|
||||
@@ -187,7 +186,7 @@ func (t *Transport) updateServers() error {
|
||||
return E.Cause(err, "dhcp: prepare interface")
|
||||
}
|
||||
|
||||
t.logger.Notice("dhcp: query DNS servers on ", iface.Name)
|
||||
t.logger.Info("dhcp: query DNS servers on ", iface.Name)
|
||||
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
||||
err = t.fetchServers0(fetchCtx, iface)
|
||||
cancel()
|
||||
@@ -296,20 +295,15 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
|
||||
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
|
||||
searchList := dhcpPacket.DomainSearch()
|
||||
if searchList != nil && len(searchList.Labels) > 0 {
|
||||
t.search = common.Filter(common.Map(searchList.Labels, mDNS.Fqdn), func(it string) bool {
|
||||
return it != "."
|
||||
})
|
||||
t.search = searchList.Labels
|
||||
} else if dhcpPacket.DomainName() != "" {
|
||||
domainName := mDNS.Fqdn(dhcpPacket.DomainName())
|
||||
if domainName != "." {
|
||||
t.search = []string{domainName}
|
||||
}
|
||||
t.search = []string{dhcpPacket.DomainName()}
|
||||
}
|
||||
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
|
||||
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||
})
|
||||
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
||||
t.logger.Notice("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
|
||||
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
|
||||
}
|
||||
t.servers = serverAddrs
|
||||
return nil
|
||||
|
||||
@@ -120,16 +120,13 @@ func NewHTTPSRaw(
|
||||
serverAddr M.Socksaddr,
|
||||
tlsConfig tls.Config,
|
||||
) *HTTPSTransport {
|
||||
if tlsConfig != nil {
|
||||
dialer = tls.NewDialer(dialer, tlsConfig)
|
||||
}
|
||||
return &HTTPSTransport{
|
||||
TransportAdapter: adapter,
|
||||
logger: logger,
|
||||
dialer: dialer,
|
||||
destination: destination,
|
||||
headers: headers,
|
||||
transport: NewHTTPSTransportWrapper(dialer, serverAddr, destination),
|
||||
transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,11 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
@@ -24,36 +22,27 @@ type HTTPSTransportWrapper struct {
|
||||
fallback *atomic.Bool
|
||||
}
|
||||
|
||||
func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destination *url.URL) *HTTPSTransportWrapper {
|
||||
func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper {
|
||||
var fallback atomic.Bool
|
||||
if destination.Scheme == "http" {
|
||||
// plain HTTP DoH used by Tailscale
|
||||
fallback.Store(true)
|
||||
}
|
||||
return &HTTPSTransportWrapper{
|
||||
http2Transport: &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {
|
||||
resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
|
||||
tlsConn, err := dialer.DialTLSContext(ctx, serverAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn {
|
||||
state := tlsConn.ConnectionState()
|
||||
if state.NegotiatedProtocol != http2.NextProtoTLS {
|
||||
tlsConn.Close()
|
||||
fallback.Store(true)
|
||||
return nil, errFallback
|
||||
}
|
||||
state := tlsConn.ConnectionState()
|
||||
if state.NegotiatedProtocol == http2.NextProtoTLS {
|
||||
return tlsConn, nil
|
||||
}
|
||||
return resultConn, nil
|
||||
tlsConn.Close()
|
||||
fallback.Store(true)
|
||||
return nil, errFallback
|
||||
},
|
||||
},
|
||||
httpTransport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
|
||||
},
|
||||
DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
|
||||
return dialer.DialTLSContext(ctx, serverAddr)
|
||||
},
|
||||
},
|
||||
fallback: &fallback,
|
||||
@@ -63,15 +52,16 @@ func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destinati
|
||||
func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||
if h.fallback.Load() {
|
||||
return h.httpTransport.RoundTrip(request)
|
||||
}
|
||||
response, err := h.http2Transport.RoundTrip(request)
|
||||
if err != nil {
|
||||
if errors.Is(err, errFallback) {
|
||||
return h.httpTransport.RoundTrip(request)
|
||||
} else {
|
||||
response, err := h.http2Transport.RoundTrip(request)
|
||||
if err != nil {
|
||||
if errors.Is(err, errFallback) {
|
||||
return h.httpTransport.RoundTrip(request)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
return response, nil
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (h *HTTPSTransportWrapper) CloseIdleConnections() {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
@@ -78,12 +77,12 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
||||
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
|
||||
}
|
||||
}
|
||||
var myInterfaces []string
|
||||
var myInterface string
|
||||
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
|
||||
myInterfaces = networkManager.InterfaceMonitor().MyInterfaces()
|
||||
myInterface = networkManager.InterfaceMonitor().MyInterface()
|
||||
}
|
||||
for _, address := range dnsAddresses {
|
||||
if common.Contains(myInterfaces, address.ifName) {
|
||||
if address.ifName == myInterface {
|
||||
continue
|
||||
}
|
||||
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
|
||||
|
||||
@@ -126,12 +126,6 @@ func (t *HTTP3Transport) newTransport() *http3.Transport {
|
||||
conn.Close()
|
||||
return nil, dialErr
|
||||
}
|
||||
// quic-go does not take ownership of the packet conn passed to
|
||||
// DialEarly: when the connection ends it only stops reading.
|
||||
go func() {
|
||||
<-quicConn.Context().Done()
|
||||
conn.Close()
|
||||
}()
|
||||
return quicConn, nil
|
||||
},
|
||||
TLSClientConfig: t.tlsConfig,
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/quic-go"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -118,12 +117,6 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
||||
rawConn.Close()
|
||||
return nil, E.Cause(err, "establish QUIC connection")
|
||||
}
|
||||
// quic-go does not take ownership of the packet conn passed to
|
||||
// DialEarly: when the connection ends it only stops reading.
|
||||
go func() {
|
||||
<-earlyConnection.Context().Done()
|
||||
rawConn.Close()
|
||||
}()
|
||||
return earlyConnection, nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -151,11 +144,6 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.
|
||||
return nil, E.Cause(err, "open stream")
|
||||
}
|
||||
defer stream.CancelRead(0)
|
||||
stopWatch := context.AfterFunc(ctx, func() {
|
||||
stream.CancelRead(0)
|
||||
_ = stream.SetWriteDeadline(time.Now())
|
||||
})
|
||||
defer stopWatch()
|
||||
err = transport.WriteMessage(stream, 0, message)
|
||||
if err != nil {
|
||||
stream.Close()
|
||||
|
||||
@@ -217,7 +217,7 @@ func (m *TransportManager) Remove(tag string) error {
|
||||
return E.New("default server cannot be fakeip")
|
||||
}
|
||||
m.defaultTransport = nextTransport
|
||||
m.logger.Notice("updated default server to ", m.defaultTransport.Tag())
|
||||
m.logger.Info("updated default server to ", m.defaultTransport.Tag())
|
||||
} else {
|
||||
m.defaultTransport = nil
|
||||
}
|
||||
@@ -287,7 +287,7 @@ func (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger,
|
||||
}
|
||||
m.defaultTransport = transport
|
||||
if m.started {
|
||||
m.logger.Notice("updated default server to ", transport.Tag())
|
||||
m.logger.Info("updated default server to ", transport.Tag())
|
||||
}
|
||||
}
|
||||
if transport.Type() == C.DNSTypeFakeIP {
|
||||
|
||||
@@ -2,31 +2,6 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.13.16
|
||||
|
||||
* Remove client metadata from AnyTLS requests by default **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
We found that the AnyTLS client implementation uploads metadata that is
|
||||
**not used by the open-source server**, and there are reports of vendors using
|
||||
it to profile and discriminate against users. We now leave it empty by default
|
||||
and allow you to customize it, see
|
||||
[AnyTLS client metadata](/manual/misc/anytls-client-metadata/).
|
||||
|
||||
#### 1.13.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.14
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.13.12
|
||||
|
||||
* Update naiveproxy to v148.0.7778.96-1
|
||||
|
||||
@@ -31,11 +31,10 @@
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||
| `vless` | [VLESS](./vless/) | TCP |
|
||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||
| `mieru` | [Mieru](./mieru/) | :material-close: |
|
||||
| `tun` | [Tun](./tun/) | :material-close: |
|
||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||
|
||||
#### tag
|
||||
|
||||
The tag of the inbound.
|
||||
The tag of the inbound.
|
||||
@@ -31,11 +31,10 @@
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||
| `vless` | [VLESS](./vless/) | TCP |
|
||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||
| `mieru` | [Mieru](./mieru/) | :material-close: |
|
||||
| `tun` | [Tun](./tun/) | :material-close: |
|
||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||
|
||||
#### tag
|
||||
|
||||
入站的标签。
|
||||
入站的标签。
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
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
|
||||
|
||||
客户端若不发送用户提示,代理服务器将拒绝连接。
|
||||
@@ -17,7 +17,6 @@ icon: material/new-box
|
||||
"idle_session_check_interval": "30s",
|
||||
"idle_session_timeout": "30s",
|
||||
"min_idle_session": 5,
|
||||
"client_metadata": "",
|
||||
"tls": {},
|
||||
|
||||
... // Dial Fields
|
||||
@@ -56,12 +55,6 @@ In the check, close sessions that have been idle for longer than this. Default:
|
||||
|
||||
In the check, at least the first `n` idle sessions are kept open. Default value: `n`=0
|
||||
|
||||
#### client_metadata
|
||||
|
||||
!!! question "Since sing-box 1.13.16"
|
||||
|
||||
Check [AnyTLS client metadata](/manual/misc/anytls-client-metadata/).
|
||||
|
||||
#### tls
|
||||
|
||||
==Required==
|
||||
|
||||
@@ -17,7 +17,6 @@ icon: material/new-box
|
||||
"idle_session_check_interval": "30s",
|
||||
"idle_session_timeout": "30s",
|
||||
"min_idle_session": 5,
|
||||
"client_metadata": "",
|
||||
"tls": {},
|
||||
|
||||
... // 拨号字段
|
||||
@@ -56,12 +55,6 @@ AnyTLS 密码。
|
||||
|
||||
在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0
|
||||
|
||||
#### client_metadata
|
||||
|
||||
!!! question "自 sing-box 1.13.16 起"
|
||||
|
||||
参阅 [AnyTLS 客户端元数据](/zh/manual/misc/anytls-client-metadata/)。
|
||||
|
||||
#### tls
|
||||
|
||||
==必填==
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `anytls` | [AnyTLS](./anytls/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `tor` | [Tor](./tor/) |
|
||||
| `ssh` | [SSH](./ssh/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `anytls` | [AnyTLS](./anytls/) |
|
||||
| `mieru` | [Mieru](./mieru/) |
|
||||
| `tor` | [Tor](./tor/) |
|
||||
| `ssh` | [SSH](./ssh/) |
|
||||
| `dns` | [DNS](./dns/) |
|
||||
|
||||
@@ -19,7 +19,6 @@ icon: material/new-box
|
||||
"username": "asdf",
|
||||
"password": "hjkl",
|
||||
"multiplexing": "MULTIPLEXING_LOW",
|
||||
"traffic_pattern": "GgQIARAK",
|
||||
|
||||
... // Dial Fields
|
||||
}
|
||||
@@ -49,7 +48,7 @@ Must set at least one field between `server_port` and `server_ports`.
|
||||
|
||||
==Required==
|
||||
|
||||
Transmission protocol. Allowed values are `TCP` and `UDP`.
|
||||
Transmission protocol. The only allowed value is `TCP`.
|
||||
|
||||
#### username
|
||||
|
||||
@@ -67,10 +66,6 @@ mieru password.
|
||||
|
||||
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
|
||||
|
||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||
|
||||
@@ -19,7 +19,6 @@ icon: material/new-box
|
||||
"username": "asdf",
|
||||
"password": "hjkl",
|
||||
"multiplexing": "MULTIPLEXING_LOW",
|
||||
"traffic_pattern": "GgQIARAK",
|
||||
|
||||
... // 拨号字段
|
||||
}
|
||||
@@ -49,7 +48,7 @@ icon: material/new-box
|
||||
|
||||
==必填==
|
||||
|
||||
通信协议。可设为 `TCP` 或 `UDP`。
|
||||
通信协议。仅可设为 `TCP`。
|
||||
|
||||
#### username
|
||||
|
||||
@@ -67,10 +66,6 @@ mieru 密码。
|
||||
|
||||
多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
|
||||
|
||||
#### traffic_pattern
|
||||
|
||||
一个 base64 字符串用于微调网络行为。
|
||||
|
||||
### 拨号字段
|
||||
|
||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||
|
||||
@@ -52,7 +52,6 @@ Multiplex protocol.
|
||||
| smux | https://github.com/xtaci/smux |
|
||||
| yamux | https://github.com/hashicorp/yamux |
|
||||
| h2mux | https://golang.org/x/net/http2 |
|
||||
| rmux | https://github.com/shtorm-7/rmux |
|
||||
|
||||
h2mux is used by default.
|
||||
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
| smux | https://github.com/xtaci/smux |
|
||||
| yamux | https://github.com/hashicorp/yamux |
|
||||
| h2mux | https://golang.org/x/net/http2 |
|
||||
| rmux | https://github.com/shtorm-7/rmux |
|
||||
|
||||
默认使用 h2mux。
|
||||
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
---
|
||||
icon: material/incognito
|
||||
---
|
||||
|
||||
# AnyTLS client metadata
|
||||
|
||||
The AnyTLS protocol has a design flaw: its settings frame requires the client
|
||||
to send its software name and version to the server, and the protocol
|
||||
specification requires that clients not disguise this information.
|
||||
|
||||
This field serves no protocol purpose — AnyTLS already has a separate version
|
||||
field for compatibility negotiation, and the open-source server implementation
|
||||
does not use client metadata. However, the field allows vendors to collect and track client types, and for
|
||||
platform-specific clients, potentially infer private information such as the operating system type and version range —
|
||||
something that should not, and is not expected by users to, appear in an
|
||||
anti-censorship protocol. We have received reports that
|
||||
commercial proxy providers use this information to identify and block
|
||||
connections from the official library provided by AnyTLS for sing-box
|
||||
integration, reportedly because abusive users connect to their servers with
|
||||
sing-box or with clients using the same official library. This indicates that
|
||||
client metadata is being collected and used for discrimination in practice.
|
||||
|
||||
The protocol specification states that "disguising it has no value." We
|
||||
disagree: the situation is analogous to browsers implementing TLS ECH GREASE —
|
||||
without it, privacy-protecting clients can be fingerprinted and treated
|
||||
differently.
|
||||
|
||||
## Status
|
||||
|
||||
### 2025-02-20
|
||||
|
||||
We merged the
|
||||
[pull request adding this protocol](https://github.com/SagerNet/sing-box/pull/2615).
|
||||
Since the metadata was fixed at `sing-anytls/<library version>` in the
|
||||
implementation provided for our use, and we did not carefully review the
|
||||
protocol specification and other implementations, we wrongly believed that it
|
||||
was not private information.
|
||||
|
||||
### 2025-04-05
|
||||
|
||||
The protocol document
|
||||
[added](https://github.com/anytls/anytls-go/commit/8812aae7ab29dd88bb89067b9ca676e2e7e29171)
|
||||
the requirement that third-party implementations fill in the real software
|
||||
name and version, claiming that "disguising it has no value".
|
||||
|
||||
### 2026-07-18
|
||||
|
||||
A [pull request submitted to sing-box](https://github.com/SagerNet/sing-box/pull/4311)
|
||||
was found to additionally upload the `sing-box` name and the actual version;
|
||||
the change was subsequently reverted and was never released.
|
||||
|
||||
### 2026-08-03
|
||||
|
||||
sing-box 1.13.16 and 1.14.0-beta.5 have been released; the client metadata in
|
||||
AnyTLS requests is now empty by default. For compatibility, the
|
||||
[client_metadata](/configuration/outbound/anytls/#client_metadata) outbound
|
||||
option allows users to set a custom value.
|
||||
|
||||
Since the open-source server implementation does not use this information and
|
||||
it has no legitimate use, this is not considered a breaking change.
|
||||
|
||||
## Recommendations
|
||||
|
||||
We recommend that the AnyTLS protocol remove the client metadata, or replace
|
||||
it with an option that is not sent by default and can be customized by the
|
||||
user; and that other client implementations also take action, to jointly stop
|
||||
statistics collection and discrimination based on client metadata.
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
icon: material/incognito
|
||||
---
|
||||
|
||||
# AnyTLS 客户端元数据
|
||||
|
||||
AnyTLS 协议具有设计缺陷:其 settings 帧要求客户端向服务器发送软件名称和版本,且协议规范要求客户端不得伪装此信息。
|
||||
|
||||
此字段不承担协议功能——AnyTLS 已有独立的版本字段用于兼容性协商,且开源服务端实现不使用客户端元数据。然而,此字段使得供应商可以收集并统计客户端类型,对于某些平台特定的客户端,还可能推断出操作系统类型与版本范围等隐私信息,而这不应该,也不是被用户预期的,在一个反审查协议中出现。我们收到报告,有商业代理提供商利用此信息识别和阻止来自 sing-box 使用的、由 AnyTLS 提供的用于 sing-box 集成的官方代码库的连接,据传原因是恶意用户使用 sing-box 或使用相同官方代码库的客户端连接到服务器,这表明客户端元数据在实践中已被用于收集和区别对待。
|
||||
|
||||
协议规范称「伪装它没有任何意义」。我们不同意:这类似于浏览器实现 TLS ECH GREASE——如果没有这一机制,保护隐私的客户端会被识别并受到差别对待。
|
||||
|
||||
## 状态
|
||||
|
||||
### 2025-02-20
|
||||
|
||||
我们合并了[添加此协议的 PR](https://github.com/SagerNet/sing-box/pull/2615)。由于在供我们使用的实现中,metadata 被固定在 `sing-anytls/<library version>`,且我们没有仔细审查协议规范和其他实现,我们错误地认为这不是隐私信息。
|
||||
|
||||
### 2025-04-05
|
||||
|
||||
协议文档[加入](https://github.com/anytls/anytls-go/commit/8812aae7ab29dd88bb89067b9ca676e2e7e29171)了要求第三方实现填写真实软件名称与版本号的条款,并声称「伪装它没有任何意义」。
|
||||
|
||||
### 2026-07-18
|
||||
|
||||
[向 sing-box 提出的 PR](https://github.com/SagerNet/sing-box/pull/4311) 被发现额外上传了 `sing-box` 和实际版本的字符串,随后此更改被回退,没有发布。
|
||||
|
||||
### 2026-08-03
|
||||
|
||||
发布了 sing-box 1.13.16 和 1.14.0-beta.5,现在 AnyTLS 请求中的客户端元数据默认为空。出于兼容性考虑,[client_metadata](/zh/configuration/outbound/anytls/#client_metadata) 出站选项允许用户自定义此值。
|
||||
|
||||
由于开源服务端实现不使用此信息,且它没有合理用途,这不被视为破坏性更改。
|
||||
|
||||
## 建议
|
||||
|
||||
我们建议 AnyTLS 协议移除客户端元数据,或将其替换为非默认提供、且用户可以自定义的选项;并建议其他客户端实现也采取行动,共同阻止基于客户端元数据的统计和区别对待。
|
||||
@@ -32,7 +32,7 @@
|
||||
"tag": "my-manager",
|
||||
"database": {
|
||||
"driver": "sqlite",
|
||||
"dsn": "file:manager.db?_pragma=foreign_keys(on)&_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)&_time_format=sqlite" // also supported Postgresql
|
||||
"dsn": "file:manager.db?_pragma=foreign_keys(on)&_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)" // also supported Postgresql
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -64,8 +64,6 @@
|
||||
"listen_port": 7000,
|
||||
"manager": "my-manager",
|
||||
"api_key": "change-me-secret",
|
||||
"keep_alive": "10s",
|
||||
"keep_alive_timeout": "5s",
|
||||
// Enable TLS for production deployments (the node connects via gRPC over h2):
|
||||
// "tls": { // https://sing-box.sagernet.org/configuration/shared/tls/#inbound
|
||||
// "enabled": true,
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"action": "hijack-dns"
|
||||
}
|
||||
],
|
||||
"final": "traffic-limiter"
|
||||
"final": "connection-limiter"
|
||||
},
|
||||
"services": [
|
||||
{
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"address": "example.com",
|
||||
"port": 10001,
|
||||
"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",
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-in",
|
||||
"platform": "dion",
|
||||
"read_buffer": 32768,
|
||||
"cookies": [
|
||||
{ "name": "vc-refresh-token", "value": "" }
|
||||
],
|
||||
// optional: re-login if refresh cookie fails
|
||||
"email": "",
|
||||
"password": "",
|
||||
// empty = create new call
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{ "type": "direct", "tag": "direct" }
|
||||
],
|
||||
"route": {
|
||||
"final": "direct",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "socks",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 1080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-out",
|
||||
"platform": "dion",
|
||||
"read_buffer": 32768,
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"final": "call-out",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-in",
|
||||
"platform": "telemost",
|
||||
"read_buffer": 32768,
|
||||
"cookies": [
|
||||
{ "name": "Session_id", "value": "" },
|
||||
{ "name": "sessionid2", "value": "" }
|
||||
],
|
||||
// empty = create new call
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{ "type": "direct", "tag": "direct" }
|
||||
],
|
||||
"route": {
|
||||
"final": "direct",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "socks",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 1080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-out",
|
||||
"platform": "telemost",
|
||||
"read_buffer": 32768,
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"final": "call-out",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-in",
|
||||
"platform": "vk",
|
||||
"read_buffer": 32768,
|
||||
"cookies": [
|
||||
{ "name": "remixsid", "value": "" },
|
||||
{ "name": "remixnsid", "value": "" },
|
||||
{ "name": "p", "value": "" }
|
||||
],
|
||||
// empty = create new call
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{ "type": "direct", "tag": "direct" }
|
||||
],
|
||||
"route": {
|
||||
"final": "direct",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "socks",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 1080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-out",
|
||||
"platform": "vk",
|
||||
"read_buffer": 32768,
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"final": "call-out",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-in",
|
||||
"platform": "wbstream",
|
||||
// dc = fast data channel, video = slow VP8-smuggled (default)
|
||||
"mode": "dc",
|
||||
"read_buffer": 32768,
|
||||
"cookies": [
|
||||
{ "name": "__wb_device_id", "value": "" },
|
||||
{ "name": "wbx-refresh", "value": "" },
|
||||
{ "name": "x_wbaas_token", "value": "" },
|
||||
{ "name": "_wbauid", "value": "" },
|
||||
{ "name": "wbx-validation-key", "value": "" }
|
||||
],
|
||||
// empty = create new call
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{ "type": "direct", "tag": "direct" }
|
||||
],
|
||||
"route": {
|
||||
"final": "direct",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"log": {
|
||||
"level": "info"
|
||||
},
|
||||
"dns": {
|
||||
"servers": [
|
||||
{ "type": "local", "tag": "default" }
|
||||
]
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "socks",
|
||||
"listen": "127.0.0.1",
|
||||
"listen_port": 1080
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "call",
|
||||
"tag": "call-out",
|
||||
"platform": "wbstream",
|
||||
// dc = fast data channel, video = slow VP8-smuggled (default)
|
||||
"mode": "dc",
|
||||
"read_buffer": 32768,
|
||||
"join_link": ""
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"final": "call-out",
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,6 @@
|
||||
{
|
||||
"type": "masque",
|
||||
"tag": "masque-out",
|
||||
"system": false,
|
||||
"name": "masque0",
|
||||
"use_http2": false,
|
||||
"use_ipv6": false,
|
||||
"profile": {
|
||||
@@ -39,19 +37,14 @@
|
||||
"udp_keepalive_period": "30s",
|
||||
"udp_initial_packet_size": 0,
|
||||
"reconnect_delay": "5s",
|
||||
"congestion_controller": "bbr",
|
||||
"cwnd": 0,
|
||||
"tls": { // TLS fields for HTTP2
|
||||
"server_name": "", // SNI; empty = default "consumer-masque.cloudflareclient.com"
|
||||
"insecure": false,
|
||||
"cipher_suites": [],
|
||||
"curve_preferences": [],
|
||||
"fragment": false,
|
||||
"fragment_fallback_delay": "500ms",
|
||||
"record_fragment": false,
|
||||
"kernel_tx": false,
|
||||
"kernel_rx": false
|
||||
}
|
||||
// TLS fields for HTTP2
|
||||
"insecure": false,
|
||||
"cipher_suites": [],
|
||||
"curve_preferences": [],
|
||||
"fragment": false,
|
||||
"record_fragment": false,
|
||||
"kernel_tx": false,
|
||||
"kernel_rx": false
|
||||
// Dial Fields
|
||||
}
|
||||
],
|
||||
@@ -60,4 +53,4 @@
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,16 +27,14 @@
|
||||
"tag": "mieru-out",
|
||||
"server": "example.com",
|
||||
"server_port": 27017,
|
||||
"server_ports": [
|
||||
"27017-27019"
|
||||
],
|
||||
"server_ports": "27017-27019",
|
||||
"transport": "TCP",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
// valid: MULTIPLEXING_DEFAULT / MULTIPLEXING_OFF / MULTIPLEXING_LOW
|
||||
// MULTIPLEXING_MIDDLE / MULTIPLEXING_HIGH
|
||||
"multiplexing": "MULTIPLEXING_LOW",
|
||||
"traffic_pattern": "GgQIARAK"
|
||||
"multiplexing": "MULTIPLEXING_LOW"
|
||||
// Dial Fields
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"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,15 +31,7 @@
|
||||
"packet_encoding": "",
|
||||
"transport": {
|
||||
"type": "mkcp",
|
||||
"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"
|
||||
"mtu": 1500
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -24,15 +24,7 @@
|
||||
],
|
||||
"transport": {
|
||||
"type": "mkcp",
|
||||
"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"
|
||||
"mtu": 1500
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
"concurrency": 8192,
|
||||
// domain_fronting_port is a port we use to connect to a fronting domain.
|
||||
"domain_fronting_port": 443,
|
||||
// domain_fronting_host is the address (IP or hostname) to use when connecting
|
||||
// to the fronting domain instead of resolving the hostname from the secret via DNS.
|
||||
"domain_fronting_host": "",
|
||||
// domain_fronting_ip is an IP address to use when connecting to the fronting
|
||||
// domain instead of resolving the hostname from the secret via DNS.
|
||||
"domain_fronting_ip": "",
|
||||
// domain_fronting_proxy_protocol is used if communication between upstream
|
||||
// endpoint and sing-box supports proxy protocol.
|
||||
"domain_fronting_proxy_protocol": false,
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,8 @@
|
||||
{
|
||||
"type": "parser",
|
||||
"tag": "vless-out",
|
||||
// Supported protocols: hysteria, hysteria2, shadowsocks, trojan, tuic, vless, vmess, anytls
|
||||
// Supported protocols: hysteria, hysteria2, shadowsocks, trojan, tuic, vless, vmess
|
||||
"link": "vless://b5e41c8c-c437-4689-b863-76208a3efb4b@0.0.0.0:443?..."
|
||||
// Dial Fields
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
@@ -35,4 +34,4 @@
|
||||
"default_domain_resolver": "default",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user