Compare commits

..

144 Commits

Author SHA1 Message Date
Sergei Maklagin
be1fe118c8 Add naiveproxy build 2026-05-29 23:11:38 +03:00
Sergei Maklagin
a09cfe2bf8 Fix xmux 2026-05-29 19:30:34 +03:00
Sergei Maklagin
4e0f7e7e62 Fix wireguard bind 2026-05-29 15:39:53 +03:00
Sergei Maklagin
1a54d79022 Disable Wireguard ICMP 2026-05-29 15:31:29 +03:00
Sergei Maklagin
ca0b484d7a Merge tag 'v1.13.12-extended-2.1.3' into extended 2026-05-29 14:41:56 +03:00
Sergei Maklagin
6c1d568876 Remove reserved 2026-05-29 14:34:11 +03:00
Sergei Maklagin
41040ba1e2 Update release pipeline 2026-05-29 04:38:59 +03:00
Sergei Maklagin
a47bdbe2ae Update wireguard 2026-05-29 03:55:48 +03:00
Sergei Maklagin
8e8eca01fe Resolve conflicts 2026-05-29 01:33:34 +03:00
Sergei Maklagin
41e815e18b Update sing-box core, refactor MASQUE, update XHTTP 2026-05-29 01:31:57 +03:00
Sergei Maklagin
b586c4f313 Remove unused file 2026-05-11 02:18:20 +03:00
Sergei Maklagin
3bd162ed6f Add new admin panel, failover, dns fallback, providers, limiters. Update XHTTP 2026-05-11 00:59:35 +03:00
Sergei Maklagin
652e0baf57 Fix go.sum 2026-05-05 12:59:07 +03:00
Sergei Maklagin
8be5c8fabe Fix legacy build 2026-05-05 12:58:01 +03:00
Sergei Maklagin
eb36c934a7 Fix examples 2026-05-03 04:14:59 +03:00
Sergei Maklagin
52edfdb059 Remove IPBlocklist and IPAllowlist 2026-05-03 04:04:16 +03:00
Sergei Maklagin
eecab479fa Fix Dockerfile 2026-05-02 15:27:46 +03:00
Sergei Maklagin
d8670a742a Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-05-02 15:00:57 +03:00
Sergei Maklagin
e2ef7d83a1 Fix creating config for WARP 2026-05-02 14:54:26 +03:00
Shtorm
af75b8074f Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 23:39:22 +03:00
Sergei Maklagin
2e0306ae41 Disable Wireguard ICMP 2026-04-30 19:20:23 +03:00
Sergei Maklagin
ee2945ac8f Fix OverrideGateway 2026-04-30 19:19:40 +03:00
Sergei Maklagin
b2503ca860 Update chi 2026-04-30 19:18:50 +03:00
Sergei Maklagin
dcb1da8683 Fix examples 2026-04-30 13:20:45 +03:00
Sergei Maklagin
22aeb48794 Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-04-30 13:14:37 +03:00
Sergei Maklagin
019103587b Update examples 2026-04-30 13:14:17 +03:00
Sergei Maklagin
3139f18bf1 Update android build tags 2026-04-30 12:17:37 +03:00
Shtorm
493538c743 Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 11:15:15 +03:00
Shtorm
578bc159fb Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 01:15:21 +03:00
Shtorm
f0c317cb0b Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 01:12:28 +03:00
Shtorm
32bc1a9fce Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-04-30 01:08:36 +03:00
Sergei Maklagin
41922ba731 Update README.md 2026-04-29 22:40:15 +03:00
Sergei Maklagin
bf8fe79a7a Fix go.mod 2026-04-29 22:16:10 +03:00
Sergei Maklagin
398b6387ab Resolve conflicts 2026-04-29 22:14:30 +03:00
Sergei Maklagin
04908a6a67 Add MTProxy, MASQUE, VPN, Link parser. Update AmneziaWG. Remove Tunneling 2026-04-29 22:11:30 +03:00
Sergei Maklagin
88a80e961b Fix README.md 2026-04-27 11:38:09 +03:00
Sergei Maklagin
05b82f63da Update tailscale 2026-04-26 21:31:13 +03:00
Sergei Maklagin
00c08995c7 Update sing-box core 2026-04-26 21:11:31 +03:00
Sergei Maklagin
09f9f114aa Update sing-box core 2026-04-22 19:23:23 +03:00
Sergei Maklagin
1995ba4279 Update sing-box core 2026-04-17 01:10:31 +03:00
Sergei Maklagin
2d33dee415 Update sing-box core 2026-04-06 20:54:24 +03:00
Sergei Maklagin
cb3131b3d4 Update dependencies 2026-03-11 20:10:06 +03:00
Sergei Maklagin
20bf40e822 Update dependencies 2026-03-11 17:32:12 +03:00
Sergei Maklagin
861aff60f0 Update dependencies 2026-03-11 06:45:24 +03:00
Sergei Maklagin
47a62927f0 Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2026-03-10 09:06:57 +03:00
Sergei Maklagin
2df1cffb0f Resolve conflicts 2026-03-10 08:43:01 +03:00
Sergei Maklagin
f683fcb3cb Update dependencies 2026-03-10 08:37:04 +03:00
Sergei Maklagin
bc6ca6e2ea Update sing-box core 2026-03-10 04:50:32 +03:00
Sergei Maklagin
6cd8b832fe Update sing-box core 2026-03-10 04:25:01 +03:00
世界
c0d45aebfa Bump version 2026-03-07 15:55:42 +08:00
Sergei Maklagin
0503006f48 Add Kmutex 2026-03-03 00:51:20 +03:00
Sergei Maklagin
517f5152e7 Add migrate 2026-03-03 00:51:04 +03:00
Sergei Maklagin
b1b7aa81cd Update Amnezia H1-H4 format 2026-03-02 21:48:54 +03:00
Sergei Maklagin
195e941c35 Fix typo 2026-03-02 21:27:47 +03:00
Sergei Maklagin
35bc351564 Fix failover 2026-03-02 19:48:06 +03:00
Sergei Maklagin
290dbed7b8 Fix failover 2026-03-02 19:33:59 +03:00
Sergei Maklagin
d7a8207f44 Update AmneziaWG 2026-03-02 19:33:07 +03:00
Sergei Maklagin
57c5ca13eb Fix bond outbound 2026-03-02 19:31:23 +03:00
Sergei Maklagin
7fc33134fb Update AmneziaWG 2026-03-01 16:42:21 +03:00
Sergei Maklagin
881ab6d436 Fix examples 2026-02-27 00:47:22 +03:00
Sergei Maklagin
0443b93328 Update README.md 2026-02-27 00:19:37 +03:00
Sergei Maklagin
75557830a8 Merge branch 'extended' into extended-next 2026-02-26 22:58:59 +03:00
Sergei Maklagin
9d5273ba1e Resolve conflicts 2026-02-26 22:58:45 +03:00
Sergei Maklagin
5f2a65f01b Add examples 2026-02-26 22:57:44 +03:00
Sergei Maklagin
06a519db27 Resolve conflicts 2026-02-26 22:57:25 +03:00
Sergei Maklagin
65e73fe817 Add examples 2026-02-26 22:55:24 +03:00
Sergei Maklagin
c0aa3480c5 Add admin panel, manager, node_manager, bandwidth limiter, connection limiter, bonding, failover, vless encryption, mkcp transport 2026-02-26 22:44:31 +03:00
Sergei Maklagin
69f6c75dd7 Add vless encryption 2026-02-26 18:03:59 +03:00
Shtorm
f95b34a8a7 Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2026-02-26 02:43:48 +03:00
Sergei Maklagin
b62000e924 Update README.md 2026-02-25 14:10:09 +03:00
Sergei Maklagin
a03af44c61 Add DONATE.md 2026-02-25 14:03:36 +03:00
Sergei Maklagin
aa103fdfc6 Fix examples 2026-02-22 18:10:15 +03:00
Sergei Maklagin
c82e613c52 Fix typo 2026-02-22 17:59:02 +03:00
Sergei Maklagin
3d16078651 Fix padding 2026-02-22 16:22:52 +03:00
Sergei Maklagin
18b1101fbe Update Dockerfile 2026-02-22 15:50:39 +03:00
Sergei Maklagin
4ebe870306 Update xhttp examples 2026-02-22 15:50:00 +03:00
Sergei Maklagin
50c5e9df0d Fix xhttp options 2026-02-22 15:46:12 +03:00
Sergei Maklagin
c8a993834e Fix Range 2026-02-22 15:45:53 +03:00
Sergei Maklagin
260bbbfb45 Fix examples 2026-02-22 14:51:16 +03:00
Sergei Maklagin
82337299b9 Update xhttp 2026-02-22 14:48:52 +03:00
Sergei Maklagin
c229c79dcc Update sing-box core 2026-02-22 14:46:42 +03:00
世界
f63091d14d Bump version 2026-02-15 21:05:34 +08:00
世界
1c4a01ee90 Fix matching multi predefined 2026-02-15 19:20:31 +08:00
世界
4d7f99310c Fix matching rule-set invert 2026-02-15 19:20:11 +08:00
世界
6fc511f56e wireguard: Fix missing fallback for gso 2026-02-15 19:20:03 +08:00
世界
d18d2b352a Bump version 2026-02-09 13:57:18 +08:00
世界
534128bba9 tuic: Fix udp context 2026-02-09 13:55:09 +08:00
世界
736a7368c6 Fix naive padding 2026-02-09 13:53:32 +08:00
世界
e7a9c90213 Fix DNS cache lock goroutine leak
The cache deduplication in Client.Exchange uses a channel-based lock
per DNS question. Waiting goroutines blocked on <-cond without context
awareness, causing them to accumulate indefinitely when the owning
goroutine's transport call stalls. Add select on ctx.Done() so waiters
respect context cancellation and timeouts.
2026-02-06 22:28:30 +08:00
世界
0f3774e501 Bump version 2026-02-05 17:13:38 +08:00
世界
2f8e656522 Update Go to 1.25.7 2026-02-05 17:12:42 +08:00
世界
3ba30e3f00 Fix route_address_set duplicated IP sets causing route creation failure
The FlatMap calls pre-populated routeAddressSet and routeExcludeAddressSet
before the for-loops which appended the same IP sets again, doubling every
entry. On Windows this caused CreateIpForwardEntry2 to return
ERROR_OBJECT_ALREADY_EXISTS.

