mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-09 03:47:14 +03:00
Compare commits
140 Commits
v1.13.16-e
...
ca0b484d7a
| 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
|
-s dir
|
||||||
--name sing-box-extended
|
--name sing-box
|
||||||
--category net
|
--category net
|
||||||
--license GPL-3.0-or-later
|
--license GPL-3.0-or-later
|
||||||
--description "The universal proxy platform (extended)."
|
--description "The universal proxy platform."
|
||||||
--url "https://sing-box.sagernet.org/"
|
--url "https://sing-box.sagernet.org/"
|
||||||
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
--maintainer "nekohasekai <contact-git@sekai.icu>"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
|
|
||||||
--provides sing-box
|
|
||||||
--conflicts sing-box
|
|
||||||
--replaces sing-box
|
|
||||||
|
|
||||||
--config-files /etc/config/sing-box
|
--config-files /etc/config/sing-box
|
||||||
--config-files /etc/sing-box/config.json
|
--config-files /etc/sing-box/config.json
|
||||||
|
|
||||||
|
|||||||
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)
|
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
|
||||||
|
|
||||||
# Convert version to APK format:
|
# 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"
|
APK_VERSION="${APK_VERSION}-r0"
|
||||||
|
|
||||||
ROOT_DIR=$(mktemp -d)
|
ROOT_DIR=$(mktemp -d)
|
||||||
@@ -75,16 +78,15 @@ done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
|
|||||||
|
|
||||||
# Build APK
|
# Build APK
|
||||||
apk --root "$APK_ROOT_DIR" mkpkg \
|
apk --root "$APK_ROOT_DIR" mkpkg \
|
||||||
--info "name:sing-box-extended" \
|
--info "name:sing-box" \
|
||||||
--info "version:${APK_VERSION}" \
|
--info "version:${APK_VERSION}" \
|
||||||
--info "description:The universal proxy platform (extended)." \
|
--info "description:The universal proxy platform." \
|
||||||
--info "arch:${ARCHITECTURE}" \
|
--info "arch:${ARCHITECTURE}" \
|
||||||
--info "license:GPL-3.0-or-later" \
|
--info "license:GPL-3.0-or-later" \
|
||||||
--info "origin:sing-box-extended" \
|
--info "origin:sing-box" \
|
||||||
--info "url:https://sing-box.sagernet.org/" \
|
--info "url:https://sing-box.sagernet.org/" \
|
||||||
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
|
||||||
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
|
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
|
||||||
--info "provides:sing-box" \
|
|
||||||
--info "provider-priority:100" \
|
--info "provider-priority:100" \
|
||||||
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
|
||||||
--files "$ROOT_DIR" \
|
--files "$ROOT_DIR" \
|
||||||
|
|||||||
57
.github/build_openwrt_packages.sh
vendored
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
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="1.25.12"
|
VERSION="1.25.9"
|
||||||
PATCH_COMMITS=(
|
PATCH_COMMITS=(
|
||||||
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
"afe69d3cec1c6dcf0f1797b20546795730850070"
|
||||||
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"
|
||||||
|
|||||||
14
.github/setup_go_for_windows7.sh
vendored
14
.github/setup_go_for_windows7.sh
vendored
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="1.25.12"
|
VERSION="1.25.9"
|
||||||
PATCH_COMMITS=(
|
PATCH_COMMITS=(
|
||||||
"da4094da73b3b419e3f347594d805e2831f65667"
|
"466f6c7a29bc098b0d4c987b803c779222894a11"
|
||||||
"824aa60e77f06dbae86c20a164c78df722eb7047"
|
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
|
||||||
"a3b6ba31c8cc67b6d899b978bba7b53e95afc46b"
|
"a90777dcf692dd2168577853ba743b4338721b06"
|
||||||
"edfa8de63435a409a59f60731b66ab5940d6d3a4"
|
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
|
||||||
"284f9b24d6284984966a8431e30fdc2583938f96"
|
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
|
||||||
"9864798dee8dd47b55d1d5100d2f1b909a2a6e6c"
|
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
|
||||||
)
|
)
|
||||||
CURL_ARGS=(
|
CURL_ARGS=(
|
||||||
-fL
|
-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
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.25.12
|
go-version: ~1.25.9
|
||||||
- name: Clone cronet-go
|
- name: Clone cronet-go
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
5
.github/workflows/linux.yml
vendored
5
.github/workflows/linux.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.25.12
|
go-version: ~1.25.9
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -72,7 +72,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.25.12
|
go-version: ~1.25.9
|
||||||
- name: Clone cronet-go
|
- name: Clone cronet-go
|
||||||
if: matrix.naive
|
if: matrix.naive
|
||||||
run: |
|
run: |
|
||||||
@@ -194,7 +194,6 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
sudo gem install fpm
|
sudo gem install fpm
|
||||||
echo '%_rpmformat 4' > "$HOME/.rpmmacros"
|
|
||||||
cp .fpm_systemd .fpm
|
cp .fpm_systemd .fpm
|
||||||
fpm -t rpm \
|
fpm -t rpm \
|
||||||
--name "${NAME}" \
|
--name "${NAME}" \
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -23,10 +23,3 @@ AGENTS.md
|
|||||||
/.claude/
|
/.claude/
|
||||||
dist
|
dist
|
||||||
logs
|
logs
|
||||||
/*.so
|
|
||||||
/*.log
|
|
||||||
/*.db-shm
|
|
||||||
/*.db-wal
|
|
||||||
/*.db.backup.*
|
|
||||||
/test_download.bin
|
|
||||||
/wget-log
|
|
||||||
367
.goreleaser.yaml
367
.goreleaser.yaml
@@ -11,7 +11,6 @@ builds:
|
|||||||
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
|
||||||
- -s
|
- -s
|
||||||
- -buildid=
|
- -buildid=
|
||||||
- -checklinkname=0
|
|
||||||
tags:
|
tags:
|
||||||
- with_gvisor
|
- with_gvisor
|
||||||
- with_quic
|
- with_quic
|
||||||
@@ -23,16 +22,8 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
- with_openvpn
|
|
||||||
- with_trusttunnel
|
|
||||||
- with_call
|
|
||||||
- with_sudoku
|
|
||||||
- with_snell
|
|
||||||
- with_manager
|
- with_manager
|
||||||
- with_admin_panel
|
- with_admin_panel
|
||||||
- with_profiler
|
|
||||||
- badlinkname
|
|
||||||
- tfogo_checklinkname0
|
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
- GOTOOLCHAIN=local
|
- GOTOOLCHAIN=local
|
||||||
@@ -63,14 +54,6 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
- with_openvpn
|
|
||||||
- with_trusttunnel
|
|
||||||
- with_call
|
|
||||||
- with_sudoku
|
|
||||||
- with_snell
|
|
||||||
- with_profiler
|
|
||||||
- badlinkname
|
|
||||||
- tfogo_checklinkname0
|
|
||||||
targets:
|
targets:
|
||||||
- linux_mips
|
- linux_mips
|
||||||
- linux_mips_softfloat
|
- linux_mips_softfloat
|
||||||
@@ -111,247 +94,6 @@ builds:
|
|||||||
- android_arm64
|
- android_arm64
|
||||||
- android_386
|
- android_386
|
||||||
- android_amd64
|
- 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
|
- id: compressed
|
||||||
<<: *template
|
<<: *template
|
||||||
targets:
|
targets:
|
||||||
@@ -374,14 +116,6 @@ builds:
|
|||||||
- with_tailscale
|
- with_tailscale
|
||||||
- with_masque
|
- with_masque
|
||||||
- with_mtproxy
|
- with_mtproxy
|
||||||
- with_openvpn
|
|
||||||
- with_trusttunnel
|
|
||||||
- with_call
|
|
||||||
- with_sudoku
|
|
||||||
- with_snell
|
|
||||||
- with_profiler
|
|
||||||
- badlinkname
|
|
||||||
- tfogo_checklinkname0
|
|
||||||
targets:
|
targets:
|
||||||
- linux_mips
|
- linux_mips
|
||||||
- linux_mips_softfloat
|
- linux_mips_softfloat
|
||||||
@@ -420,52 +154,6 @@ builds:
|
|||||||
- android_arm64
|
- android_arm64
|
||||||
- android_386
|
- android_386
|
||||||
- android_amd64
|
- android_amd64
|
||||||
- id: openwrt
|
|
||||||
<<: *template
|
|
||||||
hooks:
|
|
||||||
post:
|
|
||||||
- cmd: bash .github/build_openwrt_packages.sh "{{ .Version }}" "{{ .Target }}" "{{ .Path }}"
|
|
||||||
targets:
|
|
||||||
- linux_amd64_v1
|
|
||||||
- linux_arm64
|
|
||||||
- linux_386
|
|
||||||
- linux_arm_7
|
|
||||||
- linux_arm_6
|
|
||||||
- linux_riscv64
|
|
||||||
- linux_loong64
|
|
||||||
- id: openwrt-mips
|
|
||||||
<<: *template
|
|
||||||
tags:
|
|
||||||
- with_gvisor
|
|
||||||
- with_quic
|
|
||||||
- with_dhcp
|
|
||||||
- with_wireguard
|
|
||||||
- with_utls
|
|
||||||
- with_acme
|
|
||||||
- with_clash_api
|
|
||||||
- with_tailscale
|
|
||||||
- with_masque
|
|
||||||
- with_mtproxy
|
|
||||||
- with_ccm
|
|
||||||
- with_ocm
|
|
||||||
- with_openvpn
|
|
||||||
- with_trusttunnel
|
|
||||||
- with_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:
|
upx:
|
||||||
- enabled: true
|
- enabled: true
|
||||||
ids:
|
ids:
|
||||||
@@ -491,52 +179,11 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- LICENSE
|
- LICENSE
|
||||||
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||||
- id: archive-naive-purego-linux-amd64
|
- id: archive-legacy
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
- naive-purego-linux-amd64
|
- legacy
|
||||||
files:
|
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
|
||||||
- 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'
|
|
||||||
- id: archive-compressed
|
- id: archive-compressed
|
||||||
<<: *template
|
<<: *template
|
||||||
builds:
|
builds:
|
||||||
@@ -562,12 +209,6 @@ release:
|
|||||||
mode: replace
|
mode: replace
|
||||||
ids:
|
ids:
|
||||||
- archive
|
- 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-compressed
|
||||||
- archive-openwrt
|
- package
|
||||||
skip_upload: true
|
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"
|
LABEL maintainer="shtorm-7"
|
||||||
COPY . /go/src/github.com/sagernet/sing-box
|
COPY . /go/src/github.com/sagernet/sing-box
|
||||||
WORKDIR /go/src/github.com/sagernet/sing-box
|
WORKDIR /go/src/github.com/sagernet/sing-box
|
||||||
ARG TARGETOS TARGETARCH
|
ARG TARGETOS TARGETARCH
|
||||||
ARG GOPROXY=""
|
ARG GOPROXY=""
|
||||||
ARG CRONET_GO_PATH=/go/src/github.com/sagernet/sing-box/cronet-go
|
ENV GOPROXY ${GOPROXY}
|
||||||
ENV GOPROXY=${GOPROXY}
|
ENV CGO_ENABLED=0
|
||||||
ENV CGO_ENABLED=1
|
|
||||||
ENV GOOS=$TARGETOS
|
ENV GOOS=$TARGETOS
|
||||||
ENV GOARCH=$TARGETARCH
|
ENV GOARCH=$TARGETARCH
|
||||||
ENV CRONET_GO_PATH=${CRONET_GO_PATH}
|
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& git config --global --add safe.directory /go/src/github.com/sagernet/sing-box \
|
&& apk add git build-base \
|
||||||
&& export GOTOOLCHAIN=local \
|
&& export COMMIT=$(git rev-parse --short HEAD) \
|
||||||
&& export CGO_ENABLED=1 GOOS=$TARGETOS GOARCH=$TARGETARCH \
|
&& export VERSION=$(go run ./cmd/internal/read_tag) \
|
||||||
&& eval "$(CGO_ENABLED=0 GOOS= GOARCH= go run -C "$CRONET_GO_PATH" ./cmd/build-naive --target=$GOOS/$GOARCH --libc=musl env --export)" \
|
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
|
||||||
&& 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) \
|
|
||||||
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
|
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
|
||||||
&& go build -v -trimpath -tags "$TAGS" \
|
&& go build -v -trimpath -tags "$TAGS" \
|
||||||
-o /go/bin/sing-box \
|
-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_IMAGE ?= shtorm7/sing-box-extended
|
||||||
DOCKER_PLATFORMS ?= linux/amd64,linux/arm64
|
DOCKER_PLATFORMS ?= linux/amd64,linux/arm64
|
||||||
|
|
||||||
CRONET_GO_PATH ?= $(shell pwd)/cronet-go
|
|
||||||
|
|
||||||
.PHONY: test release docs build
|
.PHONY: test release docs build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
|
export GOTOOLCHAIN=local && \
|
||||||
go build $(MAIN_PARAMS) $(MAIN)
|
go build $(MAIN_PARAMS) $(MAIN)
|
||||||
|
|
||||||
build_admin_panel:
|
build_admin_panel:
|
||||||
@@ -36,15 +35,6 @@ build_admin_panel:
|
|||||||
go run ./cmd/internal/admin_panel_pack \
|
go run ./cmd/internal/admin_panel_pack \
|
||||||
-dir $(ADMIN_PANEL_DIST)
|
-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:
|
race:
|
||||||
export GOTOOLCHAIN=local && \
|
export GOTOOLCHAIN=local && \
|
||||||
go build -race $(MAIN_PARAMS) $(MAIN)
|
go build -race $(MAIN_PARAMS) $(MAIN)
|
||||||
@@ -88,16 +78,13 @@ proto_install:
|
|||||||
update_certificates:
|
update_certificates:
|
||||||
go run ./cmd/internal/update_certificates
|
go run ./cmd/internal/update_certificates
|
||||||
|
|
||||||
release: build_admin_panel build_naive
|
release: build_admin_panel
|
||||||
CRONET_GO_PATH=$(CRONET_GO_PATH) go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
|
go run ./cmd/internal/build goreleaser release --skip=validate --clean -p 3 --skip publish
|
||||||
mkdir dist/release
|
mkdir dist/release
|
||||||
mv dist/*.tar.gz \
|
mv dist/*.tar.gz \
|
||||||
dist/*.zip \
|
dist/*.zip \
|
||||||
dist/*.ipk \
|
|
||||||
dist/*.apk \
|
|
||||||
dist/release
|
dist/release
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
||||||
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
|
|
||||||
rm -r dist/release
|
rm -r dist/release
|
||||||
|
|
||||||
release_repo:
|
release_repo:
|
||||||
@@ -124,14 +111,11 @@ build_android:
|
|||||||
upload_android:
|
upload_android:
|
||||||
mkdir -p dist/release_android
|
mkdir -p dist/release_android
|
||||||
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
|
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
|
||||||
VERSION_CODE=$$(grep VERSION_CODE ../sing-box-for-android/version.properties | cut -d= -f2); \
|
cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android
|
||||||
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
|
|
||||||
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
||||||
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
|
|
||||||
rm -rf dist/release_android
|
rm -rf dist/release_android
|
||||||
|
|
||||||
release_android: lib_android update_android_version build_android upload_android
|
release_android: lib_android update_android_version build_android
|
||||||
|
|
||||||
publish_android:
|
publish_android:
|
||||||
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -1,26 +1,15 @@
|
|||||||
# sing-box-extended
|
# 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.
|
Sing-box with extended features.
|
||||||
|
|
||||||
## 🔥 Features
|
## 🔥 Features
|
||||||
|
|
||||||
### Protocols
|
### Outbounds
|
||||||
- **WARP** — Cloudflare WARP integration through WireGuard
|
- **WARP** — Cloudflare WARP integration through WireGuard
|
||||||
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
|
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
|
||||||
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
|
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
|
||||||
- **Mieru** — Secure, hard to classify, hard to probe network protocol
|
- **Mieru** — Secure, hard to classify, hard to probe network protocol
|
||||||
- **OpenVPN** — OpenVPN client with tls-auth, tls-crypt and tls-crypt-v2 support
|
- **VPN** — Routed tunnel over any TCP sing-box protocol
|
||||||
- **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
|
|
||||||
- **Bond** — Link aggregation for increasing throughput
|
- **Bond** — Link aggregation for increasing throughput
|
||||||
- **Fallback** — Outbound group with priority-based switching
|
- **Fallback** — Outbound group with priority-based switching
|
||||||
- **Failover** — Automatic outbound switching with session recovery for high availability
|
- **Failover** — Automatic outbound switching with session recovery for high availability
|
||||||
@@ -36,13 +25,12 @@ Sing-box with extended features.
|
|||||||
- **Rate Limiter** — Request rate limiting
|
- **Rate Limiter** — Request rate limiting
|
||||||
|
|
||||||
### Encryption & Obfuscation
|
### Encryption & Obfuscation
|
||||||
- **Amnezia 3.0** — WireGuard traffic obfuscation
|
- **Amnezia 2.0** — WireGuard traffic obfuscation
|
||||||
- **VLESS encryption** — XRAY encryption for VLESS protocol
|
- **VLESS encryption** — XRAY encryption for VLESS protocol
|
||||||
|
|
||||||
### Transports
|
### Transports
|
||||||
- **mKCP** — Reliable UDP-based transport
|
- **mKCP** — Reliable UDP-based transport
|
||||||
- **XHTTP** — Modern XRAY transport
|
- **XHTTP** — Modern XRAY transport
|
||||||
- **rmux** — Improved smux multiplex protocol
|
|
||||||
|
|
||||||
### Services
|
### Services
|
||||||
- **Admin Panel** — Web-based management interface
|
- **Admin Panel** — Web-based management interface
|
||||||
@@ -52,7 +40,7 @@ Sing-box with extended features.
|
|||||||
|
|
||||||
### Miscellaneous
|
### Miscellaneous
|
||||||
- **Providers** — Outbound subscriptions from local files, inline lists, or remote URLs (sing-box JSON, Clash YAML, SIP008, share links)
|
- **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
|
- **Extended WireGuard options** — Advanced configuration capabilities
|
||||||
- **Unified Delay** — Unified latency measurement
|
- **Unified Delay** — Unified latency measurement
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ type OutboundWithPreferredRoutes interface {
|
|||||||
PreferredAddress(address netip.Addr) bool
|
PreferredAddress(address netip.Addr) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutboundWithMultiplex interface {
|
|
||||||
Outbound
|
|
||||||
MultiplexEnabled() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type DirectRouteOutbound interface {
|
type DirectRouteOutbound interface {
|
||||||
Outbound
|
Outbound
|
||||||
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)
|
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 m.defaultOutbound == outbound {
|
||||||
if len(m.outbounds) > 0 {
|
if len(m.outbounds) > 0 {
|
||||||
m.defaultOutbound = m.outbounds[0]
|
m.defaultOutbound = m.outbounds[0]
|
||||||
m.logger.Notice("updated default outbound to ", m.defaultOutbound.Tag())
|
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
|
||||||
} else {
|
} else {
|
||||||
m.defaultOutbound = nil
|
m.defaultOutbound = nil
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
|
|||||||
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
|
||||||
m.defaultOutbound = outbound
|
m.defaultOutbound = outbound
|
||||||
if m.started {
|
if m.started {
|
||||||
m.logger.Notice("updated default outbound to ", outbound.Tag())
|
m.logger.Info("updated default outbound to ", outbound.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ type PlatformInterface interface {
|
|||||||
|
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl(fd int) error
|
AutoDetectInterfaceControl(fd int) error
|
||||||
BindInterfaceControl(fd int, interfaceName string) error
|
|
||||||
|
|
||||||
UsePlatformInterface() bool
|
UsePlatformInterface() bool
|
||||||
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package provider
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -36,11 +34,10 @@ type Adapter struct {
|
|||||||
callbackAccess sync.Mutex
|
callbackAccess sync.Mutex
|
||||||
callbacks list.List[adapter.ProviderUpdateCallback]
|
callbacks list.List[adapter.ProviderUpdateCallback]
|
||||||
|
|
||||||
link string
|
link string
|
||||||
enabled bool
|
enabled bool
|
||||||
removeEmojis bool
|
timeout time.Duration
|
||||||
timeout time.Duration
|
interval time.Duration
|
||||||
interval time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
|
func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.OutboundManager, logFactory log.Factory, logger log.ContextLogger, providerTag string, providerType string, options option.ProviderHealthCheckOptions) Adapter {
|
||||||
@@ -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 {
|
func (a *Adapter) Start() error {
|
||||||
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
|
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
|
||||||
if a.history == nil {
|
if a.history == nil {
|
||||||
@@ -109,10 +102,6 @@ func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
|
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
|
||||||
if a.removeEmojis {
|
|
||||||
removeEmojisFromTags(newOpts)
|
|
||||||
}
|
|
||||||
uniquifyTags(newOpts)
|
|
||||||
a.removeUseless(newOpts)
|
a.removeUseless(newOpts)
|
||||||
var (
|
var (
|
||||||
oldOptByTag = make(map[string]option.Outbound)
|
oldOptByTag = make(map[string]option.Outbound)
|
||||||
@@ -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()
|
s.Close()
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,7 +477,7 @@ func (s *Box) Start() error {
|
|||||||
s.Close()
|
s.Close()
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ func prepareAppStore(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(builds.Data) == 0 {
|
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)
|
buildID := common.Ptr(builds.Data[0].ID)
|
||||||
if version.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")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_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")
|
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
|
||||||
// memcTags = append(memcTags, "with_tailscale")
|
// memcTags = append(memcTags, "with_tailscale")
|
||||||
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")
|
||||||
|
|||||||
@@ -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 {
|
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
||||||
globalSettings := collectBuildSettings(objectsMap)
|
|
||||||
var objectKeyList []string
|
var objectKeyList []string
|
||||||
for objectKey, object := range objectsMap {
|
for objectKey, object := range objectsMap {
|
||||||
buildSettings := object.(map[string]any)["buildSettings"]
|
buildSettings := object.(map[string]any)["buildSettings"]
|
||||||
@@ -117,51 +116,13 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
|
|||||||
if bundleIDObject == nil {
|
if bundleIDObject == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
bundleID := expandBuildVariables(bundleIDObject.(string), globalSettings)
|
if common.Contains(bundleIDList, bundleIDObject.(string)) {
|
||||||
if common.Contains(bundleIDList, bundleID) {
|
|
||||||
objectKeyList = append(objectKeyList, objectKey)
|
objectKeyList = append(objectKeyList, objectKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return objectKeyList
|
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 {
|
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
|
||||||
var objectKeyList []string
|
var objectKeyList []string
|
||||||
for objectKey, object := range objectsMap {
|
for objectKey, object := range objectsMap {
|
||||||
|
|||||||
@@ -179,7 +179,6 @@ func run() error {
|
|||||||
for {
|
for {
|
||||||
osSignal := <-osSignals
|
osSignal := <-osSignals
|
||||||
if osSignal == syscall.SIGHUP {
|
if osSignal == syscall.SIGHUP {
|
||||||
log.Notice("received SIGHUP, reloading...")
|
|
||||||
err = check()
|
err = check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(E.Cause(err, "reload service"))
|
log.Error(E.Cause(err, "reload service"))
|
||||||
|
|||||||
@@ -1,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
|
udpAddr4 string
|
||||||
udpAddr6 string
|
udpAddr6 string
|
||||||
netns string
|
netns string
|
||||||
autoDetectBindFunc control.Func
|
|
||||||
connectionManager adapter.ConnectionManager
|
connectionManager adapter.ConnectionManager
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
networkStrategy *C.NetworkStrategy
|
networkStrategy *C.NetworkStrategy
|
||||||
@@ -61,7 +60,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
networkType []C.InterfaceType
|
networkType []C.InterfaceType
|
||||||
fallbackNetworkType []C.InterfaceType
|
fallbackNetworkType []C.InterfaceType
|
||||||
networkFallbackDelay time.Duration
|
networkFallbackDelay time.Duration
|
||||||
autoDetectBindFunc control.Func
|
|
||||||
)
|
)
|
||||||
if networkManager != nil {
|
if networkManager != nil {
|
||||||
interfaceFinder = networkManager.InterfaceFinder()
|
interfaceFinder = networkManager.InterfaceFinder()
|
||||||
@@ -72,20 +70,9 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
||||||
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
||||||
}
|
}
|
||||||
if platformInterface != nil && platformInterface.UsePlatformAutoDetectInterfaceControl() {
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
interfaceName := options.BindInterface
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
bindFunc := func(network, address string, conn syscall.RawConn) error {
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
if options.RoutingMark > 0 {
|
||||||
if !C.IsLinux {
|
if !C.IsLinux {
|
||||||
@@ -132,7 +119,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
bindFunc := networkManager.AutoDetectInterfaceFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
autoDetectBindFunc = bindFunc
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
|
||||||
@@ -227,7 +213,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
udpAddr4: udpAddr4,
|
udpAddr4: udpAddr4,
|
||||||
udpAddr6: udpAddr6,
|
udpAddr6: udpAddr6,
|
||||||
netns: options.NetNs,
|
netns: options.NetNs,
|
||||||
autoDetectBindFunc: autoDetectBindFunc,
|
|
||||||
connectionManager: connectionManager,
|
connectionManager: connectionManager,
|
||||||
networkManager: networkManager,
|
networkManager: networkManager,
|
||||||
networkStrategy: networkStrategy,
|
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) {
|
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
|
||||||
if d.networkStrategy == nil {
|
if d.networkStrategy == nil {
|
||||||
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
|
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() {
|
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() {
|
} 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 {
|
} else {
|
||||||
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
} else {
|
} 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) {
|
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
|
||||||
interfaces := networkManager.NetworkInterfaces()
|
interfaces := networkManager.NetworkInterfaces()
|
||||||
myInterfaces := networkManager.InterfaceMonitor().MyInterfaces()
|
myInterface := networkManager.InterfaceMonitor().MyInterface()
|
||||||
if len(myInterfaces) > 0 {
|
if myInterface != "" {
|
||||||
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
|
||||||
return !common.Contains(myInterfaces, it.Name)
|
return it.Name != myInterface
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
switch strategy {
|
switch strategy {
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ type Conn struct {
|
|||||||
|
|
||||||
func (c *Conn) Close() error {
|
func (c *Conn) Close() error {
|
||||||
c.group.access.Lock()
|
c.group.access.Lock()
|
||||||
|
defer c.group.access.Unlock()
|
||||||
c.group.connections.Remove(c.element)
|
c.group.connections.Remove(c.element)
|
||||||
c.group.access.Unlock()
|
|
||||||
return c.Conn.Close()
|
return c.Conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,8 +58,8 @@ type PacketConn struct {
|
|||||||
|
|
||||||
func (c *PacketConn) Close() error {
|
func (c *PacketConn) Close() error {
|
||||||
c.group.access.Lock()
|
c.group.access.Lock()
|
||||||
|
defer c.group.access.Unlock()
|
||||||
c.group.connections.Remove(c.element)
|
c.group.connections.Remove(c.element)
|
||||||
c.group.access.Unlock()
|
|
||||||
return c.PacketConn.Close()
|
return c.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +87,8 @@ type SingPacketConn struct {
|
|||||||
|
|
||||||
func (c *SingPacketConn) Close() error {
|
func (c *SingPacketConn) Close() error {
|
||||||
c.group.access.Lock()
|
c.group.access.Lock()
|
||||||
|
defer c.group.access.Unlock()
|
||||||
c.group.connections.Remove(c.element)
|
c.group.connections.Remove(c.element)
|
||||||
c.group.access.Unlock()
|
|
||||||
return c.PacketConn.Close()
|
return c.PacketConn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,19 +47,15 @@ func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool, isProvider
|
|||||||
|
|
||||||
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
func (g *Group) Interrupt(interruptExternalConnections bool) {
|
||||||
g.access.Lock()
|
g.access.Lock()
|
||||||
|
defer g.access.Unlock()
|
||||||
var toDelete []*list.Element[*groupConnItem]
|
var toDelete []*list.Element[*groupConnItem]
|
||||||
var toClose []io.Closer
|
|
||||||
for element := g.connections.Front(); element != nil; element = element.Next() {
|
for element := g.connections.Front(); element != nil; element = element.Next() {
|
||||||
if !element.Value.isExternal || interruptExternalConnections {
|
if !element.Value.isExternal || interruptExternalConnections {
|
||||||
|
element.Value.conn.Close()
|
||||||
toDelete = append(toDelete, element)
|
toDelete = append(toDelete, element)
|
||||||
toClose = append(toClose, element.Value.conn)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, element := range toDelete {
|
for _, element := range toDelete {
|
||||||
g.connections.Remove(element)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
l.logger.Notice("tcp server started at ", tcpListener.Addr())
|
l.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||||
l.tcpListener = tcpListener
|
l.tcpListener = tcpListener
|
||||||
return tcpListener, err
|
return tcpListener, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
}
|
}
|
||||||
l.udpConn = udpConn.(*net.UDPConn)
|
l.udpConn = udpConn.(*net.UDPConn)
|
||||||
l.udpAddr = bindAddr
|
l.udpAddr = bindAddr
|
||||||
l.logger.Notice("udp server started at ", udpConn.LocalAddr())
|
l.logger.Info("udp server started at ", udpConn.LocalAddr())
|
||||||
return udpConn, err
|
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-box/adapter"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Searcher = (*androidSearcher)(nil)
|
var _ Searcher = (*androidSearcher)(nil)
|
||||||
@@ -17,9 +16,6 @@ type androidSearcher struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSearcher(config Config) (Searcher, error) {
|
func NewSearcher(config Config) (Searcher, error) {
|
||||||
if config.PackageManager == nil {
|
|
||||||
return nil, E.New("missing package manager")
|
|
||||||
}
|
|
||||||
return &androidSearcher{config.PackageManager}, nil
|
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()))
|
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||||
|
|
||||||
hello.SessionId[0] = 26
|
hello.SessionId[0] = 1
|
||||||
hello.SessionId[1] = 7
|
hello.SessionId[1] = 8
|
||||||
hello.SessionId[2] = 11
|
hello.SessionId[2] = 1
|
||||||
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
|
||||||
copy(hello.SessionId[8:], e.shortID[:])
|
copy(hello.SessionId[8:], e.shortID[:])
|
||||||
if debug.Enabled {
|
if debug.Enabled {
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
|
|||||||
if len(certificate) > 0 {
|
if len(certificate) > 0 {
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
if !certPool.AppendCertsFromPEM(certificate) {
|
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
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,14 +164,14 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
config.Certificates = []tls.Certificate{keyPair}
|
config.Certificates = []tls.Certificate{keyPair}
|
||||||
c.config = config
|
c.config = config
|
||||||
c.access.Unlock()
|
c.access.Unlock()
|
||||||
c.logger.Notice("reloaded TLS certificate")
|
c.logger.Info("reloaded TLS certificate")
|
||||||
} else if common.Contains(c.clientCertificatePath, path) {
|
} else if common.Contains(c.clientCertificatePath, path) {
|
||||||
clientCertificateCA := x509.NewCertPool()
|
clientCertificateCA := x509.NewCertPool()
|
||||||
var reloaded bool
|
var reloaded bool
|
||||||
for _, certPath := range c.clientCertificatePath {
|
for _, certPath := range c.clientCertificatePath {
|
||||||
content, err := os.ReadFile(certPath)
|
content, err := os.ReadFile(certPath)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
if !clientCertificateCA.AppendCertsFromPEM(content) {
|
if !clientCertificateCA.AppendCertsFromPEM(content) {
|
||||||
@@ -188,7 +188,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
config.ClientCAs = clientCertificateCA
|
config.ClientCAs = clientCertificateCA
|
||||||
c.config = config
|
c.config = config
|
||||||
c.access.Unlock()
|
c.access.Unlock()
|
||||||
c.logger.Notice("reloaded client certificates")
|
c.logger.Info("reloaded client certificates")
|
||||||
} else if path == c.echKeyPath {
|
} else if path == c.echKeyPath {
|
||||||
echKey, err := os.ReadFile(c.echKeyPath)
|
echKey, err := os.ReadFile(c.echKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -198,7 +198,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.logger.Notice("reloaded ECH keys")
|
c.logger.Info("reloaded ECH keys")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
|
|||||||
if len(certificate) > 0 {
|
if len(certificate) > 0 {
|
||||||
certPool := x509.NewCertPool()
|
certPool := x509.NewCertPool()
|
||||||
if !certPool.AppendCertsFromPEM(certificate) {
|
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
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -73,18 +72,7 @@ func (s *HistoryStorage) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func URLTest(ctx context.Context, link string, detour N.Dialer) (uint16, error) {
|
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err 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) {
|
|
||||||
if link == "" {
|
if link == "" {
|
||||||
link = "https://www.gstatic.com/generate_204"
|
link = "https://www.gstatic.com/generate_204"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
Xbadoption "github.com/sagernet/sing-box/common/xray/json/badoption"
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
"github.com/sagernet/sing/common/json/badoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -68,8 +69,8 @@ func DecodeBase64URLSafe(content string) (string, error) {
|
|||||||
return string(result), nil
|
return string(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseXHTTPRange(value string) (badoption.Range[int], error) {
|
func ParseXHTTPRange(value string) (Xbadoption.Range, error) {
|
||||||
result := badoption.Range[int]{}
|
result := Xbadoption.Range{}
|
||||||
encoded, err := json.Marshal(value)
|
encoded, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
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"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
TypeTrustTunnel = "trusttunnel"
|
|
||||||
TypeNaive = "naive"
|
TypeNaive = "naive"
|
||||||
TypeWireGuard = "wireguard"
|
TypeWireGuard = "wireguard"
|
||||||
TypeWARP = "warp"
|
TypeWARP = "warp"
|
||||||
TypeMASQUE = "masque"
|
TypeMASQUE = "masque"
|
||||||
TypeOpenVPN = "openvpn"
|
|
||||||
TypeMTProxy = "mtproxy"
|
TypeMTProxy = "mtproxy"
|
||||||
TypeParser = "parser"
|
TypeParser = "parser"
|
||||||
TypeHysteria = "hysteria"
|
TypeHysteria = "hysteria"
|
||||||
@@ -27,9 +25,6 @@ const (
|
|||||||
TypeShadowTLS = "shadowtls"
|
TypeShadowTLS = "shadowtls"
|
||||||
TypeMieru = "mieru"
|
TypeMieru = "mieru"
|
||||||
TypeAnyTLS = "anytls"
|
TypeAnyTLS = "anytls"
|
||||||
TypeSudoku = "sudoku"
|
|
||||||
TypeSnell = "snell"
|
|
||||||
TypeCall = "call"
|
|
||||||
TypeShadowsocksR = "shadowsocksr"
|
TypeShadowsocksR = "shadowsocksr"
|
||||||
TypeVLESS = "vless"
|
TypeVLESS = "vless"
|
||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
@@ -43,7 +38,6 @@ const (
|
|||||||
TypeBandwidthLimiter = "bandwidth-limiter"
|
TypeBandwidthLimiter = "bandwidth-limiter"
|
||||||
TypeTrafficLimiter = "traffic-limiter"
|
TypeTrafficLimiter = "traffic-limiter"
|
||||||
TypeRateLimiter = "rate-limiter"
|
TypeRateLimiter = "rate-limiter"
|
||||||
TypeFairQueue = "fair-queue"
|
|
||||||
TypeAdminPanel = "admin-panel"
|
TypeAdminPanel = "admin-panel"
|
||||||
TypeManagerAPI = "manager-api"
|
TypeManagerAPI = "manager-api"
|
||||||
TypeNodeManagerAPI = "node-manager-api"
|
TypeNodeManagerAPI = "node-manager-api"
|
||||||
@@ -90,8 +84,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "VMess"
|
return "VMess"
|
||||||
case TypeTrojan:
|
case TypeTrojan:
|
||||||
return "Trojan"
|
return "Trojan"
|
||||||
case TypeTrustTunnel:
|
|
||||||
return "TrustTunnel"
|
|
||||||
case TypeNaive:
|
case TypeNaive:
|
||||||
return "Naive"
|
return "Naive"
|
||||||
case TypeWireGuard:
|
case TypeWireGuard:
|
||||||
@@ -100,8 +92,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "WARP"
|
return "WARP"
|
||||||
case TypeMASQUE:
|
case TypeMASQUE:
|
||||||
return "MASQUE"
|
return "MASQUE"
|
||||||
case TypeOpenVPN:
|
|
||||||
return "OpenVPN"
|
|
||||||
case TypeMTProxy:
|
case TypeMTProxy:
|
||||||
return "MTProxy"
|
return "MTProxy"
|
||||||
case TypeParser:
|
case TypeParser:
|
||||||
@@ -130,12 +120,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Mieru"
|
return "Mieru"
|
||||||
case TypeAnyTLS:
|
case TypeAnyTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
case TypeSudoku:
|
|
||||||
return "Sudoku"
|
|
||||||
case TypeSnell:
|
|
||||||
return "Snell"
|
|
||||||
case TypeCall:
|
|
||||||
return "Call"
|
|
||||||
case TypeFallback:
|
case TypeFallback:
|
||||||
return "Fallback"
|
return "Fallback"
|
||||||
case TypeTailscale:
|
case TypeTailscale:
|
||||||
@@ -152,8 +136,6 @@ func ProxyDisplayName(proxyType string) string {
|
|||||||
return "Traffic Limiter"
|
return "Traffic Limiter"
|
||||||
case TypeRateLimiter:
|
case TypeRateLimiter:
|
||||||
return "Rate Limiter"
|
return "Rate Limiter"
|
||||||
case TypeFairQueue:
|
|
||||||
return "Fair Queue"
|
|
||||||
case TypeVPNClient:
|
case TypeVPNClient:
|
||||||
return "VPN Client"
|
return "VPN Client"
|
||||||
case TypeVPNServer:
|
case TypeVPNServer:
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const (
|
|||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
UDPTimeout = 5 * time.Minute
|
UDPTimeout = 5 * time.Minute
|
||||||
ICMPTimeout = 10 * time.Second
|
|
||||||
DefaultURLTestInterval = 3 * time.Minute
|
DefaultURLTestInterval = 3 * time.Minute
|
||||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||||
StartTimeout = 10 * time.Second
|
StartTimeout = 10 * time.Second
|
||||||
|
|||||||
@@ -20,14 +20,13 @@ const (
|
|||||||
type LogLevel int32
|
type LogLevel int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LogLevel_PANIC LogLevel = 0
|
LogLevel_PANIC LogLevel = 0
|
||||||
LogLevel_FATAL LogLevel = 1
|
LogLevel_FATAL LogLevel = 1
|
||||||
LogLevel_ERROR LogLevel = 2
|
LogLevel_ERROR LogLevel = 2
|
||||||
LogLevel_WARN LogLevel = 3
|
LogLevel_WARN LogLevel = 3
|
||||||
LogLevel_NOTICE LogLevel = 4
|
LogLevel_INFO LogLevel = 4
|
||||||
LogLevel_INFO LogLevel = 5
|
LogLevel_DEBUG LogLevel = 5
|
||||||
LogLevel_DEBUG LogLevel = 6
|
LogLevel_TRACE LogLevel = 6
|
||||||
LogLevel_TRACE LogLevel = 7
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for LogLevel.
|
// Enum value maps for LogLevel.
|
||||||
@@ -37,20 +36,18 @@ var (
|
|||||||
1: "FATAL",
|
1: "FATAL",
|
||||||
2: "ERROR",
|
2: "ERROR",
|
||||||
3: "WARN",
|
3: "WARN",
|
||||||
4: "NOTICE",
|
4: "INFO",
|
||||||
5: "INFO",
|
5: "DEBUG",
|
||||||
6: "DEBUG",
|
6: "TRACE",
|
||||||
7: "TRACE",
|
|
||||||
}
|
}
|
||||||
LogLevel_value = map[string]int32{
|
LogLevel_value = map[string]int32{
|
||||||
"PANIC": 0,
|
"PANIC": 0,
|
||||||
"FATAL": 1,
|
"FATAL": 1,
|
||||||
"ERROR": 2,
|
"ERROR": 2,
|
||||||
"WARN": 3,
|
"WARN": 3,
|
||||||
"NOTICE": 4,
|
"INFO": 4,
|
||||||
"INFO": 5,
|
"DEBUG": 5,
|
||||||
"DEBUG": 6,
|
"TRACE": 6,
|
||||||
"TRACE": 7,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -59,10 +59,9 @@ enum LogLevel {
|
|||||||
FATAL = 1;
|
FATAL = 1;
|
||||||
ERROR = 2;
|
ERROR = 2;
|
||||||
WARN = 3;
|
WARN = 3;
|
||||||
NOTICE = 4;
|
INFO = 4;
|
||||||
INFO = 5;
|
DEBUG = 5;
|
||||||
DEBUG = 6;
|
TRACE = 6;
|
||||||
TRACE = 7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Log {
|
message Log {
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ type Client struct {
|
|||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
cache freelru.Cache[dns.Question, *dns.Msg]
|
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]
|
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
|
||||||
transportCacheLock compatible.Map[transportCacheKey, chan struct{}]
|
transportCacheLock compatible.Map[dns.Question, chan struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
@@ -106,23 +106,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
|
|||||||
return 0, false
|
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) {
|
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 len(message.Question) == 0 {
|
||||||
if c.logger != nil {
|
if c.logger != nil {
|
||||||
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
|
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) &&
|
len(message.Extra[0].(*dns.OPT).Option) == 0) &&
|
||||||
!options.ClientSubnet.IsValid()
|
!options.ClientSubnet.IsValid()
|
||||||
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
|
||||||
ctx = contextWithTransportStack(ctx, append(transportStack, transport.Tag()))
|
|
||||||
if !disableCache {
|
if !disableCache {
|
||||||
cacheKey := transportCacheKey{Question: question, transportTag: transport.Tag()}
|
|
||||||
if c.cache != nil {
|
if c.cache != nil {
|
||||||
cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{}))
|
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
|
||||||
if loaded {
|
if loaded {
|
||||||
select {
|
select {
|
||||||
case <-cond:
|
case <-cond:
|
||||||
@@ -166,12 +148,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
c.cacheLock.Delete(cacheKey)
|
c.cacheLock.Delete(question)
|
||||||
close(cond)
|
close(cond)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
} else if c.transportCache != nil {
|
} 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 {
|
if loaded {
|
||||||
select {
|
select {
|
||||||
case <-cond:
|
case <-cond:
|
||||||
@@ -180,7 +162,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defer func() {
|
defer func() {
|
||||||
c.transportCacheLock.Delete(cacheKey)
|
c.transportCacheLock.Delete(question)
|
||||||
close(cond)
|
close(cond)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -194,6 +176,11 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
}
|
}
|
||||||
|
|
||||||
messageId := message.Id
|
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 {
|
if !disableCache && responseChecker != nil && c.rdrc != nil {
|
||||||
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
|
||||||
if rejected {
|
if rejected {
|
||||||
@@ -210,8 +197,6 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
} else {
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
stripDNSPadding(response)
|
|
||||||
}
|
}
|
||||||
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
|
||||||
validResponse := response
|
validResponse := response
|
||||||
@@ -561,36 +546,15 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
|
|||||||
return addresses
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
type transportStackKey struct{}
|
type transportKey struct{}
|
||||||
|
|
||||||
func contextWithTransportStack(ctx context.Context, stack []string) context.Context {
|
func contextWithTransportTag(ctx context.Context, transportTag string) context.Context {
|
||||||
return context.WithValue(ctx, transportStackKey{}, stack)
|
return context.WithValue(ctx, transportKey{}, transportTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func transportStackFromContext(ctx context.Context) []string {
|
func transportTagFromContext(ctx context.Context) (string, bool) {
|
||||||
value, _ := ctx.Value(transportStackKey{}).([]string)
|
value, loaded := ctx.Value(transportKey{}).(string)
|
||||||
return value
|
return value, loaded
|
||||||
}
|
|
||||||
|
|
||||||
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 FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ func (t *Transport) Close() error {
|
|||||||
func (t *Transport) Reset() {
|
func (t *Transport) Reset() {
|
||||||
t.transportLock.Lock()
|
t.transportLock.Lock()
|
||||||
t.updatedAt = time.Time{}
|
t.updatedAt = time.Time{}
|
||||||
t.lastError = nil
|
|
||||||
t.servers = nil
|
t.servers = nil
|
||||||
t.transportLock.Unlock()
|
t.transportLock.Unlock()
|
||||||
}
|
}
|
||||||
@@ -187,7 +186,7 @@ func (t *Transport) updateServers() error {
|
|||||||
return E.Cause(err, "dhcp: prepare interface")
|
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)
|
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
|
||||||
err = t.fetchServers0(fetchCtx, iface)
|
err = t.fetchServers0(fetchCtx, iface)
|
||||||
cancel()
|
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 {
|
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
|
||||||
searchList := dhcpPacket.DomainSearch()
|
searchList := dhcpPacket.DomainSearch()
|
||||||
if searchList != nil && len(searchList.Labels) > 0 {
|
if searchList != nil && len(searchList.Labels) > 0 {
|
||||||
t.search = common.Filter(common.Map(searchList.Labels, mDNS.Fqdn), func(it string) bool {
|
t.search = searchList.Labels
|
||||||
return it != "."
|
|
||||||
})
|
|
||||||
} else if dhcpPacket.DomainName() != "" {
|
} else if dhcpPacket.DomainName() != "" {
|
||||||
domainName := mDNS.Fqdn(dhcpPacket.DomainName())
|
t.search = []string{dhcpPacket.DomainName()}
|
||||||
if domainName != "." {
|
|
||||||
t.search = []string{domainName}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
|
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
|
||||||
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
|
||||||
})
|
})
|
||||||
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
|
||||||
t.logger.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
|
t.servers = serverAddrs
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -120,16 +120,13 @@ func NewHTTPSRaw(
|
|||||||
serverAddr M.Socksaddr,
|
serverAddr M.Socksaddr,
|
||||||
tlsConfig tls.Config,
|
tlsConfig tls.Config,
|
||||||
) *HTTPSTransport {
|
) *HTTPSTransport {
|
||||||
if tlsConfig != nil {
|
|
||||||
dialer = tls.NewDialer(dialer, tlsConfig)
|
|
||||||
}
|
|
||||||
return &HTTPSTransport{
|
return &HTTPSTransport{
|
||||||
TransportAdapter: adapter,
|
TransportAdapter: adapter,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: dialer,
|
dialer: dialer,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
transport: NewHTTPSTransportWrapper(dialer, serverAddr, destination),
|
transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/tls"
|
"github.com/sagernet/sing-box/common/tls"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
@@ -24,36 +22,27 @@ type HTTPSTransportWrapper struct {
|
|||||||
fallback *atomic.Bool
|
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
|
var fallback atomic.Bool
|
||||||
if destination.Scheme == "http" {
|
|
||||||
// plain HTTP DoH used by Tailscale
|
|
||||||
fallback.Store(true)
|
|
||||||
}
|
|
||||||
return &HTTPSTransportWrapper{
|
return &HTTPSTransportWrapper{
|
||||||
http2Transport: &http2.Transport{
|
http2Transport: &http2.Transport{
|
||||||
DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn {
|
state := tlsConn.ConnectionState()
|
||||||
state := tlsConn.ConnectionState()
|
if state.NegotiatedProtocol == http2.NextProtoTLS {
|
||||||
if state.NegotiatedProtocol != http2.NextProtoTLS {
|
return tlsConn, nil
|
||||||
tlsConn.Close()
|
|
||||||
fallback.Store(true)
|
|
||||||
return nil, errFallback
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return resultConn, nil
|
tlsConn.Close()
|
||||||
|
fallback.Store(true)
|
||||||
|
return nil, errFallback
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
httpTransport: &http.Transport{
|
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) {
|
DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||||
return dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
|
return dialer.DialTLSContext(ctx, serverAddr)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fallback: &fallback,
|
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) {
|
func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {
|
||||||
if h.fallback.Load() {
|
if h.fallback.Load() {
|
||||||
return h.httpTransport.RoundTrip(request)
|
return h.httpTransport.RoundTrip(request)
|
||||||
}
|
} else {
|
||||||
response, err := h.http2Transport.RoundTrip(request)
|
response, err := h.http2Transport.RoundTrip(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errFallback) {
|
if errors.Is(err, errFallback) {
|
||||||
return h.httpTransport.RoundTrip(request)
|
return h.httpTransport.RoundTrip(request)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, err
|
return response, nil
|
||||||
}
|
}
|
||||||
return response, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPSTransportWrapper) CloseIdleConnections() {
|
func (h *HTTPSTransportWrapper) CloseIdleConnections() {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
@@ -78,12 +77,12 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
|
|||||||
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
|
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var myInterfaces []string
|
var myInterface string
|
||||||
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
|
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
|
||||||
myInterfaces = networkManager.InterfaceMonitor().MyInterfaces()
|
myInterface = networkManager.InterfaceMonitor().MyInterface()
|
||||||
}
|
}
|
||||||
for _, address := range dnsAddresses {
|
for _, address := range dnsAddresses {
|
||||||
if common.Contains(myInterfaces, address.ifName) {
|
if address.ifName == myInterface {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
|
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))
|
||||||
|
|||||||
@@ -126,12 +126,6 @@ func (t *HTTP3Transport) newTransport() *http3.Transport {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, dialErr
|
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
|
return quicConn, nil
|
||||||
},
|
},
|
||||||
TLSClientConfig: t.tlsConfig,
|
TLSClientConfig: t.tlsConfig,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/quic-go"
|
"github.com/sagernet/quic-go"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -118,12 +117,6 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
|
|||||||
rawConn.Close()
|
rawConn.Close()
|
||||||
return nil, E.Cause(err, "establish QUIC connection")
|
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
|
return earlyConnection, nil
|
||||||
})
|
})
|
||||||
if err != 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")
|
return nil, E.Cause(err, "open stream")
|
||||||
}
|
}
|
||||||
defer stream.CancelRead(0)
|
defer stream.CancelRead(0)
|
||||||
stopWatch := context.AfterFunc(ctx, func() {
|
|
||||||
stream.CancelRead(0)
|
|
||||||
_ = stream.SetWriteDeadline(time.Now())
|
|
||||||
})
|
|
||||||
defer stopWatch()
|
|
||||||
err = transport.WriteMessage(stream, 0, message)
|
err = transport.WriteMessage(stream, 0, message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stream.Close()
|
stream.Close()
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ func (m *TransportManager) Remove(tag string) error {
|
|||||||
return E.New("default server cannot be fakeip")
|
return E.New("default server cannot be fakeip")
|
||||||
}
|
}
|
||||||
m.defaultTransport = nextTransport
|
m.defaultTransport = nextTransport
|
||||||
m.logger.Notice("updated default server to ", m.defaultTransport.Tag())
|
m.logger.Info("updated default server to ", m.defaultTransport.Tag())
|
||||||
} else {
|
} else {
|
||||||
m.defaultTransport = nil
|
m.defaultTransport = nil
|
||||||
}
|
}
|
||||||
@@ -287,7 +287,7 @@ func (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger,
|
|||||||
}
|
}
|
||||||
m.defaultTransport = transport
|
m.defaultTransport = transport
|
||||||
if m.started {
|
if m.started {
|
||||||
m.logger.Notice("updated default server to ", transport.Tag())
|
m.logger.Info("updated default server to ", transport.Tag())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if transport.Type() == C.DNSTypeFakeIP {
|
if transport.Type() == C.DNSTypeFakeIP {
|
||||||
|
|||||||
@@ -2,31 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
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
|
#### 1.13.12
|
||||||
|
|
||||||
* Update naiveproxy to v148.0.7778.96-1
|
* Update naiveproxy to v148.0.7778.96-1
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||||
| `vless` | [VLESS](./vless/) | TCP |
|
| `vless` | [VLESS](./vless/) | TCP |
|
||||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||||
| `mieru` | [Mieru](./mieru/) | :material-close: |
|
|
||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
|
|||||||
@@ -31,7 +31,6 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
|
||||||
| `vless` | [VLESS](./vless/) | TCP |
|
| `vless` | [VLESS](./vless/) | TCP |
|
||||||
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
| `anytls` | [AnyTLS](./anytls/) | TCP |
|
||||||
| `mieru` | [Mieru](./mieru/) | :material-close: |
|
|
||||||
| `tun` | [Tun](./tun/) | :material-close: |
|
| `tun` | [Tun](./tun/) | :material-close: |
|
||||||
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
| `redirect` | [Redirect](./redirect/) | :material-close: |
|
||||||
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
| `tproxy` | [TProxy](./tproxy/) | :material-close: |
|
||||||
|
|||||||
@@ -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_check_interval": "30s",
|
||||||
"idle_session_timeout": "30s",
|
"idle_session_timeout": "30s",
|
||||||
"min_idle_session": 5,
|
"min_idle_session": 5,
|
||||||
"client_metadata": "",
|
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // Dial Fields
|
... // 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
|
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
|
#### tls
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ icon: material/new-box
|
|||||||
"idle_session_check_interval": "30s",
|
"idle_session_check_interval": "30s",
|
||||||
"idle_session_timeout": "30s",
|
"idle_session_timeout": "30s",
|
||||||
"min_idle_session": 5,
|
"min_idle_session": 5,
|
||||||
"client_metadata": "",
|
|
||||||
"tls": {},
|
"tls": {},
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
@@ -56,12 +55,6 @@ AnyTLS 密码。
|
|||||||
|
|
||||||
在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0
|
在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0
|
||||||
|
|
||||||
#### client_metadata
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.13.16 起"
|
|
||||||
|
|
||||||
参阅 [AnyTLS 客户端元数据](/zh/manual/misc/anytls-client-metadata/)。
|
|
||||||
|
|
||||||
#### tls
|
#### tls
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
| `mieru` | [Mieru](./mieru/) |
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `anytls` | [AnyTLS](./anytls/) |
|
| `anytls` | [AnyTLS](./anytls/) |
|
||||||
| `mieru` | [Mieru](./mieru/) |
|
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
| `hysteria2` | [Hysteria2](./hysteria2/) |
|
||||||
| `mieru` | [Mieru](./mieru/) |
|
| `mieru` | [Mieru](./mieru/) |
|
||||||
| `anytls` | [AnyTLS](./anytls/) |
|
| `anytls` | [AnyTLS](./anytls/) |
|
||||||
| `mieru` | [Mieru](./mieru/) |
|
|
||||||
| `tor` | [Tor](./tor/) |
|
| `tor` | [Tor](./tor/) |
|
||||||
| `ssh` | [SSH](./ssh/) |
|
| `ssh` | [SSH](./ssh/) |
|
||||||
| `dns` | [DNS](./dns/) |
|
| `dns` | [DNS](./dns/) |
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ icon: material/new-box
|
|||||||
"username": "asdf",
|
"username": "asdf",
|
||||||
"password": "hjkl",
|
"password": "hjkl",
|
||||||
"multiplexing": "MULTIPLEXING_LOW",
|
"multiplexing": "MULTIPLEXING_LOW",
|
||||||
"traffic_pattern": "GgQIARAK",
|
|
||||||
|
|
||||||
... // Dial Fields
|
... // Dial Fields
|
||||||
}
|
}
|
||||||
@@ -49,7 +48,7 @@ Must set at least one field between `server_port` and `server_ports`.
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
Transmission protocol. Allowed values are `TCP` and `UDP`.
|
Transmission protocol. The only allowed value is `TCP`.
|
||||||
|
|
||||||
#### username
|
#### username
|
||||||
|
|
||||||
@@ -67,10 +66,6 @@ mieru password.
|
|||||||
|
|
||||||
Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing.
|
Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing.
|
||||||
|
|
||||||
#### traffic_pattern
|
|
||||||
|
|
||||||
A base64 string to fine tune network behavior.
|
|
||||||
|
|
||||||
### Dial Fields
|
### Dial Fields
|
||||||
|
|
||||||
See [Dial Fields](/configuration/shared/dial/) for details.
|
See [Dial Fields](/configuration/shared/dial/) for details.
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ icon: material/new-box
|
|||||||
"username": "asdf",
|
"username": "asdf",
|
||||||
"password": "hjkl",
|
"password": "hjkl",
|
||||||
"multiplexing": "MULTIPLEXING_LOW",
|
"multiplexing": "MULTIPLEXING_LOW",
|
||||||
"traffic_pattern": "GgQIARAK",
|
|
||||||
|
|
||||||
... // 拨号字段
|
... // 拨号字段
|
||||||
}
|
}
|
||||||
@@ -49,7 +48,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
通信协议。可设为 `TCP` 或 `UDP`。
|
通信协议。仅可设为 `TCP`。
|
||||||
|
|
||||||
#### username
|
#### username
|
||||||
|
|
||||||
@@ -67,10 +66,6 @@ mieru 密码。
|
|||||||
|
|
||||||
多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
|
多路复用设置。可以设为 `MULTIPLEXING_OFF`,`MULTIPLEXING_LOW`,`MULTIPLEXING_MIDDLE`,`MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
|
||||||
|
|
||||||
#### traffic_pattern
|
|
||||||
|
|
||||||
一个 base64 字符串用于微调网络行为。
|
|
||||||
|
|
||||||
### 拨号字段
|
### 拨号字段
|
||||||
|
|
||||||
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
参阅 [拨号字段](/zh/configuration/shared/dial/)。
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ Multiplex protocol.
|
|||||||
| smux | https://github.com/xtaci/smux |
|
| smux | https://github.com/xtaci/smux |
|
||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
| h2mux | https://golang.org/x/net/http2 |
|
| h2mux | https://golang.org/x/net/http2 |
|
||||||
| rmux | https://github.com/shtorm-7/rmux |
|
|
||||||
|
|
||||||
h2mux is used by default.
|
h2mux is used by default.
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,6 @@
|
|||||||
| smux | https://github.com/xtaci/smux |
|
| smux | https://github.com/xtaci/smux |
|
||||||
| yamux | https://github.com/hashicorp/yamux |
|
| yamux | https://github.com/hashicorp/yamux |
|
||||||
| h2mux | https://golang.org/x/net/http2 |
|
| h2mux | https://golang.org/x/net/http2 |
|
||||||
| rmux | https://github.com/shtorm-7/rmux |
|
|
||||||
|
|
||||||
默认使用 h2mux。
|
默认使用 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",
|
"tag": "my-manager",
|
||||||
"database": {
|
"database": {
|
||||||
"driver": "sqlite",
|
"driver": "sqlite",
|
||||||
"dsn": "file:manager.db?_pragma=foreign_keys(on)&_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)&_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,
|
"listen_port": 7000,
|
||||||
"manager": "my-manager",
|
"manager": "my-manager",
|
||||||
"api_key": "change-me-secret",
|
"api_key": "change-me-secret",
|
||||||
"keep_alive": "10s",
|
|
||||||
"keep_alive_timeout": "5s",
|
|
||||||
// Enable TLS for production deployments (the node connects via gRPC over h2):
|
// Enable TLS for production deployments (the node connects via gRPC over h2):
|
||||||
// "tls": { // https://sing-box.sagernet.org/configuration/shared/tls/#inbound
|
// "tls": { // https://sing-box.sagernet.org/configuration/shared/tls/#inbound
|
||||||
// "enabled": true,
|
// "enabled": true,
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
"action": "hijack-dns"
|
"action": "hijack-dns"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"final": "traffic-limiter"
|
"final": "connection-limiter"
|
||||||
},
|
},
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
"address": "example.com",
|
"address": "example.com",
|
||||||
"port": 10001,
|
"port": 10001,
|
||||||
"public_key": "3nk7jdnkcL95Fc/z+GCiH7jOovEKhFkLIGPT+U/uLEQ=",
|
"public_key": "3nk7jdnkcL95Fc/z+GCiH7jOovEKhFkLIGPT+U/uLEQ=",
|
||||||
"allowed_ips": ["0.0.0.0/0"]
|
"allowed_ips": ["0.0.0.0/0"],
|
||||||
|
"reserved": "AAAA"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"udp_timeout": "5m0s",
|
"udp_timeout": "5m0s",
|
||||||
|
|||||||
@@ -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",
|
"type": "masque",
|
||||||
"tag": "masque-out",
|
"tag": "masque-out",
|
||||||
"system": false,
|
|
||||||
"name": "masque0",
|
|
||||||
"use_http2": false,
|
"use_http2": false,
|
||||||
"use_ipv6": false,
|
"use_ipv6": false,
|
||||||
"profile": {
|
"profile": {
|
||||||
@@ -39,19 +37,14 @@
|
|||||||
"udp_keepalive_period": "30s",
|
"udp_keepalive_period": "30s",
|
||||||
"udp_initial_packet_size": 0,
|
"udp_initial_packet_size": 0,
|
||||||
"reconnect_delay": "5s",
|
"reconnect_delay": "5s",
|
||||||
"congestion_controller": "bbr",
|
// TLS fields for HTTP2
|
||||||
"cwnd": 0,
|
"insecure": false,
|
||||||
"tls": { // TLS fields for HTTP2
|
"cipher_suites": [],
|
||||||
"server_name": "", // SNI; empty = default "consumer-masque.cloudflareclient.com"
|
"curve_preferences": [],
|
||||||
"insecure": false,
|
"fragment": false,
|
||||||
"cipher_suites": [],
|
"record_fragment": false,
|
||||||
"curve_preferences": [],
|
"kernel_tx": false,
|
||||||
"fragment": false,
|
"kernel_rx": false
|
||||||
"fragment_fallback_delay": "500ms",
|
|
||||||
"record_fragment": false,
|
|
||||||
"kernel_tx": false,
|
|
||||||
"kernel_rx": false
|
|
||||||
}
|
|
||||||
// Dial Fields
|
// Dial Fields
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -27,16 +27,14 @@
|
|||||||
"tag": "mieru-out",
|
"tag": "mieru-out",
|
||||||
"server": "example.com",
|
"server": "example.com",
|
||||||
"server_port": 27017,
|
"server_port": 27017,
|
||||||
"server_ports": [
|
"server_ports": "27017-27019",
|
||||||
"27017-27019"
|
|
||||||
],
|
|
||||||
"transport": "TCP",
|
"transport": "TCP",
|
||||||
"username": "username",
|
"username": "username",
|
||||||
"password": "password",
|
"password": "password",
|
||||||
// valid: MULTIPLEXING_DEFAULT / MULTIPLEXING_OFF / MULTIPLEXING_LOW
|
// valid: MULTIPLEXING_DEFAULT / MULTIPLEXING_OFF / MULTIPLEXING_LOW
|
||||||
// MULTIPLEXING_MIDDLE / MULTIPLEXING_HIGH
|
// MULTIPLEXING_MIDDLE / MULTIPLEXING_HIGH
|
||||||
"multiplexing": "MULTIPLEXING_LOW",
|
"multiplexing": "MULTIPLEXING_LOW"
|
||||||
"traffic_pattern": "GgQIARAK"
|
// Dial Fields
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"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": "",
|
"packet_encoding": "",
|
||||||
"transport": {
|
"transport": {
|
||||||
"type": "mkcp",
|
"type": "mkcp",
|
||||||
"mtu": 1350, // 576-1460
|
"mtu": 1500
|
||||||
"tti": 50, // 10-100, ms
|
|
||||||
"uplink_capacity": 12, // MB/s
|
|
||||||
"downlink_capacity": 100, // MB/s
|
|
||||||
"congestion": false,
|
|
||||||
"read_buffer_size": 1, // MB
|
|
||||||
"write_buffer_size": 1, // MB
|
|
||||||
"header_type": "none", // none, srtp, utp, wechat-video, dtls, wireguard
|
|
||||||
"seed": "password"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -24,15 +24,7 @@
|
|||||||
],
|
],
|
||||||
"transport": {
|
"transport": {
|
||||||
"type": "mkcp",
|
"type": "mkcp",
|
||||||
"mtu": 1350, // 576-1460
|
"mtu": 1500
|
||||||
"tti": 50, // 10-100, ms
|
|
||||||
"uplink_capacity": 12, // MB/s
|
|
||||||
"downlink_capacity": 100, // MB/s
|
|
||||||
"congestion": false,
|
|
||||||
"read_buffer_size": 1, // MB
|
|
||||||
"write_buffer_size": 1, // MB
|
|
||||||
"header_type": "none", // none, srtp, utp, wechat-video, dtls, wireguard
|
|
||||||
"seed": "password"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -26,9 +26,9 @@
|
|||||||
"concurrency": 8192,
|
"concurrency": 8192,
|
||||||
// domain_fronting_port is a port we use to connect to a fronting domain.
|
// domain_fronting_port is a port we use to connect to a fronting domain.
|
||||||
"domain_fronting_port": 443,
|
"domain_fronting_port": 443,
|
||||||
// domain_fronting_host is the address (IP or hostname) to use when connecting
|
// domain_fronting_ip is an IP address to use when connecting to the fronting
|
||||||
// to the fronting domain instead of resolving the hostname from the secret via DNS.
|
// domain instead of resolving the hostname from the secret via DNS.
|
||||||
"domain_fronting_host": "",
|
"domain_fronting_ip": "",
|
||||||
// domain_fronting_proxy_protocol is used if communication between upstream
|
// domain_fronting_proxy_protocol is used if communication between upstream
|
||||||
// endpoint and sing-box supports proxy protocol.
|
// endpoint and sing-box supports proxy protocol.
|
||||||
"domain_fronting_proxy_protocol": false,
|
"domain_fronting_proxy_protocol": false,
|
||||||
|
|||||||
@@ -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",
|
"type": "parser",
|
||||||
"tag": "vless-out",
|
"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?..."
|
"link": "vless://b5e41c8c-c437-4689-b863-76208a3efb4b@0.0.0.0:443?..."
|
||||||
// Dial Fields
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user