Fixes #3725
2026-02-02 17:29:21 +08:00
世界
f2639a5829 Fix random iproute2 table index was incorrectly removed 2026-02-02 14:13:49 +08:00
世界
69bebbda82 Bump version 2026-02-01 10:19:35 +08:00
世界
00b2c042ee Disable rp filter atomically 2026-02-01 10:17:34 +08:00
世界
d9eb8f3ab6 Fix varbin serialization 2026-02-01 10:11:15 +08:00
世界
58025a01f8 Fix auto_redirect fallback rule 2026-01-29 12:07:15 +08:00
世界
99cad72ea8 Bump version 2026-01-28 16:56:08 +08:00
世界
6e96d620fe Minor fixes 2026-01-28 16:56:08 +08:00
Sergei Maklagin
596291567f Update AmneziaWG 2026-01-25 21:24:43 +03:00
Sergei Maklagin
a2a5f46cb6 Resolve conflicts 2026-01-18 21:53:22 +03:00
Sergei Maklagin
f6da8e52b4 Fix logger 2026-01-18 21:51:30 +03:00
世界
b27d707668 Bump version 2026-01-17 04:54:24 +08:00
Sergei Maklagin
287fe834db Update XHTTP 2025-12-11 02:46:57 +03:00
Sergei Maklagin
d7f0cea4ff Fix typo 2025-12-08 23:18:51 +03:00
Sergei Maklagin
d8b470d1ba Add new wireguard options 2025-12-08 22:32:33 +03:00
Sergei Maklagin
984fc295b3 Fix XHTTP TLS 2025-12-08 22:30:58 +03:00
Shtorm
6e4b7ed744 Merge pull request #6 from starifly/extended
fix(xhttp): use download request URL for down leg
2025-11-03 19:13:46 +03:00
starifly
855d400654 fix(xhttp): use download request URL for down leg 2025-11-03 23:18:03 +08:00
Sergei Maklagin
4c5e2c6645 Remove direct detour checking 2025-11-02 17:59:12 +03:00
Sergei Maklagin
725eccdea8 Fix Range 2025-11-02 17:55:18 +03:00
Sergei Maklagin
2ff042abd2 Resolve unnecessary logger 2025-11-02 17:41:18 +03:00
Sergei Maklagin
ffb282e47e Resolve conflicts 2025-11-02 17:39:38 +03:00
Sergei Maklagin
91f9134379 Update README.md 2025-09-15 01:25:46 +03:00
Sergei Maklagin
5a4de7b242 Integrate Amnezia 1.5 2025-09-15 01:25:19 +03:00
Sergei Maklagin
bc91312a73 Update go.sum 2025-09-14 23:45:55 +03:00
Sergei Maklagin
1d603e24fd Resolve conflicts 2025-09-14 23:44:29 +03:00
Sergei Maklagin
9dc526ea1f Resolve conflicts 2025-08-15 12:57:47 +03:00
Sergei Maklagin
93eb435e26 Resolve conflicts 2025-08-15 12:56:52 +03:00
Sergei Maklagin
e22416f0d9 Merge branch 'extended' of https://github.com/shtorm-7/sing-box-extended into extended 2025-07-14 13:27:54 +03:00
Sergei Maklagin
89497dbfd5 Update dependencies 2025-07-13 21:48:31 +03:00
Sergei Maklagin
8388abbb77 Resolve conflicts 2025-07-13 21:44:23 +03:00
Sergei Maklagin
180b7c4134 Fix tunnel client 2025-07-13 21:41:41 +03:00
Sergei Maklagin
deda2cca5e Fix xhttp transport 2025-07-13 21:40:34 +03:00
Shtorm
691ecd45a9 Update README.md
Signed-off-by: Shtorm <108103062+shtorm-7@users.noreply.github.com>
2025-07-06 21:16:54 +03:00
Sergei Maklagin
209b89a4a3 Add tunnel 2025-07-06 18:31:06 +03:00
Sergei Maklagin
765111a552 Merge tag 'v1.11.14' into HEAD 2025-06-19 20:18:51 +03:00
Sergei Maklagin
824eac453b Add new examples 2025-06-15 22:23:42 +03:00
Sergei Maklagin
85a1a8a53b Format examples 2025-06-15 22:23:25 +03:00
Sergei Maklagin
ae9e7aa5f4 Fix Range 2025-06-15 22:21:58 +03:00
Sergei Maklagin
52b71c6f00 Add sdns transport 2025-06-15 20:47:05 +03:00
Sergei Maklagin
5d04673783 Fix XHTTP Fqdn 2025-06-15 19:41:22 +03:00
Sergei Maklagin
307c429715 Fix WARP endpoint error 2025-06-15 19:40:42 +03:00
Sergei Maklagin
6801b58d96 Fix interrupt_exist_connections 2025-06-15 19:32:14 +03:00
Sergei Maklagin
6272596ebc Add unified delay 2025-06-15 19:24:09 +03:00
Sergei Maklagin
7c4c2d5ca8 Add mieru protocol 2025-06-15 18:04:27 +03:00
Sergei Maklagin
6768c77fa0 Merge tag 'v1.11.13' into HEAD 2025-06-08 22:43:21 +03:00
Sergei Maklagin
0dff811977 Add examples 2025-06-08 22:38:32 +03:00
Sergei Maklagin
e1a58d12fe Resolve conflicts 2025-06-08 19:54:17 +03:00
Sergei Maklagin
4e74a4108d refactor: WARP 2025-06-08 19:51:17 +03:00
Sergei Maklagin
0238c54261 Add xhttp transport 2025-06-08 19:35:59 +03:00
Sergei Maklagin
5c911c97d8 Added WARP endpoint 2025-06-01 22:06:34 +03:00
Sergei Maklagin
2cfc8092ad Fix endpoint manager locks 2025-05-29 22:48:15 +03:00
Sergei Maklagin
26bd698462 Integrate AmneziaWG 2025-05-24 23:54:15 +03:00
461 changed files with 3503 additions and 43063 deletions

View File

@@ -1,16 +1,12 @@
-s dir
--name sing-box-extended
--name sing-box
--category net
--license GPL-3.0-or-later
--description "The universal proxy platform (extended)."
--description "The universal proxy platform."
--url "https://sing-box.sagernet.org/"
--maintainer "nekohasekai <contact-git@sekai.icu>"
--no-deb-generate-changes
--provides sing-box
--conflicts sing-box
--replaces sing-box
--config-files /etc/config/sing-box
--config-files /etc/sing-box/config.json

View File

@@ -1 +1 @@
617d38f41f935b46a68f550d9add2e38abb3f168
2faf34666c2cc8234f10f2ab6d4c4d6104d34ae2

View File

@@ -27,7 +27,10 @@ fi
PROJECT=$(cd "$(dirname "$0")/.."; pwd)
# Convert version to APK format:
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/' | sed -E 's/-[a-z]+-/./g')
# 1.13.0-beta.8 -> 1.13.0_beta8-r0
# 1.13.0-rc.3 -> 1.13.0_rc3-r0
# 1.13.0 -> 1.13.0-r0
APK_VERSION=$(echo "$VERSION" | sed -E 's/-([a-z]+)\.([0-9]+)/_\1\2/')
APK_VERSION="${APK_VERSION}-r0"
ROOT_DIR=$(mktemp -d)
@@ -75,16 +78,15 @@ done < "$PACKAGES_DIR/.conffiles" > "$PACKAGES_DIR/.conffiles_static"
# Build APK
apk --root "$APK_ROOT_DIR" mkpkg \
--info "name:sing-box-extended" \
--info "name:sing-box" \
--info "version:${APK_VERSION}" \
--info "description:The universal proxy platform (extended)." \
--info "description:The universal proxy platform." \
--info "arch:${ARCHITECTURE}" \
--info "license:GPL-3.0-or-later" \
--info "origin:sing-box-extended" \
--info "origin:sing-box" \
--info "url:https://sing-box.sagernet.org/" \
--info "maintainer:nekohasekai <contact-git@sekai.icu>" \
--info "depends:ca-bundle kmod-inet-diag kmod-tun firewall4 kmod-nft-queue" \
--info "provides:sing-box" \
--info "provider-priority:100" \
--script "pre-deinstall:${PROJECT}/release/config/openwrt.prerm" \
--files "$ROOT_DIR" \

View File

@@ -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

View File

@@ -2,7 +2,7 @@
set -euo pipefail
VERSION="1.25.12"
VERSION="1.25.9"
PATCH_COMMITS=(
"afe69d3cec1c6dcf0f1797b20546795730850070"
"1ed289b0cf87dc5aae9c6fe1aa5f200a83412938"

View File

@@ -2,14 +2,14 @@
set -euo pipefail
VERSION="1.25.12"
VERSION="1.25.9"
PATCH_COMMITS=(
"da4094da73b3b419e3f347594d805e2831f65667"
"824aa60e77f06dbae86c20a164c78df722eb7047"
"a3b6ba31c8cc67b6d899b978bba7b53e95afc46b"
"edfa8de63435a409a59f60731b66ab5940d6d3a4"
"284f9b24d6284984966a8431e30fdc2583938f96"
"9864798dee8dd47b55d1d5100d2f1b909a2a6e6c"
"466f6c7a29bc098b0d4c987b803c779222894a11"
"1bdabae205052afe1dadb2ad6f1ba612cdbc532a"
"a90777dcf692dd2168577853ba743b4338721b06"
"f6bddda4e8ff58a957462a1a09562924d5f3d05c"
"bed309eff415bcb3c77dd4bc3277b682b89a388d"
"34b899c2fb39b092db4fa67c4417e41dc046be4b"
)
CURL_ARGS=(
-fL

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.25.12
go-version: ~1.25.9
- name: Clone cronet-go
if: matrix.naive
run: |

View File

@@ -29,7 +29,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.25.12
go-version: ~1.25.9
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -72,7 +72,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.25.12
go-version: ~1.25.9
- name: Clone cronet-go
if: matrix.naive
run: |
@@ -194,7 +194,6 @@ jobs:
run: |-
set -xeuo pipefail
sudo gem install fpm
echo '%_rpmformat 4' > "$HOME/.rpmmacros"
cp .fpm_systemd .fpm
fpm -t rpm \
--name "${NAME}" \

7
.gitignore vendored
View File

@@ -23,10 +23,3 @@ AGENTS.md
/.claude/
dist
logs
/*.so
/*.log
/*.db-shm
/*.db-wal
/*.db.backup.*
/test_download.bin
/wget-log

View File

@@ -11,7 +11,6 @@ builds:
- -X github.com/sagernet/sing-box/constant.Version={{ .Version }}
- -s
- -buildid=
- -checklinkname=0
tags:
- with_gvisor
- with_quic
@@ -23,30 +22,18 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
env:
- CGO_ENABLED=0
- GOTOOLCHAIN=local
targets:
- linux_386
- linux_amd64_v1
- linux_arm64
- linux_arm_6
- linux_arm_7
- linux_s390x
- linux_riscv64
- windows_amd64_v1
- windows_386
- windows_arm64
- darwin_amd64_v1
- darwin_arm64
mod_timestamp: '{{ .CommitTimestamp }}'
@@ -63,14 +50,6 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_profiler
- badlinkname
- tfogo_checklinkname0
targets:
- linux_mips
- linux_mips_softfloat
@@ -124,17 +103,9 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_naive_outbound
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
@@ -159,17 +130,9 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_naive_outbound
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
@@ -194,17 +157,9 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_naive_outbound
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
@@ -229,17 +184,9 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_naive_outbound
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_purego
env:
- CGO_ENABLED=0
@@ -264,16 +211,8 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
env:
- CGO_ENABLED=1
@@ -315,16 +254,8 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_manager
- with_admin_panel
- with_profiler
- badlinkname
- tfogo_checklinkname0
- with_naive_outbound
- with_musl
env:
@@ -374,14 +305,6 @@ builds:
- with_tailscale
- with_masque
- with_mtproxy
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_profiler
- badlinkname
- tfogo_checklinkname0
targets:
- linux_mips
- linux_mips_softfloat
@@ -420,52 +343,6 @@ builds:
- android_arm64
- android_386
- android_amd64
- id: openwrt
<<: *template
hooks:
post:
- cmd: bash .github/build_openwrt_packages.sh "{{ .Version }}" "{{ .Target }}" "{{ .Path }}"
targets:
- linux_amd64_v1
- linux_arm64
- linux_386
- linux_arm_7
- linux_arm_6
- linux_riscv64
- linux_loong64
- id: openwrt-mips
<<: *template
tags:
- with_gvisor
- with_quic
- with_dhcp
- with_wireguard
- with_utls
- with_acme
- with_clash_api
- with_tailscale
- with_masque
- with_mtproxy
- with_ccm
- with_ocm
- with_openvpn
- with_trusttunnel
- with_call
- with_sudoku
- with_snell
- with_profiler
- badlinkname
- tfogo_checklinkname0
hooks:
post:
- cmd: bash .github/build_openwrt_packages.sh "{{ .Version }}" "{{ .Target }}" "{{ .Path }}"
targets:
- linux_arm_5
- linux_mips_softfloat
- linux_mips64_softfloat
- linux_mipsle_softfloat
- linux_mipsle_hardfloat
- linux_mips64le
upx:
- enabled: true
ids:
@@ -491,42 +368,6 @@ archives:
files:
- LICENSE
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
- id: archive-naive-purego-linux-amd64
<<: *template
builds:
- naive-purego-linux-amd64
files:
- LICENSE
- src: dist/naive-purego-linux-amd64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-purego-linux-arm64
<<: *template
builds:
- naive-purego-linux-arm64
files:
- LICENSE
- src: dist/naive-purego-linux-arm64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-purego-windows-amd64
<<: *template
builds:
- naive-purego-windows-amd64
files:
- LICENSE
- src: dist/naive-purego-windows-amd64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-purego-windows-arm64
<<: *template
builds:
- naive-purego-windows-arm64
files:
- LICENSE
- src: dist/naive-purego-windows-arm64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-purego'
- id: archive-naive-glibc
<<: *template
builds:
@@ -537,6 +378,47 @@ archives:
builds:
- naive-musl
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ if and .Mips (not (eq .Mips "hardfloat")) }}-{{ .Mips }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}-musl'
- id: archive-naive-purego-linux-amd64
<<: *template
builds:
- naive-purego-linux-amd64
files:
- LICENSE
- src: dist/naive-purego-linux-amd64_*/libcronet*
strip_parent: true
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}'
- 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 }}'
- 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 }}'
- 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 }}'
- id: archive-legacy
<<: *template
builds:
- legacy
name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}-legacy'
- id: archive-compressed
<<: *template
builds:
@@ -569,5 +451,5 @@ release:
- archive-naive-purego-windows-amd64
- archive-naive-purego-windows-arm64
- archive-compressed
- archive-openwrt
- package
skip_upload: true

View File

@@ -1,23 +1,18 @@
FROM --platform=$BUILDPLATFORM golang:1.26 AS builder
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
LABEL maintainer="shtorm-7"
COPY . /go/src/github.com/sagernet/sing-box
WORKDIR /go/src/github.com/sagernet/sing-box
ARG TARGETOS TARGETARCH
ARG GOPROXY=""
ARG CRONET_GO_PATH=/go/src/github.com/sagernet/sing-box/cronet-go
ENV GOPROXY=${GOPROXY}
ENV CGO_ENABLED=1
ENV GOPROXY ${GOPROXY}
ENV CGO_ENABLED=0
ENV GOOS=$TARGETOS
ENV GOARCH=$TARGETARCH
ENV CRONET_GO_PATH=${CRONET_GO_PATH}
RUN set -ex \
&& git config --global --add safe.directory /go/src/github.com/sagernet/sing-box \
&& export GOTOOLCHAIN=local \
&& export CGO_ENABLED=1 GOOS=$TARGETOS GOARCH=$TARGETARCH \
&& eval "$(CGO_ENABLED=0 GOOS= GOARCH= go run -C "$CRONET_GO_PATH" ./cmd/build-naive --target=$GOOS/$GOARCH --libc=musl env --export)" \
&& export CGO_LDFLAGS="$CGO_LDFLAGS -Wl,-z,notext -Wl,-z,execstack" \
&& export VERSION=$(CGO_ENABLED=0 GOOS= GOARCH= go run ./cmd/internal/read_tag) \
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_DOCKER) \
&& apk add git build-base \
&& export COMMIT=$(git rev-parse --short HEAD) \
&& export VERSION=$(go run ./cmd/internal/read_tag) \
&& export TAGS=$(cat release/DEFAULT_BUILD_TAGS_OTHERS) \
&& export LDFLAGS_SHARED=$(cat release/LDFLAGS) \
&& go build -v -trimpath -tags "$TAGS" \
-o /go/bin/sing-box \

View File

@@ -27,6 +27,7 @@ CRONET_GO_PATH ?= $(shell pwd)/cronet-go
.PHONY: test release docs build
build:
export GOTOOLCHAIN=local && \
go build $(MAIN_PARAMS) $(MAIN)
build_admin_panel:
@@ -93,11 +94,8 @@ release: build_admin_panel build_naive
mkdir dist/release
mv dist/*.tar.gz \
dist/*.zip \
dist/*.ipk \
dist/*.apk \
dist/release
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release
rm -r dist/release
release_repo:
@@ -124,14 +122,11 @@ build_android:
upload_android:
mkdir -p dist/release_android
cp ../sing-box-for-android/app/build/outputs/apk/other/release/*.apk dist/release_android
VERSION_CODE=$$(grep VERSION_CODE ../sing-box-for-android/version.properties | cut -d= -f2); \
VERSION_NAME=$$(grep VERSION_NAME ../sing-box-for-android/version.properties | cut -d= -f2); \
printf '{\n "version_code": %s,\n "version_name": "%s"\n}\n' "$$VERSION_CODE" "$$VERSION_NAME" > dist/release_android/SFA-version-metadata.json
cp ../sing-box-for-android/app/build/outputs/apk/otherLegacy/release/*.apk dist/release_android
ghr --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
./codeberg-release.sh --replace --draft --prerelease -p 5 "v${VERSION}" dist/release_android
rm -rf dist/release_android
release_android: lib_android update_android_version build_android upload_android
release_android: lib_android update_android_version build_android
publish_android:
cd ../sing-box-for-android && ./gradlew :app:publishPlayReleaseBundle && ./gradlew --stop

View File

@@ -1,26 +1,15 @@
# sing-box-extended
[![license](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE)
[![go](https://img.shields.io/badge/go-1.26-00ADD8.svg)](go.mod)
[![codeberg](https://img.shields.io/badge/mirror-codeberg-2185D0.svg)](https://codeberg.org/shtorm-7/sing-box-extended)
[![telegram](https://img.shields.io/badge/telegram-chat-26A5E4.svg?logo=telegram)](https://t.me/sing_box_extended)
Sing-box with extended features.
## 🔥 Features
### Protocols
### Outbounds
- **WARP** — Cloudflare WARP integration through WireGuard
- **MASQUE** — Cloudflare MASQUE proxy over QUIC / HTTP-2
- **MTProxy** — Telegram MTProxy server with FakeTLS and domain fronting
- **Mieru** — Secure, hard to classify, hard to probe network protocol
- **OpenVPN** — OpenVPN client with tls-auth, tls-crypt and tls-crypt-v2 support
- **TrustTunnel** — AdGuard's obfuscated VPN protocol, indistinguishable from HTTPS traffic
- **Sudoku** — Traffic obfuscation protocol based on 4×4 Sudoku puzzles with low-entropy fingerprints
- **Snell** — Lightweight encrypted proxy (v1v5) with TLS / HTTP obfuscation
- **SSH** — SSH client and server with certificate authentication and upstream fallback
- **Call** — Traffic tunneling through video-call platforms (VK, Dion, Telemost, WBStream)
- **VPN** — Routed tunnel over any sing-box protocol
- **VPN** — Routed tunnel over any TCP sing-box protocol
- **Bond** — Link aggregation for increasing throughput
- **Fallback** — Outbound group with priority-based switching
- **Failover** — Automatic outbound switching with session recovery for high availability
@@ -36,13 +25,12 @@ Sing-box with extended features.
- **Rate Limiter** — Request rate limiting
### Encryption & Obfuscation
- **Amnezia 3.0** — WireGuard traffic obfuscation
- **Amnezia 2.0** — WireGuard traffic obfuscation
- **VLESS encryption** — XRAY encryption for VLESS protocol
### Transports
- **mKCP** — Reliable UDP-based transport
- **XHTTP** — Modern XRAY transport
- **rmux** — Improved smux multiplex protocol
### Services
- **Admin Panel** — Web-based management interface
@@ -52,7 +40,7 @@ Sing-box with extended features.
### Miscellaneous
- **Providers** — Outbound subscriptions from local files, inline lists, or remote URLs (sing-box JSON, Clash YAML, SIP008, share links)
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC, AnyTLS)
- **Link Parser** — Outbound configured from a share link (VLESS, VMess, Shadowsocks, Trojan, Hysteria, Hysteria2, TUIC)
- **Extended WireGuard options** — Advanced configuration capabilities
- **Unified Delay** — Unified latency measurement

View File

@@ -27,11 +27,6 @@ type OutboundWithPreferredRoutes interface {
PreferredAddress(address netip.Addr) bool
}
type OutboundWithMultiplex interface {
Outbound
MultiplexEnabled() bool
}
type DirectRouteOutbound interface {
Outbound
NewDirectRouteConnection(metadata InboundContext, routeContext tun.DirectRouteContext, timeout time.Duration) (tun.DirectRouteDestination, error)

View File

@@ -233,7 +233,7 @@ func (m *Manager) Remove(tag string) error {
if m.defaultOutbound == outbound {
if len(m.outbounds) > 0 {
m.defaultOutbound = m.outbounds[0]
m.logger.Notice("updated default outbound to ", m.defaultOutbound.Tag())
m.logger.Info("updated default outbound to ", m.defaultOutbound.Tag())
} else {
m.defaultOutbound = nil
}
@@ -303,7 +303,7 @@ func (m *Manager) Create(ctx context.Context, router adapter.Router, logger log.
if tag == m.defaultTag || (m.defaultTag == "" && m.defaultOutbound == nil) {
m.defaultOutbound = outbound
if m.started {
m.logger.Notice("updated default outbound to ", outbound.Tag())
m.logger.Info("updated default outbound to ", outbound.Tag())
}
}
return nil

View File

@@ -13,7 +13,6 @@ type PlatformInterface interface {
UsePlatformAutoDetectInterfaceControl() bool
AutoDetectInterfaceControl(fd int) error
BindInterfaceControl(fd int, interfaceName string) error
UsePlatformInterface() bool
OpenInterface(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)

View File

@@ -3,8 +3,6 @@ package provider
import (
"context"
"reflect"
"regexp"
"strings"
"sync"
"sync/atomic"
"time"
@@ -38,7 +36,6 @@ type Adapter struct {
link string
enabled bool
removeEmojis bool
timeout time.Duration
interval time.Duration
}
@@ -71,10 +68,6 @@ func NewAdapter(ctx context.Context, router adapter.Router, outbound adapter.Out
}
}
func (a *Adapter) SetRemoveEmojis(remove bool) {
a.removeEmojis = remove
}
func (a *Adapter) Start() error {
a.history = service.FromContext[adapter.URLTestHistoryStorage](a.ctx)
if a.history == nil {
@@ -109,10 +102,6 @@ func (a *Adapter) Outbound(tag string) (adapter.Outbound, bool) {
}
func (a *Adapter) UpdateOutbounds(oldOpts []option.Outbound, newOpts []option.Outbound) {
if a.removeEmojis {
removeEmojisFromTags(newOpts)
}
uniquifyTags(newOpts)
a.removeUseless(newOpts)
var (
oldOptByTag = make(map[string]option.Outbound)
@@ -276,34 +265,3 @@ func (a *Adapter) removeUseless(newOpts []option.Outbound) {
}
}
}
func uniquifyTags(opts []option.Outbound) {
count := make(map[string]int)
for i, opt := range opts {
count[opt.Tag]++
if count[opt.Tag] > 1 {
opts[i].Tag = F.ToString(opt.Tag, " #", count[opt.Tag])
}
}
}
func removeEmojisFromTags(opts []option.Outbound) {
for i, opt := range opts {
cleaned := flagRegex.ReplaceAllStringFunc(opt.Tag, flagToCountryCode)
cleaned = emojiRegex.ReplaceAllString(cleaned, "")
cleaned = multiSpaceRegex.ReplaceAllString(cleaned, " ")
opts[i].Tag = strings.TrimSpace(cleaned)
}
}
func flagToCountryCode(flag string) string {
runes := []rune(flag)
if len(runes) == 2 {
return string(rune(runes[0]-0x1F1E6+'A')) + string(rune(runes[1]-0x1F1E6+'A')) + " "
}
return ""
}
var flagRegex = regexp.MustCompile(`[\x{1F1E6}-\x{1F1FF}]{2}`)
var emojiRegex = regexp.MustCompile(`[\x{1F1E0}-\x{1F1FF}\x{1F300}-\x{1F9FF}\x{2600}-\x{27BF}\x{FE00}-\x{FE0F}\x{200D}]+`)
var multiSpaceRegex = regexp.MustCompile(`\s{2,}`)

View File

@@ -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
View File

@@ -458,7 +458,7 @@ func (s *Box) PreStart() error {
s.Close()
return err
}
s.logger.Notice("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}
@@ -477,7 +477,7 @@ func (s *Box) Start() error {
s.Close()
return err
}
s.logger.Notice("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)")
return nil
}

View File

@@ -289,7 +289,7 @@ func prepareAppStore(ctx context.Context) error {
return err
}
if len(builds.Data) == 0 {
log.Fatal(string(platform), " ", tag, " no build found")
log.Fatal(platform, " ", tag, " no build found")
}
buildID := common.Ptr(builds.Data[0].ID)
if version.ID == "" {

View File

@@ -63,7 +63,7 @@ func init() {
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0")
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -X internal/godebug.defaultGODEBUG=multipathtcp=0 -checklinkname=0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_trusttunnel", "with_call", "with_openvpn", "with_sudoku", "with_snell", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_masque", "with_mtproxy", "with_utls", "with_naive_outbound", "with_clash_api", "badlinkname", "tfogo_checklinkname0")
darwinTags = append(darwinTags, "with_dhcp", "grpcnotrace")
// memcTags = append(memcTags, "with_tailscale")
sharedTags = append(sharedTags, "with_tailscale", "ts_omit_logtail", "ts_omit_ssh", "ts_omit_drive", "ts_omit_taildrop", "ts_omit_webclient", "ts_omit_doctor", "ts_omit_capture", "ts_omit_kube", "ts_omit_aws", "ts_omit_synology", "ts_omit_bird")

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -106,7 +106,6 @@ func findAndReplaceProjectVersion(objectsMap map[string]any, projectContent stri
}
func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
globalSettings := collectBuildSettings(objectsMap)
var objectKeyList []string
for objectKey, object := range objectsMap {
buildSettings := object.(map[string]any)["buildSettings"]
@@ -117,51 +116,13 @@ func findObjectKey(objectsMap map[string]any, bundleIDList []string) []string {
if bundleIDObject == nil {
continue
}
bundleID := expandBuildVariables(bundleIDObject.(string), globalSettings)
if common.Contains(bundleIDList, bundleID) {
if common.Contains(bundleIDList, bundleIDObject.(string)) {
objectKeyList = append(objectKeyList, objectKey)
}
}
return objectKeyList
}
func collectBuildSettings(objectsMap map[string]any) map[string]string {
settings := make(map[string]string)
for _, object := range objectsMap {
buildSettings, loaded := object.(map[string]any)["buildSettings"].(map[string]any)
if !loaded {
continue
}
for key, value := range buildSettings {
valueString, isString := value.(string)
if !isString {
continue
}
settings[key] = valueString
}
}
return settings
}
var buildVariableRegexp = regexp.MustCompile(`\$[({]([A-Za-z0-9_]+)[)}]`)
func expandBuildVariables(value string, settings map[string]string) string {
for {
expanded := buildVariableRegexp.ReplaceAllStringFunc(value, func(match string) string {
name := buildVariableRegexp.FindStringSubmatch(match)[1]
replacement, loaded := settings[name]
if !loaded {
return match
}
return replacement
})
if expanded == value {
return expanded
}
value = expanded
}
}
func findObjectKeyByDirectory(objectsMap map[string]any, directoryList []string) []string {
var objectKeyList []string
for objectKey, object := range objectsMap {

View File

@@ -179,7 +179,6 @@ func run() error {
for {
osSignal := <-osSignals
if osSignal == syscall.SIGHUP {
log.Notice("received SIGHUP, reloading...")
err = check()
if err != nil {
log.Error(E.Cause(err, "reload service"))

View File

@@ -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."

View 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
View 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
}

View 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))
}
}

View File

@@ -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)
}
}

View File

@@ -36,7 +36,6 @@ type DefaultDialer struct {
udpAddr4 string
udpAddr6 string
netns string
autoDetectBindFunc control.Func
connectionManager adapter.ConnectionManager
networkManager adapter.NetworkManager
networkStrategy *C.NetworkStrategy
@@ -61,7 +60,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
networkType []C.InterfaceType
fallbackNetworkType []C.InterfaceType
networkFallbackDelay time.Duration
autoDetectBindFunc control.Func
)
if networkManager != nil {
interfaceFinder = networkManager.InterfaceFinder()
@@ -72,21 +70,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
}
if platformInterface != nil && platformInterface.UsePlatformAutoDetectInterfaceControl() {
interfaceName := options.BindInterface
bindFunc := func(network, address string, conn syscall.RawConn) error {
return control.Raw(conn, func(fd uintptr) error {
return platformInterface.BindInterfaceControl(int(fd), interfaceName)
})
}
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
} else {
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
}
if options.RoutingMark > 0 {
if !C.IsLinux {
return nil, E.New("`routing_mark` is only supported on Linux")
@@ -132,7 +119,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
bindFunc := networkManager.AutoDetectInterfaceFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
autoDetectBindFunc = bindFunc
}
}
if options.RoutingMark == 0 && defaultOptions.RoutingMark != 0 {
@@ -227,7 +213,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
udpAddr4: udpAddr4,
udpAddr6: udpAddr6,
netns: options.NetNs,
autoDetectBindFunc: autoDetectBindFunc,
connectionManager: connectionManager,
networkManager: networkManager,
networkStrategy: networkStrategy,
@@ -332,18 +317,12 @@ func (d *DefaultDialer) DialParallelInterface(ctx context.Context, network strin
func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
if d.networkStrategy == nil {
return d.trackPacketConn(listener.ListenNetworkNamespace[net.PacketConn](d.netns, func() (net.PacketConn, error) {
listenConfig := d.udpListener
if d.autoDetectBindFunc != nil && destination.Addr.IsValid() {
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
return d.autoDetectBindFunc(network, destination.String(), conn)
})
}
if destination.IsIPv6() {
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr6)
} else if destination.IsIPv4() && !destination.Addr.IsUnspecified() {
return listenConfig.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP+"4", d.udpAddr4)
} else {
return listenConfig.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
return d.udpListener.ListenPacket(ctx, N.NetworkUDP, d.udpAddr4)
}
}))
} else {

View File

@@ -182,10 +182,10 @@ func (d *DefaultDialer) listenSerialInterfacePacket(ctx context.Context, listene
func selectInterfaces(networkManager adapter.NetworkManager, strategy C.NetworkStrategy, interfaceType []C.InterfaceType, fallbackInterfaceType []C.InterfaceType) (primaryInterfaces []adapter.NetworkInterface, fallbackInterfaces []adapter.NetworkInterface) {
interfaces := networkManager.NetworkInterfaces()
myInterfaces := networkManager.InterfaceMonitor().MyInterfaces()
if len(myInterfaces) > 0 {
myInterface := networkManager.InterfaceMonitor().MyInterface()
if myInterface != "" {
interfaces = common.Filter(interfaces, func(it adapter.NetworkInterface) bool {
return !common.Contains(myInterfaces, it.Name)
return it.Name != myInterface
})
}
switch strategy {

View File

@@ -29,8 +29,8 @@ type Conn struct {
func (c *Conn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
c.group.access.Unlock()
return c.Conn.Close()
}
@@ -58,8 +58,8 @@ type PacketConn struct {
func (c *PacketConn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
c.group.access.Unlock()
return c.PacketConn.Close()
}
@@ -87,8 +87,8 @@ type SingPacketConn struct {
func (c *SingPacketConn) Close() error {
c.group.access.Lock()
defer c.group.access.Unlock()
c.group.connections.Remove(c.element)
c.group.access.Unlock()
return c.PacketConn.Close()
}

View File

@@ -47,19 +47,15 @@ func (g *Group) NewSingPacketConn(conn N.PacketConn, isExternal bool, isProvider
func (g *Group) Interrupt(interruptExternalConnections bool) {
g.access.Lock()
defer g.access.Unlock()
var toDelete []*list.Element[*groupConnItem]
var toClose []io.Closer
for element := g.connections.Front(); element != nil; element = element.Next() {
if !element.Value.isExternal || interruptExternalConnections {
element.Value.conn.Close()
toDelete = append(toDelete, element)
toClose = append(toClose, element.Value.conn)
}
}
for _, element := range toDelete {
g.connections.Remove(element)
}
g.access.Unlock()
for _, conn := range toClose {
_ = conn.Close()
}
}

View File

@@ -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")
}
}
}

View File

@@ -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
}

View File

@@ -77,7 +77,7 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
if err != nil {
return nil, err
}
l.logger.Notice("tcp server started at ", tcpListener.Addr())
l.logger.Info("tcp server started at ", tcpListener.Addr())
l.tcpListener = tcpListener
return tcpListener, err
}

View File

@@ -54,7 +54,7 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
}
l.udpConn = udpConn.(*net.UDPConn)
l.udpAddr = bindAddr
l.logger.Notice("udp server started at ", udpConn.LocalAddr())
l.logger.Info("udp server started at ", udpConn.LocalAddr())
return udpConn, err
}

View File

@@ -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()
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
var _ Searcher = (*androidSearcher)(nil)
@@ -17,9 +16,6 @@ type androidSearcher struct {
}
func NewSearcher(config Config) (Searcher, error) {
if config.PackageManager == nil {
return nil, E.New("missing package manager")
}
return &androidSearcher{config.PackageManager}, nil
}

View File

@@ -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))
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -168,9 +168,9 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
}
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
hello.SessionId[0] = 26
hello.SessionId[1] = 7
hello.SessionId[2] = 11
hello.SessionId[0] = 1
hello.SessionId[1] = 8
hello.SessionId[2] = 1
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
copy(hello.SessionId[8:], e.shortID[:])
if debug.Enabled {

View File

@@ -165,7 +165,7 @@ func NewSTDClient(ctx context.Context, logger logger.ContextLogger, serverAddres
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, E.New("failed to parse certificate:\n\n", string(certificate))
return nil, E.New("failed to parse certificate:\n\n", certificate)
}
tlsConfig.RootCAs = certPool
}

View File

@@ -164,14 +164,14 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
config.Certificates = []tls.Certificate{keyPair}
c.config = config
c.access.Unlock()
c.logger.Notice("reloaded TLS certificate")
c.logger.Info("reloaded TLS certificate")
} else if common.Contains(c.clientCertificatePath, path) {
clientCertificateCA := x509.NewCertPool()
var reloaded bool
for _, certPath := range c.clientCertificatePath {
content, err := os.ReadFile(certPath)
if err != nil {
c.logger.Error(E.Cause(err, "reload certificate from ", certPath))
c.logger.Error(E.Cause(err, "reload certificate from ", c.clientCertificatePath))
continue
}
if !clientCertificateCA.AppendCertsFromPEM(content) {
@@ -188,7 +188,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
config.ClientCAs = clientCertificateCA
c.config = config
c.access.Unlock()
c.logger.Notice("reloaded client certificates")
c.logger.Info("reloaded client certificates")
} else if path == c.echKeyPath {
echKey, err := os.ReadFile(c.echKeyPath)
if err != nil {
@@ -198,7 +198,7 @@ func (c *STDServerConfig) certificateUpdated(path string) error {
if err != nil {
return err
}
c.logger.Notice("reloaded ECH keys")
c.logger.Info("reloaded ECH keys")
}
return nil
}

View File

@@ -218,7 +218,7 @@ func NewUTLSClient(ctx context.Context, logger logger.ContextLogger, serverAddre
if len(certificate) > 0 {
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(certificate) {
return nil, E.New("failed to parse certificate:\n\n", string(certificate))
return nil, E.New("failed to parse certificate:\n\n", certificate)
}
tlsConfig.RootCAs = certPool
}

View File

@@ -11,7 +11,6 @@ import (
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing/common"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
@@ -73,18 +72,7 @@ func (s *HistoryStorage) Close() error {
return nil
}
func URLTest(ctx context.Context, link string, detour N.Dialer) (uint16, error) {
multiplexOutbound, isMultiplexOutbound := common.Cast[adapter.OutboundWithMultiplex](detour)
if isMultiplexOutbound && multiplexOutbound.MultiplexEnabled() {
_, err := urlTest(ctx, link, detour)
if err != nil {
return 0, err
}
}
return urlTest(ctx, link, detour)
}
func urlTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
if link == "" {
link = "https://www.gstatic.com/generate_204"
}

View File

@@ -9,6 +9,7 @@ import (
"strings"
"time"
Xbadoption "github.com/sagernet/sing-box/common/xray/json/badoption"
"github.com/sagernet/sing/common/json/badoption"
)
@@ -68,8 +69,8 @@ func DecodeBase64URLSafe(content string) (string, error) {
return string(result), nil
}
func ParseXHTTPRange(value string) (badoption.Range[int], error) {
result := badoption.Range[int]{}
func ParseXHTTPRange(value string) (Xbadoption.Range, error) {
result := Xbadoption.Range{}
encoded, err := json.Marshal(value)
if err != nil {
return result, err

View 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)))
}

View File

@@ -13,12 +13,10 @@ const (
TypeShadowsocks = "shadowsocks"
TypeVMess = "vmess"
TypeTrojan = "trojan"
TypeTrustTunnel = "trusttunnel"
TypeNaive = "naive"
TypeWireGuard = "wireguard"
TypeWARP = "warp"
TypeMASQUE = "masque"
TypeOpenVPN = "openvpn"
TypeMTProxy = "mtproxy"
TypeParser = "parser"
TypeHysteria = "hysteria"
@@ -27,9 +25,6 @@ const (
TypeShadowTLS = "shadowtls"
TypeMieru = "mieru"
TypeAnyTLS = "anytls"
TypeSudoku = "sudoku"
TypeSnell = "snell"
TypeCall = "call"
TypeShadowsocksR = "shadowsocksr"
TypeVLESS = "vless"
TypeTUIC = "tuic"
@@ -43,7 +38,6 @@ const (
TypeBandwidthLimiter = "bandwidth-limiter"
TypeTrafficLimiter = "traffic-limiter"
TypeRateLimiter = "rate-limiter"
TypeFairQueue = "fair-queue"
TypeAdminPanel = "admin-panel"
TypeManagerAPI = "manager-api"
TypeNodeManagerAPI = "node-manager-api"
@@ -90,8 +84,6 @@ func ProxyDisplayName(proxyType string) string {
return "VMess"
case TypeTrojan:
return "Trojan"
case TypeTrustTunnel:
return "TrustTunnel"
case TypeNaive:
return "Naive"
case TypeWireGuard:
@@ -100,8 +92,6 @@ func ProxyDisplayName(proxyType string) string {
return "WARP"
case TypeMASQUE:
return "MASQUE"
case TypeOpenVPN:
return "OpenVPN"
case TypeMTProxy:
return "MTProxy"
case TypeParser:
@@ -130,12 +120,6 @@ func ProxyDisplayName(proxyType string) string {
return "Mieru"
case TypeAnyTLS:
return "AnyTLS"
case TypeSudoku:
return "Sudoku"
case TypeSnell:
return "Snell"
case TypeCall:
return "Call"
case TypeFallback:
return "Fallback"
case TypeTailscale:
@@ -152,8 +136,6 @@ func ProxyDisplayName(proxyType string) string {
return "Traffic Limiter"
case TypeRateLimiter:
return "Rate Limiter"
case TypeFairQueue:
return "Fair Queue"
case TypeVPNClient:
return "VPN Client"
case TypeVPNServer:

View File

@@ -10,7 +10,6 @@ const (
ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second
UDPTimeout = 5 * time.Minute
ICMPTimeout = 10 * time.Second
DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute
StartTimeout = 10 * time.Second

View File

@@ -24,10 +24,9 @@ const (
LogLevel_FATAL LogLevel = 1
LogLevel_ERROR LogLevel = 2
LogLevel_WARN LogLevel = 3
LogLevel_NOTICE LogLevel = 4
LogLevel_INFO LogLevel = 5
LogLevel_DEBUG LogLevel = 6
LogLevel_TRACE LogLevel = 7
LogLevel_INFO LogLevel = 4
LogLevel_DEBUG LogLevel = 5
LogLevel_TRACE LogLevel = 6
)
// Enum value maps for LogLevel.
@@ -37,20 +36,18 @@ var (
1: "FATAL",
2: "ERROR",
3: "WARN",
4: "NOTICE",
5: "INFO",
6: "DEBUG",
7: "TRACE",
4: "INFO",
5: "DEBUG",
6: "TRACE",
}
LogLevel_value = map[string]int32{
"PANIC": 0,
"FATAL": 1,
"ERROR": 2,
"WARN": 3,
"NOTICE": 4,
"INFO": 5,
"DEBUG": 6,
"TRACE": 7,
"INFO": 4,
"DEBUG": 5,
"TRACE": 6,
}
)

View File

@@ -59,10 +59,9 @@ enum LogLevel {
FATAL = 1;
ERROR = 2;
WARN = 3;
NOTICE = 4;
INFO = 5;
DEBUG = 6;
TRACE = 7;
INFO = 4;
DEBUG = 5;
TRACE = 6;
}
message Log {

View File

@@ -41,9 +41,9 @@ type Client struct {
initRDRCFunc func() adapter.RDRCStore
logger logger.ContextLogger
cache freelru.Cache[dns.Question, *dns.Msg]
cacheLock compatible.Map[transportCacheKey, chan struct{}]
cacheLock compatible.Map[dns.Question, chan struct{}]
transportCache freelru.Cache[transportCacheKey, *dns.Msg]
transportCacheLock compatible.Map[transportCacheKey, chan struct{}]
transportCacheLock compatible.Map[dns.Question, chan struct{}]
}
type ClientOptions struct {
@@ -106,23 +106,7 @@ func extractNegativeTTL(response *dns.Msg) (uint32, bool) {
return 0, false
}
func stripDNSPadding(response *dns.Msg) {
for _, record := range response.Extra {
opt, isOpt := record.(*dns.OPT)
if !isOpt {
continue
}
opt.Option = common.Filter(opt.Option, func(it dns.EDNS0) bool {
return it.Option() != dns.EDNS0PADDING
})
}
}
func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, message *dns.Msg, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) (*dns.Msg, error) {
transportStack := transportStackFromContext(ctx)
if containsTransport(transportStack, transport.Tag()) {
return nil, E.New("DNS resolution loop detected: ", formatTransportLoop(transportStack, transport.Tag()))
}
if len(message.Question) == 0 {
if c.logger != nil {
c.logger.WarnContext(ctx, "bad question size: ", len(message.Question))
@@ -153,11 +137,9 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
len(message.Extra[0].(*dns.OPT).Option) == 0) &&
!options.ClientSubnet.IsValid()
disableCache := !isSimpleRequest || c.disableCache || options.DisableCache
ctx = contextWithTransportStack(ctx, append(transportStack, transport.Tag()))
if !disableCache {
cacheKey := transportCacheKey{Question: question, transportTag: transport.Tag()}
if c.cache != nil {
cond, loaded := c.cacheLock.LoadOrStore(cacheKey, make(chan struct{}))
cond, loaded := c.cacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
select {
case <-cond:
@@ -166,12 +148,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
} else {
defer func() {
c.cacheLock.Delete(cacheKey)
c.cacheLock.Delete(question)
close(cond)
}()
}
} else if c.transportCache != nil {
cond, loaded := c.transportCacheLock.LoadOrStore(cacheKey, make(chan struct{}))
cond, loaded := c.transportCacheLock.LoadOrStore(question, make(chan struct{}))
if loaded {
select {
case <-cond:
@@ -180,7 +162,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
} else {
defer func() {
c.transportCacheLock.Delete(cacheKey)
c.transportCacheLock.Delete(question)
close(cond)
}()
}
@@ -194,6 +176,11 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
}
messageId := message.Id
contextTransport, clientSubnetLoaded := transportTagFromContext(ctx)
if clientSubnetLoaded && transport.Tag() == contextTransport {
return nil, E.New("DNS query loopback in transport[", contextTransport, "]")
}
ctx = contextWithTransportTag(ctx, transport.Tag())
if !disableCache && responseChecker != nil && c.rdrc != nil {
rejected := c.rdrc.LoadRDRC(transport.Tag(), question.Name, question.Qtype)
if rejected {
@@ -210,8 +197,6 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
} else {
return nil, err
}
} else {
stripDNSPadding(response)
}
/*if question.Qtype == dns.TypeA || question.Qtype == dns.TypeAAAA {
validResponse := response
@@ -561,36 +546,15 @@ func MessageToAddresses(response *dns.Msg) []netip.Addr {
return addresses
}
type transportStackKey struct{}
type transportKey struct{}
func contextWithTransportStack(ctx context.Context, stack []string) context.Context {
return context.WithValue(ctx, transportStackKey{}, stack)
func contextWithTransportTag(ctx context.Context, transportTag string) context.Context {
return context.WithValue(ctx, transportKey{}, transportTag)
}
func transportStackFromContext(ctx context.Context) []string {
value, _ := ctx.Value(transportStackKey{}).([]string)
return value
}
func containsTransport(stack []string, tag string) bool {
for _, t := range stack {
if t == tag {
return true
}
}
return false
}
func formatTransportLoop(stack []string, loopTag string) string {
result := ""
for i, t := range stack {
if i > 0 {
result += " -> "
}
result += t
}
result += " -> " + loopTag
return result
func transportTagFromContext(ctx context.Context) (string, bool) {
value, loaded := ctx.Value(transportKey{}).(string)
return value, loaded
}
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {

View File

@@ -111,7 +111,6 @@ func (t *Transport) Close() error {
func (t *Transport) Reset() {
t.transportLock.Lock()
t.updatedAt = time.Time{}
t.lastError = nil
t.servers = nil
t.transportLock.Unlock()
}
@@ -187,7 +186,7 @@ func (t *Transport) updateServers() error {
return E.Cause(err, "dhcp: prepare interface")
}
t.logger.Notice("dhcp: query DNS servers on ", iface.Name)
t.logger.Info("dhcp: query DNS servers on ", iface.Name)
fetchCtx, cancel := context.WithTimeout(t.ctx, C.DHCPTimeout)
err = t.fetchServers0(fetchCtx, iface)
cancel()
@@ -296,20 +295,15 @@ func (t *Transport) fetchServersResponse(iface *control.Interface, packetConn ne
func (t *Transport) recreateServers(iface *control.Interface, dhcpPacket *dhcpv4.DHCPv4) error {
searchList := dhcpPacket.DomainSearch()
if searchList != nil && len(searchList.Labels) > 0 {
t.search = common.Filter(common.Map(searchList.Labels, mDNS.Fqdn), func(it string) bool {
return it != "."
})
t.search = searchList.Labels
} else if dhcpPacket.DomainName() != "" {
domainName := mDNS.Fqdn(dhcpPacket.DomainName())
if domainName != "." {
t.search = []string{domainName}
}
t.search = []string{dhcpPacket.DomainName()}
}
serverAddrs := common.Map(dhcpPacket.DNS(), func(it net.IP) M.Socksaddr {
return M.SocksaddrFrom(M.AddrFromIP(it), 53)
})
if len(serverAddrs) > 0 && !slices.Equal(t.servers, serverAddrs) {
t.logger.Notice("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
t.logger.Info("dhcp: updated DNS servers from ", iface.Name, ": [", strings.Join(common.Map(serverAddrs, M.Socksaddr.String), ","), "], search: [", strings.Join(t.search, ","), "]")
}
t.servers = serverAddrs
return nil

View File

@@ -120,16 +120,13 @@ func NewHTTPSRaw(
serverAddr M.Socksaddr,
tlsConfig tls.Config,
) *HTTPSTransport {
if tlsConfig != nil {
dialer = tls.NewDialer(dialer, tlsConfig)
}
return &HTTPSTransport{
TransportAdapter: adapter,
logger: logger,
dialer: dialer,
destination: destination,
headers: headers,
transport: NewHTTPSTransportWrapper(dialer, serverAddr, destination),
transport: NewHTTPSTransportWrapper(tls.NewDialer(dialer, tlsConfig), serverAddr),
}
}

View File

@@ -5,13 +5,11 @@ import (
"errors"
"net"
"net/http"
"net/url"
"sync/atomic"
"github.com/sagernet/sing-box/common/tls"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"golang.org/x/net/http2"
)
@@ -24,36 +22,27 @@ type HTTPSTransportWrapper struct {
fallback *atomic.Bool
}
func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destination *url.URL) *HTTPSTransportWrapper {
func NewHTTPSTransportWrapper(dialer tls.Dialer, serverAddr M.Socksaddr) *HTTPSTransportWrapper {
var fallback atomic.Bool
if destination.Scheme == "http" {
// plain HTTP DoH used by Tailscale
fallback.Store(true)
}
return &HTTPSTransportWrapper{
http2Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, _, _ string, _ *tls.STDConfig) (net.Conn, error) {
resultConn, err := dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
tlsConn, err := dialer.DialTLSContext(ctx, serverAddr)
if err != nil {
return nil, err
}
if tlsConn, isTLSConn := resultConn.(tls.Conn); isTLSConn {
state := tlsConn.ConnectionState()
if state.NegotiatedProtocol != http2.NextProtoTLS {
if state.NegotiatedProtocol == http2.NextProtoTLS {
return tlsConn, nil
}
tlsConn.Close()
fallback.Store(true)
return nil, errFallback
}
}
return resultConn, nil
},
},
httpTransport: &http.Transport{
DialContext: func(ctx context.Context, _, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
},
DialTLSContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, N.NetworkTCP, serverAddr)
return dialer.DialTLSContext(ctx, serverAddr)
},
},
fallback: &fallback,
@@ -63,7 +52,7 @@ func NewHTTPSTransportWrapper(dialer N.Dialer, serverAddr M.Socksaddr, destinati
func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response, error) {
if h.fallback.Load() {
return h.httpTransport.RoundTrip(request)
}
} else {
response, err := h.http2Transport.RoundTrip(request)
if err != nil {
if errors.Is(err, errFallback) {
@@ -73,6 +62,7 @@ func (h *HTTPSTransportWrapper) RoundTrip(request *http.Request) (*http.Response
}
return response, nil
}
}
func (h *HTTPSTransportWrapper) CloseIdleConnections() {
h.http2Transport.CloseIdleConnections()

View File

@@ -11,7 +11,6 @@ import (
"unsafe"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/service"
"golang.org/x/sys/windows"
@@ -78,12 +77,12 @@ func dnsReadConfig(ctx context.Context, _ string) *dnsConfig {
}{ifName: windows.UTF16PtrToString(address.FriendlyName), Addr: dnsServerAddr})
}
}
var myInterfaces []string
var myInterface string
if networkManager := service.FromContext[adapter.NetworkManager](ctx); networkManager != nil {
myInterfaces = networkManager.InterfaceMonitor().MyInterfaces()
myInterface = networkManager.InterfaceMonitor().MyInterface()
}
for _, address := range dnsAddresses {
if common.Contains(myInterfaces, address.ifName) {
if address.ifName == myInterface {
continue
}
conf.servers = append(conf.servers, net.JoinHostPort(address.String(), "53"))

View File

@@ -126,12 +126,6 @@ func (t *HTTP3Transport) newTransport() *http3.Transport {
conn.Close()
return nil, dialErr
}
// quic-go does not take ownership of the packet conn passed to
// DialEarly: when the connection ends it only stops reading.
go func() {
<-quicConn.Context().Done()
conn.Close()
}()
return quicConn, nil
},
TLSClientConfig: t.tlsConfig,

View File

@@ -4,7 +4,6 @@ import (
"context"
"errors"
"os"
"time"
"github.com/sagernet/quic-go"
"github.com/sagernet/sing-box/adapter"
@@ -118,12 +117,6 @@ func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg,
rawConn.Close()
return nil, E.Cause(err, "establish QUIC connection")
}
// quic-go does not take ownership of the packet conn passed to
// DialEarly: when the connection ends it only stops reading.
go func() {
<-earlyConnection.Context().Done()
rawConn.Close()
}()
return earlyConnection, nil
})
if err != nil {
@@ -151,11 +144,6 @@ func (t *Transport) exchange(ctx context.Context, message *mDNS.Msg, conn *quic.
return nil, E.Cause(err, "open stream")
}
defer stream.CancelRead(0)
stopWatch := context.AfterFunc(ctx, func() {
stream.CancelRead(0)
_ = stream.SetWriteDeadline(time.Now())
})
defer stopWatch()
err = transport.WriteMessage(stream, 0, message)
if err != nil {
stream.Close()

View File

@@ -217,7 +217,7 @@ func (m *TransportManager) Remove(tag string) error {
return E.New("default server cannot be fakeip")
}
m.defaultTransport = nextTransport
m.logger.Notice("updated default server to ", m.defaultTransport.Tag())
m.logger.Info("updated default server to ", m.defaultTransport.Tag())
} else {
m.defaultTransport = nil
}
@@ -287,7 +287,7 @@ func (m *TransportManager) Create(ctx context.Context, logger log.ContextLogger,
}
m.defaultTransport = transport
if m.started {
m.logger.Notice("updated default server to ", transport.Tag())
m.logger.Info("updated default server to ", transport.Tag())
}
}
if transport.Type() == C.DNSTypeFakeIP {

View File

@@ -2,31 +2,6 @@
icon: material/alert-decagram
---
#### 1.13.16
* Remove client metadata from AnyTLS requests by default **1**
* Fixes and improvements
**1**:
We found that the AnyTLS client implementation uploads metadata that is
**not used by the open-source server**, and there are reports of vendors using
it to profile and discriminate against users. We now leave it empty by default
and allow you to customize it, see
[AnyTLS client metadata](/manual/misc/anytls-client-metadata/).
#### 1.13.15
* Fixes and improvements
#### 1.13.14
* Fixes and improvements
#### 1.13.13
* Fixes and improvements
#### 1.13.12
* Update naiveproxy to v148.0.7778.96-1

View File

@@ -31,7 +31,6 @@
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
| `vless` | [VLESS](./vless/) | TCP |
| `anytls` | [AnyTLS](./anytls/) | TCP |
| `mieru` | [Mieru](./mieru/) | :material-close: |
| `tun` | [Tun](./tun/) | :material-close: |
| `redirect` | [Redirect](./redirect/) | :material-close: |
| `tproxy` | [TProxy](./tproxy/) | :material-close: |

View File

@@ -31,7 +31,6 @@
| `hysteria2` | [Hysteria2](./hysteria2/) | :material-close: |
| `vless` | [VLESS](./vless/) | TCP |
| `anytls` | [AnyTLS](./anytls/) | TCP |
| `mieru` | [Mieru](./mieru/) | :material-close: |
| `tun` | [Tun](./tun/) | :material-close: |
| `redirect` | [Redirect](./redirect/) | :material-close: |
| `tproxy` | [TProxy](./tproxy/) | :material-close: |

View File

@@ -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.

View File

@@ -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
客户端若不发送用户提示,代理服务器将拒绝连接。

View File

@@ -17,7 +17,6 @@ icon: material/new-box
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s",
"min_idle_session": 5,
"client_metadata": "",
"tls": {},
... // Dial Fields
@@ -56,12 +55,6 @@ In the check, close sessions that have been idle for longer than this. Default:
In the check, at least the first `n` idle sessions are kept open. Default value: `n`=0
#### client_metadata
!!! question "Since sing-box 1.13.16"
Check [AnyTLS client metadata](/manual/misc/anytls-client-metadata/).
#### tls
==Required==

View File

@@ -17,7 +17,6 @@ icon: material/new-box
"idle_session_check_interval": "30s",
"idle_session_timeout": "30s",
"min_idle_session": 5,
"client_metadata": "",
"tls": {},
... // 拨号字段
@@ -56,12 +55,6 @@ AnyTLS 密码。
在检查中,至少前 `n` 个空闲会话保持打开状态。默认值:`n`=0
#### client_metadata
!!! question "自 sing-box 1.13.16 起"
参阅 [AnyTLS 客户端元数据](/zh/manual/misc/anytls-client-metadata/)。
#### tls
==必填==

View File

@@ -32,7 +32,6 @@
| `hysteria2` | [Hysteria2](./hysteria2/) |
| `mieru` | [Mieru](./mieru/) |
| `anytls` | [AnyTLS](./anytls/) |
| `mieru` | [Mieru](./mieru/) |
| `tor` | [Tor](./tor/) |
| `ssh` | [SSH](./ssh/) |
| `dns` | [DNS](./dns/) |

View File

@@ -32,7 +32,6 @@
| `hysteria2` | [Hysteria2](./hysteria2/) |
| `mieru` | [Mieru](./mieru/) |
| `anytls` | [AnyTLS](./anytls/) |
| `mieru` | [Mieru](./mieru/) |
| `tor` | [Tor](./tor/) |
| `ssh` | [SSH](./ssh/) |
| `dns` | [DNS](./dns/) |

View File

@@ -19,7 +19,6 @@ icon: material/new-box
"username": "asdf",
"password": "hjkl",
"multiplexing": "MULTIPLEXING_LOW",
"traffic_pattern": "GgQIARAK",
... // Dial Fields
}
@@ -49,7 +48,7 @@ Must set at least one field between `server_port` and `server_ports`.
==Required==
Transmission protocol. Allowed values are `TCP` and `UDP`.
Transmission protocol. The only allowed value is `TCP`.
#### username
@@ -67,10 +66,6 @@ mieru password.
Multiplexing level. Supported values are `MULTIPLEXING_OFF`, `MULTIPLEXING_LOW`, `MULTIPLEXING_MIDDLE`, `MULTIPLEXING_HIGH`. `MULTIPLEXING_OFF` disables multiplexing.
#### traffic_pattern
A base64 string to fine tune network behavior.
### Dial Fields
See [Dial Fields](/configuration/shared/dial/) for details.

View File

@@ -19,7 +19,6 @@ icon: material/new-box
"username": "asdf",
"password": "hjkl",
"multiplexing": "MULTIPLEXING_LOW",
"traffic_pattern": "GgQIARAK",
... // 拨号字段
}
@@ -49,7 +48,7 @@ icon: material/new-box
==必填==
通信协议。可设为 `TCP``UDP`
通信协议。可设为 `TCP`
#### username
@@ -67,10 +66,6 @@ mieru 密码。
多路复用设置。可以设为 `MULTIPLEXING_OFF``MULTIPLEXING_LOW``MULTIPLEXING_MIDDLE``MULTIPLEXING_HIGH`。其中 `MULTIPLEXING_OFF` 会关闭多路复用功能。
#### traffic_pattern
一个 base64 字符串用于微调网络行为。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/)。

View File

@@ -52,7 +52,6 @@ Multiplex protocol.
| smux | https://github.com/xtaci/smux |
| yamux | https://github.com/hashicorp/yamux |
| h2mux | https://golang.org/x/net/http2 |
| rmux | https://github.com/shtorm-7/rmux |
h2mux is used by default.

View File

@@ -51,7 +51,6 @@
| smux | https://github.com/xtaci/smux |
| yamux | https://github.com/hashicorp/yamux |
| h2mux | https://golang.org/x/net/http2 |
| rmux | https://github.com/shtorm-7/rmux |
默认使用 h2mux。

View File

@@ -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.

View File

@@ -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 协议移除客户端元数据,或将其替换为非默认提供、且用户可以自定义的选项;并建议其他客户端实现也采取行动,共同阻止基于客户端元数据的统计和区别对待。

View File

@@ -32,7 +32,7 @@
"tag": "my-manager",
"database": {
"driver": "sqlite",
"dsn": "file:manager.db?_pragma=foreign_keys(on)&_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)&_time_format=sqlite" // also supported Postgresql
"dsn": "file:manager.db?_pragma=foreign_keys(on)&_pragma=journal_mode(wal)&_pragma=busy_timeout(5000)" // also supported Postgresql
}
},
{
@@ -64,8 +64,6 @@
"listen_port": 7000,
"manager": "my-manager",
"api_key": "change-me-secret",
"keep_alive": "10s",
"keep_alive_timeout": "5s",
// Enable TLS for production deployments (the node connects via gRPC over h2):
// "tls": { // https://sing-box.sagernet.org/configuration/shared/tls/#inbound
// "enabled": true,

View File

@@ -66,7 +66,7 @@
"action": "hijack-dns"
}
],
"final": "traffic-limiter"
"final": "connection-limiter"
},
"services": [
{

View File

@@ -23,7 +23,8 @@
"address": "example.com",
"port": 10001,
"public_key": "3nk7jdnkcL95Fc/z+GCiH7jOovEKhFkLIGPT+U/uLEQ=",
"allowed_ips": ["0.0.0.0/0"]
"allowed_ips": ["0.0.0.0/0"],
"reserved": "AAAA"
}
],
"udp_timeout": "5m0s",

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -25,8 +25,6 @@
{
"type": "masque",
"tag": "masque-out",
"system": false,
"name": "masque0",
"use_http2": false,
"use_ipv6": false,
"profile": {
@@ -39,19 +37,14 @@
"udp_keepalive_period": "30s",
"udp_initial_packet_size": 0,
"reconnect_delay": "5s",
"congestion_controller": "bbr",
"cwnd": 0,
"tls": { // TLS fields for HTTP2
"server_name": "", // SNI; empty = default "consumer-masque.cloudflareclient.com"
// TLS fields for HTTP2
"insecure": false,
"cipher_suites": [],
"curve_preferences": [],
"fragment": false,
"fragment_fallback_delay": "500ms",
"record_fragment": false,
"kernel_tx": false,
"kernel_rx": false
}
// Dial Fields
}
],

View File

@@ -27,16 +27,14 @@
"tag": "mieru-out",
"server": "example.com",
"server_port": 27017,
"server_ports": [
"27017-27019"
],
"server_ports": "27017-27019",
"transport": "TCP",
"username": "username",
"password": "password",
// valid: MULTIPLEXING_DEFAULT / MULTIPLEXING_OFF / MULTIPLEXING_LOW
// MULTIPLEXING_MIDDLE / MULTIPLEXING_HIGH
"multiplexing": "MULTIPLEXING_LOW",
"traffic_pattern": "GgQIARAK"
"multiplexing": "MULTIPLEXING_LOW"
// Dial Fields
}
],
"route": {

View File

@@ -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"
}
}

View File

@@ -31,15 +31,7 @@
"packet_encoding": "",
"transport": {
"type": "mkcp",
"mtu": 1350, // 576-1460
"tti": 50, // 10-100, ms
"uplink_capacity": 12, // MB/s
"downlink_capacity": 100, // MB/s
"congestion": false,
"read_buffer_size": 1, // MB
"write_buffer_size": 1, // MB
"header_type": "none", // none, srtp, utp, wechat-video, dtls, wireguard
"seed": "password"
"mtu": 1500
}
}
],

View File

@@ -24,15 +24,7 @@
],
"transport": {
"type": "mkcp",
"mtu": 1350, // 576-1460
"tti": 50, // 10-100, ms
"uplink_capacity": 12, // MB/s
"downlink_capacity": 100, // MB/s
"congestion": false,
"read_buffer_size": 1, // MB
"write_buffer_size": 1, // MB
"header_type": "none", // none, srtp, utp, wechat-video, dtls, wireguard
"seed": "password"
"mtu": 1500
}
}
],

View File

@@ -26,9 +26,9 @@
"concurrency": 8192,
// domain_fronting_port is a port we use to connect to a fronting domain.
"domain_fronting_port": 443,
// domain_fronting_host is the address (IP or hostname) to use when connecting
// to the fronting domain instead of resolving the hostname from the secret via DNS.
"domain_fronting_host": "",
// domain_fronting_ip is an IP address to use when connecting to the fronting
// domain instead of resolving the hostname from the secret via DNS.
"domain_fronting_ip": "",
// domain_fronting_proxy_protocol is used if communication between upstream
// endpoint and sing-box supports proxy protocol.
"domain_fronting_proxy_protocol": false,

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -25,9 +25,8 @@
{
"type": "parser",
"tag": "vless-out",
// Supported protocols: hysteria, hysteria2, shadowsocks, trojan, tuic, vless, vmess, anytls
// Supported protocols: hysteria, hysteria2, shadowsocks, trojan, tuic, vless, vmess
"link": "vless://b5e41c8c-c437-4689-b863-76208a3efb4b@0.0.0.0:443?..."
// Dial Fields
}
],
"route": {

Some files were not shown because too many files have changed in this diff Show More